From a4308fffe74bfee93e5d8221e7a7945004263430 Mon Sep 17 00:00:00 2001 From: Steven Vachon Date: Thu, 26 Mar 2020 12:15:58 -0400 Subject: [PATCH] @grafana/e2e: API improvements (#23079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Minor changes * Fixtures path is now relative to the project directory * URL support module now has individual exports * Scenario context timing issues resolved ... caused by being ran synchronously, instead of as part of Cypress' asynchronous queue. * Scenario context API now supports multiple keys per function call * addDataSource flow accepts a config argument … and optionally checks datasource health status * Added readProvisions command * Added addPanel flow --- e2e/suite1/specs/queryVariableCrud.spec.ts | 24 +++++--- e2e/suite1/specs/smoketests.spec.ts | 6 +- .../cypress/plugins/extendConfig.js | 1 + packages/grafana-e2e/cypress/plugins/index.js | 3 +- .../cypress/plugins/readProvisions.js | 14 +++++ .../grafana-e2e/cypress/support/commands.ts | 7 +++ .../grafana-e2e/cypress/support/index.d.ts | 1 + packages/grafana-e2e/package.json | 3 +- .../grafana-e2e/src/flows/addDashboard.ts | 8 ++- .../grafana-e2e/src/flows/addDataSource.ts | 56 ++++++++++++++++--- packages/grafana-e2e/src/flows/addPanel.ts | 30 ++++++++++ .../grafana-e2e/src/flows/deleteDashboard.ts | 4 +- .../grafana-e2e/src/flows/deleteDataSource.ts | 4 +- packages/grafana-e2e/src/flows/index.ts | 18 +++--- .../grafana-e2e/src/flows/saveNewDashboard.ts | 2 +- packages/grafana-e2e/src/noTypeCheck.ts | 5 +- packages/grafana-e2e/src/support/scenario.ts | 55 +++++++++--------- .../src/support/scenarioContext.ts | 43 ++++++++------ packages/grafana-e2e/src/support/types.ts | 6 +- packages/grafana-e2e/src/support/url.ts | 41 +++++++------- 20 files changed, 226 insertions(+), 105 deletions(-) create mode 100644 packages/grafana-e2e/cypress/plugins/readProvisions.js create mode 100644 packages/grafana-e2e/src/flows/addPanel.ts diff --git a/e2e/suite1/specs/queryVariableCrud.spec.ts b/e2e/suite1/specs/queryVariableCrud.spec.ts index 8a6a9605ac9..d0ad081cfbb 100644 --- a/e2e/suite1/specs/queryVariableCrud.spec.ts +++ b/e2e/suite1/specs/queryVariableCrud.spec.ts @@ -190,13 +190,17 @@ const assertAdding3dependantQueryVariablesScenario = (queryVariables: QueryVaria for (let queryVariableIndex = 0; queryVariableIndex < queryVariables.length; queryVariableIndex++) { const { name, label, query, options, selectedOption } = queryVariables[queryVariableIndex]; const asserts = queryVariables.slice(0, queryVariableIndex + 1); - createQueryVariable({ - dataSourceName: e2e.context().get('lastAddedDataSource'), - name, - label, - query, - options, - selectedOption, + // @todo remove `@ts-ignore` when possible + // @ts-ignore + e2e.getScenarioContext().then(({ lastAddedDataSource }) => { + createQueryVariable({ + dataSourceName: lastAddedDataSource, + name, + label, + query, + options, + selectedOption, + }); }); assertVariableTable(asserts); @@ -565,7 +569,11 @@ e2e.scenario({ addScenarioDashBoard: true, skipScenario: false, scenario: () => { - e2e.flows.openDashboard(e2e.context().get('lastAddedDashboardUid')); + // @todo remove `@ts-ignore` when possible + // @ts-ignore + e2e.getScenarioContext().then(({ lastAddedDashboardUid }) => { + e2e.flows.openDashboard(lastAddedDashboardUid); + }); e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click(); e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click(); diff --git a/e2e/suite1/specs/smoketests.spec.ts b/e2e/suite1/specs/smoketests.spec.ts index 077575401ec..2fba0010689 100644 --- a/e2e/suite1/specs/smoketests.spec.ts +++ b/e2e/suite1/specs/smoketests.spec.ts @@ -7,7 +7,11 @@ e2e.scenario({ addScenarioDashBoard: true, skipScenario: false, scenario: () => { - e2e.flows.openDashboard(e2e.context().get('lastAddedDashboardUid')); + // @todo remove `@ts-ignore` when possible + // @ts-ignore + e2e.getScenarioContext().then(({ lastAddedDashboardUid }) => { + e2e.flows.openDashboard(lastAddedDashboardUid); + }); e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); e2e.pages.AddDashboard.ctaButtons('Add Query').click(); diff --git a/packages/grafana-e2e/cypress/plugins/extendConfig.js b/packages/grafana-e2e/cypress/plugins/extendConfig.js index db56ae16782..62cf1a3145c 100644 --- a/packages/grafana-e2e/cypress/plugins/extendConfig.js +++ b/packages/grafana-e2e/cypress/plugins/extendConfig.js @@ -12,6 +12,7 @@ module.exports = async baseConfig => { if (CWD) { const projectConfig = { + fixturesFolder: `${CWD}/cypress/fixtures`, integrationFolder: `${CWD}/cypress/integration`, screenshotsFolder: `${CWD}/cypress/screenshots`, videosFolder: `${CWD}/cypress/videos`, diff --git a/packages/grafana-e2e/cypress/plugins/index.js b/packages/grafana-e2e/cypress/plugins/index.js index 3865d647c00..f5afa717b24 100644 --- a/packages/grafana-e2e/cypress/plugins/index.js +++ b/packages/grafana-e2e/cypress/plugins/index.js @@ -1,5 +1,6 @@ const compareSnapshotsPlugin = require('./compareSnapshots'); const extendConfig = require('./extendConfig'); +const readProvisions = require('./readProvisions'); const typescriptPreprocessor = require('./typescriptPreprocessor'); module.exports = (on, config) => { @@ -10,7 +11,7 @@ module.exports = (on, config) => { // failed: require('cypress-failed-log/src/failed')(), // }); on('file:preprocessor', typescriptPreprocessor); - on('task', { compareSnapshotsPlugin }); + on('task', { compareSnapshotsPlugin, readProvisions }); on('task', { log({ message, optional }) { optional ? console.log(message, optional) : console.log(message); diff --git a/packages/grafana-e2e/cypress/plugins/readProvisions.js b/packages/grafana-e2e/cypress/plugins/readProvisions.js new file mode 100644 index 00000000000..00135a5cbc6 --- /dev/null +++ b/packages/grafana-e2e/cypress/plugins/readProvisions.js @@ -0,0 +1,14 @@ +'use strict'; +const { parse: parseYml } = require('yaml'); +const { + promises: { readFile }, +} = require('fs'); +const { resolve: resolvePath } = require('path'); + +const readProvision = filePath => readFile(filePath, 'utf8').then(contents => parseYml(contents)); + +const readProvisions = filePaths => Promise.all(filePaths.map(readProvision)); + +// Paths are relative to /provisioning +module.exports = ({ CWD, filePaths }) => + readProvisions(filePaths.map(filePath => resolvePath(CWD, 'provisioning', filePath))); diff --git a/packages/grafana-e2e/cypress/support/commands.ts b/packages/grafana-e2e/cypress/support/commands.ts index 1a66172088a..b237699e96b 100644 --- a/packages/grafana-e2e/cypress/support/commands.ts +++ b/packages/grafana-e2e/cypress/support/commands.ts @@ -25,3 +25,10 @@ Cypress.Commands.add('compareSnapshot', (args: CompareSnapshotArgs) => { Cypress.Commands.add('logToConsole', (message: string, optional?: any) => { cy.task('log', { message, optional }); }); + +Cypress.Commands.add('readProvisions', (filePaths: string[]) => { + cy.task('readProvisions', { + CWD: Cypress.env('CWD'), + filePaths, + }); +}); diff --git a/packages/grafana-e2e/cypress/support/index.d.ts b/packages/grafana-e2e/cypress/support/index.d.ts index e7967f8563b..9f3ed2fb449 100644 --- a/packages/grafana-e2e/cypress/support/index.d.ts +++ b/packages/grafana-e2e/cypress/support/index.d.ts @@ -4,5 +4,6 @@ declare namespace Cypress { interface Chainable { compareSnapshot(args: CompareSnapshotArgs): void; logToConsole(message: string, optional?: any): void; + readProvisions(filePaths: string[]): Chainable; } } diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index 6dec966ae11..ad079f7bd9e 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -55,6 +55,7 @@ "cypress": "3.7.0", "execa": "4.0.0", "ts-loader": "6.2.1", - "typescript": "3.7.2" + "typescript": "3.7.2", + "yaml": "^1.8.3" } } diff --git a/packages/grafana-e2e/src/flows/addDashboard.ts b/packages/grafana-e2e/src/flows/addDashboard.ts index f67f269ab24..04da89bbf8d 100644 --- a/packages/grafana-e2e/src/flows/addDashboard.ts +++ b/packages/grafana-e2e/src/flows/addDashboard.ts @@ -1,5 +1,5 @@ import { e2e } from '../index'; -import { Url } from '../support/url'; +import { getDashboardUid } from '../support/url'; export const addDashboard = () => { e2e().logToConsole('Adding dashboard'); @@ -11,7 +11,9 @@ export const addDashboard = () => { e2e() .url() .then((url: string) => { - e2e.context().set('lastAddedDashboard', dashboardTitle); - e2e.context().set('lastAddedDashboardUid', Url.getDashboardUid(url)); + e2e.setScenarioContext({ + lastAddedDashboard: dashboardTitle, + lastAddedDashboardUid: getDashboardUid(url), + }); }); }; diff --git a/packages/grafana-e2e/src/flows/addDataSource.ts b/packages/grafana-e2e/src/flows/addDataSource.ts index 713d06e09e5..5de0269689b 100644 --- a/packages/grafana-e2e/src/flows/addDataSource.ts +++ b/packages/grafana-e2e/src/flows/addDataSource.ts @@ -1,22 +1,64 @@ import { e2e } from '../index'; +import { fromBaseUrl, getDataSourceId } from '../support/url'; +import { setScenarioContext } from '../support/scenarioContext'; -export const addDataSource = (pluginName?: string): string => { - pluginName = pluginName || 'TestData DB'; - e2e().logToConsole('Adding data source with pluginName:', pluginName); +export interface AddDataSourceConfig { + checkHealth: boolean; + expectedAlertMessage: string; + form: Function; + name: string; +} + +const DEFAULT_ADD_DATA_SOURCE_CONFIG: AddDataSourceConfig = { + checkHealth: false, + expectedAlertMessage: 'Data source is working', + form: () => {}, + name: 'TestData DB', +}; + +export const addDataSource = (config?: Partial): string => { + const { checkHealth, expectedAlertMessage, form, name } = { ...DEFAULT_ADD_DATA_SOURCE_CONFIG, ...config }; + + e2e().logToConsole('Adding data source with name:', name); e2e.pages.AddDataSource.visit(); - e2e.pages.AddDataSource.dataSourcePlugins(pluginName) + e2e.pages.AddDataSource.dataSourcePlugins(name) .scrollIntoView() .should('be.visible') // prevents flakiness .click(); - const dataSourceName = `e2e-${new Date().getTime()}`; + const dataSourceName = `e2e-${Date.now()}`; e2e.pages.DataSource.name().clear(); e2e.pages.DataSource.name().type(dataSourceName); + form(); e2e.pages.DataSource.saveAndTest().click(); e2e.pages.DataSource.alert().should('exist'); - e2e.pages.DataSource.alertMessage().should('contain.text', 'Data source is working'); + e2e.pages.DataSource.alertMessage().should('contain.text', expectedAlertMessage); e2e().logToConsole('Added data source with name:', dataSourceName); - e2e.context().set('lastAddedDataSource', dataSourceName); + + if (checkHealth) { + e2e() + .url() + .then((url: string) => { + const dataSourceId = getDataSourceId(url); + + setScenarioContext({ + lastAddedDataSource: dataSourceName, + lastAddedDataSourceId: dataSourceId, + }); + + const healthUrl = fromBaseUrl(`/api/datasources/${dataSourceId}/health`); + e2e().logToConsole(`Fetching ${healthUrl}`); + e2e() + .request(healthUrl) + .its('body') + .should('have.property', 'status') + .and('eq', 'OK'); + }); + } else { + setScenarioContext({ + lastAddedDataSource: dataSourceName, + }); + } return dataSourceName; }; diff --git a/packages/grafana-e2e/src/flows/addPanel.ts b/packages/grafana-e2e/src/flows/addPanel.ts new file mode 100644 index 00000000000..4b385d79c19 --- /dev/null +++ b/packages/grafana-e2e/src/flows/addPanel.ts @@ -0,0 +1,30 @@ +import { e2e } from '../index'; +import { getScenarioContext } from '../support/scenarioContext'; + +export interface AddPanelConfig { + dataSourceName: string; + queriesForm: Function; +} + +const DEFAULT_ADD_PANEL_CONFIG: AddPanelConfig = { + dataSourceName: 'TestData DB', + queriesForm: () => {}, +}; + +export const addPanel = (config?: Partial) => { + const { dataSourceName, queriesForm } = { ...DEFAULT_ADD_PANEL_CONFIG, ...config }; + + // @todo remove `@ts-ignore` when possible + // @ts-ignore + getScenarioContext().then(({ lastAddedDashboardUid }) => { + e2e.flows.openDashboard(lastAddedDashboardUid); + e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); + e2e.pages.AddDashboard.ctaButtons('Add Query').click(); + e2e() + .get('.ds-picker') + .click() + .contains(dataSourceName) + .click(); + queriesForm(); + }); +}; diff --git a/packages/grafana-e2e/src/flows/deleteDashboard.ts b/packages/grafana-e2e/src/flows/deleteDashboard.ts index 4310efd42eb..1bf2216520a 100644 --- a/packages/grafana-e2e/src/flows/deleteDashboard.ts +++ b/packages/grafana-e2e/src/flows/deleteDashboard.ts @@ -1,9 +1,9 @@ -import { Url } from '../support/url'; import { e2e } from '../index'; +import { fromBaseUrl } from '../support/url'; export const deleteDashboard = (dashBoardUid: string) => { e2e().logToConsole('Deleting dashboard with uid:', dashBoardUid); - e2e().request('DELETE', Url.fromBaseUrl(`/api/dashboards/uid/${dashBoardUid}`)); + e2e().request('DELETE', fromBaseUrl(`/api/dashboards/uid/${dashBoardUid}`)); /* https://github.com/cypress-io/cypress/issues/2831 Flows.openDashboard(dashboardName); diff --git a/packages/grafana-e2e/src/flows/deleteDataSource.ts b/packages/grafana-e2e/src/flows/deleteDataSource.ts index 0f988909070..70dee352261 100644 --- a/packages/grafana-e2e/src/flows/deleteDataSource.ts +++ b/packages/grafana-e2e/src/flows/deleteDataSource.ts @@ -1,9 +1,9 @@ -import { Url } from '../support/url'; import { e2e } from '../index'; +import { fromBaseUrl } from '../support/url'; export const deleteDataSource = (dataSourceName: string) => { e2e().logToConsole('Deleting data source with name:', dataSourceName); - e2e().request('DELETE', Url.fromBaseUrl(`/api/datasources/name/${dataSourceName}`)); + e2e().request('DELETE', fromBaseUrl(`/api/datasources/name/${dataSourceName}`)); /* https://github.com/cypress-io/cypress/issues/2831 Pages.DataSources.visit(); diff --git a/packages/grafana-e2e/src/flows/index.ts b/packages/grafana-e2e/src/flows/index.ts index b0c5c925a73..4cad138cfa1 100644 --- a/packages/grafana-e2e/src/flows/index.ts +++ b/packages/grafana-e2e/src/flows/index.ts @@ -1,21 +1,23 @@ -import { login } from './login'; -import { addDataSource } from './addDataSource'; -import { deleteDataSource } from './deleteDataSource'; import { addDashboard } from './addDashboard'; +import { addDataSource } from './addDataSource'; +import { addPanel } from './addPanel'; import { assertSuccessNotification } from './assertSuccessNotification'; import { deleteDashboard } from './deleteDashboard'; +import { deleteDataSource } from './deleteDataSource'; +import { login } from './login'; import { openDashboard } from './openDashboard'; -import { saveNewDashboard } from './saveNewDashboard'; import { saveDashboard } from './saveDashboard'; +import { saveNewDashboard } from './saveNewDashboard'; export const Flows = { - login, - addDataSource, - deleteDataSource, addDashboard, + addDataSource, + addPanel, assertSuccessNotification, deleteDashboard, + deleteDataSource, + login, openDashboard, - saveNewDashboard, saveDashboard, + saveNewDashboard, }; diff --git a/packages/grafana-e2e/src/flows/saveNewDashboard.ts b/packages/grafana-e2e/src/flows/saveNewDashboard.ts index 712c8f92ce2..34dd3d1b648 100644 --- a/packages/grafana-e2e/src/flows/saveNewDashboard.ts +++ b/packages/grafana-e2e/src/flows/saveNewDashboard.ts @@ -3,7 +3,7 @@ import { e2e } from '../index'; export const saveNewDashboard = () => { e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard').click(); - const dashboardTitle = `e2e-${new Date().getTime()}`; + const dashboardTitle = `e2e-${Date.now()}`; e2e.pages.SaveDashboardAsModal.newName().clear(); e2e.pages.SaveDashboardAsModal.newName().type(dashboardTitle); e2e.pages.SaveDashboardAsModal.save().click(); diff --git a/packages/grafana-e2e/src/noTypeCheck.ts b/packages/grafana-e2e/src/noTypeCheck.ts index 3fad777793a..34b860d9655 100644 --- a/packages/grafana-e2e/src/noTypeCheck.ts +++ b/packages/grafana-e2e/src/noTypeCheck.ts @@ -6,7 +6,7 @@ import { e2eScenario, ScenarioArguments } from './support/scenario'; import { Pages } from './pages'; import { Flows } from './flows'; -import { scenarioContext } from './support/scenarioContext'; +import { getScenarioContext, setScenarioContext } from './support/scenarioContext'; export type SelectorFunction = (text?: string) => Cypress.Chainable>; export type SelectorObject = { @@ -20,9 +20,10 @@ const e2eObject = { blobToBase64String: (blob: any) => Cypress.Blob.blobToBase64String(blob), imgSrcToBlob: (url: string) => Cypress.Blob.imgSrcToBlob(url), scenario: (args: ScenarioArguments) => e2eScenario(args), - context: scenarioContext, pages: Pages, flows: Flows, + getScenarioContext, + setScenarioContext, }; export const e2e: (() => Cypress.cy) & typeof e2eObject = Object.assign(() => cy, e2eObject); diff --git a/packages/grafana-e2e/src/support/scenario.ts b/packages/grafana-e2e/src/support/scenario.ts index fccf104a321..2c6f5bec7f4 100644 --- a/packages/grafana-e2e/src/support/scenario.ts +++ b/packages/grafana-e2e/src/support/scenario.ts @@ -3,7 +3,7 @@ import { e2e } from '../index'; export interface ScenarioArguments { describeName: string; itName: string; - scenario: () => void; + scenario: Function; skipScenario?: boolean; addScenarioDataSource?: boolean; addScenarioDashBoard?: boolean; @@ -19,34 +19,33 @@ export const e2eScenario = ({ }: ScenarioArguments) => { describe(describeName, () => { if (skipScenario) { - it.skip(itName, () => { - // @ts-ignore yarn start in root throws error otherwise - expect(false).equals(true); + it.skip(itName, () => scenario()); + } else { + beforeEach(() => { + e2e.flows.login('admin', 'admin'); + if (addScenarioDataSource) { + e2e.flows.addDataSource(); + } + if (addScenarioDashBoard) { + e2e.flows.addDashboard(); + } }); - return; + + afterEach(() => { + // @todo remove `@ts-ignore` when possible + // @ts-ignore + e2e.getScenarioContext().then(({ lastAddedDashboardUid, lastAddedDataSource }) => { + if (lastAddedDataSource) { + e2e.flows.deleteDataSource(lastAddedDataSource); + } + + if (lastAddedDashboardUid) { + e2e.flows.deleteDashboard(lastAddedDashboardUid); + } + }); + }); + + it(itName, () => scenario()); } - - beforeEach(() => { - e2e.flows.login('admin', 'admin'); - if (addScenarioDataSource) { - e2e.flows.addDataSource('TestData DB'); - } - if (addScenarioDashBoard) { - e2e.flows.addDashboard(); - } - }); - - afterEach(() => { - if (e2e.context().get('lastAddedDataSource')) { - e2e.flows.deleteDataSource(e2e.context().get('lastAddedDataSource')); - } - if (e2e.context().get('lastAddedDashboardUid')) { - e2e.flows.deleteDashboard(e2e.context().get('lastAddedDashboardUid')); - } - }); - - it(itName, () => { - scenario(); - }); }); }; diff --git a/packages/grafana-e2e/src/support/scenarioContext.ts b/packages/grafana-e2e/src/support/scenarioContext.ts index cc8803e3802..4f9571dcf4c 100644 --- a/packages/grafana-e2e/src/support/scenarioContext.ts +++ b/packages/grafana-e2e/src/support/scenarioContext.ts @@ -1,29 +1,36 @@ +import { e2e } from '../index'; + export interface ScenarioContext { - lastAddedDataSource: string; lastAddedDashboard: string; lastAddedDashboardUid: string; + lastAddedDataSource: string; + lastAddedDataSourceId: string; [key: string]: any; } -const scenarioContexts: ScenarioContext = { - lastAddedDataSource: '', +const scenarioContext: ScenarioContext = { lastAddedDashboard: '', lastAddedDashboardUid: '', + lastAddedDataSource: '', + lastAddedDataSourceId: '', }; -export interface ScenarioContextApi { - get: (name: string | keyof ScenarioContext) => T; - set: (name: string | keyof ScenarioContext, value: T) => void; -} +// @todo this actually returns type `Cypress.Chainable` +export const getScenarioContext = (): any => + e2e() + .wrap({ + getScenarioContext: () => ({ ...scenarioContext } as ScenarioContext), + }) + .invoke('getScenarioContext'); -export const scenarioContext = (): ScenarioContextApi => { - const get = (name: string | keyof ScenarioContext): T => scenarioContexts[name] as T; - const set = (name: string | keyof ScenarioContext, value: T): void => { - scenarioContexts[name] = value; - }; - - return { - get, - set, - }; -}; +// @todo this actually returns type `Cypress.Chainable` +export const setScenarioContext = (newContext: Partial): any => + e2e() + .wrap({ + setScenarioContext: () => { + Object.entries(newContext).forEach(([key, value]) => { + scenarioContext[key] = value; + }); + }, + }) + .invoke('setScenarioContext'); diff --git a/packages/grafana-e2e/src/support/types.ts b/packages/grafana-e2e/src/support/types.ts index 86d96dd6871..c3a5801178e 100644 --- a/packages/grafana-e2e/src/support/types.ts +++ b/packages/grafana-e2e/src/support/types.ts @@ -1,5 +1,5 @@ import { Selector } from './selector'; -import { Url } from './url'; +import { fromBaseUrl } from './url'; import { e2e } from '../index'; import { SelectorFunction, SelectorObject } from '../noTypeCheck'; @@ -19,11 +19,11 @@ export const pageFactory = ({ url, selectors }: PageFactory let parsedUrl = ''; if (typeof url === 'string') { - parsedUrl = Url.fromBaseUrl(url); + parsedUrl = fromBaseUrl(url); } if (typeof url === 'function' && args) { - parsedUrl = Url.fromBaseUrl(url(args)); + parsedUrl = fromBaseUrl(url(args)); } e2e().logToConsole('Visiting', parsedUrl); diff --git a/packages/grafana-e2e/src/support/url.ts b/packages/grafana-e2e/src/support/url.ts index 69bd1705cb0..d36b8c6bc2a 100644 --- a/packages/grafana-e2e/src/support/url.ts +++ b/packages/grafana-e2e/src/support/url.ts @@ -1,25 +1,26 @@ import { e2e } from '../index'; -export interface UrlApi { - fromBaseUrl: (url: string | undefined) => string; - getDashboardUid: (url: string) => string; -} - -const uidRegex = '\\/d\\/(.*)\\/'; const getBaseUrl = () => e2e.env('BASE_URL') || e2e.config().baseUrl || 'http://localhost:3000'; -export const Url: UrlApi = { - fromBaseUrl: (url: string | undefined) => { - url = url || ''; - const strippedUrl = url.replace('^/', ''); - return `${getBaseUrl()}${strippedUrl}`; - }, - getDashboardUid: (url: string) => { - const matches = url.match(uidRegex); - if (!matches) { - throw new Error(`Couldn't parse uid from ${url}`); - } - - return matches[1]; - }, +export const fromBaseUrl = (url = ''): string => { + const strippedUrl = url.replace('^/', ''); + return `${getBaseUrl()}${strippedUrl}`; +}; + +export const getDashboardUid = (url: string): string => { + const matches = url.match(/\/d\/(.*)\//); + if (!matches) { + throw new Error(`Couldn't parse uid from ${url}`); + } else { + return matches[1]; + } +}; + +export const getDataSourceId = (url: string): string => { + const matches = url.match(/\/edit\/(.*)\//); + if (!matches) { + throw new Error(`Couldn't parse id from ${url}`); + } else { + return matches[1]; + } };