diff --git a/.github/workflows/manual-create-pd-pr.yaml b/.github/workflows/manual-create-pd-pr.yaml index 782465f739..5aca918001 100644 --- a/.github/workflows/manual-create-pd-pr.yaml +++ b/.github/workflows/manual-create-pd-pr.yaml @@ -12,8 +12,9 @@ jobs: name: Create PD PR runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - branch: [master, release-5.3, release-5.4] + branch: [master, release-5.4, release-6.1] steps: - name: Check out PD code base uses: actions/checkout@master @@ -22,19 +23,23 @@ jobs: ref: ${{ matrix.branch }} - uses: actions/setup-go@v2 with: - go-version: "1.13.5" + go-version: "1.18.1" - name: Update TiDB Dashboard in PD code base run: | go get -d "github.com/pingcap/tidb-dashboard@${{ github.event.inputs.release_version }}" go mod tidy + cd tests/client + go mod tidy + cd ../.. make pd-server go mod tidy - name: Commit PD code base change id: git_commit run: | + git diff git config user.name "tidb-dashboard-bot" git config user.email "tidb-dashboard-bot@pingcap.com" - git add go.mod go.sum + git add . if git status | grep -q "Changes to be committed" then git commit --signoff --message "Update TiDB Dashboard to ${{ github.event.inputs.release_version }}, ref #4257" @@ -55,6 +60,8 @@ jobs: body: | ### What problem does this PR solve? + Issue Number: ref #4257 + Update TiDB Dashboard to ${{ github.event.inputs.release_version }}. Upstream commit: https://github.com/${{ github.repository }}/commit/${{ github.sha }} . diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a66bb6dc90..acc7deff2c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -88,8 +88,9 @@ jobs: runs-on: ubuntu-latest needs: release strategy: + fail-fast: false matrix: - branch: [master, release-5.3, release-5.4] + branch: [master, release-5.4, release-6.1] steps: - name: Check out PD code base uses: actions/checkout@master @@ -98,19 +99,23 @@ jobs: ref: ${{ matrix.branch }} - uses: actions/setup-go@v2 with: - go-version: "1.16.5" + go-version: "1.18.1" - name: Update TiDB Dashboard in PD code base run: | go get -d "github.com/pingcap/tidb-dashboard@${{ needs.release.outputs.release_version }}" go mod tidy + cd tests/client + go mod tidy + cd ../.. make pd-server go mod tidy - name: Commit PD code base change id: git_commit run: | + git diff git config user.name "tidb-dashboard-bot" git config user.email "tidb-dashboard-bot@pingcap.com" - git add go.mod go.sum + git add . if git status | grep -q "Changes to be committed" then git commit --signoff --message "Update TiDB Dashboard to ${{ needs.release.outputs.release_version }}, ref #4257" @@ -131,6 +136,8 @@ jobs: body: | ### What problem does this PR solve? + Issue Number: ref #4257 + This is an automatic updating PR for TiDB Dashboard. See #4257 for details. This PR updates TiDB Dashboard to ${{ needs.release.outputs.release_version }} for upstream commit: https://github.com/${{ github.repository }}/commit/${{ github.sha }} . diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 28d7659647..1c202c6bf9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -76,12 +76,21 @@ jobs: matrix: # test latest features and compatibility of lower version include: + - feature_version: 6.0.0 + tidb_version: latest + without_ngm: false + - feature_version: 6.0.0 + tidb_version: latest + without_ngm: true - feature_version: 6.0.0 tidb_version: nightly + without_ngm: false - feature_version: 5.4.0 tidb_version: v5.4.0 + without_ngm: false - feature_version: 5.0.0 tidb_version: v5.0.0 + without_ngm: false steps: - name: Checkout code uses: actions/checkout@v2 @@ -117,7 +126,7 @@ jobs: - name: Install and run TiUP in the background run: | chmod u+x scripts/start_tiup.sh - scripts/start_tiup.sh ${{ matrix.tidb_version }} + scripts/start_tiup.sh ${{ matrix.tidb_version }} ${{ matrix.without_ngm }} - name: Build UI run: | make ui @@ -150,18 +159,14 @@ jobs: UI: 1 FEATURE_VERSION: ${{ matrix.feature_version }} - name: Run E2E Features Test - run: make test_e2e + run: make e2e_test env: SERVER_URL: http://127.0.0.1:12333/dashboard/ CI: true FEATURE_VERSION: ${{ matrix.feature_version }} TIDB_VERSION: ${{ matrix.tidb_version }} - - name: Archive Test Results - if: always() - run: | - cat ui/start_tiup.log - echo "===============" - cat ui/wait_tiup.log + CYPRESS_ALLOW_SCREENSHOT: true + WITHOUT_NGM: ${{ matrix.without_ngm }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: diff --git a/.github/workflows/upload-e2e-snapshots.yaml b/.github/workflows/upload-e2e-snapshots.yaml index 05d81153a4..d35555db6b 100644 --- a/.github/workflows/upload-e2e-snapshots.yaml +++ b/.github/workflows/upload-e2e-snapshots.yaml @@ -7,6 +7,9 @@ on: ref: description: "The branch, tag or SHA to create snapshots" required: true + spec: + description: "Specify the spec files to run, example: `topsql/topsql.spec.ts`" + required: true jobs: e2e_test_snapshots: @@ -19,7 +22,7 @@ jobs: # test latest features and compatibility of lower version include: - feature_version: 6.0.0 - tidb_version: nightly + tidb_version: latest - feature_version: 5.4.0 tidb_version: v5.4.0 - feature_version: 5.0.0 @@ -61,7 +64,7 @@ jobs: - name: Install and run TiUP in the background run: | chmod u+x scripts/start_tiup.sh - scripts/start_tiup.sh ${{ matrix.tidb_version }} + scripts/start_tiup.sh ${{ matrix.tidb_version }} false - name: Build UI run: | make ui @@ -92,22 +95,26 @@ jobs: env: UI: 1 FEATURE_VERSION: ${{ matrix.feature_version }} + - name: Delete Previous Snapshots + run: rm -rf ${{ github.workspace }}/ui/cypress/snapshots - name: Run E2E Features Test - run: make e2e_test + run: make e2e_test_specify env: SERVER_URL: http://127.0.0.1:12333/dashboard/ CI: true FEATURE_VERSION: ${{ matrix.feature_version }} TIDB_VERSION: ${{ matrix.tidb_version }} CYPRESS_ALLOW_SCREENSHOT: true - - name: Archive Test Results + E2E_SPEC: cypress/integration/${{ github.event.inputs.spec }} + - name: Archive Test Video if: always() - run: | - cat ui/start_tiup.log - echo "===============" - cat ui/wait_tiup.log + uses: actions/upload-artifact@v2 + with: + name: e2e-video-${{ matrix.feature_version }} + path: ${{ github.workspace }}/ui/cypress/videos/**/* - name: Upload snapshots artifact uses: actions/upload-artifact@v2 + if: always() with: name: e2e-snapshots-${{ matrix.feature_version }} path: ${{ github.workspace }}/ui/cypress/snapshots/**/* diff --git a/Makefile b/Makefile index 4928064593..f6dc9ecef4 100755 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ LDFLAGS ?= FEATURE_VERSION ?= 6.0.0 +WITHOUT_NGM ?= false + +E2E_SPEC ?= + ifeq ($(UI),1) BUILD_TAGS += ui_server endif @@ -45,6 +49,39 @@ integration_test: @mkdir -p ./coverage @TIDB_VERSION=${TIDB_VERSION} tests/run.sh +.PHONY: e2e_test +e2e_test: + @if $(WITHOUT_NGM); then\ + make e2e_without_ngm_test;\ + else\ + make e2e_compat_features_test;\ + make e2e_common_features_test;\ + fi + +.PHONY: e2e_compat_features_test +e2e_compat_features_test: + cd ui &&\ + yarn &&\ + yarn run:e2e-test:compat-features --env FEATURE_VERSION=$(FEATURE_VERSION) TIDB_VERSION=$(TIDB_VERSION) + +.PHONY: e2e_common_features_test +e2e_common_features_test: + cd ui &&\ + yarn &&\ + yarn run:e2e-test:common-features --env TIDB_VERSION=$(TIDB_VERSION) + +.PHONY: e2e_without_ngm_test +e2e_without_ngm_test: + cd ui &&\ + yarn &&\ + yarn run:e2e-test:without-ngm --env TIDB_VERSION=$(TIDB_VERSION) WITHOUT_NGM=$(WITHOUT_NGM) + +.PHONY: e2e_test_specify +e2e_test_specify: + cd ui &&\ + yarn &&\ + yarn run:e2e-test:specify --env TIDB_VERSION=$(TIDB_VERSION) -- --spec $(E2E_SPEC) + .PHONY: dev dev: lint default @@ -74,13 +111,3 @@ endif .PHONY: run run: bin/tidb-dashboard --debug --experimental --feature-version "$(FEATURE_VERSION)" --host 0.0.0.0 - -test_e2e_compat_features: - cd ui &&\ - yarn run:e2e-test:compat-features --env FEATURE_VERSION=$(FEATURE_VERSION) TIDB_VERSION=$(TIDB_VERSION) - -test_e2e_common_features: - cd ui &&\ - yarn run:e2e-test:common-features --env TIDB_VERSION=$(TIDB_VERSION) - -test_e2e: test_e2e_compat_features test_e2e_common_features \ No newline at end of file diff --git a/pkg/apiserver/debugapi/apis.go b/pkg/apiserver/debugapi/apis.go index 61b2cfdb47..dc7747ec1d 100644 --- a/pkg/apiserver/debugapi/apis.go +++ b/pkg/apiserver/debugapi/apis.go @@ -387,7 +387,7 @@ var apiEndpoints = []endpoint.APIDefinition{ { ID: "pd_pprof", Component: topo.KindPD, - Path: "/pd/api/v1/debug/pprof/{kind}", + Path: "/debug/pprof/{kind}", Method: resty.MethodGet, PathParams: []endpoint.APIParamDefinition{ commomParamPprofKinds, diff --git a/pkg/apiserver/profiling/fetcher.go b/pkg/apiserver/profiling/fetcher.go index 3fbe48de50..25ab29599a 100644 --- a/pkg/apiserver/profiling/fetcher.go +++ b/pkg/apiserver/profiling/fetcher.go @@ -94,6 +94,6 @@ func (f *pdFetcher) fetch(op *fetchOptions) ([]byte, error) { return f.client. WithTimeout(maxProfilingTimeout). WithBaseURL(baseURL). - AddRequestHeader("PD-Allow-follower-handle", "true"). + WithoutPrefix(). // pprof API does not have /pd/api/v1 prefix SendGetRequest(op.path) } diff --git a/pkg/pd/client.go b/pkg/pd/client.go index 070dfb542d..13d35bf539 100644 --- a/pkg/pd/client.go +++ b/pkg/pd/client.go @@ -23,11 +23,12 @@ const ( ) type Client struct { - httpScheme string - baseURL string - httpClient *httpc.Client - lifecycleCtx context.Context - timeout time.Duration + httpScheme string + baseURL string + withoutPrefix bool + httpClient *httpc.Client + lifecycleCtx context.Context + timeout time.Duration } func NewPDClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client { @@ -64,13 +65,25 @@ func (c Client) WithTimeout(timeout time.Duration) *Client { return &c } +func (c Client) WithoutPrefix() *Client { + c.withoutPrefix = true + return &c +} + +func (c Client) getPrefix() string { + if c.withoutPrefix { + return "" + } + return "/pd/api/v1" +} + func (c Client) AddRequestHeader(key, value string) *Client { c.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value) return &c } func (c *Client) Get(relativeURI string) (*httpc.Response, error) { - uri := fmt.Sprintf("%s/pd/api/v1%s", c.baseURL, relativeURI) + uri := fmt.Sprintf("%s%s%s", c.baseURL, c.getPrefix(), relativeURI) return c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrPDClientRequestFailed, distro.R().PD) } @@ -83,6 +96,6 @@ func (c *Client) SendGetRequest(relativeURI string) ([]byte, error) { } func (c *Client) SendPostRequest(relativeURI string, body io.Reader) ([]byte, error) { - uri := fmt.Sprintf("%s/pd/api/v1%s", c.baseURL, relativeURI) + uri := fmt.Sprintf("%s%s%s", c.baseURL, c.getPrefix(), relativeURI) return c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrPDClientRequestFailed, distro.R().PD) } diff --git a/tests/_inc/download_tools.sh b/scripts/_inc/download_tools.sh similarity index 71% rename from tests/_inc/download_tools.sh rename to scripts/_inc/download_tools.sh index e22ab44e12..71c34296b8 100755 --- a/tests/_inc/download_tools.sh +++ b/scripts/_inc/download_tools.sh @@ -10,22 +10,26 @@ BIN="${PROJECT_DIR}/bin" download_tools() { echo "+ Download tools" - if ! command -v tiup >/dev/null 2>&1; then - echo " - Downloading tiup..." - curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh - fi + download_tiup mkdir -p $BIN if [ ! -e "$BIN/toolkit.tar.gz" ]; then echo " - Downloading toolkit..." - curl -L -f -o "$BIN/toolkit.tar.gz" "https://download.pingcap.org/tidb-toolkit-nightly-linux-amd64.tar.gz" + curl -L -f -o "$BIN/toolkit.tar.gz" "https://download.pingcap.org/tidb-toolkit-v6.0.0-linux-amd64.tar.gz" fi if [ ! -e "$BIN/dumpling" ]; then - tar -x -f "$BIN/toolkit.tar.gz" -C "$BIN/" tidb-toolkit-nightly-linux-amd64/bin/dumpling - mv "$BIN"/tidb-toolkit-nightly-linux-amd64/bin/dumpling "$BIN/dumpling" + tar -x -f "$BIN/toolkit.tar.gz" -C "$BIN/" tidb-toolkit-v6.0.0-linux-amd64/bin/dumpling + mv "$BIN"/tidb-toolkit-v6.0.0-linux-amd64/bin/dumpling "$BIN/dumpling" fi echo "+ All binaries are now available." } + +download_tiup() { + if ! command -v tiup >/dev/null 2>&1; then + echo " - Downloading tiup..." + curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh + fi +} diff --git a/tests/_inc/run_services.sh b/scripts/_inc/run_services.sh similarity index 100% rename from tests/_inc/run_services.sh rename to scripts/_inc/run_services.sh diff --git a/scripts/start_tiup.sh b/scripts/start_tiup.sh index ea32a81249..0b9fae5e1e 100755 --- a/scripts/start_tiup.sh +++ b/scripts/start_tiup.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash set -ex tidb_version=$1 -mode=$2 +without_ngm=${2:-false} +mode=${3:-"start"} TIUP_BIN_DIR=$HOME/.tiup/bin/tiup DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" @@ -26,5 +27,5 @@ else $TIUP_BIN_DIR update playground # Run Tiup - $TIUP_BIN_DIR playground ${tidb_version} --tiflash=0 &> start_tiup.log & + $TIUP_BIN_DIR playground ${tidb_version} --without-monitor=${without_ngm} --tiflash=0 &> start_tiup.log & fi diff --git a/tests/dump.sh b/tests/dump.sh index e4e0394c82..8070daf870 100755 --- a/tests/dump.sh +++ b/tests/dump.sh @@ -4,8 +4,8 @@ set -euo pipefail PROJECT_DIR="$(dirname "$0")/.." -source tests/_inc/download_tools.sh >/dev/null -source tests/_inc/run_services.sh >/dev/null +source scripts/_inc/download_tools.sh >/dev/null +source scripts/_inc/run_services.sh >/dev/null download_tools dump_schema $@ diff --git a/tests/run.sh b/tests/run.sh index 45dab98743..4ea8f39e48 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -15,8 +15,8 @@ set -euo pipefail PROJECT_DIR="$(dirname "$0")/.." -source tests/_inc/download_tools.sh >/dev/null -source tests/_inc/run_services.sh >/dev/null +source scripts/_inc/download_tools.sh >/dev/null +source scripts/_inc/run_services.sh >/dev/null download_tools diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index e30a0a39e3..d8431e68e9 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - extends: ['react-app'], + extends: ['react-app', 'plugin:cypress/recommended'], ignorePatterns: ['lib/client/api/*.ts'], rules: { 'react/react-in-jsx-scope': 'error', @@ -13,5 +13,6 @@ module.exports = { '^use(Async|AsyncFn|AsyncRetry|UpdateEffect|IsomorphicLayoutEffect|DeepCompareEffect|ShallowCompareEffect)$', }, ], + 'no-script-url': 'off', }, } diff --git a/ui/builder.js b/ui/builder.js index b48cf7d98b..4b4efbe96e 100755 --- a/ui/builder.js +++ b/ui/builder.js @@ -1,5 +1,6 @@ const fs = require('fs-extra') const os = require('os') +const path = require('path') const chalk = require('chalk') const { start } = require('live-server') const { createProxyMiddleware } = require('http-proxy-middleware') @@ -47,7 +48,7 @@ const devServerParams = { } const lessModifyVars = { - '@primary-color': '#3351ff', + '@primary-color': '#4263eb', '@body-background': '#fff', '@tooltip-bg': 'rgba(0, 0, 0, 0.9)', '@tooltip-max-width': '500px', @@ -142,6 +143,11 @@ const esbuildParams = { }, enableCache: true, plugins: [autoprefixer], + // work same as the webpack NormalModuleReplacementPlugin + moduleReplacements: { + [path.resolve(__dirname, 'node_modules/antd/es/style/index.less')]: + path.resolve(__dirname, 'lib/antd.less'), + }, }), yamlPlugin(), logTime(), diff --git a/ui/cypress.json b/ui/cypress.json index c5863ab049..520a541125 100644 --- a/ui/cypress.json +++ b/ui/cypress.json @@ -6,12 +6,9 @@ "video": true, "env": { "FEATURE_VERSION": "6.0.0", - "TIDB_VERSION": "nightly", + "TIDB_VERSION": "latest", "ALLOW_SCREENSHOT": false }, "experimentalSessionSupport": true, - "viewportWidth": 1500, - "viewportHeight": 1000, - "ignoreTestFiles": ["**/__snapshots__/*", "**/__image_snapshots__/*"], - "projectId": "tckdax" + "ignoreTestFiles": ["**/__snapshots__/*", "**/__image_snapshots__/*"] } diff --git a/ui/cypress/README.md b/ui/cypress/README.md index 8f7a3039eb..eeec5902e4 100644 --- a/ui/cypress/README.md +++ b/ui/cypress/README.md @@ -1,6 +1,6 @@ # E2E Test -Since there are some features is different from version to version, we have `make test_e2e_compat_features` and `make test_e2e_common_features` to test features compatibility in different versions and common features in all versions, respectively. +Since there are some features is different from version to version, we have `make e2e_compat_features_test` and `make e2e_common_features_test` to test features compatibility in different versions and common features in all versions, respectively. ## Install Cypress @@ -44,5 +44,23 @@ make ui # start backend server UI=1 make && make run FEATURE_VERSION=${FEATURE_VERSION} # run e2e_compat_features and e2e_common_features tests -make test_e2e FEATURE_VERSION=${FEATURE_VERSION} +make e2e_test FEATURE_VERSION=${FEATURE_VERSION} ``` + +### Upload Visual Test Snapshots + +> TODO: Use the official cypress docker image to make sure visual test stable between operating systems. + +Since there was no cypress image of m1 before. So we use github actions to generate the snapshots that we need for visual tests. + +#### How to generate snapshots in GitHub Actions + +1. Go to [tidb-dashboard Actions - Upload E2E Snapshots](https://github.com/pingcap/tidb-dashboard/actions/workflows/upload-e2e-snapshots.yaml) + +2. Click "Run workflow" + +3. Enter which git SHA you want the test to run on + +4. Specify the test specs to generate the snapshots, base path is `${PROJECT_DIR}/ui/cypress/integration` + +5. Enter the action after all jobs finished, download the e2e-snapshots artifact below. diff --git a/ui/cypress/fixtures/topsql_instance:end=1641934800&start=1641916800.json b/ui/cypress/fixtures/topsql_instance:end=1641934800&start=1641916800.json new file mode 100644 index 0000000000..ab74f90335 --- /dev/null +++ b/ui/cypress/fixtures/topsql_instance:end=1641934800&start=1641916800.json @@ -0,0 +1,7 @@ +{ + "data": [ + { "instance": "127.0.0.1:10080", "instance_type": "tidb" }, + { "instance": "127.0.0.1:20160", "instance_type": "tikv" } + ], + "status": "ok" +} diff --git a/ui/cypress/fixtures/topsql_summary:end=1641934800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641916800&top=5&window=123s.json b/ui/cypress/fixtures/topsql_summary:end=1641934800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641916800&top=5&window=123s.json new file mode 100644 index 0000000000..d0dacc3d67 --- /dev/null +++ b/ui/cypress/fixtures/topsql_summary:end=1641934800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641916800&top=5&window=123s.json @@ -0,0 +1,454 @@ +{ + "data": [ + { + "sql_digest": "b95a604794f9eff17a1a6a37d754324be11ede348a0d1e53da2bc3c32d6a4142", + "sql_text": "select `variable_name` , `variable_value` from `mysql` . `global_variables` where `variable_name` in ( ... )", + "is_other": false, + "cpu_time_ms": 390, + "exec_count_per_sec": 0.333259263374257, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "9449388a4efbc35c8eca1639aec164392df687869239f9ad16ea37887d98c42a", + "plan_text": "\tBatch_Point_Get\troot\ttable:GLOBAL_VARIABLES, index:PRIMARY(VARIABLE_NAME), keep order:false, desc:false", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918021, 1641918144, 1641918267, 1641918390, 1641918513, + 1641918636, 1641918759, 1641918882, 1641919005, 1641919128, + 1641919251, 1641919374, 1641919497, 1641919620, 1641919743, + 1641919866, 1641919989, 1641920112, 1641920235, 1641920358, + 1641920481, 1641920604, 1641920727, 1641920850, 1641920973, + 1641921096, 1641921219, 1641921342, 1641921465, 1641921588, + 1641921711, 1641921834, 1641921957, 1641922080, 1641922203, + 1641922326, 1641922449, 1641922572, 1641922695, 1641922818, + 1641922941, 1641923064, 1641923187, 1641923310, 1641923433, + 1641923556, 1641923679, 1641923802, 1641923925, 1641924048, + 1641924171, 1641924294, 1641924417, 1641924540, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925647, 1641925770, 1641925893, + 1641926016, 1641926139, 1641926262, 1641926385, 1641926508, + 1641926631, 1641926754, 1641926877, 1641927000, 1641927123, + 1641927246, 1641927369, 1641927492, 1641927615, 1641927738, + 1641927861, 1641927984, 1641928107, 1641928230, 1641928353, + 1641928476, 1641928599, 1641928722, 1641928845, 1641928968, + 1641929091, 1641929214, 1641929337, 1641929460, 1641929583, + 1641929706, 1641929829, 1641929952, 1641930075, 1641930198, + 1641930321, 1641930444, 1641930567, 1641930690, 1641930813, + 1641930936, 1641931059, 1641931182, 1641931305, 1641931428, + 1641931551, 1641931674, 1641931797, 1641931920, 1641932043, + 1641932166, 1641932289, 1641932412, 1641932535, 1641932658, + 1641932781, 1641932904, 1641933027, 1641933150, 1641933273, + 1641933396, 1641933519, 1641933642, 1641933765, 1641933888, + 1641934011, 1641934134, 1641934257, 1641934380, 1641934503, + 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 10, 0, 0, 10, 0, 10, 0, 10, 10, 0, 0, 10, 0, 0, 0, 0, + 0, 0, 10, 0, 0, 0, 0, 0, 10, 10, 0, 0, 0, 10, 0, 10, 10, 0, 10, 0, + 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 10, 10, 10, + 0, 0, 0, 0, 0, 20, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, + 0, 10, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 10, 0, 10, 10, 0, 0, 10, 0, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, + 0 + ], + "exec_count_per_sec": 0.333259263374257, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "e2769f5619db3dc4916298c4c6b3bc2c36c6d64a84e2a5549284521482564ce0", + "sql_text": "select high_priority ( `variable_value` ) from `mysql` . `tidb` where `variable_name` = ? for update", + "is_other": false, + "cpu_time_ms": 390, + "exec_count_per_sec": 0.06994055885784123, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641916791, 1641917406, 1641917775, 1641918144, 1641918513, + 1641918759, 1641919005, 1641919866, 1641920112, 1641920604, + 1641920850, 1641921342, 1641921465, 1641922695, 1641923187, + 1641923433, 1641923925, 1641925524, 1641925647, 1641926508, + 1641926877, 1641927861, 1641928845, 1641929706, 1641931059, + 1641932289, 1641932658, 1641932904, 1641933150, 1641933396 + ], + "cpu_time_ms": [ + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 20, 10, 10, 10, 10, + 10, 20, 10, 20, 10, 10, 10, 10, 10, 10, 10, 10, 10 + ], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "892b61d58192589f43d81f3ee50710d71e6a8c286a59877069eaa00cd3bb8f5c", + "plan_text": "\tProjection \troot\tmysql.tidb.variable_value\n\t└─SelectLock \troot\t\n\t └─IndexLookUp \troot\t\n\t ├─IndexRangeScan\tcop \ttable:tidb, index:PRIMARY(VARIABLE_NAME), range:[?,?], keep order:false\n\t └─TableRowIDScan\tcop \ttable:tidb, keep order:false", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918021, 1641918144, 1641918267, 1641918390, 1641918513, + 1641918636, 1641918759, 1641918882, 1641919005, 1641919128, + 1641919251, 1641919374, 1641919497, 1641919620, 1641919743, + 1641919866, 1641919989, 1641920112, 1641920235, 1641920358, + 1641920481, 1641920604, 1641920727, 1641920850, 1641920973, + 1641921096, 1641921219, 1641921342, 1641921465, 1641921588, + 1641921711, 1641921834, 1641921957, 1641922080, 1641922203, + 1641922326, 1641922449, 1641922572, 1641922695, 1641922818, + 1641922941, 1641923064, 1641923187, 1641923310, 1641923433, + 1641923556, 1641923679, 1641923802, 1641923925, 1641924048, + 1641924171, 1641924294, 1641924417, 1641924540, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925647, 1641925770, 1641925893, + 1641926016, 1641926139, 1641926262, 1641926385, 1641926508, + 1641926631, 1641926754, 1641926877, 1641927000, 1641927123, + 1641927246, 1641927369, 1641927492, 1641927615, 1641927738, + 1641927861, 1641927984, 1641928107, 1641928230, 1641928353, + 1641928476, 1641928599, 1641928722, 1641928845, 1641928968, + 1641929091, 1641929214, 1641929337, 1641929460, 1641929583, + 1641929706, 1641929829, 1641929952, 1641930075, 1641930198, + 1641930321, 1641930444, 1641930567, 1641930690, 1641930813, + 1641930936, 1641931059, 1641931182, 1641931305, 1641931428, + 1641931551, 1641931674, 1641931797, 1641931920, 1641932043, + 1641932166, 1641932289, 1641932412, 1641932535, 1641932658, + 1641932781, 1641932904, 1641933027, 1641933150, 1641933273, + 1641933396, 1641933519, 1641933642, 1641933765, 1641933888, + 1641934011, 1641934134, 1641934257, 1641934380, 1641934503, + 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "exec_count_per_sec": 0.06994055885784123, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "da5f21b51be132c69a10de76148c95ab1ad8e6fb6c0fdacb3dcedfe4b6d3c41f", + "sql_text": "select distinct `table_id` from `mysql` . `stats_feedback`", + "is_other": false, + "cpu_time_ms": 320, + "exec_count_per_sec": 0.06666296316871285, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641924294, 1641928476, 1641928845, 1641929460, 1641930075, + 1641931551, 1641931797, 1641932535, 1641933396, 1641934011, + 1641934749 + ], + "cpu_time_ms": [10, 10, 10, 10, 10, 10, 10, 10, 10, 20, 10], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "8d5611b5d6f8ade5b8d2f721db81164a09cf915d836dfcfc3fd00237750350ca", + "plan_text": "\tHashAgg \troot\tgroup by:mysql.stats_feedback.table_id, funcs:firstrow(mysql.stats_feedback.table_id)-\u003emysql.stats_feedback.table_id\n\t└─IndexReader \troot\tindex:HashAgg_4\n\t └─HashAgg \tcop \tgroup by:mysql.stats_feedback.table_id, \n\t └─IndexFullScan\tcop \ttable:stats_feedback, index:hist(table_id, is_index, hist_id), range:[?,?], keep order:false", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918021, 1641918144, 1641918267, 1641918390, 1641918513, + 1641918636, 1641918759, 1641918882, 1641919005, 1641919128, + 1641919251, 1641919374, 1641919497, 1641919620, 1641919743, + 1641919866, 1641919989, 1641920112, 1641920235, 1641920358, + 1641920481, 1641920604, 1641920727, 1641920850, 1641920973, + 1641921096, 1641921219, 1641921342, 1641921465, 1641921588, + 1641921711, 1641921834, 1641921957, 1641922080, 1641922203, + 1641922326, 1641922449, 1641922572, 1641922695, 1641922818, + 1641922941, 1641923064, 1641923187, 1641923310, 1641923433, + 1641923556, 1641923679, 1641923802, 1641923925, 1641924048, + 1641924171, 1641924294, 1641924417, 1641924540, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925647, 1641925770, 1641925893, + 1641926016, 1641926139, 1641926262, 1641926385, 1641926508, + 1641926631, 1641926754, 1641926877, 1641927000, 1641927123, + 1641927246, 1641927369, 1641927492, 1641927615, 1641927738, + 1641927861, 1641927984, 1641928107, 1641928230, 1641928353, + 1641928476, 1641928599, 1641928722, 1641928845, 1641928968, + 1641929091, 1641929214, 1641929337, 1641929460, 1641929583, + 1641929706, 1641929829, 1641929952, 1641930075, 1641930198, + 1641930321, 1641930444, 1641930567, 1641930690, 1641930813, + 1641930936, 1641931059, 1641931182, 1641931305, 1641931428, + 1641931551, 1641931674, 1641931797, 1641931920, 1641932043, + 1641932166, 1641932289, 1641932412, 1641932535, 1641932658, + 1641932781, 1641932904, 1641933027, 1641933150, 1641933273, + 1641933396, 1641933519, 1641933642, 1641933765, 1641933888, + 1641934011, 1641934134, 1641934257, 1641934380, 1641934503, + 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 20, 0, 0, 0, 0, 0, 20, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 0, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 10, 0, 10, 0, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, + 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0 + ], + "exec_count_per_sec": 0.06666296316871285, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "ea4709893ffb8edc8d58191ccbd93c4c4fdfc1d20ebbcc7f48707df328d6dbb2", + "sql_text": "select `version` , `table_id` , `modify_count` , `count` from `mysql` . `stats_meta` where `version` \u003e ? order by `version`", + "is_other": false, + "cpu_time_ms": 3590, + "exec_count_per_sec": 0.333259263374257, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918144, 1641918390, 1641918513, 1641918636, 1641918759, + 1641919005, 1641919251, 1641919620, 1641919743, 1641919866, + 1641919989, 1641920112, 1641920235, 1641920358, 1641920481, + 1641920604, 1641920850, 1641920973, 1641921342, 1641921465, + 1641921588, 1641921711, 1641921834, 1641921957, 1641922080, + 1641922203, 1641922326, 1641922449, 1641922572, 1641922695, + 1641922818, 1641922941, 1641923064, 1641923187, 1641923310, + 1641923433, 1641923556, 1641923679, 1641923802, 1641923925, + 1641924048, 1641924171, 1641924294, 1641924417, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925770, 1641925893, 1641926016, + 1641926139, 1641926262, 1641926385, 1641926631, 1641926754, + 1641926877, 1641927000, 1641927246, 1641927369, 1641927492, + 1641927738, 1641927861, 1641927984, 1641928230, 1641928476, + 1641928599, 1641928722, 1641928845, 1641929091, 1641929214, + 1641929337, 1641929460, 1641929583, 1641929706, 1641929829, + 1641929952, 1641930075, 1641930321, 1641930444, 1641930567, + 1641930813, 1641930936, 1641931059, 1641931182, 1641931305, + 1641931428, 1641931551, 1641931674, 1641931797, 1641931920, + 1641932043, 1641932166, 1641932289, 1641932412, 1641932535, + 1641932658, 1641932781, 1641932904, 1641933027, 1641933150, + 1641933273, 1641933396, 1641933519, 1641933642, 1641933765, + 1641933888, 1641934011, 1641934134, 1641934257, 1641934380, + 1641934503, 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 50, 10, 10, 10, 20, 30, 30, 20, 30, 40, 10, 40, 20, 30, 40, 50, 20, + 20, 10, 30, 50, 40, 40, 10, 20, 30, 10, 10, 10, 10, 50, 10, 10, 20, + 20, 30, 10, 20, 20, 20, 10, 20, 40, 70, 40, 20, 20, 20, 10, 10, 20, + 10, 10, 30, 10, 50, 20, 40, 30, 30, 10, 30, 30, 20, 30, 30, 30, 50, + 10, 30, 20, 30, 10, 10, 20, 30, 10, 40, 20, 10, 30, 20, 10, 30, 20, + 20, 10, 20, 20, 30, 30, 40, 40, 10, 20, 20, 10, 30, 20, 30, 40, 30, + 20, 10, 30, 30, 10, 10, 20, 40, 10, 30, 20, 20, 30, 10, 20, 40, 20, + 10, 10, 20, 20, 10, 30, 20, 10, 20 + ], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "42d48b331dfe53300ddea68d4217dc467244bd898f80f10a913f2104d26d4989", + "plan_text": "\tProjection \troot\tmysql.stats_meta.count, mysql.stats_meta.modify_count, mysql.stats_meta.table_id, mysql.stats_meta.version\n\t└─IndexLookUp \troot\t\n\t ├─IndexRangeScan\tcop \ttable:stats_meta, index:idx_ver(version), range:[?,?], keep order:true\n\t └─TableRowIDScan\tcop \ttable:stats_meta, keep order:false", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918021, 1641918144, 1641918267, 1641918390, 1641918513, + 1641918636, 1641918759, 1641918882, 1641919005, 1641919128, + 1641919251, 1641919374, 1641919497, 1641919620, 1641919743, + 1641919866, 1641919989, 1641920112, 1641920235, 1641920358, + 1641920481, 1641920604, 1641920727, 1641920850, 1641920973, + 1641921096, 1641921219, 1641921342, 1641921465, 1641921588, + 1641921711, 1641921834, 1641921957, 1641922080, 1641922203, + 1641922326, 1641922449, 1641922572, 1641922695, 1641922818, + 1641922941, 1641923064, 1641923187, 1641923310, 1641923433, + 1641923556, 1641923679, 1641923802, 1641923925, 1641924048, + 1641924171, 1641924294, 1641924417, 1641924540, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925647, 1641925770, 1641925893, + 1641926016, 1641926139, 1641926262, 1641926385, 1641926508, + 1641926631, 1641926754, 1641926877, 1641927000, 1641927123, + 1641927246, 1641927369, 1641927492, 1641927615, 1641927738, + 1641927861, 1641927984, 1641928107, 1641928230, 1641928353, + 1641928476, 1641928599, 1641928722, 1641928845, 1641928968, + 1641929091, 1641929214, 1641929337, 1641929460, 1641929583, + 1641929706, 1641929829, 1641929952, 1641930075, 1641930198, + 1641930321, 1641930444, 1641930567, 1641930690, 1641930813, + 1641930936, 1641931059, 1641931182, 1641931305, 1641931428, + 1641931551, 1641931674, 1641931797, 1641931920, 1641932043, + 1641932166, 1641932289, 1641932412, 1641932535, 1641932658, + 1641932781, 1641932904, 1641933027, 1641933150, 1641933273, + 1641933396, 1641933519, 1641933642, 1641933765, 1641933888, + 1641934011, 1641934134, 1641934257, 1641934380, 1641934503, + 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 10, + 0, 0, 0, 10, 0, 0, 0, 0, 0, 10, 0, 10, 0, 0, 0, 0, 0, 10, 40, 0, 0, + 10, 10, 20, 10, 0, 10, 10, 10, 10, 20, 0, 10, 0, 0, 10, 10, 0, 20, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 10, 0, + 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 10, 10, 0, 0, 0, 0, + 0, 10, 0, 0, 0, 0, 10, 10, 0, 0, 0, 10, 0, 10, 10, 0, 0, 20, 10, 0, + 0, 0, 0, 20, 10, 40, 0, 0, 0, 0, 0, 10, 0, 20, 0, 0, 10, 0, 0, 0, 0, + 10, 10, 20 + ], + "exec_count_per_sec": 0.333259263374257, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "61f4cce222f1a26d6b0650865051ebed3d077412ae053636e2f5acf9dc426a42", + "sql_text": "insert high_priority into `mysql` . `tidb` values ( ... ) on duplicate key update `variable_value` = ? , comment = ?", + "is_other": false, + "cpu_time_ms": 260, + "exec_count_per_sec": 0.019998888950613854, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918021, 1641918144, 1641918267, 1641918390, 1641918513, + 1641918636, 1641918759, 1641918882, 1641919005, 1641919128, + 1641919251, 1641919374, 1641919497, 1641919620, 1641919743, + 1641919866, 1641919989, 1641920112, 1641920235, 1641920358, + 1641920481, 1641920604, 1641920727, 1641920850, 1641920973, + 1641921096, 1641921219, 1641921342, 1641921465, 1641921588, + 1641921711, 1641921834, 1641921957, 1641922080, 1641922203, + 1641922326, 1641922449, 1641922572, 1641922695, 1641922818, + 1641922941, 1641923064, 1641923187, 1641923310, 1641923433, + 1641923556, 1641923679, 1641923802, 1641923925, 1641924048, + 1641924171, 1641924294, 1641924417, 1641924540, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925647, 1641925770, 1641925893, + 1641926016, 1641926139, 1641926262, 1641926385, 1641926508, + 1641926631, 1641926754, 1641926877, 1641927000, 1641927123, + 1641927246, 1641927369, 1641927492, 1641927615, 1641927738, + 1641927861, 1641927984, 1641928107, 1641928230, 1641928353, + 1641928476, 1641928599, 1641928722, 1641928845, 1641928968, + 1641929091, 1641929214, 1641929337, 1641929460, 1641929583, + 1641929706, 1641929829, 1641929952, 1641930075, 1641930198, + 1641930321, 1641930444, 1641930567, 1641930690, 1641930813, + 1641930936, 1641931059, 1641931182, 1641931305, 1641931428, + 1641931551, 1641931674, 1641931797, 1641931920, 1641932043, + 1641932166, 1641932289, 1641932412, 1641932535, 1641932658, + 1641932781, 1641932904, 1641933027, 1641933150, 1641933273, + 1641933396, 1641933519, 1641933642, 1641933765, 1641933888, + 1641934011, 1641934134, 1641934257, 1641934380, 1641934503, + 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, + 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, + 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, + 0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 + ], + "exec_count_per_sec": 0.019998888950613854, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "", + "sql_text": "", + "is_other": true, + "cpu_time_ms": 660, + "exec_count_per_sec": 1.161546580745514, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641916791, 1641916914, 1641917037, 1641917160, 1641917283, + 1641917406, 1641917529, 1641917652, 1641917775, 1641917898, + 1641918021, 1641918144, 1641918267, 1641918390, 1641918513, + 1641918636, 1641918759, 1641918882, 1641919005, 1641919128, + 1641919251, 1641919374, 1641919497, 1641919620, 1641919743, + 1641919866, 1641919989, 1641920112, 1641920235, 1641920358, + 1641920481, 1641920604, 1641920727, 1641920850, 1641920973, + 1641921096, 1641921219, 1641921342, 1641921465, 1641921588, + 1641921711, 1641921834, 1641921957, 1641922080, 1641922203, + 1641922326, 1641922449, 1641922572, 1641922695, 1641922818, + 1641922941, 1641923064, 1641923187, 1641923310, 1641923433, + 1641923556, 1641923679, 1641923802, 1641923925, 1641924048, + 1641924171, 1641924294, 1641924417, 1641924540, 1641924663, + 1641924786, 1641924909, 1641925032, 1641925155, 1641925278, + 1641925401, 1641925524, 1641925647, 1641925770, 1641925893, + 1641926016, 1641926139, 1641926262, 1641926385, 1641926508, + 1641926631, 1641926754, 1641926877, 1641927000, 1641927123, + 1641927246, 1641927369, 1641927492, 1641927615, 1641927738, + 1641927861, 1641927984, 1641928107, 1641928230, 1641928353, + 1641928476, 1641928599, 1641928722, 1641928845, 1641928968, + 1641929091, 1641929214, 1641929337, 1641929460, 1641929583, + 1641929706, 1641929829, 1641929952, 1641930075, 1641930198, + 1641930321, 1641930444, 1641930567, 1641930690, 1641930813, + 1641930936, 1641931059, 1641931182, 1641931305, 1641931428, + 1641931551, 1641931674, 1641931797, 1641931920, 1641932043, + 1641932166, 1641932289, 1641932412, 1641932535, 1641932658, + 1641932781, 1641932904, 1641933027, 1641933150, 1641933273, + 1641933396, 1641933519, 1641933642, 1641933765, 1641933888, + 1641934011, 1641934134, 1641934257, 1641934380, 1641934503, + 1641934626, 1641934749 + ], + "cpu_time_ms": [ + 0, 0, 10, 0, 0, 0, 0, 0, 20, 30, 10, 20, 0, 0, 10, 0, 10, 0, 10, 0, + 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 10, 20, 0, 10, 0, 0, 0, 0, 0, 10, 0, + 0, 0, 0, 0, 10, 0, 20, 10, 0, 0, 0, 0, 10, 0, 10, 30, 0, 0, 0, 0, 0, + 0, 0, 10, 10, 0, 10, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 10, 20, 0, 0, + 0, 0, 10, 0, 0, 0, 30, 10, 10, 0, 20, 0, 20, 0, 0, 0, 20, 0, 20, 0, + 0, 10, 10, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 10, 0, + 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 20, 10, 10, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 10, 0 + ], + "exec_count_per_sec": 1.161546580745514, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + } + ], + "status": "ok" +} diff --git a/ui/cypress/fixtures/topsql_summary_large_timerange:end=1641916800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641484800&top=5&window=2929s.json b/ui/cypress/fixtures/topsql_summary_large_timerange:end=1641916800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641484800&top=5&window=2929s.json new file mode 100644 index 0000000000..3b83d88593 --- /dev/null +++ b/ui/cypress/fixtures/topsql_summary_large_timerange:end=1641916800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641484800&top=5&window=2929s.json @@ -0,0 +1,290 @@ +{ + "data": [ + { + "sql_digest": "e6f07d43b5c21db0fbb9a31feac2dc599787763393dd5acbfad80e247eb02ad5", + "sql_text": "begin", + "is_other": false, + "cpu_time_ms": 1570, + "exec_count_per_sec": 0.0991201409255997, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 20, 30, 30, 30, 30, 20, 10, 0, 60, 40, 20, 20, 40, 30, 10, 100, 30, + 50, 30, 90, 30, 40, 80, 40, 40, 30, 50, 60, 40, 40, 50, 60, 40, 40, + 70, 70, 20, 50, 30 + ], + "exec_count_per_sec": 0.0991201409255997, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "ea4709893ffb8edc8d58191ccbd93c4c4fdfc1d20ebbcc7f48707df328d6dbb2", + "sql_text": "select `version` , `table_id` , `modify_count` , `count` from `mysql` . `stats_meta` where `version` \u003e ? order by `version`", + "is_other": false, + "cpu_time_ms": 20790, + "exec_count_per_sec": 0.08658544771887101, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 180, 340, 440, 490, 410, 410, 400, 630, 480, 450, 390, 570, 500, + 490, 440, 500, 530, 530, 580, 390, 420, 440, 480, 490, 540, 550, + 520, 470, 480, 370, 430, 430, 430, 410, 380, 590, 360, 300, 370 + ], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "42d48b331dfe53300ddea68d4217dc467244bd898f80f10a913f2104d26d4989", + "plan_text": "\tProjection \troot\tmysql.stats_meta.count, mysql.stats_meta.modify_count, mysql.stats_meta.table_id, mysql.stats_meta.version\n\t└─IndexLookUp \troot\t\n\t ├─IndexRangeScan\tcop \ttable:stats_meta, index:idx_ver(version), range:[?,?], keep order:true\n\t └─TableRowIDScan\tcop \ttable:stats_meta, keep order:false", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 40, 170, 80, 70, 80, 40, 110, 100, 70, 90, 50, 60, 50, 120, 50, 80, + 60, 80, 90, 110, 50, 50, 50, 60, 50, 90, 60, 100, 120, 100, 60, 160, + 120, 100, 70, 90, 30, 110, 110 + ], + "exec_count_per_sec": 0.08658544771887101, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "e2769f5619db3dc4916298c4c6b3bc2c36c6d64a84e2a5549284521482564ce0", + "sql_text": "select high_priority ( `variable_value` ) from `mysql` . `tidb` where `variable_name` = ? for update", + "is_other": false, + "cpu_time_ms": 2030, + "exec_count_per_sec": 0.01817588385212071, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 30, 60, 50, 40, 60, 50, 10, 20, 110, 50, 60, 50, 40, 90, 20, 30, 60, + 20, 50, 30, 50, 30, 100, 10, 60, 20, 10, 70, 10, 90, 30, 60, 50, 20, + 30, 80, 60, 40, 30 + ], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "892b61d58192589f43d81f3ee50710d71e6a8c286a59877069eaa00cd3bb8f5c", + "plan_text": "\tProjection \troot\tmysql.tidb.variable_value\n\t└─SelectLock \troot\t\n\t └─IndexLookUp \troot\t\n\t ├─IndexRangeScan\tcop \ttable:tidb, index:PRIMARY(VARIABLE_NAME), range:[?,?], keep order:false\n\t └─TableRowIDScan\tcop \ttable:tidb, keep order:false", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 10, 10, 0, 10, 20, 30, 0, 10, 10, 0, 0, 20, 10, 0, 0, + 10, 0, 10, 10, 0, 0, 20, 10, 0, 0, 10, 10, 0, 20, 0, 0, 0, 10, 10 + ], + "exec_count_per_sec": 0.01817588385212071, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "b95a604794f9eff17a1a6a37d754324be11ede348a0d1e53da2bc3c32d6a4142", + "sql_text": "select `variable_name` , `variable_value` from `mysql` . `global_variables` where `variable_name` in ( ... )", + "is_other": false, + "cpu_time_ms": 2380, + "exec_count_per_sec": 0.08658544771887101, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [1641845092, 1641850950, 1641874382], + "cpu_time_ms": [10, 10, 10], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "9449388a4efbc35c8eca1639aec164392df687869239f9ad16ea37887d98c42a", + "plan_text": "\tBatch_Point_Get\troot\ttable:GLOBAL_VARIABLES, index:PRIMARY(VARIABLE_NAME), keep order:false, desc:false", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 10, 40, 50, 40, 40, 40, 50, 80, 40, 90, 100, 70, 90, 60, 50, 60, + 110, 100, 40, 30, 20, 60, 100, 80, 80, 20, 90, 50, 50, 100, 60, 40, + 50, 70, 80, 40, 40, 80, 50 + ], + "exec_count_per_sec": 0.08658544771887101, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "da5f21b51be132c69a10de76148c95ab1ad8e6fb6c0fdacb3dcedfe4b6d3c41f", + "sql_text": "select distinct `table_id` from `mysql` . `stats_feedback`", + "is_other": false, + "cpu_time_ms": 1810, + "exec_count_per_sec": 0.017321719162687123, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641807015, 1641809944, 1641812873, 1641815802, 1641818731, + 1641821660, 1641833376, 1641836305, 1641842163, 1641848021, + 1641853879, 1641859737, 1641862666, 1641865595, 1641871453, + 1641874382, 1641877311, 1641880240, 1641883169, 1641886098, + 1641889027, 1641891956, 1641894885, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 10, 20, 10, 10, 10, 20, 20, 10, 10, 20, 20, 20, 20, 10, 20, 20, 30, + 10, 10, 10, 20, 30, 30, 20, 10, 10, 40, 10, 30 + ], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "8d5611b5d6f8ade5b8d2f721db81164a09cf915d836dfcfc3fd00237750350ca", + "plan_text": "\tHashAgg \troot\tgroup by:mysql.stats_feedback.table_id, funcs:firstrow(mysql.stats_feedback.table_id)-\u003emysql.stats_feedback.table_id\n\t└─IndexReader \troot\tindex:HashAgg_4\n\t └─HashAgg \tcop \tgroup by:mysql.stats_feedback.table_id, \n\t └─IndexFullScan\tcop \ttable:stats_feedback, index:hist(table_id, is_index, hist_id), range:[?,?], keep order:false", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 10, 40, 50, 30, 10, 40, 70, 50, 30, 60, 20, 10, 50, 50, 20, 40, 40, + 80, 30, 20, 70, 60, 10, 50, 40, 0, 30, 20, 10, 20, 20, 30, 10, 30, + 40, 40, 30, 20, 20 + ], + "exec_count_per_sec": 0.017321719162687123, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "", + "sql_text": "", + "is_other": true, + "cpu_time_ms": 5390, + "exec_count_per_sec": 0.21266848919331205, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641804086, 1641807015, 1641809944, 1641812873, 1641815802, + 1641818731, 1641821660, 1641824589, 1641827518, 1641830447, + 1641833376, 1641836305, 1641839234, 1641842163, 1641845092, + 1641848021, 1641850950, 1641853879, 1641856808, 1641859737, + 1641862666, 1641865595, 1641868524, 1641871453, 1641874382, + 1641877311, 1641880240, 1641883169, 1641886098, 1641889027, + 1641891956, 1641894885, 1641897814, 1641900743, 1641903672, + 1641906601, 1641909530, 1641912459, 1641915388 + ], + "cpu_time_ms": [ + 90, 120, 290, 180, 160, 120, 100, 50, 150, 180, 140, 120, 120, 90, + 120, 110, 190, 190, 120, 140, 120, 160, 150, 190, 240, 120, 130, 40, + 140, 180, 110, 70, 180, 120, 160, 80, 130, 180, 110 + ], + "exec_count_per_sec": 0.21266848919331205, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + } + ], + "status": "ok" +} diff --git a/ui/cypress/fixtures/topsql_summary_small_timerange:end=1641920460&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641920400&top=5&window=1s.json b/ui/cypress/fixtures/topsql_summary_small_timerange:end=1641920460&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641920400&top=5&window=1s.json new file mode 100644 index 0000000000..9bb3043111 --- /dev/null +++ b/ui/cypress/fixtures/topsql_summary_small_timerange:end=1641920460&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641920400&top=5&window=1s.json @@ -0,0 +1,177 @@ +{ + "data": [ + { + "sql_digest": "61f4cce222f1a26d6b0650865051ebed3d077412ae053636e2f5acf9dc426a42", + "sql_text": "insert high_priority into `mysql` . `tidb` values ( ... ) on duplicate key update `variable_value` = ? , comment = ?", + "is_other": false, + "cpu_time_ms": 10, + "exec_count_per_sec": 0.01639344262295082, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [1641920435, 1641920436], + "cpu_time_ms": [10, 0], + "exec_count_per_sec": 0.01639344262295082, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "e6f07d43b5c21db0fbb9a31feac2dc599787763393dd5acbfad80e247eb02ad5", + "sql_text": "begin", + "is_other": false, + "cpu_time_ms": 10, + "exec_count_per_sec": 0.3770491803278688, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641920402, 1641920405, 1641920408, 1641920411, 1641920414, + 1641920417, 1641920420, 1641920423, 1641920426, 1641920429, + 1641920432, 1641920435, 1641920436, 1641920438, 1641920441, + 1641920444, 1641920447, 1641920450, 1641920453, 1641920456, + 1641920459 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "exec_count_per_sec": 0.3770491803278688, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "ea4709893ffb8edc8d58191ccbd93c4c4fdfc1d20ebbcc7f48707df328d6dbb2", + "sql_text": "select `version` , `table_id` , `modify_count` , `count` from `mysql` . `stats_meta` where `version` \u003e ? order by `version`", + "is_other": false, + "cpu_time_ms": 10, + "exec_count_per_sec": 0.32786885245901637, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [1641920426], + "cpu_time_ms": [10], + "exec_count_per_sec": 0, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + }, + { + "plan_digest": "42d48b331dfe53300ddea68d4217dc467244bd898f80f10a913f2104d26d4989", + "plan_text": "\tProjection \troot\tmysql.stats_meta.count, mysql.stats_meta.modify_count, mysql.stats_meta.table_id, mysql.stats_meta.version\n\t└─IndexLookUp \troot\t\n\t ├─IndexRangeScan\tcop \ttable:stats_meta, index:idx_ver(version), range:[?,?], keep order:true\n\t └─TableRowIDScan\tcop \ttable:stats_meta, keep order:false", + "timestamp_sec": [ + 1641920402, 1641920405, 1641920408, 1641920411, 1641920414, + 1641920417, 1641920420, 1641920423, 1641920426, 1641920429, + 1641920432, 1641920435, 1641920438, 1641920441, 1641920444, + 1641920447, 1641920450, 1641920453, 1641920456, 1641920459 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "exec_count_per_sec": 0.32786885245901637, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "b95a604794f9eff17a1a6a37d754324be11ede348a0d1e53da2bc3c32d6a4142", + "sql_text": "select `variable_name` , `variable_value` from `mysql` . `global_variables` where `variable_name` in ( ... )", + "is_other": false, + "cpu_time_ms": 10, + "exec_count_per_sec": 0.32786885245901637, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "9449388a4efbc35c8eca1639aec164392df687869239f9ad16ea37887d98c42a", + "plan_text": "\tBatch_Point_Get\troot\ttable:GLOBAL_VARIABLES, index:PRIMARY(VARIABLE_NAME), keep order:false, desc:false", + "timestamp_sec": [ + 1641920402, 1641920405, 1641920408, 1641920411, 1641920414, + 1641920417, 1641920420, 1641920423, 1641920426, 1641920429, + 1641920432, 1641920435, 1641920438, 1641920441, 1641920444, + 1641920447, 1641920450, 1641920453, 1641920456, 1641920459 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "exec_count_per_sec": 0.32786885245901637, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "e2769f5619db3dc4916298c4c6b3bc2c36c6d64a84e2a5549284521482564ce0", + "sql_text": "select high_priority ( `variable_value` ) from `mysql` . `tidb` where `variable_name` = ? for update", + "is_other": false, + "cpu_time_ms": 0, + "exec_count_per_sec": 0.06557377049180328, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "892b61d58192589f43d81f3ee50710d71e6a8c286a59877069eaa00cd3bb8f5c", + "plan_text": "\tProjection \troot\tmysql.tidb.variable_value\n\t└─SelectLock \troot\t\n\t └─IndexLookUp \troot\t\n\t ├─IndexRangeScan\tcop \ttable:tidb, index:PRIMARY(VARIABLE_NAME), range:[?,?], keep order:false\n\t └─TableRowIDScan\tcop \ttable:tidb, keep order:false", + "timestamp_sec": [1641920435, 1641920436], + "cpu_time_ms": [0, 0], + "exec_count_per_sec": 0.06557377049180328, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + }, + { + "sql_digest": "", + "sql_text": "", + "is_other": true, + "cpu_time_ms": 0, + "exec_count_per_sec": 0.7868852459016393, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0, + "plans": [ + { + "plan_digest": "", + "plan_text": "", + "timestamp_sec": [ + 1641920402, 1641920405, 1641920408, 1641920411, 1641920414, + 1641920417, 1641920419, 1641920420, 1641920423, 1641920426, + 1641920429, 1641920432, 1641920435, 1641920436, 1641920438, + 1641920441, 1641920444, 1641920447, 1641920449, 1641920450, + 1641920453, 1641920456, 1641920459 + ], + "cpu_time_ms": [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "exec_count_per_sec": 0.7868852459016393, + "duration_per_exec_ms": 0, + "scan_records_per_sec": 0, + "scan_indexes_per_sec": 0 + } + ] + } + ], + "status": "ok" +} diff --git a/ui/cypress/fixtures/uri.json b/ui/cypress/fixtures/uri.json index 196880c1c3..ba5de29593 100644 --- a/ui/cypress/fixtures/uri.json +++ b/ui/cypress/fixtures/uri.json @@ -4,5 +4,6 @@ "overview": "/overview", "slow_query": "/slow_query", "statement": "/statement", - "configuration": "/configuration" + "configuration": "/configuration", + "topsql": "/topsql" } diff --git a/ui/cypress/integration/slow_query/01-list.spec.js b/ui/cypress/integration/slow_query/01-list.spec.js index b2214293b8..1ce398a08e 100644 --- a/ui/cypress/integration/slow_query/01-list.spec.js +++ b/ui/cypress/integration/slow_query/01-list.spec.js @@ -253,26 +253,37 @@ describe('SlowQuery list page', () => { }) it('Search item with space', () => { - cy.get('[data-e2e=slow_query_search]') - .type(' SELECT sleep\\(1\\) {enter}') - .then(() => { - cy.get('[data-automation-key=query]').should('has.length', 1) - }) + cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + 'slow_query_list' + ) + cy.wait('@slow_query_list') + + cy.get('[data-e2e=slow_query_search]').type( + ' SELECT sleep\\(1\\) {enter}' + ) + + cy.wait('@slow_query_list') + cy.get('[data-automation-key=query]').should('has.length', 1) // clear search text - cy.get('[data-e2e=slow_query_search]') - .clear() - .type('{enter}') - .then(() => { - cy.get('[data-automation-key=query]').should('has.length', 3) - }) + cy.get('[data-e2e=slow_query_search]').clear().type('{enter}') + + cy.wait('@slow_query_list') + cy.get('[data-automation-key=query]').should('has.length', 3) }) it('Type search without pressing enter then reload', () => { + cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + 'slow_query_list' + ) + cy.wait('@slow_query_list') + cy.get('[data-e2e=slow_query_search]').type(' SELECT sleep\\(1\\)') + cy.wait('@slow_query_list') + cy.get('[data-automation-key=query]').should('has.length', 1) cy.reload() - cy.get('[data-automation-key=query]').should('has.length', 3) + cy.get('[data-automation-key=query]').should('has.length', 1) }) }) @@ -296,17 +307,17 @@ describe('SlowQuery list page', () => { }) it('Check config remembered', () => { - cy.get('[data-e2e=slow_query_limit_select]') - .click() - .then(() => { - cy.get('[data-e2e=slow_query_limit_option]') - .eq(1) - .click() - .then(() => { - cy.reload() - cy.get('[data-e2e=slow_query_limit_select]').contains('200') - }) - }) + cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + 'slow_query_list' + ) + cy.wait('@slow_query_list') + + cy.get('[data-e2e=slow_query_limit_select]').click() + cy.get('[data-e2e=slow_query_limit_option]').eq(1).click() + + cy.wait('@slow_query_list') + cy.reload() + cy.get('[data-e2e=slow_query_limit_select]').contains('200') }) }) @@ -348,65 +359,56 @@ describe('SlowQuery list page', () => { }) }) - it('Select all column fields', () => { - cy.get('[data-e2e=columns_selector_popover]') - .trigger('mouseover') - .then(() => { - cy.get('[data-e2e=column_selector_title]') - .check() - .then(() => { - cy.get('[role=columnheader]') - .not('.is-empty') - .should('have.length', 44) - }) - }) - }) + it('Select all column fields and then reset', () => { + cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + 'slow_query_list' + ) + cy.wait('@slow_query_list') - it('Reset selected column fields', () => { - cy.get('[data-e2e=columns_selector_popover]') - .trigger('mouseover') - .then(() => { - cy.get('[data-e2e=column_selector_reset]') - .click() - .then(() => { - cy.get('[role=columnheader]') - .not('.is-empty') - .should('have.length', 4) - }) - }) - }) + cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover') + cy.get('[data-e2e=column_selector_title]').check() - it('Select an orbitary column field', () => { - cy.get('[data-e2e=columns_selector_popover]') - .trigger('mouseover') - .then(() => { - cy.contains('Max Disk') - .within(() => { - cy.get('[data-e2e=columns_selector_field_disk_max]').check() - }) - .then(() => { - cy.get('[role=columnheader]') - .not('.is-empty') - .last() - .should('have.text', 'Max Disk ') - }) - }) + cy.wait('@slow_query_list') + cy.get('[role=columnheader]').not('.is-empty').should('have.length', 44) + + // Columns should be remembered + cy.reload() + cy.wait('@slow_query_list') + cy.get('[role=columnheader]').not('.is-empty').should('have.length', 44) + + // Click reset + cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover') + cy.get('[data-e2e=column_selector_reset]').click() + + cy.wait('@slow_query_list') + cy.get('[role=columnheader]').not('.is-empty').should('have.length', 4) }) - it('UnCheck last selected orbitary column field', () => { - cy.get('[data-e2e=columns_selector_popover]') - .trigger('mouseover') - .then(() => { - cy.contains('Max Disk') - .within(() => { - cy.get('[data-e2e=columns_selector_field_disk_max]').uncheck() - }) - .then(() => { - cy.get('[role=columnheader]') - .eq(1) - .should('have.text', 'Finish Time ') - }) - }) + it('Select an arbitary column field', () => { + cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + 'slow_query_list' + ) + cy.wait('@slow_query_list') + + cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover') + + cy.contains('Max Disk').within(() => { + cy.get('[data-e2e=columns_selector_field_disk_max]').check() + }) + + cy.wait('@slow_query_list') + cy.get('[role=columnheader]') + .not('.is-empty') + .last() + .should('have.text', 'Max Disk ') + + // FIXME: the next contains should be performed over the popup only + // cy.contains('Max Disk').within(() => { + // cy.get('[data-e2e=columns_selector_field_disk_max]').uncheck() + // }) + + // cy.wait('@slow_query_list') + // cy.get('[role=columnheader]').eq(1).should('have.text', 'Finish Time ') }) it('Check SLOW_QUERY_SHOW_FULL_SQL', () => { @@ -436,20 +438,19 @@ describe('SlowQuery list page', () => { describe('Refresh table list', () => { it('Click refresh will update table list', () => { - cy.get('[data-automation-key=query]').should('have.length', 3) + cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + 'slow_query_list' + ) + cy.wait('@slow_query_list') + cy.contains('SELECT sleep(1.2)').should('not.exist') const queryData = { query: 'SELECT sleep(1.2)', } - cy.task('queryDB', { ...queryData }) - cy.wait(1000) - - cy.get('[data-e2e=slow_query_refresh]') - .click() - .then(() => { - cy.get('[data-automation-key=query]').should('have.length', 4) - }) + cy.get('[data-e2e=slow_query_search]').type('{enter}') + cy.wait('@slow_query_list') + cy.contains('SELECT sleep(1.2)') }) }) @@ -537,6 +538,102 @@ describe('SlowQuery list page', () => { }) }) + // FIXME: The following tests will break slow-query details E2E since it executes a SQL. + // Fix the slow-query details E2E first. + + // describe('Slow network condition', () => { + // const slowNetworkText = 'On-the-fly update is disabled' + + // it('Does not show slow information when network is fast', () => { + // cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as( + // 'slow_query_list' + // ) + + // cy.wait('@slow_query_list') + + // cy.wait(500) + // cy.contains(slowNetworkText).should('not.exist') + // }) + + // it('Show slow information', () => { + // cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`, (req) => { + // req.on('response', (res) => { + // res.setDelay(3000) + // }) + // }).as('slow_query_list') + + // cy.wait('@slow_query_list') + // cy.contains(slowNetworkText) + // }) + + // it('Does not send request automatically when network is slow', () => { + // cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`, (req) => { + // req.on('response', (res) => { + // res.setDelay(3000) + // }) + // }).as('slow_query_list') + + // cy.wait('@slow_query_list') + // cy.contains(slowNetworkText) + + // const queryData = { + // query: 'SELECT 41212, sleep(1)', + // } + // cy.task('queryDB', { ...queryData }) + // cy.reload() + // cy.wait('@slow_query_list') + // cy.contains(slowNetworkText) + + // cy.get('[data-e2e=slow_query_search]').type('SELECT 41212') + + // cy.wait(1000) + // cy.get('[data-e2e=syntax_highlighter_compact]').contains( + // 'SELECT sleep(1.2)' + // ) // TODO: this depends on a previous test to finish.. + + // // request is sent only after a manual refresh + // cy.get('[data-e2e=slow_query_search]').type('{enter}') + // cy.wait('@slow_query_list') + // cy.get('[data-e2e=syntax_highlighter_compact]').contains('SELECT 41212') + // cy.get('[data-e2e=syntax_highlighter_compact]') + // .contains('SELECT sleep(1.2)') + // .should('not.exist') + // }) + + // it('Updates the info when network is no longer slow', () => { + // let shouldDelay = true + // cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`, (req) => { + // req.on('response', (res) => { + // if (shouldDelay) { + // res.setDelay(3000) + // } + // }) + // }).as('slow_query_list') + + // cy.wait('@slow_query_list') + // cy.contains(slowNetworkText) + // cy.get('[data-e2e=syntax_highlighter_compact]') + // .contains('SELECT sleep(1.2)') + // .then(() => { + // shouldDelay = false + // }) + + // cy.get('[data-e2e=slow_query_search]').type('{enter}') + // cy.wait('@slow_query_list') + + // cy.wait(500) + // cy.contains(slowNetworkText).should('not.exist') + + // // On-the-fly request should be recovered + // cy.get('[data-e2e=slow_query_search]').type('SELECT 41212') + // cy.wait('@slow_query_list') + // cy.get('[data-e2e=syntax_highlighter_compact]').contains('SELECT 41212') + // cy.get('[data-e2e=syntax_highlighter_compact]') + // .contains('SELECT sleep(1.2)') + // .should('not.exist') + // }) + // }) + describe('Export slow query CSV ', () => { it('validate CSV File', () => { const downloadsFolder = Cypress.config('downloadsFolder') diff --git a/ui/cypress/integration/statement/01-list.spec.js b/ui/cypress/integration/statement/01-list.spec.js index 4879ae73dd..b68774b65a 100644 --- a/ui/cypress/integration/statement/01-list.spec.js +++ b/ui/cypress/integration/statement/01-list.spec.js @@ -441,7 +441,6 @@ describe('SQL statements list page', () => { cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as( 'statements_list' ) - cy.wait('@statements_list') cy.get('[data-e2e=columns_selector_popover]') @@ -465,39 +464,35 @@ describe('SQL statements list page', () => { .should('have.length', 5) }) - it('Select an orbitary column field', () => { - cy.get('[data-e2e=columns_selector_popover]') - .trigger('mouseover') - .then(() => { - cy.contains('Total Coprocessor Tasks') - .within(() => { - cy.get( - '[data-e2e=columns_selector_field_sum_cop_task_num]' - ).check() - }) - .then(() => { - cy.get('[data-item-key=sum_cop_task_num]').should( - 'have.text', - 'Total Coprocessor Tasks' - ) - }) - }) - }) + it('Select an arbitary column field', () => { + cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as( + 'statements_list' + ) + cy.wait('@statements_list') - it('UnCheck last selected orbitary column field', () => { - cy.get('[data-e2e=columns_selector_popover]') - .trigger('mouseover') + cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover') + + cy.contains('Total Coprocessor Tasks') + .within(() => { + cy.get('[data-e2e=columns_selector_field_sum_cop_task_num]').check() + }) .then(() => { - cy.contains('Total Coprocessor Tasks') - .within(() => { - cy.get( - '[data-e2e=columns_selector_field_sum_cop_task_num]' - ).uncheck() - }) - .then(() => { - cy.get('[data-item-key=sum_cop_task_num]').should('not.exist') - }) + cy.wait('@statements_list') + cy.get('[data-item-key=sum_cop_task_num]').should( + 'have.text', + 'Total Coprocessor Tasks' + ) }) + + // FIXME: the next contains should be performed over the popup only + // cy.contains('Total Coprocessor Tasks') + // .within(() => { + // cy.get('[data-e2e=columns_selector_field_sum_cop_task_num]').uncheck() + // }) + // .then(() => { + // cy.wait('@statements_list') + // cy.get('[data-item-key=sum_cop_task_num]').should('not.exist') + // }) }) it('Check SHOW_FULL_QUERY_TEXT', () => { @@ -776,6 +771,10 @@ describe('SQL statements list page', () => { }) it('Failed to save config list', () => { + cy.on('uncaught:exception', function () { + return false + }) + const staticResponse = { statusCode: 400, body: { diff --git a/ui/cypress/integration/statement/02-detail.spec.js b/ui/cypress/integration/statement/02-detail.spec.js index e276dec36a..ac6f8dded2 100644 --- a/ui/cypress/integration/statement/02-detail.spec.js +++ b/ui/cypress/integration/statement/02-detail.spec.js @@ -32,7 +32,10 @@ describe('Statement detail page E2E test', () => { cy.visit(this.uri.statement) cy.url().should('include', this.uri.statement) cy.wait('@statements_list') - cy.get('[data-automation-key=plan_count]').contains(2).eq(0).click() + cy.get('[data-automation-key=plan_count]') + .contains(2) + .eq(0) + .click({ force: true }) }) describe('Statement Template', () => { diff --git a/ui/cypress/integration/topsql/topsql.spec.ts b/ui/cypress/integration/topsql/topsql.spec.ts new file mode 100644 index 0000000000..d5bc4fe117 --- /dev/null +++ b/ui/cypress/integration/topsql/topsql.spec.ts @@ -0,0 +1,412 @@ +// Copyright 2022 PingCAP, Inc. Licensed under Apache-2.0. +import dayjs from 'dayjs' +import { skipOn, onlyOn } from '@cypress/skip-test' + +function setCustomTimeRange(timeRange) { + if (!document.querySelector('[data-e2e="timerange_selector_dropdown"]')) { + cy.getByTestId('timerange-selector').click() + } + cy.getByTestId('timerange_selector_dropdown').should('be.visible') + + cy.getByTestId('timerange_selector_dropdown') + .find('.ant-picker.ant-picker-range') + .type(timeRange) + cy.getByTestId('timerange_selector_dropdown').should('be.not.visible') +} + +function clearCustomTimeRange() { + if (!document.querySelector('[data-e2e="timerange_selector_dropdown"]')) { + cy.getByTestId('timerange-selector').click() + } + cy.getByTestId('timerange_selector_dropdown').should('be.visible') + + cy.getByTestId('timerange_selector_dropdown') + .find('.ant-picker-clear') + .click() + cy.getByTestId('timerange_selector_dropdown').should('be.not.visible') +} + +function enableTopSQL() { + cy.getByTestId('topsql_settings').click() + cy.wait('@getTopsqlConfig') + + cy.getByTestId('topsql_settings_enable').click() + cy.getByTestId('topsql_settings_save').click() + + // confirm the tips which about the data will be delayed + cy.get('.ant-modal-body').should('be.visible') + cy.get('.ant-modal-body .ant-btn-primary').click() +} + +skipOn(Cypress.env('TIDB_VERSION') !== 'latest', () => { + describe('Top SQL page', function () { + before(() => { + cy.intercept(`${Cypress.env('apiBasePath')}/topsql/config`).as( + 'getTopsqlConfig' + ) + + cy.fixture('uri.json').then((uri) => { + this.uri = uri + + cy.login('root') + cy.visit(this.uri.topsql) + + cy.wait('@getTopsqlConfig').then((interception) => { + if (!interception.response?.body.enable) { + enableTopSQL() + } + }) + + cy.visit(this.uri.overview) + }) + }) + + beforeEach(() => { + cy.login('root') + + cy.intercept(`${Cypress.env('apiBasePath')}/topsql/config`).as( + 'getTopsqlConfig' + ) + // mock summary and instance data from 2022-01-12 00:00:00 to 2022-01-12 05:00:00 + cy.intercept(`${Cypress.env('apiBasePath')}/topsql/summary?*`, { + fixture: + 'topsql_summary:end=1641934800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641916800&top=5&window=123s.json', + }).as('getTopsqlSummary') + cy.intercept( + { + url: `${Cypress.env('apiBasePath')}/topsql/instances?*`, + }, + { fixture: 'topsql_instance:end=1641934800&start=1641916800.json' } + ) + + // clear the user preference before visit top sql page + cy.window().then((win) => win.sessionStorage.clear()) + cy.visit(this.uri.topsql) + + // consume the first screen intercepted request when page loaded + cy.wait('@getTopsqlSummary') + cy.wait('@getTopsqlConfig') + }) + + describe('Update time range', () => { + it('custom the time range, chart displays the data within the time range', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + + cy.wait('@getTopsqlSummary') + cy.getByTestId('topsql_list_chart').matchImageSnapshot() + }) + + it('zoom out the time range, chart displays the data that extends the 50% time range', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.get('.anticon-zoom-out').click() + + cy.getByTestId('timerange-selector').should( + 'contain', + '01-11 21:30:00 ~ 01-12 07:30:00' + ) + cy.getByTestId('topsql_list_chart').matchImageSnapshot() + }) + }) + + describe('Select instance', () => { + it('default time range with instance', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('instance-selector').should( + 'contain', + 'tidb - 127.0.0.1:10080' + ) + }) + + it('change time range, keep the selected instance', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('instance-selector').should( + 'contain', + 'tidb - 127.0.0.1:10080' + ) + + // No `tidb - 127.0.0.1:10080` data in the time range + clearCustomTimeRange() + cy.wait('@getTopsqlSummary') + + setCustomTimeRange( + '1970-01-01 08:00:00{enter}1970-01-01 09:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('instance-selector').should( + 'contain', + 'tidb - 127.0.0.1:10080' + ) + }) + }) + + describe('Refresh', () => { + it('click refresh button with the recent x time range, fetch the recent x time range data', () => { + cy.getByTestId('timerange-selector').click() + cy.getByTestId('timerange_selector_dropdown').should('be.visible') + + const recent = 300 + const now = dayjs().unix() + cy.clock(now * 1000) + + cy.getByTestId(`timerange-${recent}`).click({ force: true }) + cy.wait('@getTopsqlSummary') + .its('request.url') + .should('include', `start=${now - recent}`) + + cy.getByTestId('auto-refresh-button').first().click() + cy.wait('@getTopsqlSummary') + .its('request.url') + .should('include', `start=${now - recent}`) + }) + + it("click refresh button after custom the time range, the data won't change", () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('auto-refresh-button').first().click() + cy.getByTestId('timerange-selector').should( + 'contain', + '01-12 00:00:00 ~ 01-12 05:00:00' + ) + cy.wait('@getTopsqlSummary') + cy.getByTestId('topsql_list_chart').matchImageSnapshot() + }) + + it('set auto refresh, show auto refresh secs aside button', () => { + cy.getByTestId('auto-refresh-button').children().eq(1).click() + cy.getByTestId('auto_refresh_time_30').click() + cy.getByTestId('auto-refresh-button').should('contain', '30 s') + }) + + it('set auto refresh, it will be refreshed automatically after the time', () => { + cy.getByTestId('auto-refresh-button').children().eq(1).click() + cy.getByTestId('auto_refresh_time_30').should('be.visible') + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000) // wait auto_refresh_time_30 item can be clicked before clock freeze the animate + + cy.clock() + cy.getByTestId('auto_refresh_time_30').click() + for (let i = 0; i < 35; i++) { + cy.tick(1000) + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(0) // yield to react hooks + } + cy.clock().invoke('restore') + cy.wait('@getTopsqlSummary') + .its('response.statusCode') + .should('eq', 200) + }) + }) + + describe('Chart and table', () => { + it('when the time range is large, the chart interval is large', () => { + cy.intercept(`${Cypress.env('apiBasePath')}/topsql/summary?*`, { + fixture: + 'topsql_summary_large_timerange:end=1641916800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641484800&top=5&window=2929s.json', + }).as('getTopsqlSummaryLargeTimerange') + + setCustomTimeRange( + '2022-01-07 00:00:00{enter}2022-01-12 00:00:00{enter}' + ) + cy.wait('@getTopsqlSummaryLargeTimerange') + + cy.getByTestId('topsql_list_chart').matchImageSnapshot() + }) + + it('when the time range is small, the chart interval is small', () => { + cy.intercept(`${Cypress.env('apiBasePath')}/topsql/summary?*`, { + fixture: + 'topsql_summary_small_timerange:end=1641920460&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641920400&top=5&window=1s.json', + }).as('getTopsqlSummarySmallTimerange') + + setCustomTimeRange( + '2022-01-12 01:00:00{enter}2022-01-12 01:01:00{enter}' + ) + cy.wait('@getTopsqlSummarySmallTimerange') + + cy.getByTestId('topsql_list_chart').matchImageSnapshot() + }) + + it('the last item in the table list is others', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('topsql_list_table') + .find('.ms-List-cell') + .children() + .eq(5) + .find('[data-e2e="topsql_listtable_row_others"]') + }) + + it('table has top 5 records and the others record', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('topsql_list_table') + .find('.ms-List-cell') + .children() + .should('have.length', 6) + + cy.getByTestId('topsql_list_table') + .find('.ms-List-cell') + .each((item, index) => { + cy.wrap(item).trigger('mouseover') + cy.getByTestId('topsql_list_chart').matchImageSnapshot( + `Top SQL page -- Chart and table -- table has top 5 records and the others record - ${index}` + ) + }) + }) + + it('table can only be single selected', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('topsql_list_table') + .find('.ms-List-cell') + .each((item) => { + cy.wrap(item).click() + cy.getByTestId('topsql_list_table') + .find('.ms-DetailsRow-check[aria-checked="true"]') + .should('have.length', 1) + }) + }) + }) + + describe('Top SQL settings', () => { + it('close Top SQL by settings panel, the chart and table will still work', () => { + cy.getByTestId('topsql_settings').click() + cy.wait('@getTopsqlConfig') + + cy.getByTestId('topsql_settings_enable').click() + cy.getByTestId('topsql_settings_save').click() + cy.get('.ant-btn-primary.ant-btn-dangerous').click() + cy.getByTestId('topsql_not_enabled_alert').should('exist') + + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + .its('response.statusCode') + .should('eq', 200) + + enableTopSQL() + cy.wait('@getTopsqlConfig') + cy.getByTestId('topsql_not_enabled_alert').should('not.exist') + }) + }) + + describe('SQL statement details', () => { + it('click one table row, show the list detail table and information contents', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('topsql_list_table').find('.ms-List-cell').eq(0).click() + cy.getByTestId('topsql_listdetail_table').should('exist') + + // content + cy.getByTestId('sql_text').should('exist') + cy.getByTestId('sql_digest').should('exist') + cy.getByTestId('plan_text').should('not.exist') + cy.getByTestId('plan_digest').should('not.exist') + + // table columns + cy.get('[data-item-key="cpuTime"]').should('exist') + cy.get('[data-item-key="plan"]').should('exist') + cy.get('[data-item-key="exec_count_per_sec"]').should('exist') + cy.get('[data-item-key="latency"]').should('exist') + }) + + it('if the list detail table has more than one plan, only the real plans can be selected', () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('topsql_list_table').find('.ms-List-cell').eq(0).click() + + cy.getByTestId('topsql_listdetail_table') + .find('.ms-List-cell') + .eq(0) + .click() + cy.getByTestId('topsql_listdetail_table') + .find('.ms-DetailsRow-check[aria-checked="true"]') + .should('have.length', 0) + + cy.getByTestId('topsql_listdetail_table') + .find('.ms-List-cell') + .eq(1) + .click() + cy.getByTestId('topsql_listdetail_table') + .find('.ms-DetailsRow-check[aria-checked="true"]') + .should('have.length', 0) + + cy.getByTestId('topsql_listdetail_table') + .find('.ms-List-cell') + .eq(2) + .click() + cy.getByTestId('topsql_listdetail_table') + .find('.ms-DetailsRow-check[aria-checked="true"]') + .should('have.length', 1) + cy.getByTestId('sql_text').should('exist') + cy.getByTestId('sql_digest').should('exist') + cy.getByTestId('plan_text').should('exist') + cy.getByTestId('plan_digest').should('exist') + }) + + it("if there's only one plan in the list detail table, show the plan information directly", () => { + setCustomTimeRange( + '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}' + ) + cy.wait('@getTopsqlSummary') + + cy.getByTestId('topsql_list_table').find('.ms-List-cell').eq(1).click() + + cy.getByTestId('sql_text').should('exist') + cy.getByTestId('sql_digest').should('exist') + cy.getByTestId('plan_text').should('exist') + cy.getByTestId('plan_digest').should('exist') + }) + }) + }) +}) + +onlyOn(Cypress.env('TIDB_VERSION') === '5.0.0', () => { + describe('Ngm not supported', function () { + before(() => { + cy.fixture('uri.json').then((uri) => (this.uri = uri)) + }) + + beforeEach(() => { + cy.login('root') + }) + + it('can not see top sql menu', () => { + cy.getByTestId('menu_item_topsql').should('not.exist') + }) + }) +}) diff --git a/ui/cypress/integration/topsql/topsql.without_ngm_spec.js b/ui/cypress/integration/topsql/topsql.without_ngm_spec.js new file mode 100644 index 0000000000..de089d4833 --- /dev/null +++ b/ui/cypress/integration/topsql/topsql.without_ngm_spec.js @@ -0,0 +1,24 @@ +// Copyright 2022 PingCAP, Inc. Licensed under Apache-2.0. + +describe('TopSQL without ngm', function () { + before(() => { + cy.fixture('uri.json').then((uri) => (this.uri = uri)) + }) + + beforeEach(() => { + cy.login('root') + + cy.visit(this.uri.topsql) + }) + + describe('Ngm not deployed', () => { + it('show global notification about ngm not deployed', () => { + cy.get('.ant-notification-notice-message').should( + 'contain', + 'System Health Check Failed' + ) + + cy.get('[data-e2e="ngm_not_started"]').should('exist') + }) + }) +}) diff --git a/ui/cypress/integration/topsql/topsql_security.spec.js b/ui/cypress/integration/topsql/topsql_security.spec.js new file mode 100644 index 0000000000..a7faa5582e --- /dev/null +++ b/ui/cypress/integration/topsql/topsql_security.spec.js @@ -0,0 +1,13 @@ +// Copyright 2022 PingCAP, Inc. Licensed under Apache-2.0. + +describe('Top SQL security', function () { + it("can't access the Top SQL page without login, then redirect to login page", function () { + cy.on('uncaught:exception', function () { + return false + }) + cy.fixture('uri.json').then(function (uri) { + cy.visit(uri.topsql) + cy.url().should('include', uri.login) + }) + }) +}) diff --git a/ui/cypress/integration/utils.js b/ui/cypress/integration/utils.js index 86366f9e32..765b57aed5 100644 --- a/ui/cypress/integration/utils.js +++ b/ui/cypress/integration/utils.js @@ -16,6 +16,8 @@ export const deleteDownloadsFolder = () => { export const validateSlowQueryCSVList = (list) => { expect(list).to.have.length(4) + // FIXME: this check makes it extremely hard for adding new tests. + expect(list[0].query).to.equal('SELECT sleep(1.2);') expect(list[1].query).to.equal('SELECT sleep(1.5);') expect(list[2].query).to.equal('SELECT sleep(2);') @@ -39,7 +41,9 @@ export const validateStatementCSVList = (allStatementList) => { export const restartTiUP = () => { // Restart tiup cy.exec( - `bash ../scripts/start_tiup.sh ${Cypress.env('TIDB_VERSION')} restart`, + `bash ../scripts/start_tiup.sh ${Cypress.env( + 'TIDB_VERSION' + )} false restart`, { log: true } ) diff --git a/ui/cypress/plugins/index.js b/ui/cypress/plugins/index.js index ea4a6547a3..f56e06e47e 100644 --- a/ui/cypress/plugins/index.js +++ b/ui/cypress/plugins/index.js @@ -12,9 +12,11 @@ // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) -const mysql = require('mysql2') -const { rmdir } = require('fs') -const clipboardy = require('clipboardy') +import mysql from 'mysql2' +import { rmdir } from 'fs' +import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin' +import codecovTaskPlugin from '@cypress/code-coverage/task' +import clipboardy from 'clipboardy' function queryTestDB(query, password, database) { const dbConfig = { @@ -26,16 +28,17 @@ function queryTestDB(query, password, database) { } // creates a new mysql connection const connection = mysql.createConnection(dbConfig) - // start connection to db - connection.connect() // exec query + disconnect to db as a Promise return new Promise((resolve, reject) => { connection.query(query, (error, results) => { - if (error) reject(error) - else { - connection.end() - return resolve(results) - } + setTimeout(() => { + if (error) { + reject(error) + } else { + connection.end() + return resolve(results) + } + }, 500) // wait a few more moments for statements and slow query to finish. }) }) } @@ -62,7 +65,8 @@ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config - require('@cypress/code-coverage/task')(on, config) + codecovTaskPlugin(on, config) + addMatchImageSnapshotPlugin(on, config) config.baseUrl = (process.env.SERVER_URL || 'http://localhost:3001/dashboard') + '#' diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 0.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 0.snap.png new file mode 100644 index 0000000000..412619c33e Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 0.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 1.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 1.snap.png new file mode 100644 index 0000000000..01915fe822 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 1.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 2.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 2.snap.png new file mode 100644 index 0000000000..702c97f00f Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 2.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 3.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 3.snap.png new file mode 100644 index 0000000000..3583d14625 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 3.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 4.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 4.snap.png new file mode 100644 index 0000000000..82d049bfa4 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 4.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 5.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 5.snap.png new file mode 100644 index 0000000000..a109bb1190 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- table has top 5 records and the others record - 5.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- when the time range is large, the chart interval is large.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- when the time range is large, the chart interval is large.snap.png new file mode 100644 index 0000000000..c598b5c2b1 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- when the time range is large, the chart interval is large.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- when the time range is small, the chart interval is small.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- when the time range is small, the chart interval is small.snap.png new file mode 100644 index 0000000000..5496ec9ebd Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Chart and table -- when the time range is small, the chart interval is small.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Refresh -- click refresh button after custom the time range, the data won't change.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Refresh -- click refresh button after custom the time range, the data won't change.snap.png new file mode 100644 index 0000000000..2d7050a0f8 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Refresh -- click refresh button after custom the time range, the data won't change.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Update time range -- custom the time range, chart displays the data within the time range.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Update time range -- custom the time range, chart displays the data within the time range.snap.png new file mode 100644 index 0000000000..2d7050a0f8 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Update time range -- custom the time range, chart displays the data within the time range.snap.png differ diff --git a/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Update time range -- zoom out the time range, chart displays the data that extends the 50% time range.snap.png b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Update time range -- zoom out the time range, chart displays the data that extends the 50% time range.snap.png new file mode 100644 index 0000000000..5d955c1db3 Binary files /dev/null and b/ui/cypress/snapshots/topsql/topsql.spec.ts/Top SQL page -- Update time range -- zoom out the time range, chart displays the data that extends the 50% time range.snap.png differ diff --git a/ui/cypress/support/commands.js b/ui/cypress/support/commands.js index 4e3665adad..55639bac47 100644 --- a/ui/cypress/support/commands.js +++ b/ui/cypress/support/commands.js @@ -50,12 +50,24 @@ Cypress.Commands.overwrite('request', (originalFn, ...options) => { return originalFn(...options) }) -const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/ -Cypress.on('uncaught:exception', (err) => { - /* returning false here prevents Cypress from failing the test */ - if (resizeObserverLoopErrRe.test(err.message)) { - return false +// We overwrite the command, so it does not take a sceenshot if we run the tests inside the test runner +Cypress.Commands.overwrite( + 'matchImageSnapshot', + (originalFn, snapshotName, options) => { + if (Cypress.env('ALLOW_SCREENSHOT')) { + originalFn(snapshotName, options) + } else { + cy.log(`Screenshot comparison is disabled`) + } } +) + +Cypress.Commands.add('getByTestId', (selector, ...args) => { + return cy.get(`[data-e2e="${selector}"]`, ...args) +}) + +Cypress.Commands.add('getByTestIdLike', (selector, ...args) => { + return cy.get(`[data-e2e*="${selector}"]`, ...args) }) // diff --git a/ui/cypress/support/index.js b/ui/cypress/support/index.js index 997221d2bd..cae62d5bfb 100644 --- a/ui/cypress/support/index.js +++ b/ui/cypress/support/index.js @@ -14,9 +14,17 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' import '@cypress/code-coverage/support' +import '@cypress/skip-test/support' import 'cypress-real-events/support' +import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command' -// Alternatively you can use CommonJS syntax: -// require('./commands') +addMatchImageSnapshotCommand() + +require('./commands') + +// https://github.com/cypress-io/cypress/issues/8418 +Cypress.on( + 'uncaught:exception', + (err) => !err.message.includes('ResizeObserver loop limit exceeded') +) diff --git a/ui/cypress/tsconfig.json b/ui/cypress/tsconfig.json new file mode 100644 index 0000000000..d9d9fb534e --- /dev/null +++ b/ui/cypress/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "include": ["./**/*.ts"], + "exclude": [], + "compilerOptions": { + "types": ["cypress"], + "lib": ["es2015", "dom"], + "isolatedModules": false, + "allowJs": true, + "noEmit": true + } +} diff --git a/ui/cypress/types/global.d.ts b/ui/cypress/types/global.d.ts new file mode 100644 index 0000000000..a66fdcc281 --- /dev/null +++ b/ui/cypress/types/global.d.ts @@ -0,0 +1,16 @@ +/// + +declare namespace Cypress { + interface Chainable { + login(username: string, password?: string): void + + getByTestId(dataTestAttribute: string, args?: any): Chainable + getByTestIdLike( + dataTestPrefixAttribute: string, + args?: any + ): Chainable + + matchImageSnapshot(nameOrOptions?: string | Options): void + matchImageSnapshot(name: string, options: Options): void + } +} diff --git a/ui/cypress/types/mocha.d.ts b/ui/cypress/types/mocha.d.ts new file mode 100644 index 0000000000..5b4591f790 --- /dev/null +++ b/ui/cypress/types/mocha.d.ts @@ -0,0 +1,8 @@ +/// +import 'mocha' + +declare module 'mocha' { + interface Suite { + uri: any + } +} diff --git a/ui/dashboardApp/index.ts b/ui/dashboardApp/index.ts index 32f65fb720..9a94fb1d26 100755 --- a/ui/dashboardApp/index.ts +++ b/ui/dashboardApp/index.ts @@ -156,9 +156,9 @@ async function webPageStart() { // NOTE: Don't remove above comment line, it is a placeholder for code generator try { - await reloadWhoAmI() + const ok = await reloadWhoAmI() - if (routing.isLocationMatch('/')) { + if (routing.isLocationMatch('/') && ok) { singleSpa.navigateToUrl('#' + registry.getDefaultRouter()) } } catch (e) { diff --git a/ui/dashboardApp/layout/main/Sider/Banner.module.less b/ui/dashboardApp/layout/main/Sider/Banner.module.less index a769c2419e..cb115cbee6 100644 --- a/ui/dashboardApp/layout/main/Sider/Banner.module.less +++ b/ui/dashboardApp/layout/main/Sider/Banner.module.less @@ -40,6 +40,6 @@ } .bannerVersion { - font-size: 0.8rem; + font-size: 0.9rem; opacity: 0.7; } diff --git a/ui/dashboardApp/layout/main/Sider/Banner.tsx b/ui/dashboardApp/layout/main/Sider/Banner.tsx index 41dda7a6cd..fcc6f1d351 100644 --- a/ui/dashboardApp/layout/main/Sider/Banner.tsx +++ b/ui/dashboardApp/layout/main/Sider/Banner.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useRef } from 'react' -import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons' +import { CaretRightOutlined, CaretLeftOutlined } from '@ant-design/icons' import { useSize } from 'ahooks' import Flexbox from '@g07cha/flexbox-react' import { useSpring, animated } from 'react-spring' @@ -97,9 +97,9 @@ export default function ToggleBanner({ {collapsed ? ( - + ) : ( - + )} diff --git a/ui/dashboardApp/layout/main/Sider/index.module.less b/ui/dashboardApp/layout/main/Sider/index.module.less index 34269759f7..87c32d19f1 100644 --- a/ui/dashboardApp/layout/main/Sider/index.module.less +++ b/ui/dashboardApp/layout/main/Sider/index.module.less @@ -1,6 +1,8 @@ @import 'antd/es/style/themes/default.less'; -@sider-background: #f7f7fa; +@sider-background: rgb(246, 246, 246); +@sider-highlight-height: 36px; +@sider-ribbon-height: 30px; .sider { position: fixed; @@ -8,13 +10,37 @@ top: 0; height: 100%; z-index: 1; - background: linear-gradient(@sider-background, #ebeffa); + background: @sider-background; overflow-x: hidden; overflow-y: auto; transition: none; user-select: none; :global { + /* cancel text animations */ + .ant-menu-item .ant-menu-item-icon, + .ant-menu-submenu-title .ant-menu-item-icon, + .ant-menu-item .anticon, + .ant-menu-submenu-title .anticon { + transition-duration: 0.1s; + } + + .ant-menu-item .ant-menu-item-icon + span, + .ant-menu-submenu-title .ant-menu-item-icon + span, + .ant-menu-item .anticon + span, + .ant-menu-submenu-title .anticon + span { + transition-duration: 0.1s; + } + + .ant-menu-item, + .ant-menu-submenu-title { + transition-duration: 0.1s; + } + + .ant-menu-title-content { + transition-duration: 0.1s; + } + .ant-layout-sider-children { display: flex; flex-direction: column; @@ -35,32 +61,68 @@ } .ant-menu-item { + background: none !important; + &::after { left: 0; - right: auto; - border-width: 5px; + top: 0; + height: @sider-ribbon-height; + margin-top: -(@sider-ribbon-height / 2); + top: 50%; + border: 0px; + width: 5px; + border-radius: 0 5px 5px 0; + background: @primary-color; } - &:hover { - background: rgba(darken(@sider-background, 30%), 0.15); + &::before { + content: ''; + position: absolute; + left: 0; + right: 20px; + height: @sider-highlight-height; + top: 50%; + margin-top: -(@sider-highlight-height / 2); + border-radius: 0 (@sider-highlight-height / 2) + (@sider-highlight-height / 2) 0; + z-index: -1; + } + + &:hover::before { + background: rgba(lighten(@primary-color, 20%), 0.2); } a { color: #666; + transition-duration: 0s; &:hover { - color: @primary-color; + color: #000; } } &.ant-menu-item-selected { - background: rgba(darken(@sider-background, 30%), 0.15); + &::before { + background: rgba(darken(@sider-background, 30%), 0.15); + } a { color: #000; } } } + + .ant-menu-submenu-title { + background: none !important; + } + + .ant-menu-inline-collapsed .ant-menu-item { + &::before { + right: 10px; + left: 10px; + border-radius: (@sider-highlight-height / 2); + } + } } } diff --git a/ui/dashboardApp/layout/main/index.module.less b/ui/dashboardApp/layout/main/index.module.less index e239d1efa2..b6aecf8413 100644 --- a/ui/dashboardApp/layout/main/index.module.less +++ b/ui/dashboardApp/layout/main/index.module.less @@ -25,5 +25,5 @@ top: 0; height: 100%; right: 0; - box-shadow: 0 0 30px rgba(#000, 0.15); + box-shadow: 0 0 20px rgba(#000, 0.1); } diff --git a/ui/dashboardApp/layout/signin/index.tsx b/ui/dashboardApp/layout/signin/index.tsx index 05673162b0..3e05d98604 100755 --- a/ui/dashboardApp/layout/signin/index.tsx +++ b/ui/dashboardApp/layout/signin/index.tsx @@ -184,20 +184,25 @@ function useSignInSubmit( const { handled, message, errCode } = e as any if (!handled) { const errMsg = t('signin.message.error', { msg: message }) - if (isDistro || errCode !== 'api.user.signin.insufficient_priv') { + if (errCode !== 'api.user.signin.insufficient_priv') { setError(errMsg) } else { // only add help link for TiDB distro when meeting insufficient_privileges error const errComp = ( <> {errMsg} - - {t('signin.message.access_doc')} - + {!isDistro && ( + <> + {' '} + + {t('signin.message.access_doc')} + + + )} ) setError(errComp) diff --git a/ui/dashboardApp/layout/translations/en.yaml b/ui/dashboardApp/layout/translations/en.yaml index 4bf3e9e24e..9752e076ac 100755 --- a/ui/dashboardApp/layout/translations/en.yaml +++ b/ui/dashboardApp/layout/translations/en.yaml @@ -35,5 +35,5 @@ nav: debug: Advanced Debugging experimental: Experimental Features health_check: - failed_notification_title: System Health Check Falied + failed_notification_title: System Health Check Failed ngm_not_started: A required component `NgMonitoring` is not started in this cluster. Some features may not work. diff --git a/ui/dashboardApp/layout/translations/zh.yaml b/ui/dashboardApp/layout/translations/zh.yaml index e2b2d24490..8feea3eb6c 100755 --- a/ui/dashboardApp/layout/translations/zh.yaml +++ b/ui/dashboardApp/layout/translations/zh.yaml @@ -36,5 +36,5 @@ nav: debug: 高级调试 experimental: 实验性功能 health_check: - notification_failed_title: 系统健康检查失败 + failed_notification_title: 系统健康检查失败 ngm_not_started: 集群中未启动必要组件 `NgMonitoring`,部分功能将不可用。 diff --git a/ui/lib/apps/ContinuousProfiling/pages/Detail.tsx b/ui/lib/apps/ContinuousProfiling/pages/Detail.tsx index 7442a334df..74540fa450 100644 --- a/ui/lib/apps/ContinuousProfiling/pages/Detail.tsx +++ b/ui/lib/apps/ContinuousProfiling/pages/Detail.tsx @@ -1,4 +1,4 @@ -import { Badge, Button } from 'antd' +import { Badge, Button, Modal, Space } from 'antd' import React, { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' @@ -9,20 +9,22 @@ import { IGroup } from 'office-ui-fabric-react/lib/DetailsList' import client, { ConprofProfileDetail } from '@lib/client' import { + Card, CardTable, DateTime, Descriptions, Head, - ActionsButton, + // ActionsButton, } from '@lib/components' import { useClientRequest } from '@lib/utils/useClientRequest' import { InstanceKindName } from '@lib/utils/instanceTable' import useQueryParams from '@lib/utils/useQueryParams' import publicPathPrefix from '@lib/utils/publicPathPrefix' import { telemetry } from '../utils/telemetry' +import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' const COMMON_ACTIONS: string[] = ['view_flamegraph', 'view_graph', 'download'] -const TEXT_ACTIONS: string[] = ['view_text', 'download'] +const TEXT_ACTIONS: string[] = ['view_text'] const profileTypeSortOrder: { [key: string]: number } = { profile: 1, @@ -147,15 +149,15 @@ export default function Page() { { name: t('conprof.detail.table.columns.instance'), key: 'instance', - minWidth: 150, - maxWidth: 300, + minWidth: 100, + maxWidth: 200, onRender: (record) => record.target.address, }, { name: t('conprof.detail.table.columns.content'), key: 'content', - minWidth: 150, - maxWidth: 300, + minWidth: 100, + maxWidth: 100, onRender: (record) => { const profileType = record.profile_type if (profileType === 'profile') { @@ -167,8 +169,8 @@ export default function Page() { { name: t('conprof.detail.table.columns.status'), key: 'status', - minWidth: 150, - maxWidth: 200, + minWidth: 100, + maxWidth: 150, onRender: (record) => { if (record.state === 'finished' || record.state === 'success') { return ( @@ -179,32 +181,62 @@ export default function Page() { ) } if (record.state === 'failed') { - return + return ( + + ) } return }, }, { - name: t('conprof.detail.table.columns.actions'), - key: 'actions', - minWidth: 150, - maxWidth: 200, + name: t('conprof.detail.table.columns.view_as.title'), + key: 'view_as', + minWidth: 250, + maxWidth: 400, onRender: (record) => { + if (record.state === 'failed') { + return ( + { + Modal.error({ + title: 'Profile Error', + content: record.error, + }) + }} + > + {t('conprof.detail.table.columns.view_as.error')} + + ) + } + + if (record.state !== 'finished' && record.state !== 'success') { + return <> + } + const rec = record as ConprofProfileDetail let actionsKey = TEXT_ACTIONS if (rec.profile_type !== 'goroutine') { actionsKey = COMMON_ACTIONS } - const actions = actionsKey.map((key) => ({ - key, - text: t(`conprof.detail.table.actions.${key}`), - })) + return ( - handleClick(act, rec)} - /> + + {actionsKey.map((action) => { + return ( + handleClick(action, record)} + key={action} + > + {t(`conprof.detail.table.columns.view_as.${action}`)} + + ) + })} + ) }, }, @@ -213,7 +245,7 @@ export default function Page() { ) return ( -
+
} - > - {groupProfileDetail && ( - - - - - - )} - - - +
+ + {groupProfileDetail && ( + + + + + + + + )} + + +
) } diff --git a/ui/lib/apps/ContinuousProfiling/pages/List.tsx b/ui/lib/apps/ContinuousProfiling/pages/List.tsx index 8368f1d9e2..7a2bb2ebbd 100644 --- a/ui/lib/apps/ContinuousProfiling/pages/List.tsx +++ b/ui/lib/apps/ContinuousProfiling/pages/List.tsx @@ -15,6 +15,7 @@ import { useNavigate } from 'react-router-dom' import { useMemoizedFn, useSessionStorageState } from 'ahooks' import { LoadingOutlined, + QuestionCircleOutlined, ReloadOutlined, SettingOutlined, } from '@ant-design/icons' @@ -31,6 +32,7 @@ import ConProfSettingForm from './ConProfSettingForm' import styles from './List.module.less' import { telemetry } from '../utils/telemetry' +import { isDistro } from '@lib/utils/distroStringsRes' export default function Page() { const [endTime, setEndTime] = useSessionStorageState( @@ -225,6 +227,8 @@ export default function Page() { @@ -235,6 +239,8 @@ export default function Page() { )} @@ -245,6 +251,20 @@ export default function Page() { }} /> + {!isDistro && ( + + { + window.open(t('conprof.settings.help_url'), '_blank') + }} + /> + + )} @@ -264,15 +284,26 @@ export default function Page() { title={t('conprof.settings.disabled_result.title')} subTitle={t('conprof.settings.disabled_result.sub_title')} extra={ - + + + {!isDistro && ( + + )} + } /> ) : ( @@ -280,6 +311,7 @@ export default function Page() { record.target.display_name, }, { name: t('instance_profiling.detail.table.columns.content'), key: 'content', - minWidth: 150, - maxWidth: 300, + minWidth: 100, + maxWidth: 100, onRender: (record) => { if (record.profiling_type === 'cpu') { return `CPU - ${profileDuration}s` @@ -226,43 +220,37 @@ export default function Page() { { name: t('instance_profiling.detail.table.columns.status'), key: 'status', - minWidth: 150, - maxWidth: 200, + minWidth: 100, + maxWidth: 150, onRender: (record) => { if (record.state === taskState.Running) { return ( -
- -
+ ) } else if (record.state === taskState.Error) { return ( - - - + ) } else if (record.state === taskState.Skipped) { - let tooltipTransKey = - 'instance_profiling.detail.table.tooltip.skipped' - if (record.profiling_type === 'heap') { - tooltipTransKey = - 'instance_profiling.detail.table.tooltip.to_be_supported' - } return ( - + + + + ) } else { @@ -276,24 +264,60 @@ export default function Page() { }, }, { - name: t('instance_profiling.detail.table.columns.selection.actions'), - key: 'output_type', - minWidth: 150, - maxWidth: 200, + name: t('instance_profiling.detail.table.columns.view_as.title'), + key: 'view_as', + minWidth: 250, + maxWidth: 400, onRender: (record) => { + if (record.state === taskState.Error) { + return ( + { + Modal.error({ + title: 'Profile Error', + content: record.error, + }) + }} + > + {t('instance_profiling.detail.table.columns.view_as.error')} + + ) + } + + if (record.state === taskState.Running) { + return ( +
+ +
+ ) + } + + if (record.state !== taskState.Success) { + return <> + } + const rec = record as IRecord - const actions = rec.view_options.map((key) => ({ - key, - text: t( - `instance_profiling.detail.table.columns.selection.types.${key}` - ), - })) return ( - openResult(act, rec)} - /> + + {rec.view_options.map((action) => { + return ( + openResult(action, record)} + key={action} + > + {t( + `instance_profiling.detail.table.columns.view_as.${action}` + )} + + ) + })} + ) }, }, @@ -328,23 +352,28 @@ export default function Page() { {t('instance_profiling.detail.download')} } - > - {respData && ( - - - - - - )} - + />
+ {respData && ( + + + + + + + + )}
diff --git a/ui/lib/apps/InstanceProfiling/pages/List.tsx b/ui/lib/apps/InstanceProfiling/pages/List.tsx index aa671abe19..51a9294ca6 100644 --- a/ui/lib/apps/InstanceProfiling/pages/List.tsx +++ b/ui/lib/apps/InstanceProfiling/pages/List.tsx @@ -1,4 +1,4 @@ -import { Badge, Button, Form, Select, Modal, Alert } from 'antd' +import { Badge, Button, Form, Select, Modal, Alert, Space, Tooltip } from 'antd' import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' import React, { useMemo, useState, useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' @@ -15,6 +15,7 @@ import { InstanceSelect, IInstanceSelectRefProps, MultiSelect, + Toolbar, } from '@lib/components' import DateTime from '@lib/components/DateTime' import openLink from '@lib/utils/openLink' @@ -23,6 +24,8 @@ import { combineTargetStats } from '../utils' import styles from './List.module.less' import { upperFirst } from 'lodash' +import { QuestionCircleOutlined } from '@ant-design/icons' +import { isDistro } from '@lib/utils/distroStringsRes' const profilingDurationsSec = [10, 30, 60, 120] const defaultProfilingDuration = 30 @@ -201,72 +204,108 @@ export default function Page() { return (
-
- - - - - - - - - - - - -
+ + + + + + + + + + + + + + + + {!isDistro && ( + + { + window.open( + t('instance_profiling.settings.help_url'), + '_blank' + ) + }} + /> + + )} + +
{conprofEnable && (
+ {t('instance_profiling.list.disable_warning')}{' '} + {!isDistro && ( + + {t('instance_profiling.settings.help')} + + )} + + } showIcon />
@@ -276,6 +315,7 @@ export default function Page() { { title={t('keyviz.settings.disabled_result.title')} subTitle={t('keyviz.settings.disabled_result.sub_title')} extra={ - + + + {!isDistro && ( + + )} + } /> ) diff --git a/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx b/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx index 1c0092dc27..ed5d732a90 100644 --- a/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx +++ b/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx @@ -6,6 +6,7 @@ import { ClockCircleOutlined, DownOutlined, LoadingOutlined, + QuestionCircleOutlined, SettingOutlined, } from '@ant-design/icons' import { Slider, Spin, Select, Dropdown, Button, Tooltip, Space } from 'antd' @@ -13,6 +14,7 @@ import { withTranslation, WithTranslation } from 'react-i18next' import Flexbox from '@g07cha/flexbox-react' import { AutoRefreshButton, Card, Toolbar } from '@lib/components' import { getValueFormat } from '@baurine/grafana-value-formats' +import { isDistro } from '@lib/utils/distroStringsRes' export interface IKeyVizToolbarProps { enabled: boolean @@ -190,9 +192,27 @@ class KeyVizToolbar extends Component { - + + {!isDistro && ( + + { + window.open(t('keyviz.settings.help_url'), '_blank') + }} + /> + + )} diff --git a/ui/lib/apps/KeyViz/translations/en.yaml b/ui/lib/apps/KeyViz/translations/en.yaml index a1b045a731..b5c53263fb 100644 --- a/ui/lib/apps/KeyViz/translations/en.yaml +++ b/ui/lib/apps/KeyViz/translations/en.yaml @@ -33,3 +33,5 @@ keyviz: save: Save close: Disable cancel: Cancel + help: Help + help_url: https://docs.pingcap.com/tidb/dev/dashboard-key-visualizer diff --git a/ui/lib/apps/KeyViz/translations/zh.yaml b/ui/lib/apps/KeyViz/translations/zh.yaml index 527190a92c..f6bc78921c 100644 --- a/ui/lib/apps/KeyViz/translations/zh.yaml +++ b/ui/lib/apps/KeyViz/translations/zh.yaml @@ -33,3 +33,5 @@ keyviz: save: 保存 close: 确认 cancel: 取消 + help: 帮助 + help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-key-visualizer diff --git a/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx b/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx index 51f1fe522a..de227fcd87 100644 --- a/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx +++ b/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx @@ -30,7 +30,7 @@ for (const key in translations) { export function NgmNotStarted() { const { t } = useTranslation() return ( - + .buttons { margin-top: 12px; diff --git a/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx b/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx index 6c982e10cf..32960cc0f0 100644 --- a/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx +++ b/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx @@ -215,6 +215,7 @@ export default function LogSearchingHistory() { { } function SlowQueriesTable({ controller, ...restProps }: Props) { - const { - loadingSlowQueries, - tableColumns, - slowQueries, - orderOptions: { orderBy, desc }, - changeOrder, - errors, - visibleColumnKeys, - - saveClickedItemIndex, - getClickedItemIndex, - } = controller - const navigate = useNavigate() const handleRowClick = useMemoizedFn( (rec, idx, ev: React.MouseEvent) => { - saveClickedItemIndex(idx) + controller.saveClickedItemIndex(idx) const qs = DetailPage.buildQuery({ digest: rec.digest, connectId: rec.connection_id, @@ -42,16 +29,16 @@ function SlowQueriesTable({ controller, ...restProps }: Props) { return ( diff --git a/ui/lib/apps/SlowQuery/pages/List/index.tsx b/ui/lib/apps/SlowQuery/pages/List/index.tsx index cd37870bf6..ed32d555c8 100644 --- a/ui/lib/apps/SlowQuery/pages/List/index.tsx +++ b/ui/lib/apps/SlowQuery/pages/List/index.tsx @@ -1,42 +1,46 @@ -import React, { useContext } from 'react' +import React, { useContext, useState } from 'react' import { useTranslation } from 'react-i18next' import { Select, Space, - Tooltip, Input, Checkbox, message, Menu, Dropdown, + Alert, + Tooltip, } from 'antd' import { - ReloadOutlined, LoadingOutlined, ExportOutlined, MenuOutlined, + QuestionCircleOutlined, } from '@ant-design/icons' import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' - import { Card, ColumnsSelector, TimeRangeSelector, Toolbar, MultiSelect, + TimeRange, + toTimeRangeValue, } from '@lib/components' import { CacheContext } from '@lib/utils/useCache' import { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState' - import SlowQueriesTable from '../../components/SlowQueriesTable' import useSlowQueryTableController, { DEF_SLOW_QUERY_COLUMN_KEYS, + DEF_SLOW_QUERY_OPTIONS, } from '../../utils/useSlowQueryTableController' - import styles from './List.module.less' +import { useDebounceFn, useMemoizedFn } from 'ahooks' +import { useDeepCompareChange } from '@lib/utils/useChange' +import client from '@lib/client' +import { isDistro } from '@lib/utils/distroStringsRes' const { Option } = Select -const { Search } = Input const SLOW_QUERY_VISIBLE_COLUMN_KEYS = 'slow_query.visible_column_keys' const SLOW_QUERY_SHOW_FULL_SQL = 'slow_query.show_full_sql' @@ -45,7 +49,7 @@ const LIMITS = [100, 200, 500, 1000] function List() { const { t } = useTranslation() - const slowQueryCacheMgr = useContext(CacheContext) + const cacheMgr = useContext(CacheContext) const [visibleColumnKeys, setVisibleColumnKeys] = useVersionedLocalStorageState(SLOW_QUERY_VISIBLE_COLUMN_KEYS, { @@ -55,32 +59,25 @@ function List() { SLOW_QUERY_SHOW_FULL_SQL, { defaultValue: false } ) + const [downloading, setDownloading] = useState(false) - const controller = useSlowQueryTableController( - slowQueryCacheMgr, - visibleColumnKeys, - showFullSQL - ) - const { - queryOptions, - setQueryOptions, - refresh, - allSchemas, - loadingSlowQueries, - tableColumns, - downloadCSV, - downloading, - } = controller - - function exportCSV() { - const hide = message.loading(t('slow_query.toolbar.exporting') + '...', 0) - downloadCSV().finally(hide) - } + const controller = useSlowQueryTableController({ + cacheMgr, + showFullSQL, + initialQueryOptions: { + ...DEF_SLOW_QUERY_OPTIONS, + visibleColumnKeys, + }, + }) function menuItemClick({ key }) { switch (key) { case 'export': - exportCSV() + const hide = message.loading( + t('slow_query.toolbar.exporting') + '...', + 0 + ) + downloadCSV().finally(hide) break } } @@ -100,46 +97,93 @@ function List() { ) + const [timeRange, setTimeRange] = useState( + controller.queryOptions.timeRange + ) + const [filterSchema, setFilterSchema] = useState( + controller.queryOptions.schemas + ) + const [filterLimit, setFilterLimit] = useState( + controller.queryOptions.limit + ) + const [filterText, setFilterText] = useState( + controller.queryOptions.searchText + ) + + const sendQueryNow = useMemoizedFn(() => { + cacheMgr?.clear() + controller.setQueryOptions({ + timeRange, + schemas: filterSchema, + limit: filterLimit, + searchText: filterText, + visibleColumnKeys, + digest: '', + plans: [], + }) + }) + + const sendQueryDebounced = useDebounceFn(sendQueryNow, { + wait: 300, + }).run + + useDeepCompareChange(() => { + if ( + controller.isDataLoadedSlowly || // if data was loaded slowly + controller.isDataLoadedSlowly === null // or a request is not yet finished (which means slow network).. + ) { + // do not send requests on-the-fly. + return + } + sendQueryDebounced() + }, [timeRange, filterSchema, filterLimit, filterText, visibleColumnKeys]) + + const downloadCSV = useMemoizedFn(async () => { + // use last effective query options + const timeRangeValue = toTimeRangeValue(controller.queryOptions.timeRange) + try { + setDownloading(true) + const res = await client.getInstance().slowQueryDownloadTokenPost({ + fields: '*', + begin_time: timeRangeValue[0], + end_time: timeRangeValue[1], + db: controller.queryOptions.schemas, + text: controller.queryOptions.searchText, + orderBy: controller.orderOptions.orderBy, + desc: controller.orderOptions.desc, + limit: 10000, + digest: '', + plans: [], + }) + const token = res.data + if (token) { + window.location.href = `${client.getBasePath()}/slow_query/download?token=${token}` + } + } finally { + setDownloading(false) + } + }) + return (
- - setQueryOptions({ - ...queryOptions, - timeRange, - }) - } - /> + - setQueryOptions({ - ...queryOptions, - schemas, - }) - } - items={allSchemas} + onChange={setFilterSchema} + items={controller.allSchemas} data-e2e="execution_database_name" /> - - setQueryOptions({ ...queryOptions, searchText }) - } - data-e2e="slow_query_search" - /> + setFilterText(e.target.value)} + onSearch={sendQueryNow} + placeholder={t('slow_query.toolbar.keyword.placeholder')} + data-e2e="slow_query_search" + enterButton={t('slow_query.toolbar.query')} + /> + {controller.isLoading && } - - {tableColumns.length > 0 && ( + {controller.availableColumnsInTable.length > 0 && ( )} - - {loadingSlowQueries ? ( - - ) : ( - - )} -
+ {!isDistro && ( + + { + window.open(t('slow_query.toolbar.help_url'), '_blank') + }} + /> + + )}
-
+ {controller.isDataLoadedSlowly && ( + + + + )}
diff --git a/ui/lib/apps/SlowQuery/translations/en.yaml b/ui/lib/apps/SlowQuery/translations/en.yaml index fd858c2d06..95e2abe823 100644 --- a/ui/lib/apps/SlowQuery/translations/en.yaml +++ b/ui/lib/apps/SlowQuery/translations/en.yaml @@ -129,5 +129,10 @@ slow_query: select_columns: show_full_sql: Show Full Query Text refresh: Refresh + keyword: + placeholder: Filter keyword + query: Query export: Export exporting: Exporting + help: Help + help_url: https://docs.pingcap.com/tidb/dev/dashboard-slow-query diff --git a/ui/lib/apps/SlowQuery/translations/zh.yaml b/ui/lib/apps/SlowQuery/translations/zh.yaml index 71bb62c336..9b330f60ef 100644 --- a/ui/lib/apps/SlowQuery/translations/zh.yaml +++ b/ui/lib/apps/SlowQuery/translations/zh.yaml @@ -132,5 +132,10 @@ slow_query: select_columns: show_full_sql: 显示完整 SQL 文本 refresh: 刷新 + keyword: + placeholder: 关键字过滤 + query: 查询 export: 导出 exporting: 正在导出 + help: 帮助 + help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-slow-query diff --git a/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts b/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts index 007e480bce..ad250c58a4 100644 --- a/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts +++ b/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts @@ -1,22 +1,22 @@ -import { useEffect, useMemo, useState } from 'react' -import { useSessionStorageState } from 'ahooks' +import { useMemo, useState } from 'react' +import { useMemoizedFn, useSessionStorageState } from 'ahooks' import { IColumn } from 'office-ui-fabric-react/lib/DetailsList' - import client, { ErrorStrategy, SlowqueryModel } from '@lib/client' import { - calcTimeRange, TimeRange, IColumnKeys, - stringifyTimeRange, + DEFAULT_TIME_RANGE, + toTimeRangeValue, } from '@lib/components' import useOrderState, { IOrderOptions } from '@lib/utils/useOrderState' - import { getSelectedFields } from '@lib/utils/tableColumnFactory' import { CacheMgr } from '@lib/utils/useCache' import useCacheItemIndex from '@lib/utils/useCacheItemIndex' - import { derivedFields, slowQueryColumns } from './tableColumns' import { useSchemaColumns } from './useSchemaColumns' +import { useChange } from '@lib/utils/useChange' + +const SLOW_DATA_LOAD_THRESHOLD = 2000 export const DEF_SLOW_QUERY_COLUMN_KEYS: IColumnKeys = { query: true, @@ -32,18 +32,26 @@ const DEF_ORDER_OPTIONS: IOrderOptions = { desc: true, } +interface RuntimeCacheEntity { + data: SlowqueryModel[] + isDataLoadedSlowly: boolean +} + export interface ISlowQueryOptions { - timeRange?: TimeRange + visibleColumnKeys: IColumnKeys + timeRange: TimeRange schemas: string[] searchText: string limit: number + // below is for showing slow queries in the statement detail page digest: string plans: string[] } export const DEF_SLOW_QUERY_OPTIONS: ISlowQueryOptions = { - timeRange: undefined, + visibleColumnKeys: DEF_SLOW_QUERY_COLUMN_KEYS, + timeRange: DEFAULT_TIME_RANGE, schemas: [], searchText: '', limit: 100, @@ -52,101 +60,91 @@ export const DEF_SLOW_QUERY_OPTIONS: ISlowQueryOptions = { plans: [], } +function useQueryOptions( + initial?: ISlowQueryOptions, + persistInSession: boolean = true +) { + const [memoryQueryOptions, setMemoryQueryOptions] = useState( + initial || DEF_SLOW_QUERY_OPTIONS + ) + const [sessionQueryOptions, setSessionQueryOptions] = useSessionStorageState( + QUERY_OPTIONS, + { defaultValue: initial || DEF_SLOW_QUERY_OPTIONS } + ) + const queryOptions = persistInSession + ? sessionQueryOptions + : memoryQueryOptions + const setQueryOptions = useMemoizedFn( + (value: React.SetStateAction) => { + if (persistInSession) { + setSessionQueryOptions(value as any) + } else { + setMemoryQueryOptions(value) + } + } + ) + return { + queryOptions, + setQueryOptions, + } +} + +export interface ISlowQueryTableControllerOpts { + cacheMgr?: CacheMgr + showFullSQL?: boolean + initialQueryOptions?: ISlowQueryOptions + persistQueryInSession?: boolean +} + export interface ISlowQueryTableController { queryOptions: ISlowQueryOptions - setQueryOptions: (options: ISlowQueryOptions) => void + setQueryOptions: (value: React.SetStateAction) => void // Updating query options will result in a refresh + orderOptions: IOrderOptions changeOrder: (orderBy: string, desc: boolean) => void - refresh: () => void - allSchemas: string[] - loadingSlowQueries: boolean - slowQueries: SlowqueryModel[] - queryTimeRange: { beginTime: number; endTime: number } + isLoading: boolean + data?: SlowqueryModel[] + isDataLoadedSlowly: boolean | null // SLOW_DATA_LOAD_THRESHOLD. NULL = Unknown + allSchemas: string[] errors: Error[] - tableColumns: IColumn[] - visibleColumnKeys: IColumnKeys - - downloadCSV: () => Promise - downloading: boolean + availableColumnsInTable: IColumn[] // returned from backend saveClickedItemIndex: (idx: number) => void getClickedItemIndex: () => number } -export default function useSlowQueryTableController( - cacheMgr: CacheMgr | undefined, - visibleColumnKeys: IColumnKeys, - showFullSQL: boolean, - options?: ISlowQueryOptions, - needSave: boolean = true -): ISlowQueryTableController { +export default function useSlowQueryTableController({ + cacheMgr, + showFullSQL = false, + initialQueryOptions, + persistQueryInSession = true, +}: ISlowQueryTableControllerOpts): ISlowQueryTableController { const { orderOptions, changeOrder } = useOrderState( 'slow_query', - needSave, + persistQueryInSession, DEF_ORDER_OPTIONS ) - const [memoryQueryOptions, setMemoryQueryOptions] = useState( - options || DEF_SLOW_QUERY_OPTIONS - ) - const [sessionQueryOptions, setSessionQueryOptions] = useSessionStorageState( - QUERY_OPTIONS, - { defaultValue: options || DEF_SLOW_QUERY_OPTIONS } - ) - const queryOptions = useMemo( - () => (needSave ? sessionQueryOptions : memoryQueryOptions), - [needSave, memoryQueryOptions, sessionQueryOptions] + const { queryOptions, setQueryOptions } = useQueryOptions( + initialQueryOptions, + persistQueryInSession ) const [allSchemas, setAllSchemas] = useState([]) - const [loadingSlowQueries, setLoadingSlowQueries] = useState(false) - const [slowQueries, setSlowQueries] = useState([]) - const [refreshTimes, setRefreshTimes] = useState(0) - - const queryTimeRange = useMemo(() => { - const [beginTime, endTime] = calcTimeRange(queryOptions.timeRange) - return { beginTime, endTime } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryOptions, refreshTimes]) - - function setQueryOptions(newOptions: ISlowQueryOptions) { - if (needSave) { - setSessionQueryOptions(newOptions) - } else { - setMemoryQueryOptions(newOptions) - } - } - - const [errors, setErrors] = useState([]) - - const selectedFields = useMemo( - () => getSelectedFields(visibleColumnKeys, derivedFields).join(','), - [visibleColumnKeys] + const [isOptionsLoading, setOptionsLoading] = useState(true) + const [data, setData] = useState(undefined) + const [isDataLoading, setDataLoading] = useState(false) + const [isDataLoadedSlowly, setDataLoadedSlowly] = useState( + null ) + const [errors, setErrors] = useState([]) + const { schemaColumns, isLoading: isColumnsLoading } = useSchemaColumns() - const cacheKey = useMemo(() => { - const { schemas, digest, limit, plans, searchText, timeRange } = - queryOptions - const { desc, orderBy } = orderOptions - const cacheKey = `${schemas.join(',')}_${digest}_${limit}_${plans.join( - ',' - )}_${searchText}_${stringifyTimeRange( - timeRange - )}_${desc}_${orderBy}_${selectedFields}` - return cacheKey - }, [queryOptions, orderOptions, selectedFields]) - - function refresh() { - cacheMgr?.remove(cacheKey) - - setErrors([]) - setRefreshTimes((prev) => prev + 1) - } - - useEffect(() => { + // Reload these options when sending a new request. + useChange(() => { async function querySchemas() { try { const res = await client.getInstance().infoListDatabases({ @@ -158,41 +156,69 @@ export default function useSlowQueryTableController( } } - querySchemas() - }, []) - - const { schemaColumns, isLoading: isSchemaLoading } = useSchemaColumns() - - const tableColumns = useMemo( - () => slowQueryColumns(slowQueries, schemaColumns, showFullSQL), - [slowQueries, schemaColumns, showFullSQL] - ) - - useEffect(() => { - if (!selectedFields.length) { - setSlowQueries([]) - setLoadingSlowQueries(false) - return + async function doRequest() { + setOptionsLoading(true) + try { + await Promise.all([ + querySchemas(), + // Multiple query options can be added later + ]) + } finally { + setOptionsLoading(false) + } } + doRequest() + }, [queryOptions]) + + useChange(() => { async function getSlowQueryList() { - const cacheItem = cacheMgr?.get(cacheKey) - if (cacheItem) { - setSlowQueries(cacheItem) + // Try cache if options are unchanged. + // Note: When clicking "Query" manually, cache will be cleared before reach here. So that it + // will always send a request without looking up in the cache. + + // The cache key is built over queryOptions, instead of evaluated one. + // So that when passing in same relative times options (e.g. Recent 15min) + // the cache can be reused. + const cacheKey = JSON.stringify(queryOptions) + { + const cache = cacheMgr?.get(cacheKey) + if (cache) { + const cacheCloned = JSON.parse( + JSON.stringify(cache) + ) as RuntimeCacheEntity + setData(cacheCloned.data) + setDataLoadedSlowly(cacheCloned.isDataLoadedSlowly) + setDataLoading(false) + return + } + } + + // May be caused by visibleColumnKeys is empty (when available columns are not yet loaded) + // In this case, we don't send any requests. + const actualVisibleColumnKeys = getSelectedFields( + queryOptions.visibleColumnKeys, + derivedFields + ).join(',') + if (actualVisibleColumnKeys.length === 0) { return } - setLoadingSlowQueries(true) + const requestBeginAt = performance.now() + setDataLoading(true) + + const timeRange = toTimeRangeValue(queryOptions.timeRange) + try { const res = await client .getInstance() .slowQueryListGet( - queryTimeRange.beginTime, + timeRange[0], queryOptions.schemas, orderOptions.desc, queryOptions.digest, - queryTimeRange.endTime, - selectedFields, + timeRange[1], + actualVisibleColumnKeys, queryOptions.limit, orderOptions.orderBy, queryOptions.plans, @@ -201,54 +227,34 @@ export default function useSlowQueryTableController( errorStrategy: ErrorStrategy.Custom, } ) - setSlowQueries(res.data || []) - cacheMgr?.set(cacheKey, res.data || []) + const data = res?.data || [] + setData(data) setErrors([]) + + const elapsed = performance.now() - requestBeginAt + const isLoadSlow = elapsed >= SLOW_DATA_LOAD_THRESHOLD + setDataLoadedSlowly(isLoadSlow) + + const cacheEntity: RuntimeCacheEntity = { + data, + isDataLoadedSlowly: isLoadSlow, + } + cacheMgr?.set(cacheKey, cacheEntity) } catch (e) { - setErrors((prev) => prev.concat(e as Error)) + setData(undefined) + setErrors((prev) => prev.concat(e)) + } finally { + setDataLoading(false) } - setLoadingSlowQueries(false) } - if (isSchemaLoading) { - return - } getSlowQueryList() - }, [ - queryOptions, - orderOptions, - queryTimeRange, - selectedFields, - refreshTimes, - cacheKey, - cacheMgr, - isSchemaLoading, - ]) - - const [downloading, setDownloading] = useState(false) - - async function downloadCSV() { - try { - setDownloading(true) - const res = await client.getInstance().slowQueryDownloadTokenPost({ - fields: '*', - db: queryOptions.schemas, - digest: queryOptions.digest, - text: queryOptions.searchText, - plans: queryOptions.plans, - orderBy: orderOptions.orderBy, - desc: orderOptions.desc, - end_time: queryTimeRange.endTime, - begin_time: queryTimeRange.beginTime, - }) - const token = res.data - if (token) { - window.location.href = `${client.getBasePath()}/slow_query/download?token=${token}` - } - } finally { - setDownloading(false) - } - } + }, [queryOptions]) + + const availableColumnsInTable = useMemo( + () => slowQueryColumns(data ?? [], schemaColumns, showFullSQL), + [data, schemaColumns, showFullSQL] + ) const { saveClickedItemIndex, getClickedItemIndex } = useCacheItemIndex(cacheMgr) @@ -256,22 +262,18 @@ export default function useSlowQueryTableController( return { queryOptions, setQueryOptions, + orderOptions, changeOrder, - refresh, - allSchemas, - loadingSlowQueries, - slowQueries, - queryTimeRange, + isLoading: isColumnsLoading || isDataLoading || isOptionsLoading, + data, + isDataLoadedSlowly, + allSchemas, errors, - tableColumns, - visibleColumnKeys, - - downloading, - downloadCSV, + availableColumnsInTable, saveClickedItemIndex, getClickedItemIndex, diff --git a/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx b/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx index 5dbbf0e0ac..4019746101 100644 --- a/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx +++ b/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx @@ -3,31 +3,24 @@ import SlowQueriesTable from '@lib/apps/SlowQuery/components/SlowQueriesTable' import { IQuery } from './PlanDetail' import useSlowQueryTableController, { DEF_SLOW_QUERY_OPTIONS, - DEF_SLOW_QUERY_COLUMN_KEYS, } from '@lib/apps/SlowQuery/utils/useSlowQueryTableController' +import { fromTimeRangeValue } from '@lib/components' export interface ISlowQueryTabProps { query: IQuery } export default function SlowQueryTab({ query }: ISlowQueryTabProps) { - const controller = useSlowQueryTableController( - undefined, - DEF_SLOW_QUERY_COLUMN_KEYS, - false, - { + const controller = useSlowQueryTableController({ + initialQueryOptions: { ...DEF_SLOW_QUERY_OPTIONS, - timeRange: { - type: 'absolute', - value: [query.beginTime!, query.endTime!], - }, - schemas: [query.schema!], + timeRange: fromTimeRangeValue([query.beginTime!, query.endTime!]), limit: 100, digest: query.digest!, plans: query.plans, }, - false - ) + persistQueryInSession: false, + }) return } diff --git a/ui/lib/apps/Statement/pages/List/index.tsx b/ui/lib/apps/Statement/pages/List/index.tsx index 50b7f221ed..6f2a8f28fe 100644 --- a/ui/lib/apps/Statement/pages/List/index.tsx +++ b/ui/lib/apps/Statement/pages/List/index.tsx @@ -17,6 +17,7 @@ import { SettingOutlined, ExportOutlined, MenuOutlined, + QuestionCircleOutlined, } from '@ant-design/icons' import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' import { useTranslation } from 'react-i18next' @@ -28,8 +29,8 @@ import { MultiSelect, TimeRangeSelector, TimeRange, - calcTimeRange, DateTime, + toTimeRangeValue, } from '@lib/components' import { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState' import { StatementsTable } from '../../components' @@ -42,6 +43,7 @@ import styles from './List.module.less' import { useDebounceFn, useMemoizedFn } from 'ahooks' import { useDeepCompareChange } from '@lib/utils/useChange' import client, { StatementModel } from '@lib/client' +import { isDistro } from '@lib/utils/distroStringsRes' const STMT_VISIBLE_COLUMN_KEYS = 'statement.visible_column_keys' const STMT_SHOW_FULL_SQL = 'statement.show_full_sql' @@ -161,12 +163,12 @@ export default function StatementsOverview() { const downloadCSV = useMemoizedFn(async () => { // use last effective query options - const realTimeRange = calcTimeRange(controller.queryOptions.timeRange) + const timeRangeValue = toTimeRangeValue(controller.queryOptions.timeRange) try { setDownloading(true) const res = await client.getInstance().statementsDownloadTokenPost({ - begin_time: realTimeRange[0], - end_time: realTimeRange[1], + begin_time: timeRangeValue[0], + end_time: timeRangeValue[1], fields: '*', schemas: controller.queryOptions.schemas, stmt_types: controller.queryOptions.stmtTypes, @@ -257,7 +259,12 @@ export default function StatementsOverview() { } /> )} - + setShowSettings(true)} data-e2e="statement_setting" @@ -271,6 +278,20 @@ export default function StatementsOverview() {
+ {!isDistro && ( + + { + window.open(t('statement.settings.help_url'), '_blank') + }} + /> + + )}
@@ -312,9 +333,20 @@ export default function StatementsOverview() { title={t('statement.settings.disabled_result.title')} subTitle={t('statement.settings.disabled_result.sub_title')} extra={ - + + + {!isDistro && ( + + )} + } /> )} diff --git a/ui/lib/apps/Statement/translations/en.yaml b/ui/lib/apps/Statement/translations/en.yaml index 9fcd8e77ea..7783440aab 100755 --- a/ui/lib/apps/Statement/translations/en.yaml +++ b/ui/lib/apps/Statement/translations/en.yaml @@ -69,6 +69,8 @@ statement: save: Save close: Disable cancel: Cancel + help: Help + help_url: https://docs.pingcap.com/tidb/dev/dashboard-statement-list fields: table_names: Table Names related_schemas: Database diff --git a/ui/lib/apps/Statement/translations/zh.yaml b/ui/lib/apps/Statement/translations/zh.yaml index f2ea6f7d0f..a69f033546 100755 --- a/ui/lib/apps/Statement/translations/zh.yaml +++ b/ui/lib/apps/Statement/translations/zh.yaml @@ -69,6 +69,8 @@ statement: save: 保存 close: 确认 cancel: 取消 + help: 帮助 + help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-statement-list fields: related_schemas: 数据库 related_schemas_tooltip: SQL 语句涉及的数据库 diff --git a/ui/lib/apps/Statement/utils/useStatementTableController.ts b/ui/lib/apps/Statement/utils/useStatementTableController.ts index 05c201eaf1..a88ad2c395 100644 --- a/ui/lib/apps/Statement/utils/useStatementTableController.ts +++ b/ui/lib/apps/Statement/utils/useStatementTableController.ts @@ -255,6 +255,7 @@ export default function useStatementTableController({ timeRange, } setData(data) + setErrors([]) const elapsed = performance.now() - requestBeginAt const isLoadSlow = elapsed >= SLOW_DATA_LOAD_THRESHOLD @@ -266,6 +267,7 @@ export default function useStatementTableController({ } cacheMgr?.set(cacheKey, cacheEntity) } catch (e) { + setData(undefined) setErrors((prev) => prev.concat(e)) } finally { setDataLoading(false) diff --git a/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx b/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx index c54e30bbb0..de77bb4439 100644 --- a/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx +++ b/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx @@ -67,6 +67,7 @@ export function InstanceSelect({ onChange(instance) }} disabled={disabled} + data-e2e="instance-selector" {...otherProps} > {instanceGroups.map((instanceGroup) => ( diff --git a/ui/lib/apps/TopSQL/pages/List/List.tsx b/ui/lib/apps/TopSQL/pages/List/List.tsx index 991bfee087..91760bd55c 100644 --- a/ui/lib/apps/TopSQL/pages/List/List.tsx +++ b/ui/lib/apps/TopSQL/pages/List/List.tsx @@ -1,7 +1,11 @@ import { BrushEndListener, BrushEvent } from '@elastic/charts' import React, { useCallback, useEffect, useRef, useState } from 'react' import { Space, Button, Spin, Alert, Tooltip, Drawer, Result } from 'antd' -import { LoadingOutlined, SettingOutlined } from '@ant-design/icons' +import { + LoadingOutlined, + QuestionCircleOutlined, + SettingOutlined, +} from '@ant-design/icons' import { useTranslation } from 'react-i18next' import { useMount, useSessionStorage } from 'react-use' import { useMemoizedFn } from 'ahooks' @@ -29,6 +33,7 @@ import { ListChart } from './ListChart' import { SettingsForm } from './SettingsForm' import { onLegendItemOver, onLegendItemOut } from './legendAction' import { InstanceType } from './ListDetail/ListDetailTable' +import { isDistro } from '@lib/utils/distroStringsRes' const TOP_N = 5 const CHART_BAR_WIDTH = 8 @@ -118,17 +123,19 @@ export function TopSQLList() { {!isConfigLoading && !topSQLConfig?.enable && haveHistoryData && ( {t(`topsql.alert_header.body`)} + {` `} { setShowSettings(true) telemetry.clickSettings('bannerTips') }} > - {` ${t('topsql.alert_header.settings')}`} + {t('topsql.alert_header.settings')} } @@ -178,14 +185,34 @@ export function TopSQLList() { - + { setShowSettings(true) telemetry.clickSettings('settingIcon') }} /> + {!isDistro && ( + + { + window.open(t('topsql.settings.help_url'), '_blank') + }} + /> + + )} @@ -196,20 +223,34 @@ export function TopSQLList() { title={t('topsql.settings.disabled_result.title')} subTitle={t('topsql.settings.disabled_result.sub_title')} extra={ - + + + {!isDistro && ( + + )} + } /> ) : ( <> -
+
( ({ onBrushEnd, data, timeWindowSize, timeRangeTimestamp }, ref) => { const { t } = useTranslation() - // We need to update data and xDomain.minInterval at same time on the legacy @elastic/charts - // to avoid `Error: custom xDomain is invalid, custom minInterval is greater than computed minInterval` - // https://github.com/elastic/elastic-charts/pull/933 - // TODO: update @elastic/charts - // And we need update all the data at the same time and let the chart refresh only once for a better experience. const [bundle, setBundle] = useState({ data, diff --git a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx index f067b86516..271a7fdfb9 100644 --- a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx +++ b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx @@ -33,7 +33,7 @@ export function ListDetailContent({ const togglePlanExpanded = () => setPlanExpanded((prev) => !prev) return ( - + } @@ -66,7 +67,7 @@ export function ListDetailContent({ label={ - + } > @@ -79,7 +80,10 @@ export function ListDetailContent({ label={ - + } > @@ -97,7 +101,7 @@ export function ListDetailContent({ expanded={planExpanded} onClick={togglePlanExpanded} /> - + } > diff --git a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx index 7b5d5a9267..91845d97de 100644 --- a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx +++ b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx @@ -169,6 +169,11 @@ export function ListDetailTable({ return ( <> {t('topsql.table.others')} @@ -131,6 +132,11 @@ export function ListTable({

- + @@ -94,6 +97,7 @@ export function SettingsForm({ onClose, onConfigUpdated }: Props) { htmlType="submit" loading={submitting} disabled={!isWriteable} + data-e2e="topsql_settings_save" > {t('topsql.settings.actions.save')} diff --git a/ui/lib/apps/TopSQL/translations/en.yaml b/ui/lib/apps/TopSQL/translations/en.yaml index b23b70b10e..990e8e3132 100755 --- a/ui/lib/apps/TopSQL/translations/en.yaml +++ b/ui/lib/apps/TopSQL/translations/en.yaml @@ -6,7 +6,7 @@ topsql: settings: Settings settings: title: Settings - open_setting: Open Settings + open_settings: Open Settings disable_feature: Disable Top SQL Feature disable_warning: Are you sure want to disable this feature? enable: Enable Feature @@ -21,6 +21,8 @@ topsql: enable_info: title: Success content: Top SQL is enabled now and is collecting data. You need to wait for about 1 minute to view this data. + help: Help + help_url: https://docs.pingcap.com/tidb/dev/top-sql refresh: Refresh chart: cpu_time: CPU Time diff --git a/ui/lib/apps/TopSQL/translations/zh.yaml b/ui/lib/apps/TopSQL/translations/zh.yaml index 8244a826f8..58413ef87b 100755 --- a/ui/lib/apps/TopSQL/translations/zh.yaml +++ b/ui/lib/apps/TopSQL/translations/zh.yaml @@ -6,7 +6,7 @@ topsql: settings: 设置 settings: title: 设置 - open_setting: 打开设置 + open_settings: 打开设置 disable_feature: 关闭 Top SQL 功能 disable_warning: 确认要关闭该功能吗? enable: 启用功能 @@ -21,6 +21,8 @@ topsql: enable_info: title: 成功 content: Top SQL 功能现在已启用,正在收集数据。您需要等待大约 1 分钟时间以便看到该数据。 + help: 帮助 + help_url: https://docs.pingcap.com/zh/tidb/dev/top-sql refresh: 刷新 chart: cpu_time: CPU 耗时 diff --git a/ui/lib/components/ActionsButton/index.tsx b/ui/lib/components/ActionsButton/index.tsx deleted file mode 100644 index 1aa9d9de81..0000000000 --- a/ui/lib/components/ActionsButton/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import { Button, Dropdown, Menu } from 'antd' - -export type Action = { - key: string - text: string -} - -export type ActionsButtonProps = { - actions: Action[] - disabled: boolean - onClick: (action: string) => void -} - -export default function ActionsButton({ - actions, - disabled, - onClick, -}: ActionsButtonProps) { - if (actions.length === 0) { - throw new Error('actions should at least have one action') - } - - if (disabled) { - return null - } - - // actions.length > 0 - const mainAction = actions[0] - if (actions.length === 1) { - return ( - - ) - } - - // actions.length > 1 - const menu = ( - onClick(e.key as string)}> - {actions.map((act, idx) => - // skip the first option in menu since it has been show on the button. - idx === 0 ? null : {act.text} - )} - - ) - return ( - onClick(mainAction.key)}> - {mainAction.text} - - ) -} diff --git a/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx b/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx index d4caf9c262..41bc0fa347 100644 --- a/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx +++ b/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx @@ -81,7 +81,7 @@ export function AutoRefreshButton({ {options.map((sec) => { return ( - + {getValueFormat('s')(sec, 0)} ) @@ -142,6 +142,7 @@ export function AutoRefreshButton({ return ( , + HTMLSpanElement + > { data?: string displayVariant?: DisplayVariant } @@ -42,7 +46,11 @@ for (const key in translations) { }) } -function CopyLink({ data, displayVariant = 'default' }: ICopyLinkProps) { +function CopyLink({ + data, + displayVariant = 'default', + ...otherProps +}: ICopyLinkProps) { const { t } = useTranslation() const [showCopied, setShowCopied] = useState(false) @@ -56,7 +64,7 @@ function CopyLink({ data, displayVariant = 'default' }: ICopyLinkProps) { } return ( - + {!showCopied && ( diff --git a/ui/lib/components/TimeRangeSelector/index.tsx b/ui/lib/components/TimeRangeSelector/index.tsx index cd972f2a0c..96948f95b1 100644 --- a/ui/lib/components/TimeRangeSelector/index.tsx +++ b/ui/lib/components/TimeRangeSelector/index.tsx @@ -55,7 +55,7 @@ export function toTimeRangeValue(timeRange?: TimeRange): TimeRangeValue { return [...t2.value] } else { const now = dayjs().unix() - return [now - t2.value, now] + return [now - t2.value, now + 1] } } @@ -135,7 +135,10 @@ function TimeRangeSelector({ }) const dropdownContent = ( -
+
{t( diff --git a/ui/lib/components/index.ts b/ui/lib/components/index.ts index de37b22b04..3b373eeb64 100644 --- a/ui/lib/components/index.ts +++ b/ui/lib/components/index.ts @@ -53,8 +53,6 @@ export * from './AppearAnimate' export { default as AppearAnimate } from './AppearAnimate' export * from './Blink' export { default as Blink } from './Blink' -export * from './ActionsButton' -export { default as ActionsButton } from './ActionsButton' export * from './DrawerFooter' export { default as DrawerFooter } from './DrawerFooter' diff --git a/ui/lib/utils/distroAssets.ts b/ui/lib/utils/distroAssets.ts index b354004e21..d06e3d43c1 100644 --- a/ui/lib/utils/distroAssets.ts +++ b/ui/lib/utils/distroAssets.ts @@ -10,6 +10,6 @@ if (timestamp === '__DISTRO_ASSETS_RES_TIMESTAMP__') { const logoSvg = `${publicPathPrefix}/distro-res/logo.svg?t=${timestamp}` const lightLogoSvg = `${publicPathPrefix}/distro-res/logo-icon-light.svg?t=${timestamp}` -const landingSvg = `${publicPathPrefix}/distro-res/landing.svg?t=${timestamp}` +const landingSvg = `${publicPathPrefix}/distro-res/landing.png?t=${timestamp}` export { logoSvg, lightLogoSvg, landingSvg } diff --git a/ui/lib/utils/store.ts b/ui/lib/utils/store.ts index 3951d8a14f..0807e077b4 100644 --- a/ui/lib/utils/store.ts +++ b/ui/lib/utils/store.ts @@ -30,12 +30,12 @@ export const useIsFeatureSupport = (feature: string) => export const useNgmState = () => store.useState((s) => s.appInfo?.ngm_state) -export async function reloadWhoAmI() { +export async function reloadWhoAmI(): Promise { if (!getAuthToken()) { store.update((s) => { s.whoAmI = undefined }) - return + return false } try { @@ -45,10 +45,12 @@ export async function reloadWhoAmI() { store.update((s) => { s.whoAmI = resp.data }) + return true } catch (ex) { store.update((s) => { s.whoAmI = undefined }) + return false } } diff --git a/ui/package.json b/ui/package.json index b48d1432fe..aa192ac24f 100755 --- a/ui/package.json +++ b/ui/package.json @@ -68,9 +68,11 @@ "build": "gulp build", "fmt": "prettier --write .", "gen:browserlist": "gulp gen:browserlist", - "run:e2e-test:compat-features": "cypress run --spec cypress/integration/**/*.compat_spec.js", - "run:e2e-test:common-features": "cypress run --spec cypress/integration/**/*.spec.js", - "open:cypress": "cypress open" + "run:e2e-test:compat-features": "TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.compat_spec.[jt]s", + "run:e2e-test:common-features": "TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.spec.[jt]s", + "run:e2e-test:without-ngm": "TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.without_ngm_spec.[jt]s", + "run:e2e-test:specify": "TZ=Asia/Shanghai cypress run", + "open:cypress": "TZ=Asia/Shanghai cypress open" }, "husky": { "hooks": { @@ -91,8 +93,9 @@ }, "devDependencies": { "@baurine/esbuild-plugin-babel": "^0.3.0", - "@baurine/esbuild-plugin-postcss3": "^0.2.3", + "@baurine/esbuild-plugin-postcss3": "^0.3.3", "@cypress/code-coverage": "^3.9.12", + "@cypress/skip-test": "^2.6.1", "@openapitools/openapi-generator-cli": "^2.5.1", "@types/d3": "^5.7.2", "@types/d3-graphviz": "^2.6.7", @@ -108,6 +111,7 @@ "clipboardy": "2.3.0", "customize-cra": "^1.0.0", "cypress": "8.5.0", + "cypress-image-snapshot": "^4.0.1", "dotenv": "^16.0.0", "esbuild": "^0.14.38", "esbuild-plugin-yaml": "^0.0.1", diff --git a/ui/public/distro-res/landing.png b/ui/public/distro-res/landing.png new file mode 100644 index 0000000000..b8ed133f02 Binary files /dev/null and b/ui/public/distro-res/landing.png differ diff --git a/ui/public/distro-res/landing.svg b/ui/public/distro-res/landing.svg deleted file mode 100644 index 5d66f97609..0000000000 --- a/ui/public/distro-res/landing.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/src/style.less b/ui/src/style.less index e7fed5344d..7cc7bb3135 100644 --- a/ui/src/style.less +++ b/ui/src/style.less @@ -1,2 +1,2 @@ -@import 'antd/dist/antd.less'; -@import '../lib/antd.less'; +@import 'antd/lib/style/components.less'; +// it is expected to import 'antd/es/style/components.less' but it doesn't exist this file diff --git a/ui/yarn.lock b/ui/yarn.lock index 5066987ab3..64a8f72a75 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1788,10 +1788,10 @@ resolved "https://registry.yarnpkg.com/@baurine/esbuild-plugin-babel/-/esbuild-plugin-babel-0.3.0.tgz#b7409eb2a388fb03a5dacbf333154ae3604abcc7" integrity sha512-AKa/svOQUhHE7HNa8vU3qEXL+U1ln211Sl5mCBX+AQIldEEcvA80udzVENGh6VEztUtbpsXRsPdP9VI8tyX96w== -"@baurine/esbuild-plugin-postcss3@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@baurine/esbuild-plugin-postcss3/-/esbuild-plugin-postcss3-0.2.3.tgz#c5bf3bbec4c5696cb23b854b6c82cbd7e5837efb" - integrity sha512-L5D/QA/CMpo7FGq6g/6KKRDTuKGe/276091h6UrEX9HD9jBiQW/1NK1Xs8UB+Gx4r0eVeyK73forEc3rYtKOEA== +"@baurine/esbuild-plugin-postcss3@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@baurine/esbuild-plugin-postcss3/-/esbuild-plugin-postcss3-0.3.3.tgz#496e95566cb496f17d7f98a5f8431a1160f92fdf" + integrity sha512-0GiZRUW0LMtBaOaX+ICcpdhzkixPR6RzCnwOrL2GtWYQh1aPUegwEnHwAs+GHb4g1LezC0h2I3NCgs0TTjbQEw== dependencies: autoprefixer "^10.2.5" bulma "^0.9.3" @@ -2923,7 +2923,7 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: +ansi-escapes@^4.1.0, ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -2957,6 +2957,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -3052,6 +3057,13 @@ apache-md5@^1.0.6: resolved "https://registry.yarnpkg.com/apache-md5/-/apache-md5-1.1.7.tgz#dcef1802700cc231d60c5e08fd088f2f9b36375a" integrity sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw== +app-path@^3.2.0: + version "3.3.0" + resolved "https://registry.npmmirror.com/app-path/-/app-path-3.3.0.tgz#0342a909db37079c593979c720f99e872475eba3" + integrity sha512-EAgEXkdcxH1cgEePOSsmUtw9ItPl0KTxnh/pj9ZbhvbKbij9x0oX6PWpGnorDr0DS5AosLgoa5n3T/hZmKQpYA== + dependencies: + execa "^1.0.0" + append-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" @@ -3478,12 +3490,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3974,7 +3981,18 @@ chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^2.0.0, chalk@^2.4.2: +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4664,6 +4682,18 @@ customize-cra@^1.0.0: dependencies: lodash.flow "^3.5.0" +cypress-image-snapshot@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cypress-image-snapshot/-/cypress-image-snapshot-4.0.1.tgz#59084e713a8d03500c8e053ad7a76f3f18609648" + integrity sha512-PBpnhX/XItlx3/DAk5ozsXQHUi72exybBNH5Mpqj1DVmjq+S5Jd9WE5CRa4q5q0zuMZb2V2VpXHth6MjFpgj9Q== + dependencies: + chalk "^2.4.1" + fs-extra "^7.0.1" + glob "^7.1.3" + jest-image-snapshot "4.2.0" + pkg-dir "^3.0.0" + term-img "^4.0.0" + cypress-real-events@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.0.tgz#ad6a78de33af3af0e6437f5c713e30691c44472c" @@ -5716,7 +5746,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -6389,6 +6419,13 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -6560,6 +6597,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" @@ -6669,6 +6715,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g= + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -6866,6 +6917,11 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" +glur@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/glur/-/glur-1.1.2.tgz#f20ea36db103bfc292343921f1f91e83c3467689" + integrity sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA== + graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -6953,6 +7009,13 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== + dependencies: + ansi-regex "^2.0.0" + has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -8010,6 +8073,29 @@ iterare@1.2.1: resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== +iterm2-version@^4.1.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/iterm2-version/-/iterm2-version-4.2.0.tgz#b78069f747f34a772bc7dc17bda5bd9ed5e09633" + integrity sha512-IoiNVk4SMPu6uTcK+1nA5QaHNok2BMDLjSl5UomrOixe5g4GkylhPwuiGdw00ysSCrXAKNMfFTu+u/Lk5f6OLQ== + dependencies: + app-path "^3.2.0" + plist "^3.0.1" + +jest-image-snapshot@4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/jest-image-snapshot/-/jest-image-snapshot-4.2.0.tgz#559d7ade69e9918517269cef184261c80029a69e" + integrity sha512-6aAqv2wtfOgxiJeBayBCqHo1zX+A12SUNNzo7rIxiXh6W6xYVu8QyHWkada8HeRi+QUTHddp0O0Xa6kmQr+xbQ== + dependencies: + chalk "^1.1.3" + get-stdin "^5.0.1" + glur "^1.1.2" + lodash "^4.17.4" + mkdirp "^0.5.1" + pixelmatch "^5.1.0" + pngjs "^3.4.0" + rimraf "^2.6.2" + ssim.js "^3.1.1" + js-cookie@^2.2.1, js-cookie@^2.x.x: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -8111,6 +8197,13 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" @@ -8346,6 +8439,14 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -8438,7 +8539,7 @@ lodash.unionwith@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.unionwith/-/lodash.unionwith-4.6.0.tgz#74d140b5ca8146e6c643c3724f5152538d9ac1f0" integrity sha1-dNFAtcqBRubGQ8NyT1FSU42awfA= -lodash@4.17.21, lodash@^4, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: +lodash@4.17.21, lodash@^4, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8948,6 +9049,13 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -9552,7 +9660,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.2.0: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -9566,6 +9674,13 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -9849,6 +9964,20 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pixelmatch@^5.1.0: + version "5.2.1" + resolved "https://registry.npmmirror.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" + integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ== + dependencies: + pngjs "^4.0.1" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -9863,6 +9992,14 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" +plist@^3.0.1: + version "3.0.5" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987" + integrity sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA== + dependencies: + base64-js "^1.5.1" + xmlbuilder "^9.0.7" + plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" @@ -9873,6 +10010,16 @@ plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" +pngjs@^3.4.0: + version "3.4.0" + resolved "https://registry.npmmirror.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + +pngjs@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" + integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -11101,6 +11248,13 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== +rimraf@^2.6.2: + version "2.7.1" + resolved "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -11638,6 +11792,11 @@ sshpk@^1.14.1: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +ssim.js@^3.1.1: + version "3.5.0" + resolved "https://registry.npmmirror.com/ssim.js/-/ssim.js-3.5.0.tgz#d7276b9ee99b57a5ff0db34035f02f35197e62df" + integrity sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g== + stack-generator@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36" @@ -11940,6 +12099,11 @@ subarg@^1.0.0: dependencies: minimist "^1.1.0" +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== + supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" @@ -11988,6 +12152,14 @@ syntax-error@^1.1.1: dependencies: acorn-node "^1.2.0" +term-img@^4.0.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/term-img/-/term-img-4.1.0.tgz#5b170961f7aa20b2f3b22deb8ad504beb963a8a5" + integrity sha512-DFpBhaF5j+2f7kheKFc1ajsAUUDGOaNPpKPtiIMxlbfud6mvfFZuWGnTRpaujUa5J7yl6cIw/h6nyr4mSsENPg== + dependencies: + ansi-escapes "^4.1.0" + iterm2-version "^4.1.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -12492,6 +12664,11 @@ unist-util-visit@^4.0.0: unist-util-is "^5.0.0" unist-util-visit-parents "^5.0.0" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" @@ -12904,6 +13081,11 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +xmlbuilder@^9.0.7: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"