diff --git a/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/field_display_filtering.spec.js b/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/field_display_filtering.spec.js index a2281374dcc..4ab903e2178 100644 --- a/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/field_display_filtering.spec.js +++ b/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/field_display_filtering.spec.js @@ -8,7 +8,7 @@ import { INDEX_PATTERN_WITH_TIME, INDEX_WITH_TIME_1, } from '../../../../../utils/apps/constants'; -import * as dataExplorer from '../../../../../utils/apps/query_enhancements/field_display_filtering.js'; +import * as fieldFiltering from '../../../../../utils/apps/query_enhancements/field_display_filtering.js'; import { SECONDARY_ENGINE, BASE_PATH } from '../../../../../utils/constants'; import { NEW_SEARCH_BUTTON } from '../../../../../utils/dashboards/data_explorer/elements.js'; import { @@ -16,7 +16,6 @@ import { getRandomizedWorkspaceName, setDatePickerDatesAndSearchIfRelevant, } from '../../../../../utils/apps/query_enhancements/shared'; -import { generateFieldDisplayFilteringTestConfiguration } from '../../../../../utils/apps/query_enhancements/field_display_filtering'; const workspace = getRandomizedWorkspaceName(); @@ -65,162 +64,162 @@ describe('filter for value spec', () => { cy.deleteIndex(INDEX_WITH_TIME_1); }); - generateAllTestConfigurations(generateFieldDisplayFilteringTestConfiguration).forEach( - (config) => { - it(`filter actions in table field for ${config.testName}`, () => { - cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); - cy.setQueryLanguage(config.language); - setDatePickerDatesAndSearchIfRelevant(config.language); + generateAllTestConfigurations( + fieldFiltering.generateFieldDisplayFilteringTestConfiguration + ).forEach((config) => { + it(`filter actions in table field for ${config.testName}`, () => { + cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); + cy.setQueryLanguage(config.language); + setDatePickerDatesAndSearchIfRelevant(config.language); - cy.getElementByTestId('docTable').get('tbody tr').should('have.length.above', 3); // To ensure it waits until a full table is loaded into the DOM, instead of a bug where table only has 1 hit. + cy.getElementByTestId('docTable').get('tbody tr').should('have.length.above', 3); // To ensure it waits until a full table is loaded into the DOM, instead of a bug where table only has 1 hit. - const shouldText = config.isFilterButtonsEnabled ? 'exist' : 'not.exist'; - dataExplorer.getDocTableField(0, 0).within(() => { - cy.getElementByTestId('filterForValue').should(shouldText); - cy.getElementByTestId('filterOutValue').should(shouldText); - }); + const shouldText = config.isFilterButtonsEnabled ? 'exist' : 'not.exist'; + fieldFiltering.getDocTableField(0, 0).within(() => { + cy.getElementByTestId('filterForValue').should(shouldText); + cy.getElementByTestId('filterOutValue').should(shouldText); + }); + + if (config.isFilterButtonsEnabled) { + fieldFiltering.verifyDocTableFilterAction(0, 'filterForValue', '10,000', '1', true); + fieldFiltering.verifyDocTableFilterAction(0, 'filterOutValue', '10,000', '9,999', false); + } + }); - if (config.isFilterButtonsEnabled) { - dataExplorer.verifyDocTableFilterAction(0, 'filterForValue', '10,000', '1', true); - dataExplorer.verifyDocTableFilterAction(0, 'filterOutValue', '10,000', '9,999', false); + it(`filter actions in expanded table for ${config.testName}`, () => { + // Check if the first expanded Doc Table Field's first row's Filter For, Filter Out and Exists Filter buttons are disabled. + const verifyFirstExpandedFieldFilterForFilterOutFilterExistsButtons = () => { + const shouldText = config.isFilterButtonsEnabled ? 'be.enabled' : 'be.disabled'; + fieldFiltering.getExpandedDocTableRow(0, 0).within(() => { + cy.getElementByTestId('addInclusiveFilterButton').should(shouldText); + cy.getElementByTestId('removeInclusiveFilterButton').should(shouldText); + cy.getElementByTestId('addExistsFilterButton').should(shouldText); + }); + }; + + /** + * Check the Filter For or Out buttons in the expandedDocumentRowNumberth field in the expanded Document filters the correct value. + * @param {string} filterButton For or Out + * @param {number} docTableRowNumber Integer starts from 0 for the first row + * @param {number} expandedDocumentRowNumber Integer starts from 0 for the first row + * @param {string} expectedQueryHitsWithoutFilter expected number of hits in string after the filter is removed Note you should add commas when necessary e.g. 9,999 + * @param {string} expectedQueryHitsAfterFilterApplied expected number of hits in string after the filter is applied. Note you should add commas when necessary e.g. 9,999 + * @example verifyDocTableFirstExpandedFieldFirstRowFilterForButtonFiltersCorrectField('for', 0, 0, '10,000', '1'); + */ + const verifyDocTableFirstExpandedFieldFirstRowFilterForOutButtonFiltersCorrectField = ( + filterButton, + docTableRowNumber, + expandedDocumentRowNumber, + expectedQueryHitsWithoutFilter, + expectedQueryHitsAfterFilterApplied + ) => { + if (filterButton !== 'for' || filterButton !== 'out') { + cy.log('Filter button must be for or or.'); + return; } - }); - it(`filter actions in expanded table for ${config.testName}`, () => { - // Check if the first expanded Doc Table Field's first row's Filter For, Filter Out and Exists Filter buttons are disabled. - const verifyFirstExpandedFieldFilterForFilterOutFilterExistsButtons = () => { - const shouldText = config.isFilterButtonsEnabled ? 'be.enabled' : 'be.disabled'; - dataExplorer.getExpandedDocTableRow(0, 0).within(() => { - cy.getElementByTestId('addInclusiveFilterButton').should(shouldText); - cy.getElementByTestId('removeInclusiveFilterButton').should(shouldText); - cy.getElementByTestId('addExistsFilterButton').should(shouldText); + const filterButtonElement = + filterButton === 'for' ? 'addInclusiveFilterButton' : 'removeInclusiveFilterButton'; + const shouldText = filterButton === 'for' ? 'have.text' : 'not.have.text'; + + fieldFiltering + .getExpandedDocTableRowValue(docTableRowNumber, expandedDocumentRowNumber) + .then(($expandedDocumentRowValue) => { + const filterFieldText = $expandedDocumentRowValue.text(); + fieldFiltering + .getExpandedDocTableRow(docTableRowNumber, expandedDocumentRowNumber) + .within(() => { + cy.getElementByTestId(filterButtonElement).click(); + }); + // Verify pill text + cy.getElementByTestId('globalFilterLabelValue').should('have.text', filterFieldText); + cy.getElementByTestId('discoverQueryHits').should( + 'have.text', + expectedQueryHitsAfterFilterApplied + ); // checkQueryHitText must be in front of checking first line text to give time for DocTable to update. + fieldFiltering + .getExpandedDocTableRowValue(docTableRowNumber, expandedDocumentRowNumber) + .should(shouldText, filterFieldText); }); - }; - - /** - * Check the Filter For or Out buttons in the expandedDocumentRowNumberth field in the expanded Document filters the correct value. - * @param {string} filterButton For or Out - * @param {number} docTableRowNumber Integer starts from 0 for the first row - * @param {number} expandedDocumentRowNumber Integer starts from 0 for the first row - * @param {string} expectedQueryHitsWithoutFilter expected number of hits in string after the filter is removed Note you should add commas when necessary e.g. 9,999 - * @param {string} expectedQueryHitsAfterFilterApplied expected number of hits in string after the filter is applied. Note you should add commas when necessary e.g. 9,999 - * @example verifyDocTableFirstExpandedFieldFirstRowFilterForButtonFiltersCorrectField('for', 0, 0, '10,000', '1'); - */ - const verifyDocTableFirstExpandedFieldFirstRowFilterForOutButtonFiltersCorrectField = ( - filterButton, - docTableRowNumber, - expandedDocumentRowNumber, - expectedQueryHitsWithoutFilter, - expectedQueryHitsAfterFilterApplied - ) => { - if (filterButton !== 'for' || filterButton !== 'out') { - cy.log('Filter button must be for or or.'); - return; - } - - const filterButtonElement = - filterButton === 'for' ? 'addInclusiveFilterButton' : 'removeInclusiveFilterButton'; - const shouldText = filterButton === 'for' ? 'have.text' : 'not.have.text'; - - dataExplorer - .getExpandedDocTableRowValue(docTableRowNumber, expandedDocumentRowNumber) - .then(($expandedDocumentRowValue) => { - const filterFieldText = $expandedDocumentRowValue.text(); - dataExplorer - .getExpandedDocTableRow(docTableRowNumber, expandedDocumentRowNumber) - .within(() => { - cy.getElementByTestId(filterButtonElement).click(); - }); - // Verify pill text - cy.getElementByTestId('globalFilterLabelValue').should('have.text', filterFieldText); - cy.getElementByTestId('discoverQueryHits').should( - 'have.text', - expectedQueryHitsAfterFilterApplied - ); // checkQueryHitText must be in front of checking first line text to give time for DocTable to update. - dataExplorer - .getExpandedDocTableRowValue(docTableRowNumber, expandedDocumentRowNumber) - .should(shouldText, filterFieldText); - }); - cy.getElementByTestId('globalFilterBar').find('[aria-label="Delete"]').click(); - cy.getElementByTestId('discoverQueryHits').should( - 'have.text', - expectedQueryHitsWithoutFilter - ); - }; - - /** - * Check the first expanded Doc Table Field's first row's Exists Filter button filters the correct Field. - * @param {number} docTableRowNumber Integer starts from 0 for the first row - * @param {number} expandedDocumentRowNumber Integer starts from 0 for the first row - * @param {string} expectedQueryHitsWithoutFilter expected number of hits in string after the filter is removed Note you should add commas when necessary e.g. 9,999 - * @param {string} expectedQueryHitsAfterFilterApplied expected number of hits in string after the filter is applied. Note you should add commas when necessary e.g. 9,999 - */ - const verifyDocTableFirstExpandedFieldFirstRowExistsFilterButtonFiltersCorrectField = ( - docTableRowNumber, - expandedDocumentRowNumber, - expectedQueryHitsWithoutFilter, - expectedQueryHitsAfterFilterApplied - ) => { - dataExplorer - .getExpandedDocTableRowFieldName(docTableRowNumber, expandedDocumentRowNumber) - .then(($expandedDocumentRowField) => { - const filterFieldText = $expandedDocumentRowField.text(); - dataExplorer - .getExpandedDocTableRow(docTableRowNumber, expandedDocumentRowNumber) - .within(() => { - cy.getElementByTestId('addExistsFilterButton').click(); - }); - // Verify full pill text - // globalFilterLabelValue gives the inner element, but we may want all the text in the filter pill - cy.getElementByTestId('globalFilterLabelValue', { - timeout: 10000, - }) - .parent() - .should('have.text', filterFieldText + ': ' + 'exists'); - cy.getElementByTestId('discoverQueryHits').should( - 'have.text', - expectedQueryHitsAfterFilterApplied - ); - }); - cy.getElementByTestId('globalFilterBar').find('[aria-label="Delete"]').click(); - cy.getElementByTestId('discoverQueryHits').should( - 'have.text', - expectedQueryHitsWithoutFilter - ); - }; - - cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); - cy.setQueryLanguage(config.language); - setDatePickerDatesAndSearchIfRelevant(config.language); - - cy.getElementByTestId('docTable').get('tbody tr').should('have.length.above', 3); // To ensure it waits until a full table is loaded into the DOM, instead of a bug where table only has 1 hit. - dataExplorer.toggleDocTableRow(0); - verifyFirstExpandedFieldFilterForFilterOutFilterExistsButtons(); - dataExplorer.verifyDocTableFirstExpandedFieldFirstRowToggleColumnButtonHasIntendedBehavior(); - - if (config.isFilterButtonsEnabled) { - verifyDocTableFirstExpandedFieldFirstRowFilterForOutButtonFiltersCorrectField( - 'for', - 0, - 0, - '10,000', - '1' - ); - verifyDocTableFirstExpandedFieldFirstRowFilterForOutButtonFiltersCorrectField( - 'out', - 0, - 0, - '10,000', - '9,999' - ); - verifyDocTableFirstExpandedFieldFirstRowExistsFilterButtonFiltersCorrectField( - 0, - 0, - '10,000', - '10,000' - ); - } - }); - } - ); + cy.getElementByTestId('globalFilterBar').find('[aria-label="Delete"]').click(); + cy.getElementByTestId('discoverQueryHits').should( + 'have.text', + expectedQueryHitsWithoutFilter + ); + }; + + /** + * Check the first expanded Doc Table Field's first row's Exists Filter button filters the correct Field. + * @param {number} docTableRowNumber Integer starts from 0 for the first row + * @param {number} expandedDocumentRowNumber Integer starts from 0 for the first row + * @param {string} expectedQueryHitsWithoutFilter expected number of hits in string after the filter is removed Note you should add commas when necessary e.g. 9,999 + * @param {string} expectedQueryHitsAfterFilterApplied expected number of hits in string after the filter is applied. Note you should add commas when necessary e.g. 9,999 + */ + const verifyDocTableFirstExpandedFieldFirstRowExistsFilterButtonFiltersCorrectField = ( + docTableRowNumber, + expandedDocumentRowNumber, + expectedQueryHitsWithoutFilter, + expectedQueryHitsAfterFilterApplied + ) => { + fieldFiltering + .getExpandedDocTableRowFieldName(docTableRowNumber, expandedDocumentRowNumber) + .then(($expandedDocumentRowField) => { + const filterFieldText = $expandedDocumentRowField.text(); + fieldFiltering + .getExpandedDocTableRow(docTableRowNumber, expandedDocumentRowNumber) + .within(() => { + cy.getElementByTestId('addExistsFilterButton').click(); + }); + // Verify full pill text + // globalFilterLabelValue gives the inner element, but we may want all the text in the filter pill + cy.getElementByTestId('globalFilterLabelValue', { + timeout: 10000, + }) + .parent() + .should('have.text', filterFieldText + ': ' + 'exists'); + cy.getElementByTestId('discoverQueryHits').should( + 'have.text', + expectedQueryHitsAfterFilterApplied + ); + }); + cy.getElementByTestId('globalFilterBar').find('[aria-label="Delete"]').click(); + cy.getElementByTestId('discoverQueryHits').should( + 'have.text', + expectedQueryHitsWithoutFilter + ); + }; + + cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); + cy.setQueryLanguage(config.language); + setDatePickerDatesAndSearchIfRelevant(config.language); + + cy.getElementByTestId('docTable').get('tbody tr').should('have.length.above', 3); // To ensure it waits until a full table is loaded into the DOM, instead of a bug where table only has 1 hit. + fieldFiltering.toggleDocTableRow(0); + verifyFirstExpandedFieldFilterForFilterOutFilterExistsButtons(); + fieldFiltering.verifyDocTableFirstExpandedFieldFirstRowToggleColumnButtonHasIntendedBehavior(); + + if (config.isFilterButtonsEnabled) { + verifyDocTableFirstExpandedFieldFirstRowFilterForOutButtonFiltersCorrectField( + 'for', + 0, + 0, + '10,000', + '1' + ); + verifyDocTableFirstExpandedFieldFirstRowFilterForOutButtonFiltersCorrectField( + 'out', + 0, + 0, + '10,000', + '9,999' + ); + verifyDocTableFirstExpandedFieldFirstRowExistsFilterButtonFiltersCorrectField( + 0, + 0, + '10,000', + '10,000' + ); + } + }); + }); }); diff --git a/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/sidebar.spec.js b/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/sidebar.spec.js new file mode 100644 index 00000000000..ce83efdfe82 --- /dev/null +++ b/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/sidebar.spec.js @@ -0,0 +1,271 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SECONDARY_ENGINE } from '../../../../../utils/constants'; +import { + generateAllTestConfigurations, + getRandomizedWorkspaceName, + getRandomizedDatasourceName, + setDatePickerDatesAndSearchIfRelevant, +} from '../../../../../utils/apps/query_enhancements/shared'; +import { getDocTableField } from '../../../../../utils/apps/query_enhancements/field_display_filtering'; +import * as sideBar from '../../../../../utils/apps/query_enhancements/sidebar'; +import { generateSavedTestConfiguration } from '../../../../../utils/apps/query_enhancements/saved'; + +const workspaceName = getRandomizedWorkspaceName(); +const datasourceName = getRandomizedDatasourceName(); + +const addSidebarFieldsAndCheckDocTableColumns = ( + testFields, + expectedValues, + pplQuery, + sqlQuery, + isIndexPattern, + config +) => { + // Helper functions + const getDocTableHeaderByIndex = (index) => + cy.getElementByTestId('docTableHeaderField').eq(index); + + const checkTableHeaders = (headers) => { + headers.forEach((header, index) => { + getDocTableHeaderByIndex(index + 1).should('have.text', header); + }); + }; + + const checkDocTableColumn = (values, columnNumber) => { + values.forEach((value, index) => { + getDocTableField(columnNumber, index).should('have.text', value); + }); + }; + + const toggleFields = (fields) => { + fields.forEach((field) => { + cy.getElementByTestId(`fieldToggle-${field}`).click(); + }); + }; + + // Test field toggling and header persistence + cy.wrap([ + // Check initial state + () => getDocTableHeaderByIndex(1).should('have.text', '_source'), + + // Add fields and verify + () => { + toggleFields(testFields); + getDocTableHeaderByIndex(1).should('not.have.text', '_source'); + checkTableHeaders(testFields); + }, + + // Test field removal + () => { + // Remove first two fields + toggleFields(testFields.slice(0, 2)); + getDocTableHeaderByIndex(1).should('not.have.text', testFields[0]); + getDocTableHeaderByIndex(2).should('not.have.text', testFields[1]); + + // Remove remaining fields + toggleFields(testFields.slice(2)); + getDocTableHeaderByIndex(1).should('have.text', '_source'); + getDocTableHeaderByIndex(2).should('not.exist'); + + // Add all fields back + toggleFields(testFields); + getDocTableHeaderByIndex(1).should('not.have.text', '_source'); + checkTableHeaders(testFields); + }, + ]).each((fn) => fn()); + + // Check default hits only for index pattern before running queries + if (isIndexPattern && config.language !== 'OpenSearch SQL') { + cy.getElementByTestId('discoverQueryHits').should('have.text', '10,000'); + } + + // Run query based on language type + if (config.language === 'PPL') { + cy.intercept('**/api/enhancements/search/ppl').as('query'); + sideBar.sendQueryOnMultilineEditor(pplQuery); + cy.wait('@query').then(() => { + checkTableHeaders(testFields); + if (isIndexPattern) { + cy.getElementByTestId('discoverQueryHits').should('have.text', '1,152'); + } + checkDocTableColumn(expectedValues, 2); + //toggleFields(testFields); + }); + } else if (config.language === 'OpenSearch SQL') { + cy.intercept('**/api/enhancements/search/sql').as('query'); + sideBar.sendQueryOnMultilineEditor(sqlQuery); + cy.wait('@query').then(() => { + checkTableHeaders(testFields); + checkDocTableColumn(expectedValues, 2); + //toggleFields(testFields); + }); + } else if (config.language === 'DQL' || config.language === 'Lucene') { + // For DQL and Lucene, just verify the headers safter toggleFields + checkTableHeaders(testFields); + //toggleFields(testFields); + } +}; + +const checkFilteredFieldsForAllLanguages = () => { + const searchValues = [ + { search: '_index', assertion: 'equal' }, + { search: ' ', assertion: null }, + { search: 'a', assertion: 'include' }, + { search: 'age', assertion: 'include' }, + { search: 'non-existent field', assertion: null }, + ]; + + searchValues.forEach(({ search, assertion }) => { + sideBar.checkSidebarFilterBarResults(search, assertion); + }); +}; + +const checkSidebarPanelCollapseAndExpandBehavior = () => { + // Helper function to check panel visibility + const checkPanelVisibility = (shouldBeVisible) => { + cy.getElementByTestId('sidebarPanel').should(shouldBeVisible ? 'be.visible' : 'not.be.visible'); + }; + + // Test collapse + checkPanelVisibility(true); + sideBar.clickSidebarCollapseBtn(); + checkPanelVisibility(false); + + // Test expand + sideBar.clickSidebarCollapseBtn(false); + checkPanelVisibility(true); +}; + +const checkSidebarPanelCollapsedState = () => { + sideBar.clickSidebarCollapseBtn(); + cy.getElementByTestId('sidebarPanel').should('not.be.visible'); + sideBar.clickSidebarCollapseBtn(false); +}; + +describe('sidebar spec', () => { + before(() => { + // Load test data + cy.setupTestData( + SECONDARY_ENGINE.url, + ['cypress/fixtures/query_enhancements/data-logs-1/data_logs_small_time_1.mapping.json'], + ['cypress/fixtures/query_enhancements/data-logs-1/data_logs_small_time_1.data.ndjson'] + ); + // Clean out all data sources + cy.deleteAllDataSources(); + // Add data source + cy.addDataSource({ + name: datasourceName, + url: SECONDARY_ENGINE.url, + authType: 'no_auth', + }); + // Create workspace + cy.deleteWorkspaceByName(`${workspaceName}`); + cy.visit('/app/home'); + cy.createInitialWorkspaceWithDataSource(datasourceName, workspaceName); + cy.wait(2000); + cy.createWorkspaceIndexPatterns({ + workspaceName: workspaceName, + indexPattern: 'data_logs_small_time_1', + timefieldName: 'timestamp', + dataSource: datasourceName, + isEnhancement: true, + }); + cy.navigateToWorkSpaceSpecificPage({ + workspaceName: workspaceName, + page: 'discover', + isEnhancement: true, + }); + }); + + after(() => { + cy.deleteWorkspaceByName(`${workspaceName}`); + cy.deleteDataSourceByName(datasourceName); + cy.deleteIndex('data_logs_small_time_1'); + }); + + afterEach(() => { + // Clear any applied filters or selections + cy.window().then((win) => { + win.localStorage.clear(); + win.sessionStorage.clear(); + }); + cy.reload(); + }); + + describe('sidebar fields', () => { + generateAllTestConfigurations(generateSavedTestConfiguration, { + indexPattern: 'data_logs_small_time_1*', + index: 'data_logs_small_time_1', + }).forEach((config) => { + const testData = { + pplQuery: `source = ${config.dataset} | where status_code = 200`, + sqlQuery: `SELECT * FROM ${config.dataset} WHERE status_code = 200`, + simpleFields: { + fields: ['service_endpoint', 'response_time', 'bytes_transferred', 'request_url'], + expectedValues: ['3.32', '2.8', '3.35', '1.68', '4.98'], + }, + nestedFields: { + fields: [ + 'personal.name', + 'personal.age', + 'personal.birthdate', + 'personal.address.country', + ], + expectedValues: ['28', '55', '76', '56', '36'], + }, + }; + describe(`${config.testName}`, () => { + beforeEach(() => { + cy.setDataset(config.dataset, datasourceName, config.datasetType); + cy.setQueryLanguage(config.language); + setDatePickerDatesAndSearchIfRelevant(config.language); + + // Clean up any existing fields + sideBar.removeAllSelectedFields(); + }); + + describe('add fields', () => { + it('adds simple fields', () => { + addSidebarFieldsAndCheckDocTableColumns( + testData.simpleFields.fields, + testData.simpleFields.expectedValues, + testData.pplQuery, + testData.sqlQuery, + config.datasetType === 'INDEX_PATTERN', + config + ); + }); + + it('adds nested fields', () => { + addSidebarFieldsAndCheckDocTableColumns( + testData.nestedFields.fields, + testData.nestedFields.expectedValues, + testData.pplQuery, + testData.sqlQuery, + config.datasetType === 'INDEX_PATTERN', + config + ); + }); + }); + + describe('filter fields', () => { + it('filters fields correctly', () => { + checkFilteredFieldsForAllLanguages(config.datasetType === 'INDEX_PATTERN'); + }); + }); + + describe('side panel behavior', () => { + it('handles panel collapse/expand correctly', () => { + checkSidebarPanelCollapseAndExpandBehavior(config.datasetType === 'INDEX_PATTERN'); + cy.wait(1000); + checkSidebarPanelCollapsedState(config.datasetType === 'INDEX_PATTERN'); + }); + }); + }); + }); + }); +}); diff --git a/cypress/utils/apps/query_enhancements/shared.js b/cypress/utils/apps/query_enhancements/shared.js index 9fe5a24512f..e7a5e843618 100644 --- a/cypress/utils/apps/query_enhancements/shared.js +++ b/cypress/utils/apps/query_enhancements/shared.js @@ -46,18 +46,22 @@ export const getRandomizedDatasourceName = () => /** * Returns an array of test configurations for every query language + dataset permutation * @param {GenerateTestConfigurationCallback} generateTestConfigurationCallback - cb function that generates a test case for the particular permutation + * @param {Object} [options] - Optional configuration options + * @param {string} [options.indexPattern] - Custom index pattern name (defaults to INDEX_PATTERN_WITH_TIME) + * @param {string} [options.index] - Custom index name (defaults to INDEX_WITH_TIME_1) * @returns {object[]} */ -export const generateAllTestConfigurations = (generateTestConfigurationCallback) => { +export const generateAllTestConfigurations = (generateTestConfigurationCallback, options = {}) => { + const { indexPattern = INDEX_PATTERN_WITH_TIME, index = INDEX_WITH_TIME_1 } = options; return Object.values(DatasetTypes).flatMap((dataset) => dataset.supportedLanguages.map((language) => { let datasetToUse; switch (dataset.name) { case DatasetTypes.INDEX_PATTERN.name: - datasetToUse = INDEX_PATTERN_WITH_TIME; + datasetToUse = indexPattern; break; case DatasetTypes.INDEXES.name: - datasetToUse = INDEX_WITH_TIME_1; + datasetToUse = index; break; default: throw new Error( diff --git a/cypress/utils/apps/query_enhancements/sidebar.js b/cypress/utils/apps/query_enhancements/sidebar.js new file mode 100644 index 00000000000..8d0dc6c8d18 --- /dev/null +++ b/cypress/utils/apps/query_enhancements/sidebar.js @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Sends a new query via the query multiline editor. + * @param {string} query Query string + * @see https://docs.cypress.io/api/commands/type#Arguments + */ +export const sendQueryOnMultilineEditor = (query) => { + // remove syntax helper + cy.getElementByTestId('headerGlobalNav').click(); + // Clear default text on the editor by an alternative method, since + // cy.clear() won't work for some reason + cy.get('.view-line') + .invoke('text') + .then(($content) => { + const contentLen = $content.length + 1; + cy.get('.view-line').type('a'); // make sure we're at the end of the string + cy.get('.view-line').type('{backspace}'.repeat(contentLen)); + }); + // Type query + cy.get('.view-line').type(query); + // Send query + cy.getElementByTestId('querySubmitButton').click(); +}; + +/** + * Click on the sidebar collapse button. + * @param {boolean} collapse true for collapsing, false for expanding + */ +export const clickSidebarCollapseBtn = (collapse = true) => { + if (collapse) { + cy.getElementByTestId('euiResizableButton').trigger('mouseover').click(); + } + cy.get('.euiResizableToggleButton').click({ force: true }); +}; + +/** + * Check the results of the sidebar filter bar search. + * @param {string} search text to look up + * @param {string} assertion the type of assertion that is going to be performed. Example: 'eq', 'include'. If an assertion is not passed, a negative test is performend. + */ +export const checkSidebarFilterBarResults = (search, assertion) => { + cy.getElementByTestId('fieldFilterSearchInput').type(search, { force: true }); + if (assertion) { + // Get all sidebar fields and iterate over all of them + cy.get('[data-test-subj^="field-"]:not([data-test-subj$="showDetails"])').each(($field) => { + cy.wrap($field) + .should('be.visible') + .invoke('text') + .then(($fieldTxt) => { + cy.wrap($fieldTxt).should(assertion, search); + }); + }); + } else { + // No match should be found + cy.get('[data-test-subj^="field-"]:not([data-test-subj$="showDetails"])').should('not.exist'); + } + cy.get('button[aria-label="Clear input"]').click(); +}; + +/** + * Removes all currently selected fields from the sidebar + */ +export const removeAllSelectedFields = () => { + cy.get('[data-test-subj="fieldList-selected"]').then(($list) => { + if ($list.find('[data-test-subj^="field-"]').length > 0) { + // Remove all selected fields + $list.find('[data-test-subj^="fieldToggle-"]').each((_, el) => { + cy.wrap(el).click(); + }); + } + }); +}; + +/** + * The configurations needed for field display filtering tests + * @typedef {Object} FieldDisplayFilteringTestConfig + * @property {string} dataset - the dataset name to use + * @property {QueryEnhancementDataset} datasetType - the type of dataset + * @property {QueryEnhancementLanguage} language - the name of query language as it appears in the dashboard app + * @property {boolean} isFilterButtonsEnabled - whether filter button is enabled for this permutation + * @property {string} testName - the phrase to add to the test case's title + */ + +/** + * Returns the SideBarTestConfig for the provided dataset, datasetType, and language + * @param {string} dataset - the dataset name + * @param {QueryEnhancementDataset} datasetType - the type of the dataset + * @param {QueryEnhancementLanguageData} language - the relevant data for the query language to use + * @returns {SideBarTestConfig} + */ +export const generateSideBarTestConfiguration = (dataset, datasetType, language) => { + return { + dataset, + datasetType, + language: language.name, + testName: `dataset: ${datasetType} and language: ${language.name}`, + }; +};