feat: dashboard query editor vertical scroll (#7453)

### **PR Type**
Enhancement


___

### **Description**
- Enable dashboard query bar by default

- Refactor AddPanel layout with scrollable containers

- Simplify overflow styles; remove outdated splitters

- Adjust component height calculations


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>DashboardQueryEditor.vue</strong><dd><code>Simplify
query editor bar styling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

web/src/components/dashboards/addPanel/DashboardQueryEditor.vue

<li>Removed query bar toggle icon wrapper<br> <li> Commented out cursor
pointer on <code>.sql-bar</code>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7453/files#diff-68bf1b319223d103f1b99e0b681764e4eb76942399397779c81c2cd18f6485ff">+1/-14</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>AddPanel.vue</strong><dd><code>Refactor AddPanel layout
and scrolling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

web/src/views/Dashboards/addPanel/AddPanel.vue

<li>Replaced nested <code>q-splitter</code> with scrollable
<code>div</code> wrappers<br> <li> Moved
<code>DashboardQueryBuilder</code> and
<code>VariablesValueSelector</code><br> <li> Simplified outdated chart
notification styling<br> <li> Updated wrappers heights for renderer and
editor


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7453/files#diff-c9632db1ad95af95aba10161bcbc7c5608318919f228637bdafae3f3a31267e8">+143/-215</a></td>

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>useDashboardPanel.ts</strong><dd><code>Enable query bar
by default</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

web/src/composables/useDashboardPanel.ts

- Set default `showQueryBar` to `true`


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7453/files#diff-fbba1700750053b9eaf38ddd30dab84531bc44131c795ebb713811e4930cd7cb">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>

---------

Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
This commit is contained in:
ktx-vaidehi
2025-07-08 16:53:11 +05:30
committed by GitHub
co-authored by ktx-akshay
parent f8b2bd0061
commit 0253616aa5
6 changed files with 213 additions and 288 deletions
@@ -2,11 +2,10 @@ import { test, expect } from "../baseFixtures";
import path from "path";
import fs from "fs";
import logsdata from "../../../test-data/logs_data.json";
import { DashboardPage } from '../../pages/dashboardPage';
import { DashboardPage } from "../../pages/dashboardPage";
// Function to handle login
async function login(page) {
await page.goto(process.env["ZO_BASE_URL"]);
if (await page.getByText("Login as internal user").isVisible()) {
await page.getByText("Login as internal user").click();
@@ -14,8 +13,12 @@ async function login(page) {
console.log("ZO_BASE_URL", process.env["ZO_BASE_URL"]);
await page.waitForTimeout(1000);
await page.locator('[data-cy="login-user-id"]').fill(process.env["ZO_ROOT_USER_EMAIL"]);
await page.locator('[data-cy="login-password"]').fill(process.env["ZO_ROOT_USER_PASSWORD"]);
await page
.locator('[data-cy="login-user-id"]')
.fill(process.env["ZO_ROOT_USER_EMAIL"]);
await page
.locator('[data-cy="login-password"]')
.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
await page.locator('[data-cy="login-sign-in"]').click();
await page.waitForTimeout(4000);
await page.goto(process.env["ZO_BASE_URL"]);
@@ -29,23 +32,29 @@ async function ingestion(page) {
).toString("base64");
const headers = {
"Authorization": `Basic ${basicAuthCredentials}`,
Authorization: `Basic ${basicAuthCredentials}`,
"Content-Type": "application/json",
};
const response = await page.evaluate(async ({ url, headers, orgId, streamName, logsdata }) => {
const fetchResponse = await fetch(`${url}/api/${orgId}/${streamName}/_json`, {
method: "POST",
const response = await page.evaluate(
async ({ url, headers, orgId, streamName, logsdata }) => {
const fetchResponse = await fetch(
`${url}/api/${orgId}/${streamName}/_json`,
{
method: "POST",
headers: headers,
body: JSON.stringify(logsdata),
}
);
return await fetchResponse.json();
},
{
url: process.env.INGESTION_URL,
headers: headers,
body: JSON.stringify(logsdata)
});
return await fetchResponse.json();
}, {
url: process.env.INGESTION_URL,
headers: headers,
orgId: orgId,
streamName: streamName,
logsdata: logsdata
});
orgId: orgId,
streamName: streamName,
logsdata: logsdata,
}
);
console.log(response);
}
@@ -59,27 +68,23 @@ function readJsonFile(filename) {
return fs.readFileSync(filePath, "utf8");
}
test.describe("Sanity Tests", () => { let dashboardPage
test.describe("Sanity Tests", () => {
let dashboardPage;
let pictorialJSON, lineJSON;
test.beforeEach(async ({ page }) => {
dashboardPage = new DashboardPage(page);
});
});
test.beforeAll(() => {
const jsonString = readJsonFile("pictorial.json");
pictorialJSON = jsonString
pictorialJSON = jsonString;
lineJSON = readJsonFile("line.json");
});
test.beforeEach(async ({ page }) => {
await login(page);
await page.waitForTimeout(1000);
await ingestion(page);
});
@@ -90,38 +95,43 @@ test.describe("Sanity Tests", () => { let dashboardPage
return;
}
await dashboardPage.addCustomChart();
// Type the content with raw modifier to bypass autocomplete
await page.keyboard.insertText(pictorialJSON);
await page.waitForTimeout(1000);
await page.locator('[data-test="dashboard-panel-error-bar-icon"]').click();
await page.locator('[data-test="dashboard-panel-query-editor"]').getByRole("textbox").fill('select * from "e2e_automate"');
await page.waitForTimeout(2000);
await page.locator('[data-test="dashboard-apply"]').click();
await page.waitForTimeout(3000);
await expect(page.getByText("Unsafe code detected: Access to 'document' is not allowed")).toBeVisible();
// Type the content with raw modifier to bypass autocomplete
await page.keyboard.insertText(pictorialJSON);
});
await page.waitForTimeout(1000);
// await page.locator('[data-test="dashboard-panel-error-bar-icon"]').click();
await page
.locator('[data-test="dashboard-panel-query-editor"]')
.getByRole("textbox")
.fill('select * from "e2e_automate"');
await page.waitForTimeout(2000);
await page.locator('[data-test="dashboard-apply"]').click();
await page.waitForTimeout(3000);
await expect(
page.getByText(
"Unsafe code detected: Access to 'document' is not allowed"
)
).toBeVisible();
});
test("Add line JSON in Monaco Editor", async ({ page }) => {
test("Add line JSON in Monaco Editor", async ({ page }) => {
if (!lineJSON) {
console.error("Skipping test: pictorial.json not found");
return;
}
await dashboardPage.addCustomChart();
// Type the content with raw modifier to bypass autocomplete
await page.keyboard.insertText(lineJSON);
await page.waitForTimeout(1000);
await page.locator('[data-test="dashboard-panel-error-bar-icon"]').click();
await page.locator('[data-test="dashboard-panel-query-editor"]').getByRole("textbox").fill('select * from "e2e_automate"');
await page.waitForTimeout(3000);
await page.locator('[data-test="dashboard-apply"]').click();
await page.waitForTimeout(3000);
// await expect(page.getByText("Unsafe code detected: Access to 'document' is not allowed")).toBeVisible();
});
// Type the content with raw modifier to bypass autocomplete
await page.keyboard.insertText(lineJSON);
await page.waitForTimeout(1000);
// await page.locator('[data-test="dashboard-panel-error-bar-icon"]').click();
await page
.locator('[data-test="dashboard-panel-query-editor"]')
.getByRole("textbox")
.fill('select * from "e2e_automate"');
await page.waitForTimeout(3000);
await page.locator('[data-test="dashboard-apply"]').click();
await page.waitForTimeout(3000);
// await expect(page.getByText("Unsafe code detected: Access to 'document' is not allowed")).toBeVisible();
});
});
@@ -347,7 +347,7 @@ test.describe("dashboard filter testcases", () => {
await page.getByLabel("Value").fill("$variablename");
await page.locator('[data-test="dashboard-apply"]').click();
await page.getByText("arrow_rightQueryAutoPromQLCustom SQL").click();
// await page.getByText("arrow_rightQueryAutoPromQLCustom SQL").click();
await expect(page.getByText("'$variablename'").first()).toBeVisible();
await page
.locator('[data-test="dashboard-panel-data-view-query-inspector-btn"]')
@@ -596,7 +596,7 @@ test.describe("dashboard filter testcases", () => {
await page.locator('[data-test="dashboard-apply"]').click();
await page.locator('[data-test="dashboard-apply"]').click();
await page.getByText("arrow_rightQueryAutoPromQLCustom SQL").click();
// await page.getByText("arrow_rightQueryAutoPromQLCustom SQL").click();
await expect(page.getByText("'$variablename'").first()).toBeVisible();
await page
.locator('[data-test="dashboard-panel-data-view-query-inspector-btn"]')
@@ -515,7 +515,7 @@ test.describe("dashboard UI testcases", () => {
await page.getByRole("button", { name: "9" }).last().click();
await page.getByRole("button", { name: "16" }).last().click();
await page.locator('[data-test="chart-renderer"] div').click();
// await page.locator('[data-test="chart-renderer"] div').click();
await page.locator('[data-test="dashboard-apply"]').click();
await page.reload();
@@ -25,19 +25,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
style="display: flex; flex-direction: row; align-items: center"
data-test="dashboard-query-data"
>
<div>
<q-icon
flat
:name="
!dashboardPanelData.layout.showQueryBar
? 'arrow_right'
: 'arrow_drop_down'
"
text-color="black"
class="q-mr-sm"
data-test="dashboard-panel-error-bar-icon"
/>
</div>
<q-space />
<div style="max-width: 600px">
<q-tabs
@@ -602,7 +589,7 @@ export default defineComponent({
.sql-bar {
height: 40px !important;
// overflow: hidden;
cursor: pointer;
// cursor: pointer;
}
.dashboard-query-remove-icon:hover {
+1 -1
View File
@@ -157,7 +157,7 @@ const getDefaultDashboardPanelData: any = (store: any) => ({
layout: {
splitter: 20,
querySplitter: 41,
showQueryBar: false,
showQueryBar: true,
isConfigPanelOpen: false,
currentQueryIndex: 0,
vrlFunctionToggle: false,
+145 -217
View File
@@ -180,138 +180,99 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</template>
<template #after>
<div
class="row"
style="height: calc(100vh - 99px); overflow-y: auto"
>
<div class="col" style="height: 100%">
<q-splitter
class="query-editor-splitter"
v-model="dashboardPanelData.layout.querySplitter"
horizontal
@update:model-value="querySplitterUpdated"
reverse
unit="px"
:limits="
!dashboardPanelData.layout.showQueryBar
? [43, 400]
: [140, 400]
"
:disable="!dashboardPanelData.layout.showQueryBar"
style="height: 100%"
>
<template #before>
<div
class="layout-panel-container col"
style="height: 100%"
>
<DashboardQueryBuilder
:dashboardData="currentDashboardData.data"
/>
<q-separator />
<VariablesValueSelector
v-if="dateTimeForVariables ||
(dashboardPanelData.meta.dateTime
&& dashboardPanelData.meta.dateTime.start_time
&& dashboardPanelData.meta.dateTime.end_time)"
:variablesConfig="currentDashboardData.data?.variables"
:showDynamicFilters="
currentDashboardData.data?.variables
?.showDynamicFilters
"
:selectedTimeDate="
dateTimeForVariables ||
dashboardPanelData.meta.dateTime
"
@variablesData="variablesDataUpdated"
:initialVariableValues="initialVariableValues"
/>
<div class="row">
<div
class="col scroll"
style="height: calc(100vh - 99px); overflow-y: auto"
>
<div class="layout-panel-container col">
<DashboardQueryBuilder
:dashboardData="currentDashboardData.data"
/>
<q-separator />
<VariablesValueSelector
v-if="
dateTimeForVariables ||
(dashboardPanelData.meta.dateTime &&
dashboardPanelData.meta.dateTime.start_time &&
dashboardPanelData.meta.dateTime.end_time)
"
:variablesConfig="currentDashboardData.data?.variables"
:showDynamicFilters="
currentDashboardData.data?.variables?.showDynamicFilters
"
:selectedTimeDate="
dateTimeForVariables || dashboardPanelData.meta.dateTime
"
@variablesData="variablesDataUpdated"
:initialVariableValues="initialVariableValues"
/>
<div v-if="isOutDated" class="tw-p-2">
<div
:style="{
borderColor: '#c3920d',
borderWidth: '1px',
borderStyle: 'solid',
backgroundColor:
store.state.theme == 'dark'
? '#2a1f03'
: '#faf2da',
padding: '1%',
borderRadius: '5px',
}"
>
<div style="font-weight: 700">
Your chart is not up to date
</div>
<div>
Chart Configuration / Variables has been updated,
but the chart was not updated automatically. Click
on the "Apply" button to run the query again
</div>
</div>
</div>
<div class="tw-flex tw-justify-end tw-mr-2">
<span v-if="lastTriggeredAt" class="lastRefreshedAt">
<span class="lastRefreshedAtIcon">🕑</span
><RelativeTime
:timestamp="lastTriggeredAt"
fullTimePrefix="Last Refreshed At: "
/>
</span>
</div>
<div class="col" style="flex: 1">
<PanelSchemaRenderer
v-if="chartData"
@metadata-update="metaDataValue"
:key="dashboardPanelData.data.type"
:panelSchema="chartData"
:dashboard-id="queryParams?.dashboard"
:folder-id="queryParams?.folder"
:selectedTimeObj="dashboardPanelData.meta.dateTime"
:variablesData="updatedVariablesData"
:allowAnnotationsAdd="editMode"
:width="6"
@error="handleChartApiError"
@updated:data-zoom="onDataZoom"
@updated:vrlFunctionFieldList="
updateVrlFunctionFieldList
"
@last-triggered-at-update="
handleLastTriggeredAtUpdate
"
searchType="dashboards"
/>
<q-dialog v-model="showViewPanel">
<QueryInspector
:metaData="metaData"
:data="panelTitle"
></QueryInspector>
</q-dialog>
</div>
<DashboardErrorsComponent
:errors="errorData"
class="col-auto"
style="flex-shrink: 0"
/>
</div>
</template>
<template #separator>
<div v-if="isOutDated" class="tw-p-2">
<div
class="splitter"
:class="
dashboardPanelData.layout.showQueryBar
? 'splitter-enabled'
: ''
"
></div>
</template>
<template #after>
<div style="height: 100%; width: 100%" class="row column">
<DashboardQueryEditor />
:style="{
borderColor: '#c3920d',
borderWidth: '1px',
borderStyle: 'solid',
backgroundColor:
store.state.theme == 'dark' ? '#2a1f03' : '#faf2da',
padding: '1%',
borderRadius: '5px',
}"
>
<div style="font-weight: 700">
Your chart is not up to date
</div>
<div>
Chart Configuration / Variables has been updated, but
the chart was not updated automatically. Click on the
"Apply" button to run the query again
</div>
</div>
</template>
</q-splitter>
</div>
<div class="tw-flex tw-justify-end tw-mr-2">
<span v-if="lastTriggeredAt" class="lastRefreshedAt">
<span class="lastRefreshedAtIcon">🕑</span
><RelativeTime
:timestamp="lastTriggeredAt"
fullTimePrefix="Last Refreshed At: "
/>
</span>
</div>
<div class="tw-h-[calc(100vh-500px)]">
<PanelSchemaRenderer
v-if="chartData"
@metadata-update="metaDataValue"
:key="dashboardPanelData.data.type"
:panelSchema="chartData"
:dashboard-id="queryParams?.dashboard"
:folder-id="queryParams?.folder"
:selectedTimeObj="dashboardPanelData.meta.dateTime"
:variablesData="updatedVariablesData"
:allowAnnotationsAdd="editMode"
:width="6"
@error="handleChartApiError"
@updated:data-zoom="onDataZoom"
@updated:vrlFunctionFieldList="updateVrlFunctionFieldList"
@last-triggered-at-update="handleLastTriggeredAtUpdate"
searchType="dashboards"
/>
<q-dialog v-model="showViewPanel">
<QueryInspector
:metaData="metaData"
:data="panelTitle"
></QueryInspector>
</q-dialog>
</div>
<DashboardErrorsComponent
:errors="errorData"
class="col-auto"
style="flex-shrink: 0"
/>
</div>
<div class="row column tw-h-[calc(100vh-180px)]">
<DashboardQueryEditor />
</div>
</div>
<q-separator vertical />
<div class="col-auto">
@@ -432,96 +393,63 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="row"
style="height: calc(100vh - 99px); overflow-y: auto"
>
<div class="col" style="height: 100%">
<q-splitter
class="query-editor-splitter"
v-model="dashboardPanelData.layout.querySplitter"
horizontal
@update:model-value="querySplitterUpdated"
reverse
unit="px"
:limits="
!dashboardPanelData.layout.showQueryBar
? [43, 400]
: [140, 400]
"
:disable="!dashboardPanelData.layout.showQueryBar"
style="height: 100%"
<div class="col scroll" style="height: 100%">
<div
class="layout-panel-container tw-h-[calc(100vh-200px)] col"
>
<template #before>
<div
class="layout-panel-container col"
style="height: 100%"
>
<q-splitter
class="query-editor-splitter"
v-model="splitterModel"
style="height: 100%"
@update:model-value="layoutSplitterUpdated"
>
<template #before>
<CustomChartEditor
v-model="dashboardPanelData.data.customChartContent"
style="width: 100%; height: 100%"
/>
</template>
<template #separator>
<div class="splitter-vertical splitter-enabled"></div>
<q-avatar
color="primary"
text-color="white"
size="20px"
icon="drag_indicator"
style="top: 10px; left: 3.5px"
data-test="dashboard-markdown-editor-drag-indicator"
/>
</template>
<template #after>
<PanelSchemaRenderer
v-if="chartData"
@metadata-update="metaDataValue"
:key="dashboardPanelData.data.type"
:panelSchema="chartData"
:dashboard-id="queryParams?.dashboard"
:folder-id="queryParams?.folder"
:selectedTimeObj="dashboardPanelData.meta.dateTime"
:variablesData="updatedVariablesData"
:width="6"
@error="handleChartApiError"
@updated:data-zoom="onDataZoom"
@updated:vrlFunctionFieldList="
updateVrlFunctionFieldList
"
@last-triggered-at-update="
handleLastTriggeredAtUpdate
"
searchType="dashboards"
/>
</template>
</q-splitter>
<DashboardErrorsComponent
:errors="errorData"
class="col-auto"
style="flex-shrink: 0"
<q-splitter
class="query-editor-splitter"
v-model="splitterModel"
style="height: 100%"
@update:model-value="layoutSplitterUpdated"
>
<template #before>
<CustomChartEditor
v-model="dashboardPanelData.data.customChartContent"
style="width: 100%; height: 100%"
/>
</div>
</template>
<template #separator>
<div
class="splitter"
:class="
dashboardPanelData.layout.showQueryBar
? 'splitter-enabled'
: ''
"
></div>
</template>
<template #after>
<div style="height: 100%; width: 100%" class="row column">
<DashboardQueryEditor />
</div>
</template>
</q-splitter>
</template>
<template #separator>
<div class="splitter-vertical splitter-enabled"></div>
<q-avatar
color="primary"
text-color="white"
size="20px"
icon="drag_indicator"
style="top: 10px; left: 3.5px"
data-test="dashboard-markdown-editor-drag-indicator"
/>
</template>
<template #after>
<PanelSchemaRenderer
v-if="chartData"
@metadata-update="metaDataValue"
:key="dashboardPanelData.data.type"
:panelSchema="chartData"
:dashboard-id="queryParams?.dashboard"
:folder-id="queryParams?.folder"
:selectedTimeObj="dashboardPanelData.meta.dateTime"
:variablesData="updatedVariablesData"
:width="6"
@error="handleChartApiError"
@updated:data-zoom="onDataZoom"
@updated:vrlFunctionFieldList="
updateVrlFunctionFieldList
"
@last-triggered-at-update="handleLastTriggeredAtUpdate"
searchType="dashboards"
/>
</template>
</q-splitter>
<DashboardErrorsComponent
:errors="errorData"
class="col-auto"
style="flex-shrink: 0"
/>
</div>
<div class="row column tw-h-[calc(100vh-180px)]">
<DashboardQueryEditor />
</div>
</div>
<q-separator vertical />
<div class="col-auto">