utils.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import { dashboardView, nativeFilters } from 'cypress/support/directories';
  20. import {
  21. ChartSpec,
  22. setSelectSearchInput,
  23. waitForChartLoad,
  24. } from 'cypress/utils';
  25. export const WORLD_HEALTH_CHARTS = [
  26. { name: '% Rural', viz: 'world_map' },
  27. { name: 'Most Populated Countries', viz: 'table' },
  28. { name: "World's Population", viz: 'big_number' },
  29. { name: 'Growth Rate', viz: 'echarts_timeseries_line' },
  30. { name: 'Rural Breakdown', viz: 'sunburst_v2' },
  31. { name: "World's Pop Growth", viz: 'echarts_area' },
  32. { name: 'Life Expectancy VS Rural %', viz: 'bubble' },
  33. { name: 'Treemap', viz: 'treemap_v2' },
  34. { name: 'Box plot', viz: 'box_plot' },
  35. ] as ChartSpec[];
  36. export const SUPPORTED_TIER1_CHARTS = [
  37. { name: 'Big Number', viz: 'big_number_total' },
  38. { name: 'Big Number with Trendline', viz: 'big_number' },
  39. { name: 'Pie Chart', viz: 'pie' },
  40. { name: 'Table', viz: 'table' },
  41. { name: 'Pivot Table', viz: 'pivot_table_v2' },
  42. { name: 'Line Chart', viz: 'echarts_timeseries_line' },
  43. { name: 'Area Chart', viz: 'echarts_area' },
  44. { name: 'Scatter Chart', viz: 'echarts_timeseries_scatter' },
  45. { name: 'Bar Chart', viz: 'echarts_timeseries_bar' },
  46. ] as ChartSpec[];
  47. export const SUPPORTED_TIER2_CHARTS = [
  48. { name: 'Box Plot Chart', viz: 'box_plot' },
  49. { name: 'Generic Chart', viz: 'echarts_timeseries' },
  50. { name: 'Smooth Line Chart', viz: 'echarts_timeseries_smooth' },
  51. { name: 'Step Line Chart', viz: 'echarts_timeseries_step' },
  52. { name: 'Funnel Chart', viz: 'funnel' },
  53. { name: 'Gauge Chart', viz: 'gauge_chart' },
  54. { name: 'Radar Chart', viz: 'radar' },
  55. { name: 'Treemap V2 Chart', viz: 'treemap_v2' },
  56. { name: 'Mixed Chart', viz: 'mixed_timeseries' },
  57. ] as ChartSpec[];
  58. export const testItems = {
  59. dashboard: 'Cypress test Dashboard',
  60. dataset: 'Vehicle Sales',
  61. datasetForNativeFilter: 'wb_health_population',
  62. chart: 'Cypress chart',
  63. newChart: 'New Cypress Chart',
  64. createdDashboard: 'New Dashboard',
  65. defaultNameDashboard: '[ untitled dashboard ]',
  66. newDashboardTitle: `Test dashboard [NEW TEST]`,
  67. bulkFirstNameDashboard: 'First Dash',
  68. bulkSecondNameDashboard: 'Second Dash',
  69. worldBanksDataCopy: `World Bank's Data [copy]`,
  70. filterType: {
  71. value: 'Value',
  72. numerical: 'Numerical range',
  73. timeColumn: 'Time column',
  74. timeGrain: 'Time grain',
  75. timeRange: 'Time range',
  76. },
  77. topTenChart: {
  78. name: 'Most Populated Countries',
  79. filterColumn: 'country_name',
  80. filterColumnYear: 'year',
  81. filterColumnRegion: 'region',
  82. filterColumnCountryCode: 'country_code',
  83. },
  84. filterDefaultValue: 'United States',
  85. filterOtherCountry: 'China',
  86. filterTimeGrain: 'Month',
  87. filterTimeColumn: 'created',
  88. filterNumericalColumn: 'SP_RUR_TOTL_ZS',
  89. };
  90. export const nativeFilterTooltips = {
  91. searchAllFilterOptions:
  92. 'By default, each filter loads at most 1000 choices at the initial page load. Check this box if you have more than 1000 filter values and want to enable dynamically searching that loads filter values as users type (may add stress to your database).',
  93. defaultToFirstItem: 'When using this option, default value can’t be set',
  94. inverseSelection: 'Exclude selected values',
  95. required: 'User must select a value before applying the filter',
  96. multipleSelect: 'Allow selecting multiple values',
  97. defaultValue:
  98. 'Default value must be set when "Filter value is required" is checked',
  99. preFilter: `Add filter clauses to control the filter's source query, though only in the context of the autocomplete i.e., these conditions do not impact how the filter is applied to the dashboard. This is useful when you want to improve the query's performance by only scanning a subset of the underlying data or limit the available values displayed in the filter.`,
  100. };
  101. export const nativeFilterOptions = [
  102. 'Filter has default value',
  103. 'Multiple select',
  104. 'Filter value is required',
  105. 'Filter is hierarchical',
  106. 'Default to first item',
  107. 'Inverse selection',
  108. 'Search all filter options',
  109. 'Pre-filter available values',
  110. 'Sort filter values',
  111. ];
  112. export const valueNativeFilterOptions = [
  113. 'Pre-filter available values',
  114. 'Sort filter values',
  115. 'Filter has default value',
  116. 'Select first filter value by default',
  117. 'Can select multiple values',
  118. 'Dynamically search all filter values',
  119. 'Inverse selection',
  120. 'Filter value is required',
  121. ];
  122. export function interceptGet() {
  123. cy.intercept('GET', '**/api/v1/dashboard/*').as('get');
  124. }
  125. export function interceptFiltering() {
  126. cy.intercept('GET', `**/api/v1/dashboard/?q=*`).as('filtering');
  127. }
  128. export function interceptBulkDelete() {
  129. cy.intercept('DELETE', `**/api/v1/dashboard/?q=*`).as('bulkDelete');
  130. }
  131. export function interceptDelete() {
  132. cy.intercept('DELETE', `**/api/v1/dashboard/*`).as('delete');
  133. }
  134. export function interceptUpdate() {
  135. cy.intercept('PUT', `**/api/v1/dashboard/*`).as('update');
  136. }
  137. export function interceptExploreUpdate() {
  138. cy.intercept('PUT', `**/api/v1/chart/*`).as('chartUpdate');
  139. }
  140. export function interceptPost() {
  141. cy.intercept('POST', `**/api/v1/dashboard/`).as('post');
  142. }
  143. export function interceptLog() {
  144. cy.intercept('**/superset/log/?explode=events&dashboard_id=*').as('logs');
  145. }
  146. export function interceptFav() {
  147. cy.intercept({ url: `**/api/v1/dashboard/*/favorites/`, method: 'POST' }).as(
  148. 'select',
  149. );
  150. }
  151. export function interceptUnfav() {
  152. cy.intercept({ url: `**/api/v1/dashboard/*/favorites/`, method: 'POST' }).as(
  153. 'unselect',
  154. );
  155. }
  156. export function interceptDataset() {
  157. cy.intercept('GET', `**/api/v1/dataset/*`).as('getDataset');
  158. }
  159. export function interceptCharts() {
  160. cy.intercept('GET', `**/api/v1/dashboard/*/charts`).as('getCharts');
  161. }
  162. export function interceptDatasets() {
  163. cy.intercept('GET', `**/api/v1/dashboard/*/datasets`).as('getDatasets');
  164. }
  165. export function interceptFilterState() {
  166. cy.intercept('POST', `**/api/v1/dashboard/*/filter_state*`).as(
  167. 'postFilterState',
  168. );
  169. }
  170. export function setFilter(filter: string, option: string) {
  171. interceptFiltering();
  172. cy.get(`[aria-label^="${filter}"]`).first().click();
  173. cy.get(`.ant-select-item-option[title="${option}"]`).first().click({
  174. force: true,
  175. });
  176. cy.wait('@filtering');
  177. }
  178. /** ************************************************************************
  179. * Expend Native filter from the left panel on dashboard
  180. * @returns {None}
  181. * @summary helper for expend native filter
  182. ************************************************************************* */
  183. export function expandFilterOnLeftPanel() {
  184. return cy
  185. .get(nativeFilters.filterFromDashboardView.expand)
  186. .should('be.visible')
  187. .click({ force: true });
  188. }
  189. /** ************************************************************************
  190. * Collapses Native Filter from the left panel on dashboard
  191. * @returns {None}
  192. * @summary helper for collapse native filter
  193. ************************************************************************* */
  194. export function collapseFilterOnLeftPanel() {
  195. cy.get(nativeFilters.filterFromDashboardView.collapse)
  196. .should('be.visible')
  197. .click();
  198. cy.get(nativeFilters.filterFromDashboardView.collapse).should(
  199. 'not.be.visible',
  200. );
  201. }
  202. /** ************************************************************************
  203. * Enter Native Filter edit modal from the left panel on dashboard
  204. * @returns {None}
  205. * @summary helper for enter native filter edit modal
  206. ************************************************************************* */
  207. export function enterNativeFilterEditModal(waitForDataset = true) {
  208. interceptDataset();
  209. cy.get(nativeFilters.filtersPanel.filterGear).click({
  210. force: true,
  211. });
  212. cy.get(nativeFilters.filterFromDashboardView.createFilterButton).click({
  213. force: true,
  214. });
  215. cy.get(nativeFilters.modal.container).should('be.visible');
  216. if (waitForDataset) {
  217. cy.wait('@getDataset');
  218. }
  219. }
  220. /** ************************************************************************
  221. * Clicks on new filter button
  222. * @returns {None}
  223. * @summary helper for adding new filter
  224. ************************************************************************* */
  225. export function clickOnAddFilterInModal() {
  226. return cy.get(nativeFilters.modal.addNewFilterButton).click({ force: true });
  227. }
  228. /** ************************************************************************
  229. * Fills value native filter form with basic information
  230. * @param {string} type type for filter: Value, Numerical range,Time column,Time grain,Time range
  231. * @param {string} name name for filter
  232. * @param {string} dataset which dataset should be used
  233. * @param {string} filterColumn which column should be used
  234. * @returns {None}
  235. * @summary helper for filling value native filter form
  236. ************************************************************************* */
  237. export function fillNativeFilterForm(
  238. type: string,
  239. name: string,
  240. dataset?: string,
  241. filterColumn?: string,
  242. ) {
  243. cy.get(nativeFilters.filtersPanel.filterTypeInput).within(() => {
  244. cy.get('input').then($input => {
  245. setSelectSearchInput($input, type);
  246. });
  247. });
  248. cy.get(nativeFilters.modal.container)
  249. .find(nativeFilters.filtersPanel.filterName)
  250. .last()
  251. .click({ scrollBehavior: false });
  252. cy.get(nativeFilters.modal.container)
  253. .find(nativeFilters.filtersPanel.filterName)
  254. .last()
  255. .clear({ force: true });
  256. cy.get(nativeFilters.modal.container)
  257. .find(nativeFilters.filtersPanel.filterName)
  258. .last()
  259. .type(name, { scrollBehavior: false, force: true });
  260. if (dataset) {
  261. cy.get('div[aria-label="Dataset"]').within(() => {
  262. cy.get('input').then($input => {
  263. setSelectSearchInput($input, dataset, true);
  264. });
  265. });
  266. cy.get(nativeFilters.silentLoading).should('not.exist');
  267. }
  268. if (filterColumn) {
  269. cy.get('div[aria-label="Column select"]').within(() => {
  270. cy.get('input').then($input => {
  271. setSelectSearchInput($input, filterColumn, true);
  272. });
  273. });
  274. }
  275. }
  276. /** ************************************************************************
  277. * Get native filter placeholder e.g 9 options
  278. * @param {number} index which input it fills
  279. * @returns cy object for assertions
  280. * @summary helper for getting placeholder value
  281. ************************************************************************* */
  282. export function getNativeFilterPlaceholderWithIndex(index: number) {
  283. return cy.get(nativeFilters.filtersPanel.columnEmptyInput).eq(index);
  284. }
  285. /** ************************************************************************
  286. * Apply native filter value from dashboard view
  287. * @param {number} index which input it fills
  288. * @param {string} value what is filter value
  289. * @returns {null}
  290. * @summary put value to nth native filter input in view
  291. ************************************************************************* */
  292. export function applyNativeFilterValueWithIndex(index: number, value: string) {
  293. cy.get(nativeFilters.filterFromDashboardView.filterValueInput)
  294. .eq(index)
  295. .should('exist', { timeout: 10000 })
  296. .type(`${value}{enter}`, { force: true });
  297. // click the title to dismiss shown options
  298. cy.get(nativeFilters.filterFromDashboardView.filterName)
  299. .eq(index)
  300. .click({ force: true });
  301. }
  302. /** ************************************************************************
  303. * Fills parent filter input
  304. * @param {number} index which input it fills
  305. * @param {string} value on which filter it depends on
  306. * @returns {null}
  307. * @summary takes first or second input and modify the depends on filter value
  308. ************************************************************************* */
  309. export function addParentFilterWithValue(index: number, value: string) {
  310. return cy
  311. .get(nativeFilters.filterConfigurationSections.displayedSection)
  312. .within(() => {
  313. cy.get('input[aria-label^="Limit type"]')
  314. .eq(index)
  315. .click({ force: true });
  316. cy.get('input[aria-label^="Limit type"]')
  317. .eq(index)
  318. .type(`${value}{enter}`, { delay: 30, force: true });
  319. });
  320. }
  321. /** ************************************************************************
  322. * Save Native Filter Settings
  323. * @returns {None}
  324. * @summary helper for save native filters settings
  325. ************************************************************************* */
  326. export function saveNativeFilterSettings(charts: ChartSpec[]) {
  327. cy.get(nativeFilters.modal.footer)
  328. .contains('Save')
  329. .should('be.visible')
  330. .click({ force: true });
  331. // Wait for modal to either close or remain open
  332. cy.get('body').should($body => {
  333. const modalExists = $body.find(nativeFilters.modal.container).length > 0;
  334. if (modalExists) {
  335. cy.get(nativeFilters.modal.footer)
  336. .contains('Save')
  337. .should('be.visible')
  338. .click({ force: true });
  339. }
  340. });
  341. // Ensure modal is closed
  342. cy.get(nativeFilters.modal.container).should('not.exist');
  343. // Wait for all charts to load
  344. charts.forEach(chart => {
  345. waitForChartLoad(chart);
  346. });
  347. }
  348. /** ************************************************************************
  349. * Cancel Native filter settings
  350. * @returns {None}
  351. * @summary helper for cancel native filters settings
  352. ************************************************************************* */
  353. export function cancelNativeFilterSettings() {
  354. cy.get(nativeFilters.modal.footer)
  355. .find(nativeFilters.modal.cancelButton)
  356. .should('be.visible')
  357. .click();
  358. cy.get(nativeFilters.modal.alertXUnsavedFilters)
  359. .should('be.visible')
  360. .should('have.text', 'There are unsaved changes.');
  361. cy.get(nativeFilters.modal.footer)
  362. .find(nativeFilters.modal.confirmCancelButton)
  363. .contains('cancel')
  364. .click({ force: true });
  365. cy.get(nativeFilters.modal.container).should('not.exist');
  366. }
  367. /** ************************************************************************
  368. * Validate filter name on dashboard
  369. * @param name: filter name to validate
  370. * @return {null}
  371. * @summary helper for validate filter name on dashboard
  372. ************************************************************************* */
  373. export function validateFilterNameOnDashboard(name: string) {
  374. cy.get(nativeFilters.filterFromDashboardView.filterName)
  375. .should('be.visible', { timeout: 40000 })
  376. .contains(`${name}`);
  377. }
  378. /** ************************************************************************
  379. * Validate filter content on dashboard
  380. * @param filterContent: filter content to validate
  381. * @return {null}
  382. * @summary helper for validate filter content on dashboard
  383. ************************************************************************* */
  384. export function validateFilterContentOnDashboard(filterContent: string) {
  385. cy.get(nativeFilters.filterFromDashboardView.filterContent)
  386. .contains(`${filterContent}`)
  387. .should('be.visible');
  388. }
  389. /** ************************************************************************
  390. * Delete Native filter
  391. * @return {null}
  392. * @summary helper for delete native filter
  393. ************************************************************************* */
  394. export function deleteNativeFilter() {
  395. cy.get(nativeFilters.filtersList.removeIcon).first().click();
  396. }
  397. /** ************************************************************************
  398. * Undo delete Native filter
  399. * @return {null}
  400. * @summary helper for undo delete native filter
  401. ************************************************************************ */
  402. export function undoDeleteNativeFilter() {
  403. deleteNativeFilter();
  404. cy.contains('Undo?').click();
  405. }
  406. /** ************************************************************************
  407. * Check Native Filter tooltip content
  408. * @param index: tooltip index to check
  409. * @param value: tooltip value to check
  410. * @return {null}
  411. * @summary helper for checking native filter tooltip content by index
  412. ************************************************************************* */
  413. export function checkNativeFilterTooltip(index: number, value: string) {
  414. cy.get(nativeFilters.filterConfigurationSections.infoTooltip)
  415. .eq(index)
  416. .trigger('mouseover');
  417. cy.contains(`${value}`);
  418. cy.get(nativeFilters.filterConfigurationSections.infoTooltip)
  419. .eq(index)
  420. .trigger('mouseout');
  421. }
  422. /** ************************************************************************
  423. * Apply advanced time range filter on dashboard
  424. * @param startRange: starting time range
  425. * @param endRange: ending time range
  426. * @return {null}
  427. * @summary helper for applying advanced time range filter on dashboard with customize time range
  428. ************************************************************************* */
  429. export function applyAdvancedTimeRangeFilterOnDashboard(
  430. startRange?: string,
  431. endRange?: string,
  432. ) {
  433. cy.get('.control-label').contains('Range type').should('be.visible');
  434. cy.get('.ant-popover-content .ant-select-selector')
  435. .should('be.visible')
  436. .click();
  437. cy.get(`[label="Advanced"]`).should('be.visible').click();
  438. cy.get('.section-title').contains('Advanced Time Range').should('be.visible');
  439. if (startRange) {
  440. cy.get('.ant-popover-inner-content')
  441. .find('[class^=ant-input]')
  442. .first()
  443. .type(`${startRange}`);
  444. }
  445. if (endRange) {
  446. cy.get('.ant-popover-inner-content')
  447. .find('[class^=ant-input]')
  448. .last()
  449. .type(`${endRange}`);
  450. }
  451. cy.get(dashboardView.timeRangeModal.applyButton).click();
  452. cy.get(nativeFilters.applyFilter).click();
  453. }
  454. /** ************************************************************************
  455. * Input default value in Native filter in filter settings
  456. * @param defaultValue: default value for native filter
  457. * @return {null}
  458. * @summary helper for input default value in Native filter in filter settings
  459. ************************************************************************* */
  460. export function inputNativeFilterDefaultValue(
  461. defaultValue: string,
  462. multiple = false,
  463. ) {
  464. if (!multiple) {
  465. cy.contains('Filter has default value').click();
  466. cy.contains('Please choose a valid value').should('be.visible');
  467. cy.get(nativeFilters.modal.container).within(() => {
  468. cy.get(
  469. nativeFilters.filterConfigurationSections.filterPlaceholder,
  470. ).contains('options');
  471. cy.get(
  472. nativeFilters.filterConfigurationSections.collapsedSectionContainer,
  473. )
  474. .eq(1)
  475. .within(() => {
  476. cy.get('.ant-select-selection-search-input').type(
  477. `${defaultValue}{enter}`,
  478. { force: true },
  479. );
  480. });
  481. });
  482. } else {
  483. cy.getBySel('default-input').within(() => {
  484. cy.get('.ant-select-selection-search-input').click();
  485. cy.get('.ant-select-item-option-content').contains(defaultValue).click();
  486. });
  487. }
  488. }
  489. /** ************************************************************************
  490. * add filter for test column 'Country name'
  491. * @return {null}
  492. * @summary helper for add filter for test column 'Country name'
  493. ************************************************************************* */
  494. export function addCountryNameFilter() {
  495. fillNativeFilterForm(
  496. testItems.filterType.value,
  497. testItems.topTenChart.filterColumn,
  498. testItems.datasetForNativeFilter,
  499. testItems.topTenChart.filterColumn,
  500. );
  501. }
  502. export function openTab(
  503. tabComponentIndex: number,
  504. tabIndex: number,
  505. target = 'dashboard-component-tabs',
  506. ) {
  507. cy.getBySel(target)
  508. .eq(tabComponentIndex)
  509. .find('[role="tab"]')
  510. .eq(tabIndex)
  511. .click();
  512. cy.wait(500);
  513. }
  514. export const openTopLevelTab = (tabName: string) => {
  515. cy.get("div#TABS-TOP div[role='tab']").contains(tabName).click();
  516. };