feat: added dashboard list and create tests (#3989)

* feat: added dashboard list and create tests

* feat: added widget tests
This commit is contained in:
Vikrant Gupta 2023-11-21 00:43:39 +05:30 committed by GitHub
parent 2a55f3d680
commit c4536f9069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 594 additions and 9 deletions

View File

@ -118,7 +118,11 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
)}
{addPanelPermission && (
<Button onClick={onAddPanelHandler} icon={<PlusOutlined />}>
<Button
onClick={onAddPanelHandler}
icon={<PlusOutlined />}
data-testid="add-panel"
>
{t('dashboard:add_panel')}
</Button>
)}

View File

@ -203,7 +203,7 @@ function WidgetHeader({
onClick={onClickHandler}
>
<HeaderContentContainer>
<Typography.Text style={{ maxWidth: '80%' }} ellipsis>
<Typography.Text style={{ maxWidth: '80%' }} ellipsis data-testid={title}>
{title}
</Typography.Text>
<ArrowContainer hover={parentHover}>

View File

@ -328,6 +328,7 @@ function ListOfAllDashboard(): JSX.Element {
<NewDashboardButton
icon={<PlusOutlined />}
type="primary"
data-testid="create-new-dashboard"
loading={newDashboardState.loading}
danger={newDashboardState.error}
>

View File

@ -18,7 +18,12 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
return (
<>
<Button type="dashed" onClick={showDrawer} style={{ width: '100%' }}>
<Button
type="dashed"
onClick={showDrawer}
style={{ width: '100%' }}
data-testid="show-drawer"
>
<SettingOutlined /> Configure
</Button>
<DrawerContainer

View File

@ -51,7 +51,11 @@ function DashboardDescription(): JSX.Element {
<Card>
<Row gutter={16}>
<Col flex={1} span={12}>
<Typography.Title level={4} style={{ padding: 0, margin: 0 }}>
<Typography.Title
level={4}
style={{ padding: 0, margin: 0 }}
data-testid="dashboard-landing-name"
>
{isDashboardLocked && (
<Tooltip title="Dashboard Locked" placement="top">
<LockFilled /> &nbsp;
@ -60,7 +64,12 @@ function DashboardDescription(): JSX.Element {
{title}
</Typography.Title>
{description && (
<Typography className="dashboard-description">{description}</Typography>
<Typography
className="dashboard-description"
data-testid="dashboard-landing-desc"
>
{description}
</Typography>
)}
{tags && (

View File

@ -63,6 +63,7 @@ function GeneralDashboardSettings(): JSX.Element {
<div>
<Typography style={{ marginBottom: '0.5rem' }}>Name</Typography>
<Input
data-testid="dashboard-name"
value={updatedTitle}
onChange={(e): void => setUpdatedTitle(e.target.value)}
/>
@ -71,6 +72,7 @@ function GeneralDashboardSettings(): JSX.Element {
<div>
<Typography style={{ marginBottom: '0.5rem' }}>Description</Typography>
<Input.TextArea
data-testid="dashboard-desc"
rows={5}
value={updatedDescription}
onChange={(e): void => setUpdatedDescription(e.target.value)}
@ -88,6 +90,7 @@ function GeneralDashboardSettings(): JSX.Element {
disabled={updateDashboardMutation.isLoading}
loading={updateDashboardMutation.isLoading}
icon={<SaveOutlined />}
data-testid="save-dashboard-config"
onClick={onSaveHandler}
type="primary"
>

View File

@ -181,6 +181,7 @@ function VariablesSetting(): JSX.Element {
<>
<Row style={{ flexDirection: 'row-reverse', padding: '0.5rem 0' }}>
<Button
data-testid="add-new-variable"
type="primary"
onClick={(): void =>
onVariableViewModeEnter('ADD', {} as IDashboardVariable)

View File

@ -261,7 +261,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
)}
{!isSaveDisabled && (
<Button type="primary" disabled={isSaveDisabled} onClick={onSaveDashboard}>
<Button
type="primary"
data-testid="new-widget-save"
disabled={isSaveDisabled}
onClick={onSaveDashboard}
>
Save
</Button>
)}

View File

@ -33,9 +33,7 @@ if (container) {
<Provider store={store}>
<AppRoutes />
</Provider>
{process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools initialIsOpen />
)}
{process.env.NODE_ENV === 'development' && <ReactQueryDevtools />}
</QueryClientProvider>
</ThemeProvider>
</HelmetProvider>

View File

@ -0,0 +1,143 @@
import { Page, test, expect } from '@playwright/test';
import { loginApi } from '../fixtures/common';
import ROUTES from 'constants/routes';
import dashboardsListEmptyResponse from '../fixtures/api/dashboard/getDashboardListEmpty200.json';
import createNewDashboardPostResponse from '../fixtures/api/dashboard/createNewDashboardPost200.json';
import queryRangeSuccessResponse from '../fixtures/api/traces/queryRange200.json';
import getIndividualDashboardResponse from '../fixtures/api/dashboard/getIndividualDashboard200.json';
import putNewDashboardResponse from '../fixtures/api/dashboard/putNewDashboardUpdate200.json';
import putDashboardTimeSeriesResponse from '../fixtures/api/dashboard/putDashboardWithTimeSeries200.json';
import dashboardGetCallWithTimeSeriesWidgetResponse from '../fixtures/api/dashboard/dashboardGetCallWithTimeSeriesWidget200.json';
import {
addPanelID,
configureDashboardDescriptonID,
configureDashboardNameID,
configureDashboardSettings,
dashboardDescription,
dashboardHomePageDesc,
dashboardHomePageTitle,
dashboardName,
dashboardsListAndCreate,
getDashboardsListEndpoint,
getIndividualDashboard,
getIndividualDashboardsEndpoint,
getTimeSeriesQueryData,
newDashboardBtnID,
saveConfigureDashboardID,
timeSeriesGraphName,
timeSeriesPanelID,
} from './utils';
let page: Page;
test.describe('Dashboards Landing Page', () => {
test.beforeEach(async ({ baseURL, browser }) => {
const context = await browser.newContext({
storageState: 'tests/auth.json',
});
const newPage = await context.newPage();
await loginApi(newPage);
await newPage.goto(`${baseURL}${ROUTES.APPLICATION}`);
page = newPage;
});
test('Create a new dashboard and configure the name and description', async ({}) => {
// render the dashboards list page with empty response
await dashboardsListAndCreate(page, dashboardsListEmptyResponse);
// navigate to the dashboards landing page
await page.locator(`li[data-menu-id*="/dashboard"]`).click();
await page.waitForRequest(`**/${getDashboardsListEndpoint}`);
// without data we should have no data rendering
const noDataText = await page.getByText('No data');
await expect(noDataText).toBeVisible();
// create a new dashboard
await page.locator(`data-testid=${newDashboardBtnID}`).click();
await dashboardsListAndCreate(page, createNewDashboardPostResponse);
await getIndividualDashboard(page, getIndividualDashboardResponse);
await page.locator(`li[data-menu-id*="Create"]`).click();
await page.waitForRequest(`**/${getIndividualDashboardsEndpoint}`);
await page.locator(`data-testid=${configureDashboardSettings}`).click();
const dashboardNameInput = await page.locator(
`data-testid=${configureDashboardNameID}`,
);
// edit the name of the dashboard
await dashboardNameInput.fill('');
await dashboardNameInput.fill(`${dashboardName}`);
// edit the description of the dashboard
const dashboardDescInput = await page.locator(
`data-testid=${configureDashboardDescriptonID}`,
);
await dashboardDescInput.fill('');
await dashboardDescInput.fill(`${dashboardDescription}`);
await getIndividualDashboard(page, putNewDashboardResponse);
await page.locator(`data-testid=${saveConfigureDashboardID}`).click();
await page.locator(`svg[data-icon="close"]`).click();
// save the configs and check for updated values
const dashboardTitle = await page
.locator(`data-testid=${dashboardHomePageTitle}`)
.textContent();
expect(dashboardTitle).toBe(`${dashboardName}`);
const dashboardDesc = await page
.locator(`data-testid=${dashboardHomePageDesc}`)
.textContent();
expect(dashboardDesc).toBe(`${dashboardDescription}`);
await page.locator(`data-testid=${addPanelID}`).click();
await getIndividualDashboard(page, putDashboardTimeSeriesResponse, true);
await getTimeSeriesQueryData(page, queryRangeSuccessResponse);
await page.locator(`id=${timeSeriesPanelID}`).click();
await page.waitForRequest(`**/${getIndividualDashboardsEndpoint}`);
const panelTitle = await page.getByText('Panel Title').isVisible();
await expect(panelTitle).toBeTruthy();
await page.getByPlaceholder('Title').type(`${timeSeriesGraphName}`);
await page.locator('data-testid=new-widget-save').click();
await getIndividualDashboard(
page,
dashboardGetCallWithTimeSeriesWidgetResponse,
);
await page.locator('span:has-text("OK")').click();
await page.waitForLoadState('networkidle');
const timeSeriesWidget = await await page.locator(
`data-testid=${timeSeriesGraphName}`,
);
await expect(timeSeriesWidget).toBeTruthy();
});
});

View File

@ -0,0 +1,180 @@
import { Page } from '@playwright/test';
import { JsonApplicationType } from '../fixtures/constant';
// API endpoints
export const getDashboardsListEndpoint = 'v1/dashboards';
export const getIndividualDashboardsEndpoint = 'v1/dashboards/**';
export const queryRangeApiEndpoint = 'query_range';
// element's data-testid's
export const newDashboardBtnID = 'create-new-dashboard';
export const configureDashboardSettings = 'show-drawer';
export const configureDashboardNameID = 'dashboard-name';
export const configureDashboardDescriptonID = 'dashboard-desc';
export const dashboardHomePageTitle = 'dashboard-landing-name';
export const dashboardHomePageDesc = 'dashboard-landing-desc';
export const saveConfigureDashboardID = 'save-dashboard-config';
export const addNewVariableID = 'add-new-variable';
export const dashboardName = 'Playwright Dashboard';
export const dashboardDescription = 'Playwright Dashboard Description';
export const addPanelID = 'add-panel';
export const timeSeriesPanelID = 'graph';
export const valuePanelID = 'value';
export const tablePanelID = 'table';
export const timeSeriesGraphName = 'Time1';
let widgetsId: string;
// mock API calls
export const dashboardsListAndCreate = async (
page: Page,
response: any,
): Promise<void> => {
await page.route(`**/${getDashboardsListEndpoint}`, (route) =>
route.fulfill({
status: 200,
contentType: JsonApplicationType,
json: response,
}),
);
};
export const getIndividualDashboard = async (
page: Page,
response?: any,
useRequestObject?: boolean,
): Promise<void> => {
await page.route(`**/${getIndividualDashboardsEndpoint}`, (route, request) => {
if (useRequestObject && request.method() === 'PUT') {
widgetsId = request.postDataJSON()?.widgets[0].id;
}
route.fulfill({
status: 200,
contentType: JsonApplicationType,
json: useRequestObject ? insertWidgetIdInResponse(widgetsId) : response,
});
});
};
export const getTimeSeriesQueryData = async (
page: Page,
response: any,
): Promise<void> => {
await page.route(`**/${queryRangeApiEndpoint}`, (route) =>
route.fulfill({
status: 200,
contentType: JsonApplicationType,
json: response,
}),
);
};
export const insertWidgetIdInResponse = (widgetID: string) => {
return {
status: 'success',
data: {
id: 219,
uuid: 'd697fddb-a771-4bb4-aa38-810f000ed96a',
created_at: '2023-11-17T20:44:03.167646604Z',
created_by: 'vikrant@signoz.io',
updated_at: '2023-11-17T20:51:23.058536475Z',
updated_by: 'vikrant@signoz.io',
data: {
description: 'Playwright Dashboard T',
layout: [
{
h: 3,
i: '9fbcf0db-1572-4572-bf6b-0a84dd10ed85',
w: 6,
x: 0,
y: 0,
},
],
name: '',
tags: [],
title: 'Playwright Dashboard',
variables: {},
widgets: [
{
description: '',
id: widgetID,
isStacked: false,
nullZeroValues: '',
opacity: '',
panelTypes: 'graph',
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: '',
id: '------',
isColumn: false,
isJSON: false,
key: '',
type: '',
},
aggregateOperator: 'count',
dataSource: 'metrics',
disabled: false,
expression: 'A',
filters: {
items: [],
op: 'AND',
},
groupBy: [],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'sum',
stepInterval: 60,
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '6b4011e4-bcea-497d-81a9-0ee7816b679d',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: 'builder',
},
timePreferance: 'GLOBAL_TIME',
title: '',
},
],
},
isLocked: 0,
},
};
};

View File

@ -0,0 +1,16 @@
{
"status": "success",
"data": {
"id": 219,
"uuid": "d697fddb-a771-4bb4-aa38-810f000ed96a",
"created_at": "2023-11-17T18:36:36.185916891Z",
"created_by": "vikrant@signoz.io",
"updated_at": "2023-11-17T18:36:36.185916989Z",
"updated_by": "vikrant@signoz.io",
"data": {
"title": "Sample Title",
"uploadedGrafana": false
},
"isLocked": null
}
}

View File

@ -0,0 +1,91 @@
{
"status": "success",
"data": {
"id": 219,
"uuid": "d697fddb-a771-4bb4-aa38-810f000ed96a",
"created_at": "2023-11-17T20:44:03.167646604Z",
"created_by": "vikrant@signoz.io",
"updated_at": "2023-11-17T20:51:23.058536475Z",
"updated_by": "vikrant@signoz.io",
"data": {
"description": "Playwright Dashboard T",
"layout": [
{
"h": 3,
"i": "9fbcf0db-1572-4572-bf6b-0a84dd10ed85",
"w": 6,
"x": 0,
"y": 0
}
],
"name": "",
"tags": [],
"title": "Playwright Dashboard",
"variables": {},
"widgets": [
{
"description": "",
"id": "9fbcf0db-1572-4572-bf6b-0a84dd10ed85",
"isStacked": false,
"nullZeroValues": "",
"opacity": "",
"panelTypes": "graph",
"query": {
"builder": {
"queryData": [
{
"aggregateAttribute": {
"dataType": "",
"id": "------",
"isColumn": false,
"isJSON": false,
"key": "",
"type": ""
},
"aggregateOperator": "count",
"dataSource": "metrics",
"disabled": false,
"expression": "A",
"filters": {
"items": [],
"op": "AND"
},
"groupBy": [],
"having": [],
"legend": "",
"limit": null,
"orderBy": [],
"queryName": "A",
"reduceTo": "sum",
"stepInterval": 60
}
],
"queryFormulas": []
},
"clickhouse_sql": [
{
"disabled": false,
"legend": "",
"name": "A",
"query": ""
}
],
"id": "6b4011e4-bcea-497d-81a9-0ee7816b679d",
"promql": [
{
"disabled": false,
"legend": "",
"name": "A",
"query": ""
}
],
"queryType": "builder"
},
"timePreferance": "GLOBAL_TIME",
"title": "Time1"
}
]
},
"isLocked": 0
}
}

View File

@ -0,0 +1,4 @@
{
"status": "success",
"data": []
}

View File

@ -0,0 +1,16 @@
{
"status": "success",
"data": {
"id": 219,
"uuid": "d697fddb-a771-4bb4-aa38-810f000ed96a",
"created_at": "2023-11-17T18:36:36.185916891Z",
"created_by": "vikrant@signoz.io",
"updated_at": "2023-11-17T18:36:36.185916989Z",
"updated_by": "vikrant@signoz.io",
"data": {
"title": "Sample Title",
"uploadedGrafana": false
},
"isLocked": 0
}
}

View File

@ -0,0 +1,91 @@
{
"status": "success",
"data": {
"id": 219,
"uuid": "d697fddb-a771-4bb4-aa38-810f000ed96a",
"created_at": "2023-11-17T20:44:03.167646604Z",
"created_by": "vikrant@signoz.io",
"updated_at": "2023-11-17T20:51:23.058536475Z",
"updated_by": "vikrant@signoz.io",
"data": {
"description": "Playwright Dashboard T",
"layout": [
{
"h": 3,
"i": "9fbcf0db-1572-4572-bf6b-0a84dd10ed85",
"w": 6,
"x": 0,
"y": 0
}
],
"name": "",
"tags": [],
"title": "Playwright Dashboard",
"variables": {},
"widgets": [
{
"description": "",
"id": "9fbcf0db-1572-4572-bf6b-0a84dd10ed85",
"isStacked": false,
"nullZeroValues": "",
"opacity": "",
"panelTypes": "graph",
"query": {
"builder": {
"queryData": [
{
"aggregateAttribute": {
"dataType": "",
"id": "------",
"isColumn": false,
"isJSON": false,
"key": "",
"type": ""
},
"aggregateOperator": "count",
"dataSource": "metrics",
"disabled": false,
"expression": "A",
"filters": {
"items": [],
"op": "AND"
},
"groupBy": [],
"having": [],
"legend": "",
"limit": null,
"orderBy": [],
"queryName": "A",
"reduceTo": "sum",
"stepInterval": 60
}
],
"queryFormulas": []
},
"clickhouse_sql": [
{
"disabled": false,
"legend": "",
"name": "A",
"query": ""
}
],
"id": "6b4011e4-bcea-497d-81a9-0ee7816b679d",
"promql": [
{
"disabled": false,
"legend": "",
"name": "A",
"query": ""
}
],
"queryType": "builder"
},
"timePreferance": "GLOBAL_TIME",
"title": ""
}
]
},
"isLocked": 0
}
}

View File

@ -0,0 +1,18 @@
{
"status": "success",
"data": {
"id": 219,
"uuid": "d697fddb-a771-4bb4-aa38-810f000ed96a",
"created_at": "2023-11-17T18:47:15.740385406Z",
"created_by": "vikrant@signoz.io",
"updated_at": "2023-11-17T19:11:25.052190048Z",
"updated_by": "vikrant@signoz.io",
"data": {
"description": "Playwright Dashboard Description",
"tags": [],
"title": "Playwright Dashboard",
"uploadedGrafana": false
},
"isLocked": 0
}
}