diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index a6b1186880..e3a346ef82 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -118,7 +118,11 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { )} {addPanelPermission && ( - )} diff --git a/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx index 9d48113910..a1a588b85a 100644 --- a/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx @@ -203,7 +203,7 @@ function WidgetHeader({ onClick={onClickHandler} > - + {title} diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index c0db8379f7..dce9b5835d 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -328,6 +328,7 @@ function ListOfAllDashboard(): JSX.Element { } type="primary" + data-testid="create-new-dashboard" loading={newDashboardState.loading} danger={newDashboardState.error} > diff --git a/frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx b/frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx index 44b697cc67..89daacf094 100644 --- a/frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx +++ b/frontend/src/container/NewDashboard/DashboardDescription/SettingsDrawer.tsx @@ -18,7 +18,12 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element { return ( <> - - + {isDashboardLocked && (   @@ -60,7 +64,12 @@ function DashboardDescription(): JSX.Element { {title} {description && ( - {description} + + {description} + )} {tags && ( diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx index ad47526cf8..16c98bb54e 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx @@ -63,6 +63,7 @@ function GeneralDashboardSettings(): JSX.Element {
Name setUpdatedTitle(e.target.value)} /> @@ -71,6 +72,7 @@ function GeneralDashboardSettings(): JSX.Element {
Description setUpdatedDescription(e.target.value)} @@ -88,6 +90,7 @@ function GeneralDashboardSettings(): JSX.Element { disabled={updateDashboardMutation.isLoading} loading={updateDashboardMutation.isLoading} icon={} + data-testid="save-dashboard-config" onClick={onSaveHandler} type="primary" > diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx index 388dffc286..754e44f1bc 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx @@ -181,6 +181,7 @@ function VariablesSetting(): JSX.Element { <> )} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index d872b6adc2..3c3a51e75c 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -33,9 +33,7 @@ if (container) { - {process.env.NODE_ENV === 'development' && ( - - )} + {process.env.NODE_ENV === 'development' && } diff --git a/frontend/tests/dashboards/index.spec.ts b/frontend/tests/dashboards/index.spec.ts new file mode 100644 index 0000000000..38688c8e3c --- /dev/null +++ b/frontend/tests/dashboards/index.spec.ts @@ -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(); + }); +}); diff --git a/frontend/tests/dashboards/utils.ts b/frontend/tests/dashboards/utils.ts new file mode 100644 index 0000000000..b69aedf905 --- /dev/null +++ b/frontend/tests/dashboards/utils.ts @@ -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 => { + await page.route(`**/${getDashboardsListEndpoint}`, (route) => + route.fulfill({ + status: 200, + contentType: JsonApplicationType, + json: response, + }), + ); +}; + +export const getIndividualDashboard = async ( + page: Page, + response?: any, + useRequestObject?: boolean, +): Promise => { + 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 => { + 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, + }, + }; +}; diff --git a/frontend/tests/fixtures/api/dashboard/createNewDashboardPost200.json b/frontend/tests/fixtures/api/dashboard/createNewDashboardPost200.json new file mode 100644 index 0000000000..7f506807f7 --- /dev/null +++ b/frontend/tests/fixtures/api/dashboard/createNewDashboardPost200.json @@ -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 + } +} diff --git a/frontend/tests/fixtures/api/dashboard/dashboardGetCallWithTimeSeriesWidget200.json b/frontend/tests/fixtures/api/dashboard/dashboardGetCallWithTimeSeriesWidget200.json new file mode 100644 index 0000000000..e65361d6e7 --- /dev/null +++ b/frontend/tests/fixtures/api/dashboard/dashboardGetCallWithTimeSeriesWidget200.json @@ -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 + } +} diff --git a/frontend/tests/fixtures/api/dashboard/getDashboardListEmpty200.json b/frontend/tests/fixtures/api/dashboard/getDashboardListEmpty200.json new file mode 100644 index 0000000000..17448b747c --- /dev/null +++ b/frontend/tests/fixtures/api/dashboard/getDashboardListEmpty200.json @@ -0,0 +1,4 @@ +{ + "status": "success", + "data": [] +} diff --git a/frontend/tests/fixtures/api/dashboard/getIndividualDashboard200.json b/frontend/tests/fixtures/api/dashboard/getIndividualDashboard200.json new file mode 100644 index 0000000000..e39b47afa2 --- /dev/null +++ b/frontend/tests/fixtures/api/dashboard/getIndividualDashboard200.json @@ -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 + } +} diff --git a/frontend/tests/fixtures/api/dashboard/putDashboardWithTimeSeries200.json b/frontend/tests/fixtures/api/dashboard/putDashboardWithTimeSeries200.json new file mode 100644 index 0000000000..7dab6c646b --- /dev/null +++ b/frontend/tests/fixtures/api/dashboard/putDashboardWithTimeSeries200.json @@ -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 + } +} diff --git a/frontend/tests/fixtures/api/dashboard/putNewDashboardUpdate200.json b/frontend/tests/fixtures/api/dashboard/putNewDashboardUpdate200.json new file mode 100644 index 0000000000..ce5bfdc429 --- /dev/null +++ b/frontend/tests/fixtures/api/dashboard/putNewDashboardUpdate200.json @@ -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 + } +}