mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
## Summary Addresses npm security vulnerabilities in transitive dependencies. Prefer direct dependency upgrades over broad resolutions where possible. ## Changes **Direct upgrade:** - **`@slack/webhook`**: `^6.1.0` → `^7.0.0` — v7 natively uses axios v1, eliminating the axios@0.21.4 SSRF/redirect vulnerabilities. Only breaking change in v7 is dropping Node <18 (we're on Node 22). **Resolutions for transitive deps with no direct upgrade path:** - **`fast-xml-parser`**: `^4.4.0` — fixes prototype pollution (High) - **`systeminformation`**: `^5.24.0` — fixes command injection (High) ## Removed/Not Done - `axios` resolution removed — covered by the `@slack/webhook` upgrade instead - `tar` resolution removed — was a v6→v7 major jump on build-only tools (`cacache`, `node-gyp`); not present in the production image - `glob` resolution removed — was breaking test coverage tooling (`test-exclude@6` depends on glob@^7) ## Related Follow-up to #1731 which addressed base image vulnerabilities (Node, Go, ClickHouse).
183 lines
4.9 KiB
TypeScript
183 lines
4.9 KiB
TypeScript
/**
|
|
* ChartEditorComponent - Reusable component for chart/tile editor
|
|
* Used for creating and configuring dashboard tiles and chart explorer
|
|
*/
|
|
import { Locator, Page } from '@playwright/test';
|
|
|
|
import { getSqlEditor } from '../utils/locators';
|
|
|
|
export class ChartEditorComponent {
|
|
readonly page: Page;
|
|
private readonly chartNameInput: Locator;
|
|
private readonly sourceSelector: Locator;
|
|
private readonly metricSelector: Locator;
|
|
private readonly runQueryButton: Locator;
|
|
private readonly saveButton: Locator;
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
this.chartNameInput = page.getByTestId('chart-name-input');
|
|
this.sourceSelector = page.getByTestId('source-selector');
|
|
this.metricSelector = page.getByTestId('metric-name-selector');
|
|
this.runQueryButton = page.getByTestId('chart-run-query-button');
|
|
this.saveButton = page.getByTestId('chart-save-button');
|
|
}
|
|
|
|
/**
|
|
* Set chart name
|
|
*/
|
|
async setChartName(name: string) {
|
|
await this.chartNameInput.fill(name);
|
|
}
|
|
|
|
/**
|
|
* Set group by expression
|
|
*/
|
|
async setGroupBy(expression: string) {
|
|
const groupByInput = getSqlEditor(this.page, 'SQL Columns');
|
|
await groupByInput.click();
|
|
await this.page.keyboard.type(expression);
|
|
}
|
|
|
|
/**
|
|
* Select a data source
|
|
*/
|
|
async selectSource(sourceName: string) {
|
|
await this.sourceSelector.click();
|
|
// Use getByRole for more reliable selection
|
|
const sourceOption = this.page.getByRole('option', { name: sourceName });
|
|
if ((await sourceOption.getAttribute('data-combobox-active')) != 'true') {
|
|
await sourceOption.click({ timeout: 5000 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select a metric by name
|
|
*/
|
|
async selectMetric(metricName: string, metricValue?: string) {
|
|
// Wait for metric selector to be visible
|
|
await this.metricSelector.waitFor({ state: 'visible', timeout: 5000 });
|
|
|
|
// Click to open dropdown
|
|
await this.metricSelector.click();
|
|
|
|
// Type to filter
|
|
await this.metricSelector.fill(metricName);
|
|
|
|
// If a specific metric value is provided, wait for and click it
|
|
if (metricValue) {
|
|
// Use attribute selector for combobox options
|
|
const targetMetricOption = this.page.locator(
|
|
`[data-combobox-option="true"][value="${metricValue}"]`,
|
|
);
|
|
await targetMetricOption.waitFor({ state: 'visible', timeout: 5000 });
|
|
await targetMetricOption.click({ timeout: 5000 });
|
|
} else {
|
|
// Otherwise just press Enter to select the first match
|
|
await this.page.keyboard.press('Enter');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the query and wait for it to complete
|
|
*/
|
|
async runQuery() {
|
|
await this.runQueryButton.click();
|
|
// need to wait for the recharts graph to render
|
|
await this.page
|
|
.locator('.recharts-responsive-container')
|
|
.first()
|
|
.waitFor({ state: 'visible', timeout: 10000 });
|
|
}
|
|
|
|
/**
|
|
* Save the chart/tile and wait for modal to close
|
|
*/
|
|
async save() {
|
|
await this.saveButton.click();
|
|
// Wait for save button to disappear (modal closes)
|
|
await this.saveButton.waitFor({ state: 'hidden', timeout: 2000 });
|
|
}
|
|
|
|
/**
|
|
* Wait for chart editor data to load (sources, metrics, etc.)
|
|
*/
|
|
async waitForDataToLoad() {
|
|
await this.runQueryButton.waitFor({ state: 'visible', timeout: 2000 });
|
|
await this.page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
/**
|
|
* Complete workflow: create a basic chart with name and save
|
|
*/
|
|
async createBasicChart(name: string) {
|
|
// Wait for data sources to load before interacting
|
|
await this.waitForDataToLoad();
|
|
await this.setChartName(name);
|
|
await this.runQuery();
|
|
await this.save();
|
|
}
|
|
|
|
/**
|
|
* Complete workflow: create a chart with specific source and metric
|
|
*/
|
|
async createChartWithMetric(
|
|
chartName: string,
|
|
sourceName: string,
|
|
metricName: string,
|
|
metricValue?: string,
|
|
) {
|
|
// Wait for data sources to load before interacting
|
|
await this.waitForDataToLoad();
|
|
await this.selectSource(sourceName);
|
|
await this.selectMetric(metricName, metricValue);
|
|
await this.runQuery();
|
|
await this.save();
|
|
}
|
|
|
|
/**
|
|
* Complete workflow: create a chart with specific source and metric
|
|
*/
|
|
async createTable({
|
|
chartName,
|
|
sourceName,
|
|
groupBy,
|
|
}: {
|
|
chartName: string;
|
|
sourceName: string;
|
|
groupBy?: string;
|
|
}) {
|
|
// Wait for data sources to load before interacting
|
|
await this.waitForDataToLoad();
|
|
|
|
const tableButton = this.page.getByRole('tab', { name: 'Table' });
|
|
await tableButton.click();
|
|
|
|
await this.setChartName(chartName);
|
|
await this.selectSource(sourceName);
|
|
if (groupBy) await this.setGroupBy(groupBy);
|
|
await this.save();
|
|
}
|
|
|
|
// Getters for assertions
|
|
|
|
get nameInput() {
|
|
return this.chartNameInput;
|
|
}
|
|
|
|
get source() {
|
|
return this.sourceSelector;
|
|
}
|
|
|
|
get metric() {
|
|
return this.metricSelector;
|
|
}
|
|
|
|
get runButton() {
|
|
return this.runQueryButton;
|
|
}
|
|
|
|
get saveBtn() {
|
|
return this.saveButton;
|
|
}
|
|
}
|