diff --git a/changelogs/fragments/9307.yml b/changelogs/fragments/9307.yml new file mode 100644 index 00000000000..65743604f6c --- /dev/null +++ b/changelogs/fragments/9307.yml @@ -0,0 +1,2 @@ +test: +- Add all recent queries tests ([#9307](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/9307)) \ No newline at end of file diff --git a/cypress.config.ts b/cypress.config.ts index cc4b3fbfb60..7ee927b1dca 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,6 +5,7 @@ import { defineConfig } from 'cypress'; import webpackPreprocessor from '@cypress/webpack-preprocessor'; +// TODO: import { paste } from 'copy-paste'; module.exports = defineConfig({ defaultCommandTimeout: 60000, @@ -76,6 +77,12 @@ function setupNodeEvents( webpackOptions, }) ); + // TODO: Define the custom task to read clipboard + /* on('task', { + readClipboard() { + return paste(); // Return the clipboard content + }, + });*/ return config; } diff --git a/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/recent_queries.spec.js b/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/recent_queries.spec.js new file mode 100644 index 00000000000..92eac225ebe --- /dev/null +++ b/cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements/recent_queries.spec.js @@ -0,0 +1,233 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + DATASOURCE_NAME, + INDEX_PATTERN_WITH_TIME, + INDEX_WITH_TIME_1, +} from '../../../../../utils/apps/constants'; +import { BASE_PATH, PATHS } from '../../../../../utils/constants'; +import { + getRandomizedWorkspaceName, + setDatePickerDatesAndSearchIfRelevant, + generateAllTestConfigurations, +} from '../../../../../utils/apps/query_enhancements/shared'; +import { + generateRecentQueriesTestConfiguration, + BaseQuery, + TestQueries, + //TODO: QueryRegex, +} from '../../../../../utils/apps/query_enhancements/recent_queries'; +import { prepareTestSuite } from '../../../../../utils/helpers'; + +const workspace = getRandomizedWorkspaceName(); +const runRecentQueryTests = () => { + describe('recent queries spec', { testIsolation: true }, () => { + const index = INDEX_PATTERN_WITH_TIME.replace('*', ''); + beforeEach(() => { + // Load test data + cy.osd.setupTestData( + PATHS.SECONDARY_ENGINE, + ['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'] + ); + + // Add data source + cy.osd.addDataSource({ + name: DATASOURCE_NAME, + url: `${PATHS.SECONDARY_ENGINE}`, + authType: 'no_auth', + }); + // Create workspace + cy.deleteWorkspaceByName(workspace); + cy.visit('/app/home'); + cy.osd.createInitialWorkspaceWithDataSource(DATASOURCE_NAME, workspace); + cy.createWorkspaceIndexPatterns({ + workspaceName: workspace, + indexPattern: index, + timefieldName: 'timestamp', + indexPatternHasTimefield: true, + dataSource: DATASOURCE_NAME, + isEnhancement: true, + }); + + cy.navigateToWorkSpaceSpecificPage({ + url: BASE_PATH, + workspaceName: workspace, + page: 'discover', + isEnhancement: true, + }); + }); + + afterEach(() => { + cy.deleteWorkspaceByName(workspace); + cy.osd.deleteDataSourceByName(DATASOURCE_NAME); + // TODO: Modify deleteIndex to handle an array of index and remove hard code + cy.osd.deleteIndex(INDEX_WITH_TIME_1); + }); + + generateAllTestConfigurations(generateRecentQueriesTestConfiguration) + .filter(Boolean) // removes undefined values + .forEach((config) => { + it(`check max queries for ${config.testName}`, () => { + cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); + cy.setQueryLanguage(config.language); + setDatePickerDatesAndSearchIfRelevant(config.language); + const currentLang = BaseQuery[config.datasetType][config.language]; + const currentBaseQuery = currentLang.query; + const currentWhereStatement = currentLang.where; + TestQueries.forEach((query) => { + cy.setQueryEditor( + currentBaseQuery + config.dataset + currentWhereStatement + query, + {}, + true + ); + }); + cy.getElementByTestId('queryEditorFooterToggleRecentQueriesButton').click({ + force: true, + }); + // only 10 of the 11 queries should be displayed + cy.getElementByTestIdLike('row-').should('have.length', 10); + const reverseList = [...TestQueries].reverse(); + const steps = [ + { + // only check the table + action: () => {}, + }, + { + // check table after changing language and returning to the language under test + action: () => { + cy.setQueryLanguage(config.oppositeLang); + cy.setQueryLanguage(config.language); + cy.wrap(null).then(() => { + // force Cypress to run this method in order + reverseList.unshift(config.defaultQuery); + }); + }, + }, + { + // check table after changing dataset and returning to the dataset under test + action: () => { + cy.setIndexAsDataset( + config.alternativeDataset, + DATASOURCE_NAME, + config.language, + "I don't want to use the time filter" + ); + cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); + cy.wrap(null).then(() => { + // force Cypress to run this method in order + reverseList.unshift(config.defaultQuery); + }); + }, + }, + { + // check table after visiting a different URL and coming back to the workspace + action: () => { + cy.visit('/app/workspace_initial'); + cy.navigateToWorkSpaceSpecificPage({ + url: BASE_PATH, + workspaceName: workspace, + page: 'discover', + isEnhancement: true, + }); + cy.getElementByTestId('queryEditorFooterToggleRecentQueriesButton').click({ + force: true, + }); + }, + }, + ]; + steps.forEach(({ action }, stepIndex) => { + action(); + cy.getElementByTestIdLike('row-').each(($row, rowIndex) => { + let expectedQuery = ''; + if (rowIndex === 1 && stepIndex >= 2) { + expectedQuery = + currentBaseQuery + config.alternativeDataset + reverseList[rowIndex]; + } else if (rowIndex === 0 && stepIndex >= 1) { + expectedQuery = currentBaseQuery + config.dataset + reverseList[rowIndex]; + } else { + expectedQuery = + currentBaseQuery + config.dataset + currentWhereStatement + reverseList[rowIndex]; + } + expect($row.text()).to.contain(expectedQuery); + }); + }); + }); + + it(`check duplicate query for ${config.testName}`, () => { + cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); + cy.setQueryLanguage(config.language); + setDatePickerDatesAndSearchIfRelevant(config.language); + const currentLang = BaseQuery[config.datasetType][config.language]; + const currentBaseQuery = currentLang.query; + const currentWhereStatement = currentLang.where; + const testQueries = [ + currentBaseQuery + config.dataset + currentWhereStatement + ' status_code = 504', // valid + currentBaseQuery + config.dataset + currentWhereStatement, // invalid + ]; + testQueries.forEach((query, index) => { + cy.setQueryEditor(query, {}, true); + cy.setQueryEditor(query, {}, true); + if (!index) + // it remains expanded for the second iteration, no need to expand it again + cy.getElementByTestId('queryEditorFooterToggleRecentQueriesButton').click({ + force: true, + }); + cy.getElementByTestIdLike('row-').should('have.length', index + 2); + }); + }); + + /* TODO: adding these tests requires adding a dependency OR customizing the execSync function + //const { execSync } = require('child_process'); + //console.log(execSync('xclip -selection clipboard -o').toString().trim()); // for Linux + //Caveat: the commands for reading the system's clipboard is OS-dependent. + it(`check running and copying recent queries for ${config.testName}`, () => { + cy.setDataset(config.dataset, DATASOURCE_NAME, config.datasetType); + cy.setQueryLanguage(config.language); + setDatePickerDatesAndSearchIfRelevant(config.language); + // Precondition: run some queries first + const currentLang = BaseQuery[config.datasetType][config.language]; + const currentBaseQuery = currentLang.query + config.dataset + currentLang.where; + const queries = [...TestQueries].splice(0, 3); + queries.forEach((query) => { + cy.setQueryEditor(currentBaseQuery + query, {}, true); + }); + cy.getElementByTestId('queryEditorFooterToggleRecentQueriesButton').click({ + force: true, + }); + const expectedQuery = currentBaseQuery + queries[0]; + cy.getElementByTestIdLike('row-') // check query in original position + .eq(2) + .focus() + .then(($row) => { + expect($row.text()).to.include(expectedQuery); + }); + cy.getElementByTestId('action-run').eq(2).focus().click({ force: true }); // run query again + cy.wait(2000); + cy.getElementByTestIdLike('row-') // check query in altered position + .eq(0) + .focus() + .then(($row) => { + expect($row.text()).to.include(expectedQuery); + }); + cy.getElementByTestIdLike('row-') // copy another query to clipboard + .eq(1) + .focus() + .then(($row) => { + cy.get('[aria-label="Copy recent query"]').eq(1).click({ force: true }); + cy.wait(1000); // Give the clipboard some time to update + const expectedQuery = $row.text().replace(QueryRegex[config.language], '$1'); + cy.get('[aria-label="Copy recent query"]').eq(1).focus(); + cy.task('readClipboard').then((clipboardText) => { + expect(clipboardText).to.eq(expectedQuery); + }); + }); + });*/ + }); + }); +}; + +prepareTestSuite('Recent Query', runRecentQueryTests); diff --git a/cypress/utils/apps/query_enhancements/recent_queries.js b/cypress/utils/apps/query_enhancements/recent_queries.js new file mode 100644 index 00000000000..017b1ef4588 --- /dev/null +++ b/cypress/utils/apps/query_enhancements/recent_queries.js @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { QueryLanguages } from './constants'; + +export const RecentQueriesDataTypes = { + INDEX_PATTERN: { + name: 'INDEX_PATTERN', + supportedLanguages: [QueryLanguages.SQL, QueryLanguages.PPL], + }, + INDEXES: { + name: 'INDEXES', + supportedLanguages: [QueryLanguages.SQL, QueryLanguages.PPL], + }, +}; + +export const BaseQuery = { + INDEX_PATTERN: { + 'OpenSearch SQL': { + query: `SELECT * FROM `, + where: ' WHERE ', + }, + PPL: { + query: `source = `, + where: ' | where ', + }, + }, + INDEXES: { + 'OpenSearch SQL': { + query: `SELECT * FROM `, + where: ' WHERE ', + }, + PPL: { + query: `source = `, + where: ' | where ', + }, + }, +}; + +export const TestQueries = [ + 'bytes_transferred >', + 'bytes_transferred < 8000', + 'bytes_transferred > 8000', + 'status_code = 404', + 'status_code = 501', + 'status_code = 503', + 'status_code = 400', + 'status_code = 401', + 'status_code = 403', + 'status_code = 200', + 'event_sequence_number > 10000000', +]; + +/* // TODO +export const QueryRegex = { + PPL: /.*?(source .*? 8000)(?:.*)/s, + 'OpenSearch SQL': /.*?(SELECT .*? 8000)(?:.*)/s, +};*/ + +/** + * The configurations needed for recent queries tests + * @typedef {Object} RecentQueriesFilteringTestConfig + * @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 {string} testName - the phrase to add to the test case's title + */ + +/** + * Returns the SavedSearchTestConfig 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 {RecentQueriesFilteringTestConfig} + */ +export const generateRecentQueriesTestConfiguration = (dataset, datasetType, language) => { + if (language.name !== 'PPL' && language.name !== 'OpenSearch SQL') { + return; // undefined + } + const oppositeLang = { + PPL: 'OpenSearch SQL', + 'OpenSearch SQL': 'PPL', + }; + const defaultQuery = language.name === 'PPL' ? '' : ' LIMIT 10'; + const customDatasetType = RecentQueriesDataTypes[datasetType].name; + return { + dataset, + datasetType: customDatasetType, + language: language.name, + oppositeLang: oppositeLang[language.name], + alternativeDataset: '.opensearch-sap-log-types-config', + defaultQuery: defaultQuery, + testName: `dataset: ${datasetType} and language: ${language.name}`, + }; +}; diff --git a/cypress/utils/apps/query_enhancements/shared.js b/cypress/utils/apps/query_enhancements/shared.js index 87bd503657e..f7ba5b931d9 100644 --- a/cypress/utils/apps/query_enhancements/shared.js +++ b/cypress/utils/apps/query_enhancements/shared.js @@ -61,18 +61,23 @@ export const generateBaseConfiguration = (dataset, datasetType, language) => { * @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) + * @param {Object.} [options.datasetTypes] - Custom dataset types (defaults to DatasetTypes) * @returns {object[]} */ export const generateAllTestConfigurations = (generateTestConfigurationCallback, options = {}) => { - const { indexPattern = INDEX_PATTERN_WITH_TIME, index = INDEX_WITH_TIME_1 } = options; - return Object.values(DatasetTypes).flatMap((dataset) => + const { + indexPattern = INDEX_PATTERN_WITH_TIME, + index = INDEX_WITH_TIME_1, + datasetTypes = DatasetTypes, + } = options; + return Object.values(datasetTypes).flatMap((dataset) => dataset.supportedLanguages.map((language) => { let datasetToUse; switch (dataset.name) { - case DatasetTypes.INDEX_PATTERN.name: + case datasetTypes.INDEX_PATTERN.name: datasetToUse = indexPattern; break; - case DatasetTypes.INDEXES.name: + case datasetTypes.INDEXES.name: datasetToUse = index; break; default: diff --git a/package.json b/package.json index 088c1542fb3..ae9685ee32a 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "osd:ciGroup12": "BASE_PATH='cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements' && echo \"$BASE_PATH/language_specific_display.spec.js,$BASE_PATH/time_range_selection.spec.js,$BASE_PATH/saved_queries.spec.js,$BASE_PATH/saved_search_in_dashboards.spec.js\"", "osd:ciGroup13": "BASE_PATH='cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements' && echo \"$BASE_PATH/caching.spec.js,$BASE_PATH/field_display_filtering.spec.js,$BASE_PATH/inspect.spec.js,$BASE_PATH/shared_links.spec.js,$BASE_PATH/table.spec.js\"", "osd:ciGroup14": "BASE_PATH='cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements' && echo \"$BASE_PATH/s3_dataset.spec.js,$BASE_PATH/advanced_settings.spec.js,$BASE_PATH/queries_more.spec.js\"", - "osd:ciGroup15": "BASE_PATH='cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements' && echo \"$BASE_PATH/autocomplete_switch.spec.js,$BASE_PATH/autocomplete_ui.spec.js,$BASE_PATH/autocomplete_query.spec.js,$BASE_PATH/sidebar.spec.js\"", + "osd:ciGroup15": "BASE_PATH='cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/query_enhancements' && echo \"$BASE_PATH/autocomplete_switch.spec.js,$BASE_PATH/autocomplete_ui.spec.js,$BASE_PATH/autocomplete_query.spec.js,$BASE_PATH/sidebar.spec.js,$BASE_PATH/recent_queries.spec.js\"", "generate:opensearchsqlantlr": "./node_modules/antlr4ng-cli/index.js -Dlanguage=TypeScript -o ./src/plugins/data/public/antlr/opensearch_sql/.generated -visitor -no-listener -Xexact-output-dir ./src/plugins/data/public/antlr/opensearch_sql/grammar/OpenSearchSQLLexer.g4 ./src/plugins/data/public/antlr/opensearch_sql/grammar/OpenSearchSQLParser.g4", "generate:opensearchpplantlr": "./node_modules/antlr4ng-cli/index.js -Dlanguage=TypeScript -o ./src/plugins/data/public/antlr/opensearch_ppl/.generated -visitor -no-listener -Xexact-output-dir ./src/plugins/data/public/antlr/opensearch_ppl/grammar/OpenSearchPPLLexer.g4 ./src/plugins/data/public/antlr/opensearch_ppl/grammar/OpenSearchPPLParser.g4" }, @@ -200,6 +200,7 @@ "chokidar": "^3.4.2", "color": "1.0.3", "commander": "^3.0.2", + "copy-paste": "^1.5.3", "core-js": "^3.6.5", "deep-freeze-strict": "^1.1.1", "del": "^6.1.1", diff --git a/yarn.lock b/yarn.lock index 1aec3cc7b66..6e07b243177 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6322,6 +6322,13 @@ copy-concurrently@^1.0.0: rimraf "^2.5.4" run-queue "^1.0.0" +copy-paste@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/copy-paste/-/copy-paste-1.5.3.tgz#ee9e775858d05c57a91ea2a063188ab686840797" + integrity sha512-qOnFo+8l8vemGmdcoCiD7gPTefkXEg2rivYE+EBtuKOj754eFivkGhGAM9e/xqShrpuVE11evSxGnHwVAUK1Iw== + dependencies: + iconv-lite "^0.4.8" + copy-to-clipboard@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" @@ -10022,7 +10029,7 @@ hyphenate-style-name@^1.0.2: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@^0.4.8, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==