Files
openobserve/tests/ui-testing/playwright-tests/utils/panel-time-helpers.js
T
0ee7535da7 feat: dashboard time range available at panel level (#10288)
Design at:
https://github.com/openobserve/designs/blob/main/dashboards/panel-level-time-range/design.md

### **PR Type**
Enhancement, Documentation


___

### **Description**
- Add defaults for panel-level time settings in front-end

- Extend backend PanelConfig with time range fields

- Provide a comprehensive implementation guide document


___



<details> <summary><h3> File Walkthrough</h3></summary>

<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>useDashboardPanel.ts</strong><dd><code>Add default
panel time settings fields</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/composables/useDashboardPanel.ts

<ul><li>Added <code>allow_panel_time</code> default (false)<br> <li>
Introduced <code>panel_time_mode</code> default ("individual")<br> <li>
Initialized <code>panel_time_range</code> to null</ul>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>mod.rs</strong><dd><code>Extend PanelConfig with time
fields</code>&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>

src/config/src/meta/dashboards/v8/mod.rs

<ul><li>Added <code>allow_panel_time: Option<bool></code> to
PanelConfig<br> <li> Added <code>panel_time_mode: Option<String></code>
field<br> <li> Added <code>panel_time_range:
Option<DateTimeOptions></code> field</ul>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10288/files#diff-cbf3fe971525ba58892400391dfd7dac7e43d6049d6b4f5a9840ad104ebc5e08">+12/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>

<summary><strong>PANEL_TIME_RANGE_IMPLEMENTATION.md</strong><dd><code>Add
panel time range implementation guide</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

web/src/components/dashboards/PANEL_TIME_RANGE_IMPLEMENTATION.md

<ul><li>Added detailed implementation guide (v3.0)<br> <li> Outlined
requirements, UI, URL sync, testing<br> <li> Documented data flow, edge
cases, and timeline</ul>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10288/files#diff-39b68cc51ce3089b41061b75e18c45448659cab57702dd39b01a9aad63598345">+2718/-0</a></td>

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

</details>

___

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
2026-02-26 00:58:02 +05:30

257 lines
8.1 KiB
JavaScript

/**
* Panel Time Testing Helper Utilities
* Provides common methods for panel time API monitoring, assertions, and state validation
*/
import testLogger from './test-logger.js';
/**
* Monitor panel data API calls (query endpoint)
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {Object} options - Configuration options
* @param {number} options.expectedCount - Expected number of API calls (default: 1)
* @param {number} options.timeout - Timeout in milliseconds (default: 15000)
* @param {string} options.panelId - Optional panel ID to filter specific panel calls
* @returns {Promise<Object>} - {success, actualCount, calls, timedOut}
*/
export async function monitorPanelDataAPICalls(page, options = {}) {
const { expectedCount = 1, timeout = 15000, panelId = null } = options;
const startTime = Date.now();
const apiCalls = [];
return new Promise((resolve) => {
const responseHandler = async (response) => {
const url = response.url();
// Match query endpoints that panels use to fetch data
if (url.includes('/query') || url.includes('/_search')) {
try {
const status = response.status();
const callInfo = {
url,
status,
timestamp: Date.now(),
duration: Date.now() - startTime,
panelId: panelId || 'unknown',
};
apiCalls.push(callInfo);
testLogger.info(`Panel data API call ${apiCalls.length}/${expectedCount}: ${status}`);
if (apiCalls.length >= expectedCount) {
page.off('response', responseHandler);
resolve({
success: true,
actualCount: apiCalls.length,
calls: apiCalls,
timedOut: false,
totalDuration: Date.now() - startTime
});
}
} catch (error) {
testLogger.debug(`Error reading response: ${error.message}`);
}
}
};
page.on('response', responseHandler);
setTimeout(() => {
page.off('response', responseHandler);
testLogger.warn(`Panel data API monitoring timed out: ${apiCalls.length}/${expectedCount} calls completed`);
resolve({
success: apiCalls.length >= expectedCount,
actualCount: apiCalls.length,
calls: apiCalls,
timedOut: true,
totalDuration: timeout
});
}, timeout);
});
}
/**
* Assert that API calls were triggered successfully
* @param {Object} result - Result from monitorPanelDataAPICalls
* @param {Object} expectations - Expected values
*/
export function assertPanelDataAPITriggered(result, expectations = {}) {
const {
success = true,
minCalls = 1,
maxDuration = 10000
} = expectations;
if (success) {
if (!result.success) {
throw new Error(`Expected API calls to succeed, but got ${result.actualCount} calls`);
}
if (result.actualCount < minCalls) {
throw new Error(`Expected at least ${minCalls} API calls, got ${result.actualCount}`);
}
}
if (maxDuration && result.totalDuration > maxDuration) {
testLogger.warn(`API calls took ${result.totalDuration}ms, expected max ${maxDuration}ms`);
}
}
/**
* Wait for URL to contain specific panel time parameter
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {string} panelId - Panel ID
* @param {string} expectedValue - Expected time value (e.g., "1h", "7d")
* @param {number} timeout - Timeout in milliseconds
*/
export async function waitForPanelTimeInURL(page, panelId, expectedValue, timeout = 5000) {
const startTime = Date.now();
const expectedParam = `pt-period.${panelId}=${expectedValue}`;
while (Date.now() - startTime < timeout) {
const url = page.url();
if (url.includes(expectedParam)) {
return true;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error(`URL did not contain ${expectedParam} within ${timeout}ms. Current URL: ${page.url()}`);
}
/**
* Wait for panel time parameter to be removed from URL
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {string} panelId - Panel ID
* @param {number} timeout - Timeout in milliseconds
*/
export async function waitForPanelTimeRemovedFromURL(page, panelId, timeout = 5000) {
const startTime = Date.now();
const paramPeriod = `pt-period.${panelId}`;
const paramFrom = `pt-from.${panelId}`;
while (Date.now() - startTime < timeout) {
const url = page.url();
if (!url.includes(paramPeriod) && !url.includes(paramFrom)) {
return true;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error(`URL still contains panel time params after ${timeout}ms. Current URL: ${page.url()}`);
}
/**
* Get panel time value from URL
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {string} panelId - Panel ID
* @returns {Object|null} - {type: 'relative'|'absolute', value: string, from: number, to: number}
*/
export function getPanelTimeFromURL(page, panelId) {
const url = new URL(page.url());
const relativeParam = url.searchParams.get(`pt-period.${panelId}`);
const fromParam = url.searchParams.get(`pt-from.${panelId}`);
const toParam = url.searchParams.get(`pt-to.${panelId}`);
if (relativeParam) {
return {
type: 'relative',
value: relativeParam
};
}
if (fromParam && toParam) {
return {
type: 'absolute',
from: parseInt(fromParam),
to: parseInt(toParam)
};
}
return null;
}
/**
* Verify that panel data has NOT refreshed (no API calls made)
* This is used to verify that data doesn't refresh before Apply is clicked
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {number} waitTime - Time to wait in milliseconds (default: 2000)
* @returns {Promise<boolean>} - true if no API calls detected
*/
export async function verifyNoDataRefresh(page, waitTime = 2000) {
let apiCallDetected = false;
const responseHandler = async (response) => {
const url = response.url();
if (url.includes('/query') || url.includes('/_search')) {
apiCallDetected = true;
}
};
page.on('response', responseHandler);
await new Promise(resolve => setTimeout(resolve, waitTime));
page.off('response', responseHandler);
if (apiCallDetected) {
throw new Error('Unexpected API call detected - panel should not refresh yet');
}
return true;
}
/**
* Wait for panel to start loading (loading indicator appears)
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {string} panelId - Panel ID
* @param {number} timeout - Timeout in milliseconds
*/
export async function waitForPanelLoadStart(page, panelId, timeout = 5000) {
const loadingSelector = `[data-test="panel-${panelId}-loading"]`;
try {
await page.waitForSelector(loadingSelector, { state: "visible", timeout });
return true;
} catch (error) {
testLogger.debug(`Panel ${panelId} loading indicator did not appear within ${timeout}ms`);
return false;
}
}
/**
* Parse time range display text to standard format
* @param {string} displayText - Display text from time picker (e.g., "Last 1 hour", "Last 7 days")
* @returns {string} - Standard format (e.g., "1h", "7d")
*/
export function parseTimeDisplayText(displayText) {
const text = displayText.toLowerCase().trim();
const patterns = [
{ regex: /(\d+)\s*minute?s?/, unit: 'm' },
{ regex: /(\d+)\s*hour?s?/, unit: 'h' },
{ regex: /(\d+)\s*day?s?/, unit: 'd' },
{ regex: /(\d+)\s*week?s?/, unit: 'w' },
{ regex: /(\d+)\s*month?s?/, unit: 'M' },
];
for (const pattern of patterns) {
const match = text.match(pattern.regex);
if (match) {
return `${match[1]}${pattern.unit}`;
}
}
return text;
}
/**
* Wait for network idle with custom timeout
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {number} timeout - Timeout in milliseconds
*/
export async function waitForNetworkIdle(page, timeout = 3000) {
await page.waitForLoadState('networkidle', { timeout }).catch(() => {
testLogger.debug('Network did not become idle within timeout');
});
}