diff --git a/.cypress/.eslintrc.js b/.cypress/.eslintrc.js new file mode 100644 index 00000000..26edd9be --- /dev/null +++ b/.cypress/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + root: true, + extends: ['plugin:cypress/recommended'], + env: { + 'cypress/globals': true, + }, + plugins: ['cypress'], + rules: { + // Add cypress specific rules here + 'cypress/no-assigning-return-values': 'error', + 'cypress/no-unnecessary-waiting': 'error', + 'cypress/assertion-before-screenshot': 'warn', + 'cypress/no-force': 'warn', + 'cypress/no-async-tests': 'error', + }, + }; + \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..c5e1946d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules +/data +/build +/target +/.eslintrc.js +/cypress.config.js +!.cypress/ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..12f093d2 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +const LICENSE_HEADER = `/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */`; + +module.exports = { + root: true, + extends: [ + '@elastic/eslint-config-kibana', + 'plugin:@elastic/eui/recommended', + 'plugin:react-hooks/recommended', + 'plugin:jest/recommended', + 'plugin:prettier/recommended', + ], + + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@osd/eslint/no-restricted-paths': [ + 'error', + { + basePath: __dirname, + zones: [ + { + target: ['(public|server)/**/*'], + from: ['../../packages/**/*','packages/**/*'], + }, + ], + }, + ], + }, + overrides: [ + { + files: ['**/*.{js,ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + 'no-console': 0, + '@osd/eslint/require-license-header': [ + 'error', + { + licenses: [LICENSE_HEADER], + }, + ], + }, + }, + ], + "ignorePatterns": ["**/*.d.ts"] +}; diff --git a/.github/workflows/cypress-e2e-sql-workbench-test.yml b/.github/workflows/cypress-e2e-sql-workbench-test.yml new file mode 100644 index 00000000..f53ca3c4 --- /dev/null +++ b/.github/workflows/cypress-e2e-sql-workbench-test.yml @@ -0,0 +1,156 @@ +name: Cypress E2E SQL Workbench Test + +on: [pull_request, push] + +env: + CI: 1 + # avoid warnings like "tput: No value for $TERM and no -T specified" + TERM: xterm + OPENSEARCH_DASHBOARDS_VERSION: 'main' + OPENSEARCH_VERSION: '3.0.0' + OPENSEARCH_PLUGIN_VERSION: '3.0.0.0' + +jobs: + tests: + name: Run Cypress E2E SQL Workbench tests + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + jdk: [ 11 ] + runs-on: ${{ matrix.os }} + + steps: + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.jdk }} + + - name: Download SQL artifact + uses: suisei-cn/actions-download-file@v1.4.0 + with: + url: https://aws.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.opensearch.plugin&a=opensearch-sql-plugin&v=${{ env.OPENSEARCH_PLUGIN_VERSION }}-SNAPSHOT&p=zip + target: plugin-artifacts/ + filename: sql.zip + + - name: Download OpenSearch + uses: peternied/download-file@v2 + with: + url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/opensearch-min-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT-linux-x64-latest.tar.gz + + - name: Extract OpenSearch + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + - name: Install SQL plugin + run: | + /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch-plugin install file:$(pwd)/plugin-artifacts/sql.zip" + shell: bash + + - name: Run OpenSearch + run: | + /bin/bash -c "./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch &" + sleep 30 + shell: bash + + - name: Check OpenSearch Running on Linux + if: ${{ runner.os != 'Windows'}} + run: curl http://localhost:9200/ + shell: bash + + - name: Show OpenSearch Logs + if: always() + run: cat ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/logs/opensearch.log + shell: bash + + - name: Checkout OpenSearch Dashboards + uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards + repository: opensearch-project/OpenSearch-Dashboards + ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }} + fetch-depth: 0 + filter: | + cypress + test + + - name: Checkout Query Workbench in OpenSearch Dashboards Plugins Dir + uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards/plugins/dashboards-query-workbench + + - id: tool-versions + run: | + echo "node_version=$(cat .node-version)" >> $GITHUB_OUTPUT + echo "yarn_version=$(jq -r '.engines.yarn' package.json)" >> $GITHUB_OUTPUT + working-directory: OpenSearch-Dashboards + shell: bash + + - uses: actions/setup-node@v1 + with: + node-version: ${{ steps.tool-versions.outputs.node_version }} + registry-url: 'https://registry.npmjs.org' + + - name: Setup Opensearch Dashboards + run: | + npm uninstall -g yarn + echo "Installing yarn ${{ steps.tool-versions.outputs.yarn_version }}" + npm i -g yarn@${{ steps.tool-versions.outputs.yarn_version }} + yarn cache clean + yarn add sha.js + working-directory: OpenSearch-Dashboards + shell: bash + + - name: Boodstrap Opensearch Dashboards + run: | + yarn osd bootstrap + working-directory: OpenSearch-Dashboards + + - name: Run Opensearch Dashboards with Query Workbench Installed + run: | + nohup yarn start --no-base-path --no-watch | tee dashboard.log & + working-directory: OpenSearch-Dashboards + + - name : Check If OpenSearch Dashboards Is Ready + if: ${{ runner.os == 'Linux' }} + run: | + if timeout 600 grep -q "bundles compiled successfully after" <(tail -n0 -f dashboard.log); then + echo "OpenSearch Dashboards compiled successfully." + else + echo "Timeout for 600 seconds reached. OpenSearch Dashboards did not finish compiling." + exit 1 + fi + working-directory: OpenSearch-Dashboards + + - name: Install Cypress + run: | + npx cypress install + shell: bash + working-directory: OpenSearch-Dashboards/plugins/dashboards-query-workbench + + - name: Get Cypress version + id: cypress_version + run: | + echo "::set-output name=cypress_version::$(cat ./package.json | jq '.dependencies.cypress' | tr -d '"')" + working-directory: OpenSearch-Dashboards/plugins/dashboards-query-workbench + + - name: Run Cypress tests + run: | + yarn cypress:run --browser chrome --headless --spec '.cypress/integration/${{ matrix.testgroups }}/*' + working-directory: OpenSearch-Dashboards/plugins/dashboards-query-workbench + + - name: Capture failure screenshots + uses: actions/upload-artifact@v1 + if: failure() + with: + name: cypress-screenshots-${{ matrix.os }} + path: OpenSearch-Dashboards/plugins/dashboards-query-workbench/.cypress/screenshots + + - name: Capture test video + uses: actions/upload-artifact@v1 + if: failure() + with: + name: cypress-videos-${{ matrix.os }} + path: OpenSearch-Dashboards/plugins/dashboards-query-workbench/.cypress/videos diff --git a/.github/workflows/ftr-e2e-sql-workbench-test.yml b/.github/workflows/ftr-e2e-sql-workbench-test.yml new file mode 100644 index 00000000..800cc70e --- /dev/null +++ b/.github/workflows/ftr-e2e-sql-workbench-test.yml @@ -0,0 +1,164 @@ +name: FTR E2E SQL Workbench Test + +on: [pull_request, push] + +env: + CI: 1 + # avoid warnings like "tput: No value for $TERM and no -T specified" + TERM: xterm + OPENSEARCH_DASHBOARDS_VERSION: 'main' + OPENSEARCH_VERSION: '3.0.0' + OPENSEARCH_PLUGIN_VERSION: '3.0.0.0' + +jobs: + tests: + name: Run FTR E2E SQL Workbench Tests + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + jdk: [ 11 ] + runs-on: ${{ matrix.os }} + + steps: + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.jdk }} + + - name: Download SQL artifact + uses: suisei-cn/actions-download-file@v1.4.0 + with: + url: https://aws.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.opensearch.plugin&a=opensearch-sql-plugin&v=${{ env.OPENSEARCH_PLUGIN_VERSION }}-SNAPSHOT&p=zip + target: plugin-artifacts/ + filename: sql.zip + + - name: Download OpenSearch + uses: peternied/download-file@v2 + with: + url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/opensearch-min-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT-linux-x64-latest.tar.gz + + - name: Extract OpenSearch + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + - name: Install SQL plugin + run: | + /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch-plugin install file:$(pwd)/plugin-artifacts/sql.zip" + shell: bash + + - name: Run OpenSearch + run: | + /bin/bash -c "./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch &" + sleep 30 + shell: bash + + - name: Check OpenSearch Running on Linux + if: ${{ runner.os != 'Windows'}} + run: curl http://localhost:9200/ + shell: bash + + - name: Show OpenSearch Logs + if: always() + run: cat ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/logs/opensearch.log + shell: bash + + - name: Checkout OpenSearch Dashboards + uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards + repository: opensearch-project/OpenSearch-Dashboards + ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }} + fetch-depth: 0 + filter: | + cypress + test + + - name: Checkout Query Workbench in OpenSearch Dashboards Plugins Dir + uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards/plugins/dashboards-query-workbench + + - id: tool-versions + run: | + echo "node_version=$(cat .node-version)" >> $GITHUB_OUTPUT + echo "yarn_version=$(jq -r '.engines.yarn' package.json)" >> $GITHUB_OUTPUT + working-directory: OpenSearch-Dashboards + shell: bash + + - uses: actions/setup-node@v1 + with: + node-version: ${{ steps.tool-versions.outputs.node_version }} + registry-url: 'https://registry.npmjs.org' + + - name: Setup Opensearch Dashboards + run: | + npm uninstall -g yarn + echo "Installing yarn ${{ steps.tool-versions.outputs.yarn_version }}" + npm i -g yarn@${{ steps.tool-versions.outputs.yarn_version }} + yarn cache clean + yarn add sha.js + working-directory: OpenSearch-Dashboards + shell: bash + + - name: Boodstrap Opensearch Dashboards + run: | + yarn osd bootstrap + working-directory: OpenSearch-Dashboards + + - name: Run Opensearch Dashboards with Query Workbench Installed + run: | + nohup yarn start --no-base-path --no-watch | tee dashboard.log & + working-directory: OpenSearch-Dashboards + + - name : Check If OpenSearch Dashboards Is Ready + if: ${{ runner.os == 'Linux' }} + run: | + if timeout 600 grep -q "bundles compiled successfully after" <(tail -n0 -f dashboard.log); then + echo "OpenSearch Dashboards compiled successfully." + else + echo "Timeout for 600 seconds reached. OpenSearch Dashboards did not finish compiling." + exit 1 + fi + working-directory: OpenSearch-Dashboards + + - name: Checkout Dashboards Functioanl Test Repo + uses: actions/checkout@v2 + with: + path: opensearch-dashboards-functional-test + repository: opensearch-project/opensearch-dashboards-functional-test + ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }} + fetch-depth: 0 + + - name: Install Cypress + run: | + npm install cypress --save-dev + shell: bash + working-directory: opensearch-dashboards-functional-test + + - name: Get Cypress version + id: cypress_version + run: | + echo "::set-output name=cypress_version::$(cat ./package.json | jq '.dependencies.cypress' | tr -d '"')" + working-directory: opensearch-dashboards-functional-test + + - name: Run Cypress tests + run: | + yarn cypress:run-without-security --browser chromium --spec 'cypress/integration/plugins/query-workbench-dashboards/*.js' + working-directory: opensearch-dashboards-functional-test + + - name: Capture failure screenshots + uses: actions/upload-artifact@v1 + if: failure() + with: + name: cypress-screenshots-${{ matrix.os }} + path: opensearch-dashboards-functional-test/cypress/screenshots + + - name: Capture failure test video + uses: actions/upload-artifact@v1 + if: failure() + with: + name: cypress-videos-${{ matrix.os }} + path: opensearch-dashboards-functional-test/cypress/videos diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..06d08f26 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,79 @@ +name: Lint + +on: [pull_request] + +env: + PLUGIN_NAME: dashboards-query-workbench + OPENSEARCH_DASHBOARDS_VERSION: "main" + +jobs: + build: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout OpenSearch Dashboards + uses: actions/checkout@v2 + with: + repository: opensearch-project/Opensearch-Dashboards + ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }} + path: OpenSearch-Dashboards + + - name: Checkout dashboards query workbench + uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }} + fetch-depth: 0 + + - name: Get node and yarn versions + working-directory: ${{ env.WORKING_DIR }} + id: versions_step + run: | + echo "::set-output name=node_version::$(cat ./OpenSearch-Dashboards/.nvmrc | cut -d"." -f1)" + echo "::set-output name=yarn_version::$(node -p "(require('./OpenSearch-Dashboards/package.json').engines.yarn).match(/[.0-9]+/)[0]")" + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: ${{ steps.versions_step.outputs.node_version }} + registry-url: "https://registry.npmjs.org" + + - name: Install correct yarn version for OpenSearch Dashboards + run: | + npm uninstall -g yarn + echo "Installing yarn ${{ steps.versions_step.outputs.yarn_version }}" + npm i -g yarn@${{ steps.versions_step.outputs.yarn_version }} + + - name: Bootstrap the plugin + working-directory: OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }} + run: yarn osd bootstrap + + - name: Get list of changed files using GitHub Action + uses: lots0logs/gh-action-get-changed-files@2.2.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check Changes of Files + run: | + echo "FILES_MODIFIED=$(cat ${HOME}/files_modified.json)" + echo "FILES_ADDED=$(cat ${HOME}/files_added.json)" + echo "FILES_RENAMED=$(cat ${HOME}/files_renamed.json)" + echo "FILES_DELETED=$(cat ${HOME}/files_deleted.json)" + + - name: Lint Changed Files + run: | + jq -r '.[]' ${HOME}/files_modified.json ${HOME}/files_added.json | sort | uniq > /tmp/changed_files.txt + CHANGED_FILES=$(cat /tmp/changed_files.txt) + echo "These are the changed files: $CHANGED_FILES" + if [[ -n "$CHANGED_FILES" ]]; then + echo "Linting changed files..." + while IFS= read -r file; do + if [[ $file == *.js || $file == *.ts || $file == *.tsx ]]; then + echo "linting file $file" + yarn lint "$file" + fi + done < /tmp/changed_files.txt + else + echo "No matched files to lint." + fi + working-directory: OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }} diff --git a/babel.config.js b/babel.config.js index 19139b68..dee80e88 100644 --- a/babel.config.js +++ b/babel.config.js @@ -8,5 +8,5 @@ // Alternative to install them locally in node_modules module.exports = { presets: [require("@babel/preset-env"), require("@babel/preset-react"), require("@babel/preset-typescript")], - plugins: [require("@babel/plugin-proposal-class-properties"), require("@babel/plugin-proposal-object-rest-spread"), ["@babel/transform-runtime"]] + plugins: [require("@babel/plugin-transform-logical-assignment-operators"), ["@babel/transform-runtime"]] }; diff --git a/common/constants/index.ts b/common/constants/index.ts index 2f04ad9d..260eeade 100644 --- a/common/constants/index.ts +++ b/common/constants/index.ts @@ -72,6 +72,7 @@ export const ACCELERATION_AGGREGRATION_FUNCTIONS = [ export const ACCELERATION_TIME_INTERVAL = [ { text: 'millisecond(s)', value: 'millisecond' }, { text: 'second(s)', value: 'second' }, + { text: 'minutes(s)', value: 'minute' }, { text: 'hour(s)', value: 'hour' }, { text: 'day(s)', value: 'day' }, { text: 'week(s)', value: 'week' }, diff --git a/common/utils/async_query_helpers.ts b/common/utils/async_query_helpers.ts index bc4f874c..3990b189 100644 --- a/common/utils/async_query_helpers.ts +++ b/common/utils/async_query_helpers.ts @@ -12,24 +12,32 @@ import { POLL_INTERVAL_MS, } from '../constants'; -export const setAsyncSessionId = (value: string | null) => { +export const setAsyncSessionId = (dataSource: string, value: string | null) => { if (value !== null) { - sessionStorage.setItem(ASYNC_QUERY_SESSION_ID, value); + sessionStorage.setItem(`${ASYNC_QUERY_SESSION_ID}_${dataSource}`, value); } }; -export const getAsyncSessionId = () => { - return sessionStorage.getItem(ASYNC_QUERY_SESSION_ID); +export const getAsyncSessionId = (dataSource: string) => { + return sessionStorage.getItem(`${ASYNC_QUERY_SESSION_ID}_${dataSource}`); }; -export const getJobId = (query: {}, http: CoreStart['http'], callback) => { +export const getJobId = ( + currentDataSource: string, + query: {}, + http: CoreStart['http'], + callback +) => { http .post(ASYNC_QUERY_ENDPOINT, { - body: JSON.stringify({ ...query, sessionId: getAsyncSessionId() ?? undefined }), + body: JSON.stringify({ + ...query, + sessionId: getAsyncSessionId(currentDataSource) ?? undefined, + }), }) .then((res) => { const id = res.data.resp.queryId; - setAsyncSessionId(_.get(res.data.resp, 'sessionId', null)); + setAsyncSessionId(currentDataSource, _.get(res.data.resp, 'sessionId', null)); if (id === undefined) { console.error(JSON.parse(res.data.body)); } @@ -51,14 +59,14 @@ export const pollQueryStatus = (id: string, http: CoreStart['http'], callback) = status === 'scheduled' || status === 'waiting' ) { - callback({ status: status }); + callback({ status }); setTimeout(() => pollQueryStatus(id, http, callback), POLL_INTERVAL_MS); } else if (status === 'failed') { const results = res.data.resp; callback({ status: 'FAILED', error: results.error }); } else if (status === 'success') { const results = _.get(res.data.resp, 'datarows'); - callback({ status: 'SUCCESS', results: results }); + callback({ status: 'SUCCESS', results }); } }) .catch((err) => { diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 00000000..dff98e5e --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,36 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + video: true, + chromeWebSecurity: true, + fixturesFolder: '.cypress/fixtures', + screenshotsFolder: '.cypress/screenshots', + videosFolder: '.cypress/videos', + downloadsFolder: '.cypress/downloads', + viewportWidth: 2000, + viewportHeight: 1320, + requestTimeout: 60000, + responseTimeout: 60000, + defaultCommandTimeout: 60000, + //experimentalNetworkStubbing: true, + //experimentalMemoryManagement: true, + numTestsKeptInMemory: 10, //Default value 50, chrome crashes without lowering + env: { + opensearch: 'localhost:9200', + opensearchDashboards: 'localhost:5601', + security_enabled: true, + }, + 'cypress-watch-and-reload': { + watch: ['common/**', 'public/**', 'server/**'], + }, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require('./.cypress/plugins/index.js')(on, config); + }, + baseUrl: 'http://localhost:5601', + specPattern: '.cypress/integration/**/*.spec.{js,jsx,ts,tsx}', + supportFile: '.cypress/support/index.js', + }, +}); diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 53c4ba96..00000000 --- a/cypress.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "baseUrl": "http://localhost:5601", - "video": true, - "fixturesFolder": ".cypress/fixtures", - "integrationFolder": ".cypress/integration", - "pluginsFile": ".cypress/plugins/index.js", - "screenshotsFolder": ".cypress/screenshots", - "supportFile": ".cypress/support/index.js", - "videosFolder": ".cypress/videos", - "requestTimeout": 60000, - "responseTimeout": 60000, - "defaultCommandTimeout": 60000, - "env": { - "opensearch": "localhost:9200", - "opensearchDashboards": "localhost:5601", - "security_enabled": true - } -} diff --git a/package.json b/package.json index 4bb29f82..a1705325 100644 --- a/package.json +++ b/package.json @@ -12,30 +12,33 @@ "scripts": { "osd": "node ../../scripts/osd", "opensearch": "node ../../scripts/opensearch", - "lint": "tslint workbench", + "lint:es": "node ../../scripts/eslint", + "lint": "yarn lint:es", "start": "plugin-helpers start", "test:server": "plugin-helpers test:server", "test:browser": "plugin-helpers test:browser", "test:jest": "../../node_modules/.bin/jest --config test/jest.config.js", "test": "yarn test:jest", "build": "yarn plugin-helpers build", - "plugin-helpers": "node ../../scripts/plugin_helpers" + "plugin-helpers": "node ../../scripts/plugin_helpers", + "cypress:run": "TZ=America/Los_Angeles cypress run", + "cypress:open": "TZ=America/Los_Angeles cypress open" }, "dependencies": { "brace": "0.11.1", "react-double-scrollbar": "^0.0.15" }, "devDependencies": { - "@testing-library/user-event": "^13.1.9", + "@testing-library/user-event": "^14.4.3", "@types/enzyme-adapter-react-16": "^1.0.6", - "@types/react-test-renderer": "^16.9.1", - "cypress": "^5.0.0", + "@types/react-test-renderer": "^18.0.0", + "cypress": "^13.6.0", "eslint": "^6.8.0", - "husky": "^4.2.5", + "husky": "^8.0.3", + "jest-dom": "^4.0.0", "jest-raw-loader": "^1.0.1", - "lint-staged": "^10.2.0", + "lint-staged": "^13.1.0", "mutationobserver-shim": "^0.3.3", - "jest-dom": "^4.0.0", "ts-jest": "^29.1.0" }, "resolutions": { diff --git a/public/components/Main/__snapshots__/main.test.tsx.snap b/public/components/Main/__snapshots__/main.test.tsx.snap index c7fc26df..d3719bfb 100644 --- a/public/components/Main/__snapshots__/main.test.tsx.snap +++ b/public/components/Main/__snapshots__/main.test.tsx.snap @@ -8220,7 +8220,11 @@ exports[`
spec renders the component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -8317,7 +8321,11 @@ exports[`
spec renders the component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -8357,7 +8365,11 @@ exports[`
spec renders the component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
spec renders the component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + spec', () => { }); it('click clear button', async () => { - let postRequestFlag = 0; const client = httpClientMock; client.get = jest.fn().mockResolvedValue(mockDatasourcesQuery); client.post = jest.fn(() => { diff --git a/public/components/Main/main.tsx b/public/components/Main/main.tsx index bf7c101c..b0d6d1f9 100644 --- a/public/components/Main/main.tsx +++ b/public/components/Main/main.tsx @@ -58,18 +58,20 @@ export interface ResponseDetail { data?: T; } -export type TranslateResult = { [key: string]: any }; +export interface TranslateResult { + [key: string]: any; +} export interface QueryMessage { text: any; className: string; } -export type QueryResult = { +export interface QueryResult { fields: string[]; records: DataRow[]; message: string; -}; +} export interface Tab { id: string; @@ -77,18 +79,18 @@ export interface Tab { disabled: boolean; } -export type ItemIdToExpandedRowMap = { +export interface ItemIdToExpandedRowMap { [key: string]: { nodes: Tree; expandedRow?: {}; selectedNodes?: { [key: string]: any }; }; -}; +} -export type DataRow = { +export interface DataRow { rowId: number; data: { [key: string]: any }; -}; +} interface MainProps { httpClient: CoreStart['http']; @@ -112,7 +114,7 @@ interface MainState { selectedTabId: string; searchQuery: string; itemIdToExpandedRowMap: ItemIdToExpandedRowMap; - messages: Array; + messages: QueryMessage[]; isResultFullScreen: boolean; selectedDatasource: EuiComboBoxOptionOption[]; asyncLoading: boolean; @@ -127,7 +129,7 @@ interface MainState { const SUCCESS_MESSAGE = 'Success'; const errorQueryResponse = (queryResultResponseDetail: any) => { - let errorMessage = + const errorMessage = queryResultResponseDetail.errorMessage + ', this query is not runnable. \n \n' + queryResultResponseDetail.data; @@ -135,9 +137,9 @@ const errorQueryResponse = (queryResultResponseDetail: any) => { }; export function getQueryResultsForTable( - queryResults: ResponseDetail[], + queryResults: Array>, jsonParseData: boolean -): ResponseDetail[] { +): Array> { return queryResults.map( (queryResultResponseDetail: ResponseDetail): ResponseDetail => { if (!queryResultResponseDetail.fulfilled) { @@ -150,8 +152,8 @@ export function getQueryResultsForTable( ? JSON.parse(queryResultResponseDetail.data) : queryResultResponseDetail.data; const responseObj = queryResultResponseDetail.data ? resultData : ''; - let fields: string[] = []; - let dataRows: DataRow[] = []; + const fields: string[] = []; + const dataRows: DataRow[] = []; const schema: object[] = _.get(responseObj, 'schema'); const datarows: any[][] = _.get(responseObj, 'datarows'); @@ -179,9 +181,9 @@ export function getQueryResultsForTable( } for (const [id, field] of datarows.entries()) { - let row: { [key: string]: any } = {}; - row['TABLE_NAME'] = field[index]; - let dataRow: DataRow = { + const row: { [key: string]: any } = {}; + row.TABLE_NAME = field[index]; + const dataRow: DataRow = { rowId: id, data: row, }; @@ -203,12 +205,12 @@ export function getQueryResultsForTable( } for (const [id, data] of datarows.entries()) { - let row: { [key: string]: any } = {}; - for (const index of schema.keys()) { - const fieldname = fields[index]; - row[fieldname] = _.isNull(data[index]) ? '-' : data[index]; + const row: { [key: string]: any } = {}; + for (const idx of schema.keys()) { + const fieldname = fields[idx]; + row[fieldname] = _.isNull(data[idx]) ? '-' : data[idx]; } - let dataRow: DataRow = { + const dataRow: DataRow = { rowId: id, data: row, }; @@ -221,7 +223,7 @@ export function getQueryResultsForTable( return { fulfilled: queryResultResponseDetail.fulfilled, data: { - fields: fields, + fields, records: dataRows, message: SUCCESS_MESSAGE, }, @@ -309,14 +311,14 @@ export class Main extends React.Component { }; } if (!response.data.ok) { - let err = response.data.resp; + const err = response.data.resp; console.log('Error occurred when processing query response: ', err); // Exclude a special case from the error cases: // When downloading the csv result, it gets the "Unable to parse/serialize body" response // But data is also returned in data body. For this case: // Mark fulfilled to true for this case to write the csv result to downloading file - if (response.data.body && err == 'Unable to parse/serialize body') { + if (response.data.body && err === 'Unable to parse/serialize body') { return { fulfilled: true, errorMessage: err, @@ -358,7 +360,7 @@ export class Main extends React.Component { }; // It returns the error or successful message to display in the Message Tab - getMessage(queryResultsForTable: ResponseDetail[]): Array { + getMessage(queryResultsForTable: Array>): QueryMessage[] { return queryResultsForTable.map((queryResult) => { return { text: @@ -370,7 +372,7 @@ export class Main extends React.Component { }); } - getTranslateMessage(translationResult: ResponseDetail[]): Array { + getTranslateMessage(translationResult: Array>): QueryMessage[] { return translationResult.map((translation) => { return { text: translation.data ? SUCCESS_MESSAGE : translation.errorMessage, @@ -383,7 +385,7 @@ export class Main extends React.Component { const queries: string[] = getQueries(queriesString); const language = this.state.language; if (queries.length > 0) { - let endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqlquery' : 'pplquery'); + const endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqlquery' : 'pplquery'); const responsePromise = Promise.all( queries.map((query: string) => this.httpClient @@ -401,13 +403,16 @@ export class Main extends React.Component { ) ); Promise.all([responsePromise]).then(([response]) => { - const results: ResponseDetail[] = response.map((response) => - this.processQueryResponse(response as IHttpResponse) + const results: Array> = response.map((resp) => + this.processQueryResponse(resp as IHttpResponse) + ); + const resultTable: Array> = getQueryResultsForTable( + results, + true ); - const resultTable: ResponseDetail[] = getQueryResultsForTable(results, true); this.setState( { - queries: queries, + queries, queryResults: results, queryResultsTable: resultTable, selectedTabId: getDefaultTabId(results), @@ -434,6 +439,7 @@ export class Main extends React.Component { // finding regular query here const queries: string[] = getQueries(queriesString); const language = this.state.language; + const currentDataSource = this.state.selectedDatasource[0].label; if (queries.length > 0) { const responsePromise = Promise.all( queries.map((query: string) => @@ -441,9 +447,9 @@ export class Main extends React.Component { .post(ASYNC_QUERY_ENDPOINT, { body: JSON.stringify({ lang: language, - query: query, - datasource: this.state.selectedDatasource[0].label, - sessionId: getAsyncSessionId() ?? undefined, + query, + datasource: currentDataSource, + sessionId: getAsyncSessionId(currentDataSource) ?? undefined, }), }) .catch((error: any) => { @@ -460,8 +466,8 @@ export class Main extends React.Component { ); Promise.all([responsePromise]).then(([response]) => { - const results: ResponseDetail[] = response.map((response) => - this.processQueryResponse(response as IHttpResponse) + const results: Array> = response.map((resp) => + this.processQueryResponse(resp as IHttpResponse) ); results.map( (queryResultResponseDetail: ResponseDetail): ResponseDetail => { @@ -476,7 +482,7 @@ export class Main extends React.Component { : ''; const queryId: string = _.get(responseObj, 'queryId'); - setAsyncSessionId(_.get(responseObj, 'sessionId', null)); + setAsyncSessionId(currentDataSource, _.get(responseObj, 'sessionId', null)); // clear state from previous results and start async loading this.setState({ @@ -527,13 +533,16 @@ export class Main extends React.Component { const result: ResponseDetail = this.processQueryResponse( response as IHttpResponse ); - const status = result.data['status']; + const status = result.data.status; if (_.isEqual(status, 'SUCCESS')) { - const resultTable: ResponseDetail[] = getQueryResultsForTable([result], false); + const resultTable: Array> = getQueryResultsForTable( + [result], + false + ); this.setState({ - queries: queries, + queries, queryResults: [result], - queryResultsTable: result.data['schema'].length > 0 ? resultTable : [], + queryResultsTable: result.data.schema.length > 0 ? resultTable : [], selectedTabId: getDefaultTabId([result]), selectedTabName: getDefaultTabLabel([result], queries[0]), messages: this.getMessage(resultTable), @@ -544,7 +553,7 @@ export class Main extends React.Component { searchQuery: '', asyncLoading: false, asyncLoadingStatus: status, - isCallOutVisible: !(result.data['schema'].length > 0), + isCallOutVisible: !(result.data.schema.length > 0), }); } else if (_.isEqual(status, 'FAILED') || _.isEqual(status, 'CANCELLED')) { this.setState({ @@ -556,7 +565,7 @@ export class Main extends React.Component { className: 'error-message', }, ], - asyncQueryError: result.data['error'], + asyncQueryError: result.data.error, }); } else { this.setState({ @@ -589,12 +598,12 @@ export class Main extends React.Component { const language = this.state.language; if (queries.length > 0) { - let endpoint = + const endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'translatesql' : 'translateppl'); const translationPromise = Promise.all( queries.map((query: string) => this.httpClient - .post(endpoint, { body: JSON.stringify({ query: query }) }) + .post(endpoint, { body: JSON.stringify({ query }) }) .catch((error: any) => { this.setState({ messages: [ @@ -609,12 +618,12 @@ export class Main extends React.Component { ); Promise.all([translationPromise]).then(([translationResponse]) => { - const translationResult: ResponseDetail< + const translationResult: Array[] = translationResponse.map((translationResponse) => - this.processTranslateResponse(translationResponse as IHttpResponse) + >> = translationResponse.map((translationResp) => + this.processTranslateResponse(translationResp as IHttpResponse) ); - const shouldCleanResults = queries == this.state.queries; + const shouldCleanResults = queries === this.state.queries; if (shouldCleanResults) { this.setState({ queries, @@ -653,8 +662,8 @@ export class Main extends React.Component { }) ) ).then((response) => { - const results: ResponseDetail[] = response.map((response) => - this.processQueryResponse(response as IHttpResponse) + const results: Array> = response.map((resp) => + this.processQueryResponse(resp as IHttpResponse) ); this.setState( { @@ -670,11 +679,11 @@ export class Main extends React.Component { getJdbc = (queries: string[]): void => { const language = this.state.language; if (queries.length > 0) { - let endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqlquery' : 'pplquery'); + const endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqlquery' : 'pplquery'); Promise.all( queries.map((query: string) => this.httpClient - .post(endpoint, { body: JSON.stringify({ query: query }) }) + .post(endpoint, { body: JSON.stringify({ query }) }) .catch((error: any) => { this.setState({ messages: [ @@ -687,8 +696,8 @@ export class Main extends React.Component { }) ) ).then((jdbcResponse) => { - const jdbcResult: ResponseDetail[] = jdbcResponse.map((jdbcResponse) => - this.processQueryResponse(jdbcResponse as IHttpResponse) + const jdbcResult: Array> = jdbcResponse.map((jdbcResp) => + this.processQueryResponse(jdbcResp as IHttpResponse) ); this.setState( { @@ -704,11 +713,11 @@ export class Main extends React.Component { getCsv = (queries: string[]): void => { const language = this.state.language; if (queries.length > 0) { - let endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqlcsv' : 'pplcsv'); + const endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqlcsv' : 'pplcsv'); Promise.all( queries.map((query: string) => this.httpClient - .post(endpoint, { body: JSON.stringify({ query: query }) }) + .post(endpoint, { body: JSON.stringify({ query }) }) .catch((error: any) => { this.setState({ messages: [ @@ -721,8 +730,8 @@ export class Main extends React.Component { }) ) ).then((csvResponse) => { - const csvResult: ResponseDetail[] = csvResponse.map((csvResponse) => - this.processQueryResponse(csvResponse as IHttpResponse) + const csvResult: Array> = csvResponse.map((csvResp) => + this.processQueryResponse(csvResp as IHttpResponse) ); this.setState( { @@ -738,11 +747,11 @@ export class Main extends React.Component { getText = (queries: string[]): void => { const language = this.state.language; if (queries.length > 0) { - let endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqltext' : 'ppltext'); + const endpoint = '/api/sql_console/' + (_.isEqual(language, 'SQL') ? 'sqltext' : 'ppltext'); Promise.all( queries.map((query: string) => this.httpClient - .post(endpoint, { body: JSON.stringify({ query: query }) }) + .post(endpoint, { body: JSON.stringify({ query }) }) .catch((error: any) => { this.setState({ messages: [ @@ -755,8 +764,8 @@ export class Main extends React.Component { }) ) ).then((textResponse) => { - const textResult: ResponseDetail[] = textResponse.map((textResponse) => - this.processQueryResponse(textResponse as IHttpResponse) + const textResult: Array> = textResponse.map((textResp) => + this.processQueryResponse(textResp as IHttpResponse) ); this.setState( { @@ -845,7 +854,7 @@ export class Main extends React.Component { let link; let linkTitle; - if (this.state.language == 'SQL') { + if (this.state.language === 'SQL') { page = ( { - +
{page}
@@ -1082,5 +1091,3 @@ export class Main extends React.Component { ); } } - -export default Main; diff --git a/public/components/PPLPage/PPLPage.tsx b/public/components/PPLPage/PPLPage.tsx index 36a9e30a..6deb9df0 100644 --- a/public/components/PPLPage/PPLPage.tsx +++ b/public/components/PPLPage/PPLPage.tsx @@ -30,7 +30,7 @@ interface PPLPageProps { onClear: () => void; updatePPLQueries: (query: string) => void; pplQuery: string; - pplTranslations: ResponseDetail[]; + pplTranslations: Array>; selectedDatasource: EuiComboBoxOptionOption[]; asyncLoading: boolean; } @@ -122,7 +122,6 @@ export class PPLPage extends React.Component { showGutter: false, }} aria-label="Code Editor" - isReadOnly={this.props.asyncLoading} /> diff --git a/public/components/QueryResults/QueryResultsBody.test.tsx b/public/components/QueryResults/QueryResultsBody.test.tsx index b9f5aa1c..34e4ddb6 100644 --- a/public/components/QueryResults/QueryResultsBody.test.tsx +++ b/public/components/QueryResults/QueryResultsBody.test.tsx @@ -4,7 +4,7 @@ */ import '@testing-library/jest-dom/extend-expect'; -import { cleanup, fireEvent, render } from '@testing-library/react'; +import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import QueryResultsBody from './QueryResultsBody'; // @ts-ignore @@ -72,7 +72,7 @@ function renderSQLQueryResultsBody( }; } -describe(' spec', () => { +describe(' spec', () => { afterEach(cleanup); const onQueryChange = jest.fn(); const updateExpandedMap = jest.fn(); @@ -87,7 +87,7 @@ describe(' spec', () => { [ { name: '', - getValue: (item: any) => '', + getValue: () => '', isAscending: true, }, ], @@ -128,7 +128,6 @@ describe(' spec', () => { const { getAllByText, - getAllByTestId, getAllByLabelText, getByText, getByPlaceholderText, @@ -154,10 +153,10 @@ describe(' spec', () => { // Test pagination await fireEvent.click(getAllByText('Rows per page', { exact: false })[0]); - expect(getByText('10 rows')); - expect(getByText('20 rows')); - expect(getByText('50 rows')); - expect(getByText('100 rows')); + expect(getByText('10 rows')).toBeInTheDocument(); + expect(getByText('20 rows')).toBeInTheDocument(); + expect(getByText('50 rows')).toBeInTheDocument(); + expect(getByText('100 rows')).toBeInTheDocument(); await fireEvent.click(getByText('20 rows')); expect(onChangeItemsPerPage).toHaveBeenCalled(); @@ -174,10 +173,10 @@ describe(' spec', () => { const downloadButton = getAllByText('Download')[0]; expect(downloadButton).not.toBe(null); await fireEvent.click(downloadButton); - expect(getByText('Download JSON')); - expect(getByText('Download JDBC')); - expect(getByText('Download CSV')); - expect(getByText('Download Text')); + expect(getByText('Download JSON')).toBeInTheDocument(); + expect(getByText('Download JDBC')).toBeInTheDocument(); + expect(getByText('Download CSV')).toBeInTheDocument(); + expect(getByText('Download Text')).toBeInTheDocument(); await fireEvent.click(getByText('Download JSON')); await fireEvent.click(getByText('Download JDBC')); await fireEvent.click(getByText('Download CSV')); @@ -185,9 +184,13 @@ describe(' spec', () => { // Test search field const searchField = getByPlaceholderText('Search keyword'); - expect(searchField).not.toBe(null); - await userEvent.type(searchField, 'Test'); - expect(onQueryChange).toHaveBeenCalled(); + expect(searchField).toBeInTheDocument(); + act(() => { + userEvent.type(searchField, 'Test'); + }); + waitFor(() => { + expect(onQueryChange).toHaveBeenCalled(); + }); // Test collapse button expect(document.body.children[0]).toMatchSnapshot(); @@ -246,7 +249,7 @@ function renderPPLQueryResultsBody( }; } -describe(' spec', () => { +describe(' spec', () => { afterEach(cleanup); const onQueryChange = jest.fn(); const updateExpandedMap = jest.fn(); @@ -261,7 +264,7 @@ describe(' spec', () => { [ { name: '', - getValue: (item: any) => '', + getValue: () => '', isAscending: true, }, ], @@ -300,13 +303,7 @@ describe(' spec', () => { (window as any).HTMLElement.prototype.scrollIntoView = function () {}; - const { - getAllByText, - getAllByTestId, - getAllByLabelText, - getByText, - getByPlaceholderText, - } = renderPPLQueryResultsBody( + const { getAllByText, getAllByLabelText, getByText } = renderPPLQueryResultsBody( undefined, mockQueryResults[0].data, mockQueryResultJDBCResponse.data.resp, @@ -328,10 +325,10 @@ describe(' spec', () => { // Test pagination await fireEvent.click(getAllByText('Rows per page', { exact: false })[0]); - expect(getByText('10 rows')); - expect(getByText('20 rows')); - expect(getByText('50 rows')); - expect(getByText('100 rows')); + expect(getByText('10 rows')).toBeInTheDocument(); + expect(getByText('20 rows')).toBeInTheDocument(); + expect(getByText('50 rows')).toBeInTheDocument(); + expect(getByText('100 rows')).toBeInTheDocument(); await fireEvent.click(getByText('20 rows')); expect(onChangeItemsPerPage).toHaveBeenCalled(); diff --git a/public/components/QueryResults/__snapshots__/QueryResults.test.tsx.snap b/public/components/QueryResults/__snapshots__/QueryResults.test.tsx.snap index d717d8e6..64b61838 100644 --- a/public/components/QueryResults/__snapshots__/QueryResults.test.tsx.snap +++ b/public/components/QueryResults/__snapshots__/QueryResults.test.tsx.snap @@ -79,7 +79,11 @@ exports[` spec renders async query failure component 1`] = viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
@@ -9746,7 +9750,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -9935,7 +9943,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
@@ -10005,7 +10017,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -10044,7 +10060,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -10524,7 +10544,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -10901,7 +10925,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -11278,7 +11306,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -11655,7 +11687,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -12032,7 +12068,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -12409,7 +12449,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -12786,7 +12830,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -13163,7 +13211,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -13540,7 +13592,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -13917,7 +13973,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -14304,7 +14364,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -14339,7 +14403,11 @@ exports[` spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
    spec renders the component with mock query re viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + diff --git a/public/components/QueryResults/__snapshots__/QueryResultsBody.test.tsx.snap b/public/components/QueryResults/__snapshots__/QueryResultsBody.test.tsx.snap index c33b1f8e..d75233d4 100644 --- a/public/components/QueryResults/__snapshots__/QueryResultsBody.test.tsx.snap +++ b/public/components/QueryResults/__snapshots__/QueryResultsBody.test.tsx.snap @@ -1,119 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` spec renders component with mock QueryResults data 1`] = ` +exports[` spec renders component with mock QueryResults data 1`] = `
    -
    -
    -

    - Messages - - (13) - -

    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    + />
    @@ -169,7 +62,6 @@ exports[` spec renders component with mock QueryResults data > category @@ -186,7 +78,6 @@ exports[` spec renders component with mock QueryResults data > currency @@ -203,7 +94,6 @@ exports[` spec renders component with mock QueryResults data > customer_first_name @@ -220,7 +110,6 @@ exports[` spec renders component with mock QueryResults data > customer_full_name @@ -237,7 +126,6 @@ exports[` spec renders component with mock QueryResults data > customer_gender @@ -254,7 +142,6 @@ exports[` spec renders component with mock QueryResults data > customer_id @@ -271,7 +158,6 @@ exports[` spec renders component with mock QueryResults data > customer_last_name @@ -288,7 +174,6 @@ exports[` spec renders component with mock QueryResults data > customer_phone @@ -305,7 +190,6 @@ exports[` spec renders component with mock QueryResults data > day_of_week @@ -322,7 +206,6 @@ exports[` spec renders component with mock QueryResults data > day_of_week_i @@ -339,7 +222,6 @@ exports[` spec renders component with mock QueryResults data > email @@ -356,7 +238,6 @@ exports[` spec renders component with mock QueryResults data > manufacturer @@ -373,7 +254,6 @@ exports[` spec renders component with mock QueryResults data > order_date @@ -390,7 +270,6 @@ exports[` spec renders component with mock QueryResults data > order_id @@ -407,7 +286,6 @@ exports[` spec renders component with mock QueryResults data > products @@ -424,7 +302,6 @@ exports[` spec renders component with mock QueryResults data > sku @@ -441,7 +318,6 @@ exports[` spec renders component with mock QueryResults data > taxful_total_price @@ -458,7 +334,6 @@ exports[` spec renders component with mock QueryResults data > taxless_total_price @@ -475,7 +350,6 @@ exports[` spec renders component with mock QueryResults data > total_quantity @@ -492,7 +366,6 @@ exports[` spec renders component with mock QueryResults data > total_unique_products @@ -509,7 +382,6 @@ exports[` spec renders component with mock QueryResults data > type @@ -526,7 +398,6 @@ exports[` spec renders component with mock QueryResults data > user @@ -543,7 +414,6 @@ exports[` spec renders component with mock QueryResults data > geoip @@ -574,14 +444,19 @@ exports[` spec renders component with mock QueryResults data > + > + +
    @@ -951,14 +826,19 @@ exports[` spec renders component with mock QueryResults data > + > + +
    @@ -1328,14 +1208,19 @@ exports[` spec renders component with mock QueryResults data > + > + +
    @@ -1705,14 +1590,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -2082,14 +1972,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -2459,14 +2354,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -2836,14 +2736,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -3213,14 +3118,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -3590,14 +3500,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -3967,14 +3882,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -4344,14 +4264,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -4731,14 +4656,19 @@ exports[` spec renders component with mock QueryResults data > + > + + @@ -4766,14 +4696,19 @@ exports[` spec renders component with mock QueryResults data > + > + +
      spec renders component with mock QueryResults data > + > + + @@ -4803,15 +4743,137 @@ exports[` spec renders component with mock QueryResults data `; -exports[` spec renders component with mock QueryResults data 2`] = ` +exports[` spec renders the component 1`] = `
      -
      +
      -
      +

      + Press Enter to start editing. +

      +

      + When you're done, press Escape to stop editing. +

      + +
      +