From 6577d79a71c852df955ff0aa4505da8694e66223 Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 12:31:59 +0100 Subject: [PATCH 01/19] #RI-31 - Logical databases --- .circleci/config.yml | 599 +---------------- .circleci/config.yml.backup | 610 ++++++++++++++++++ .env | 2 +- .eslintrc.js | 2 + .gitignore | 1 + .vscode/settings.json | 12 +- .vscodeignore | 1 + package.json | 4 +- src/extension.ts | 2 +- src/webviews/src/actions/processCliAction.ts | 4 +- .../src/components/database-form/DbIndex.tsx | 69 -- .../src/components/database-form/index.ts | 1 - .../no-keys-message/NoKeysMessage.tsx | 9 +- .../components/scan-more/styles.module.scss | 2 +- .../cli/cliOutputComponents.spec.tsx | 37 ++ src/webviews/src/constants/core/storage.ts | 1 + src/webviews/src/constants/vscode/vscode.ts | 1 + src/webviews/src/index.tsx | 4 + src/webviews/src/interfaces/vscode/api.ts | 1 + .../src/modules/keys-tree/KeysTree.tsx | 12 +- .../database-wrapper/DatabaseWrapper.spec.tsx | 44 +- .../database-wrapper/DatabaseWrapper.tsx | 156 +++-- .../keys-summary/KeysSummary.spec.tsx | 2 + .../components/keys-summary/KeysSummary.tsx | 115 +++- .../keys-summary/styles.module.scss | 9 +- .../keys-tree-header/KeysTreeHeader.tsx | 40 +- .../components/node/styles.module.scss | 2 +- .../src/modules/keys-tree/hooks/interface.ts | 3 +- .../keys-tree/hooks/tests/useKeys.spec.ts | 7 +- .../modules/keys-tree/hooks/useKeysActions.ts | 11 +- .../modules/keys-tree/hooks/useKeysThunks.ts | 41 +- .../ManualConnectionForm.tsx | 12 - .../ManualConnectionFrom.spec.tsx | 109 ---- .../src/pages/SidebarPage/SidebarPage.tsx | 16 +- src/webviews/src/services/apiService.ts | 14 +- .../src/services/tests/apiService.spec.ts | 8 +- .../hooks/use-app-info-store/interface.ts | 1 + .../hooks/use-databases-store/interface.ts | 1 + .../useDatabasesStore.spec.ts | 18 + .../use-databases-store/useDatabasesStore.ts | 62 +- .../useSelectedKeyStore.ts | 5 +- tests/e2e/.mocharc.js | 2 +- vite.config.mjs | 7 +- yarn.lock | 83 +-- 44 files changed, 1089 insertions(+), 1053 deletions(-) create mode 100644 .circleci/config.yml.backup delete mode 100644 src/webviews/src/components/database-form/DbIndex.tsx create mode 100644 src/webviews/src/constants/cli/cliOutputComponents.spec.tsx diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ea0e3c2..57090b51 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,610 +1,25 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference version: 2.1 -aliases: - ui-deps-cache-key: &uiDepsCacheKey - key: ui-deps-{{ checksum "yarn.lock" }} - dev-filter: &devFilter - filters: - branches: - only: - - main - - /^build\/.*/ - build-filter: &buildFilter - filters: - branches: - only: - - /^build.*/ - stage-filter: &stageFilter - filters: - branches: - only: - - /^release.*/ - prod-filter: &prodFilter - filters: - branches: - only: - - latest - manual-build-conditions: &manual-build-conditions - or: - - << pipeline.parameters.linux >> - - << pipeline.parameters.mac >> - - << pipeline.parameters.windows >> - ignore-for-manual-build: &ignore-for-manual-build - when: - not: *manual-build-conditions - -orbs: - node: circleci/node@5.3.0 - win: circleci/windows@5.0.0 - aws: circleci/aws-cli@4.1.3 - executors: - linux-executor: - machine: - image: ubuntu-2004:2023.04.2 - linux-executor-dlc: - machine: - image: ubuntu-2004:2023.04.2 - docker_layer_caching: true docker-node: docker: - image: cimg/node:20.15 - docker: - docker: - - image: cibuilds/docker:19.03.5 - macos: - macos: - xcode: 14.2.0 - -parameters: - linux: - type: string - default: &ignore "" - mac: - type: string - default: *ignore - windows: - type: string - default: *ignore jobs: - unit-tests-ui: - executor: docker-node - steps: - - checkout - - restore_cache: - <<: *uiDepsCacheKey - - run: - name: Install dependencies - command: | - yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - - run: - name: Run Unit tests UI - command: | - yarn test:cov - - save_cache: - <<: *uiDepsCacheKey - paths: - - ~/.cache/yarn - - e2e-linux: - executor: linux-executor-dlc - parameters: - report: - description: Send report for test run to slack - type: boolean - default: false - parallelism: - description: Number of threads to run tests - type: integer - default: 1 - parallelism: << parameters.parallelism >> - steps: - - checkout - - node/install: - install-yarn: true - node-version: '20.15' - - attach_workspace: - at: . - - run: sudo apt-get install net-tools - - run: - name: Start Xvfb - command: | - sudo apt-get install -y xvfb - Xvfb :99 -screen 0 1920x1080x24 & - - run: - name: Install dependencies - command: cd tests/e2e && yarn install - - run: - name: Compile TypeScript - command: cd tests/e2e && yarn compile - - run: - name: Verify Compiled Files - command: ls -R tests/e2e/dist/tests/ - - run: - name: Run e2e tests - command: | - cd tests/e2e/dist - export TEST_FILES=$(circleci tests glob "tests/**/*.e2e.js" | circleci tests split --split-by=timings) - echo "Running tests: $TEST_FILES" - cd ../../.. - .circleci/e2e/test.app.sh - - when: - condition: - equal: [ true, << parameters.report >> ] - steps: - - run: - name: Send report - when: always - command: | - APP_BUILD_TYPE="VSCode (Linux)" node ./.circleci/e2e-results.js - # curl -H "Content-type: application/json" --data @e2e.report.json -H "Authorization: Bearer $SLACK_TEST_REPORT_KEY" -X POST https://slack.com/api/chat.postMessage - - store_test_results: - path: tests/e2e/mochawesome-report - - store_artifacts: - path: tests/e2e/mochawesome-report - destination: tests/e2e/mochawesome-report - - store_artifacts: - path: /tmp/test-resources/screenshots - destination: test-resources/screenshots - - # Build jobs - manual-build-validate: + # Placeholder job + placeholder-job: executor: docker-node - parameters: - os: - type: string - default: "" - target: - type: string - default: "" - steps: - - checkout - - run: - command: | - node .circleci/build/manual-build-validate.js << parameters.os >> << parameters.target >> - - linux: - executor: docker-node - resource_class: large - parameters: - env: - description: Build environment (stage || prod) - type: enum - default: stage - enum: ['stage', 'prod', 'dev'] - target: - description: Build target - type: string - default: "" - steps: - - checkout - - restore_cache: - <<: *uiDepsCacheKey - - attach_workspace: - at: . - - run: - name: Install dependencies - command: | - yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - - run: - name: Build .vsix package - command: | - envFile=".env" - packagePath="./release/redis-for-vscode-extension-linux-x64.vsix" - yarn download:backend - - if [ << parameters.env >> == 'prod' ]; then - echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY'" >> $envFile - yarn package:prod --target linux-x64 --out ${packagePath} - exit 0; - fi - - echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY_STAGE'" >> $envFile - sed -i "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile - yarn package:stage --target linux-x64 --out ${packagePath} - - persist_to_workspace: - root: . - paths: - - release/redis-for-*.vsix - - save_cache: - <<: *uiDepsCacheKey - paths: - - ~/.cache/yarn - - macosx: - executor: macos - resource_class: macos.m1.medium.gen1 - parameters: - env: - description: Build environment (stage || prod) - type: enum - default: stage - enum: ['stage', 'prod', 'dev'] - target: - description: Build target - type: string - default: "" - steps: - - checkout - - node/install: - node-version: '20.15' - - attach_workspace: - at: . - - run: - name: Install dependencies - command: | - yarn install - no_output_timeout: 15m - - run: - name: Build .vsix package - command: | - envFile=".env" - packagePath=./release/redis-for-vscode-extension-mac - - if [ << parameters.target >> ]; then - yarn download:backend << parameters.target >> - - echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY_STAGE'" >> $envFile - sed -i '' "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile - yarn package:stage --target darwin-<< parameters.target >> --out ${packagePath}-<< parameters.target >>.vsix - exit 0; - fi - - if [ << parameters.env >> == 'prod' ]; then - echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY'" >> $envFile - yarn download:backend arm64 - yarn package:prod --target darwin-arm64 --out ${packagePath}-arm64.vsix - - yarn download:backend x64 - yarn package:prod --target darwin-x64 --out ${packagePath}-x64.vsix - exit 0; - fi - - echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY_STAGE'" >> $envFile - sed -i '' "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile - yarn download:backend arm64 - yarn package:stage --target darwin-arm64 --out ${packagePath}-arm64.vsix - - yarn download:backend x64 - yarn package:stage --target darwin-x64 --out ${packagePath}-x64.vsix - - persist_to_workspace: - root: . - paths: - - release/redis-for-*.vsix - - windows: - executor: - name: win/default - parameters: - env: - description: Build environment (stage || prod) - type: enum - default: stage - enum: ['stage', 'prod', 'dev'] - target: - description: Build target - type: string - default: "" - steps: - - checkout - - attach_workspace: - at: . - - run: - name: Install dependencies - command: | - nvm install 20.15 - nvm use 20.15 - npm install --global yarn - - yarn install - shell: bash.exe - no_output_timeout: 15m - - run: - name: Build .vsix package - command: | - envFile=".env" - packagePath=./release/redis-for-vscode-extension-win-x64.vsix - yarn download:backend - - if [ << parameters.env >> == 'prod' ]; then - echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY'" >> $envFile - yarn package:prod --target win32-x64 --out ${packagePath} - exit 0; - fi - - sed -i "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile - yarn package:stage --target win32-x64 --out ${packagePath} - shell: bash.exe - no_output_timeout: 20m - - persist_to_workspace: - root: . - paths: - - release/redis-for-*.vsix - - # Release jobs - store-build-artifacts: - executor: linux-executor - steps: - - attach_workspace: - at: . - - store_artifacts: - path: release - destination: release - - release-aws-private: - executor: linux-executor - steps: - - checkout - - attach_workspace: - at: . - - store_artifacts: - path: release - destination: release - - run: - name: publish - command: | - chmod +x .circleci/build/sum_sha256.sh - .circleci/build/sum_sha256.sh - applicationVersion=$(jq -r '.version' package.json) - - aws s3 cp release/ s3://${AWS_BUCKET_NAME}/private/vscode/${applicationVersion} --recursive - - licenses-check: - executor: linux-executor steps: - checkout - - node/install: - install-yarn: true - node-version: '20.15' - - restore_cache: - <<: *uiDepsCacheKey - - run: - name: Run install all dependencies - command: | - yarn install - yarn --cwd tests/e2e install - - run: - name: Generate licenses csv files and send csv data to google sheet - command: | - npm i -g license-checker - echo "$GOOGLE_ACCOUNT_SERVICE_KEY_BASE64" | base64 -id > gasKey.json - SPREADSHEET_ID=$GOOGLE_SPREADSHEET_DEPENDENCIES_ID node .circleci/deps-licenses-report.js - - store_artifacts: - path: licenses - destination: licenses -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/configuration-reference/#workflows workflows: - license-checker: + # Placeholder workflow + placeholder: jobs: - - licenses-check: - name: Run License checker + - placeholder-job: + name: Placeholder filters: branches: only: - - /^license.*/ - - frontend-tests: - <<: *ignore-for-manual-build - jobs: - - unit-tests-ui: - name: Run Unit Tests - filters: - branches: - only: - - /^feature.*/ - - e2e-tests: - jobs: - - approve: - name: Start E2E Tests - type: approval - filters: - branches: - only: - - /^e2e/feature.*/ - - /^e2e/bugfix.*/ - - - linux: - name: Build extension - Linux (stage) - env: stage - filters: - branches: - only: - - /^e2e/feature.*/ - - /^e2e/bugfix.*/ - requires: - - Start E2E Tests - - - e2e-linux: - name: E2ETest (linux) - parallelism: 2 - requires: - - Build extension - Linux (stage) - - # Manual builds using web UI - manual-build-linux: - when: << pipeline.parameters.linux >> - jobs: - - manual-build-validate: - name: Validating build parameters - os: linux - target: << pipeline.parameters.linux >> - - linux: - name: Build extension - Linux (stage) - env: stage - target: << pipeline.parameters.linux >> - requires: - - Validating build parameters - - store-build-artifacts: - name: Store build artifacts (stage) - requires: - - Build extension - Linux (stage) - - manual-build-mac: - when: << pipeline.parameters.mac >> - jobs: - - manual-build-validate: - name: Validating build parameters - os: mac - target: << pipeline.parameters.mac >> - - macosx: - name: Build extension - MacOS (stage) - env: stage - target: << pipeline.parameters.mac >> - requires: - - Validating build parameters - - store-build-artifacts: - name: Store build artifacts (stage) - requires: - - Build extension - MacOS (stage) - - manual-build-windows: - when: << pipeline.parameters.windows >> - jobs: - - manual-build-validate: - name: Validating build parameters - os: windows - target: << pipeline.parameters.windows >> - - windows: - name: Build extension - Windows (stage) - env: stage - target: << pipeline.parameters.windows >> - requires: - - Validating build parameters - - store-build-artifacts: - name: Store build artifacts (stage) - requires: - - Build extension - Windows (stage) - - # build vscode extension (dev) from "build" branches - build: - <<: *ignore-for-manual-build - jobs: - - linux: - name: Build extension - Linux (dev) - env: dev - <<: *buildFilter - - macosx: - name: Build extension - MacOS (dev) - env: dev - <<: *buildFilter - - windows: - name: Build extension - Windows (dev) - env: dev - <<: *buildFilter - - store-build-artifacts: - name: Store build artifacts (dev) - requires: - - Build extension - Linux (dev) - - Build extension - MacOS (dev) - - Build extension - Windows (dev) - - # Main workflow for release/* and latest branches only - release: - <<: *ignore-for-manual-build - jobs: - # unit tests (on any commit) - - unit-tests-ui: - name: Run Unit Tests - filters: &releaseAndLatestFilter - branches: - only: - - /^release.*/ - - latest - - # ================== STAGE ================== - # build extensions (stage) - - linux: - name: Build extension - Linux (stage) - <<: *stageFilter - - macosx: - name: Build extension - MacOS (stage) - <<: *stageFilter - - windows: - name: Build extension - Windows (stage) - <<: *stageFilter - # e2e tests on linux build - - e2e-linux: - name: E2ETest (linux) - parallelism: 2 - requires: - - Build extension - Linux (stage) - - - store-build-artifacts: - name: Store build artifacts (stage) - requires: - - Build extension - Linux (stage) - - Build extension - MacOS (stage) - - Build extension - Windows (stage) - - # Needs approval from QA team that build was tested before merging to latest - - qa-approve: - name: Approved by QA team - type: approval - requires: - - Build extension - Linux (stage) - - Build extension - MacOS (stage) - - Build extension - Windows (stage) - - # ================== PROD ================== - # build and release vscode extension (prod) - - linux: - name: Build extension - Linux (prod) - env: prod - <<: *prodFilter - - macosx: - name: Build extension - MacOS (prod) - env: prod - <<: *prodFilter - - windows: - name: Build extension - Windows (prod) - env: prod - <<: *prodFilter - - # e2e tests on linux build - - e2e-linux: - name: E2ETest (Linux) - parallelism: 2 - requires: - - Build extension - Linux (prod) - - # upload release to prerelease AWS folder - - release-aws-private: - name: Release AWS S3 Private (prod) - requires: - - Build extension - Linux (prod) - - Build extension - MacOS (prod) - - Build extension - Windows (prod) - - # Manual approve for publish release - - approve-publish: - name: Approve Publish Release (prod) - type: approval - requires: - - Release AWS S3 Private (prod) - # # Publish release - # - publish-prod-aws: - # name: Publish AWS S3 - # requires: - # - Approve Publish Release (prod) - # <<: *prodFilter # double check for "latest" - - weekly: - triggers: - - schedule: - cron: '0 0 * * 1' - filters: - branches: - only: - - main - jobs: - # Process all licenses - - licenses-check: - name: Process licenses of packages - + - placeholder diff --git a/.circleci/config.yml.backup b/.circleci/config.yml.backup new file mode 100644 index 00000000..4ea0e3c2 --- /dev/null +++ b/.circleci/config.yml.backup @@ -0,0 +1,610 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference +version: 2.1 + +aliases: + ui-deps-cache-key: &uiDepsCacheKey + key: ui-deps-{{ checksum "yarn.lock" }} + dev-filter: &devFilter + filters: + branches: + only: + - main + - /^build\/.*/ + build-filter: &buildFilter + filters: + branches: + only: + - /^build.*/ + stage-filter: &stageFilter + filters: + branches: + only: + - /^release.*/ + prod-filter: &prodFilter + filters: + branches: + only: + - latest + manual-build-conditions: &manual-build-conditions + or: + - << pipeline.parameters.linux >> + - << pipeline.parameters.mac >> + - << pipeline.parameters.windows >> + ignore-for-manual-build: &ignore-for-manual-build + when: + not: *manual-build-conditions + +orbs: + node: circleci/node@5.3.0 + win: circleci/windows@5.0.0 + aws: circleci/aws-cli@4.1.3 + +executors: + linux-executor: + machine: + image: ubuntu-2004:2023.04.2 + linux-executor-dlc: + machine: + image: ubuntu-2004:2023.04.2 + docker_layer_caching: true + docker-node: + docker: + - image: cimg/node:20.15 + docker: + docker: + - image: cibuilds/docker:19.03.5 + macos: + macos: + xcode: 14.2.0 + +parameters: + linux: + type: string + default: &ignore "" + mac: + type: string + default: *ignore + windows: + type: string + default: *ignore + +jobs: + unit-tests-ui: + executor: docker-node + steps: + - checkout + - restore_cache: + <<: *uiDepsCacheKey + - run: + name: Install dependencies + command: | + yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - run: + name: Run Unit tests UI + command: | + yarn test:cov + - save_cache: + <<: *uiDepsCacheKey + paths: + - ~/.cache/yarn + + e2e-linux: + executor: linux-executor-dlc + parameters: + report: + description: Send report for test run to slack + type: boolean + default: false + parallelism: + description: Number of threads to run tests + type: integer + default: 1 + parallelism: << parameters.parallelism >> + steps: + - checkout + - node/install: + install-yarn: true + node-version: '20.15' + - attach_workspace: + at: . + - run: sudo apt-get install net-tools + - run: + name: Start Xvfb + command: | + sudo apt-get install -y xvfb + Xvfb :99 -screen 0 1920x1080x24 & + - run: + name: Install dependencies + command: cd tests/e2e && yarn install + - run: + name: Compile TypeScript + command: cd tests/e2e && yarn compile + - run: + name: Verify Compiled Files + command: ls -R tests/e2e/dist/tests/ + - run: + name: Run e2e tests + command: | + cd tests/e2e/dist + export TEST_FILES=$(circleci tests glob "tests/**/*.e2e.js" | circleci tests split --split-by=timings) + echo "Running tests: $TEST_FILES" + cd ../../.. + .circleci/e2e/test.app.sh + - when: + condition: + equal: [ true, << parameters.report >> ] + steps: + - run: + name: Send report + when: always + command: | + APP_BUILD_TYPE="VSCode (Linux)" node ./.circleci/e2e-results.js + # curl -H "Content-type: application/json" --data @e2e.report.json -H "Authorization: Bearer $SLACK_TEST_REPORT_KEY" -X POST https://slack.com/api/chat.postMessage + - store_test_results: + path: tests/e2e/mochawesome-report + - store_artifacts: + path: tests/e2e/mochawesome-report + destination: tests/e2e/mochawesome-report + - store_artifacts: + path: /tmp/test-resources/screenshots + destination: test-resources/screenshots + + # Build jobs + manual-build-validate: + executor: docker-node + parameters: + os: + type: string + default: "" + target: + type: string + default: "" + steps: + - checkout + - run: + command: | + node .circleci/build/manual-build-validate.js << parameters.os >> << parameters.target >> + + linux: + executor: docker-node + resource_class: large + parameters: + env: + description: Build environment (stage || prod) + type: enum + default: stage + enum: ['stage', 'prod', 'dev'] + target: + description: Build target + type: string + default: "" + steps: + - checkout + - restore_cache: + <<: *uiDepsCacheKey + - attach_workspace: + at: . + - run: + name: Install dependencies + command: | + yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - run: + name: Build .vsix package + command: | + envFile=".env" + packagePath="./release/redis-for-vscode-extension-linux-x64.vsix" + yarn download:backend + + if [ << parameters.env >> == 'prod' ]; then + echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY'" >> $envFile + yarn package:prod --target linux-x64 --out ${packagePath} + exit 0; + fi + + echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY_STAGE'" >> $envFile + sed -i "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile + yarn package:stage --target linux-x64 --out ${packagePath} + - persist_to_workspace: + root: . + paths: + - release/redis-for-*.vsix + - save_cache: + <<: *uiDepsCacheKey + paths: + - ~/.cache/yarn + + macosx: + executor: macos + resource_class: macos.m1.medium.gen1 + parameters: + env: + description: Build environment (stage || prod) + type: enum + default: stage + enum: ['stage', 'prod', 'dev'] + target: + description: Build target + type: string + default: "" + steps: + - checkout + - node/install: + node-version: '20.15' + - attach_workspace: + at: . + - run: + name: Install dependencies + command: | + yarn install + no_output_timeout: 15m + - run: + name: Build .vsix package + command: | + envFile=".env" + packagePath=./release/redis-for-vscode-extension-mac + + if [ << parameters.target >> ]; then + yarn download:backend << parameters.target >> + + echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY_STAGE'" >> $envFile + sed -i '' "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile + yarn package:stage --target darwin-<< parameters.target >> --out ${packagePath}-<< parameters.target >>.vsix + exit 0; + fi + + if [ << parameters.env >> == 'prod' ]; then + echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY'" >> $envFile + yarn download:backend arm64 + yarn package:prod --target darwin-arm64 --out ${packagePath}-arm64.vsix + + yarn download:backend x64 + yarn package:prod --target darwin-x64 --out ${packagePath}-x64.vsix + exit 0; + fi + + echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY_STAGE'" >> $envFile + sed -i '' "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile + yarn download:backend arm64 + yarn package:stage --target darwin-arm64 --out ${packagePath}-arm64.vsix + + yarn download:backend x64 + yarn package:stage --target darwin-x64 --out ${packagePath}-x64.vsix + - persist_to_workspace: + root: . + paths: + - release/redis-for-*.vsix + + windows: + executor: + name: win/default + parameters: + env: + description: Build environment (stage || prod) + type: enum + default: stage + enum: ['stage', 'prod', 'dev'] + target: + description: Build target + type: string + default: "" + steps: + - checkout + - attach_workspace: + at: . + - run: + name: Install dependencies + command: | + nvm install 20.15 + nvm use 20.15 + npm install --global yarn + + yarn install + shell: bash.exe + no_output_timeout: 15m + - run: + name: Build .vsix package + command: | + envFile=".env" + packagePath=./release/redis-for-vscode-extension-win-x64.vsix + yarn download:backend + + if [ << parameters.env >> == 'prod' ]; then + echo "RI_SEGMENT_WRITE_KEY='$RI_SEGMENT_WRITE_KEY'" >> $envFile + yarn package:prod --target win32-x64 --out ${packagePath} + exit 0; + fi + + sed -i "s/^RI_APP_FOLDER_NAME=.*/RI_APP_FOLDER_NAME='.redis-for-vscode-stage'/" $envFile + yarn package:stage --target win32-x64 --out ${packagePath} + shell: bash.exe + no_output_timeout: 20m + - persist_to_workspace: + root: . + paths: + - release/redis-for-*.vsix + + # Release jobs + store-build-artifacts: + executor: linux-executor + steps: + - attach_workspace: + at: . + - store_artifacts: + path: release + destination: release + + release-aws-private: + executor: linux-executor + steps: + - checkout + - attach_workspace: + at: . + - store_artifacts: + path: release + destination: release + - run: + name: publish + command: | + chmod +x .circleci/build/sum_sha256.sh + .circleci/build/sum_sha256.sh + applicationVersion=$(jq -r '.version' package.json) + + aws s3 cp release/ s3://${AWS_BUCKET_NAME}/private/vscode/${applicationVersion} --recursive + + licenses-check: + executor: linux-executor + steps: + - checkout + - node/install: + install-yarn: true + node-version: '20.15' + - restore_cache: + <<: *uiDepsCacheKey + - run: + name: Run install all dependencies + command: | + yarn install + yarn --cwd tests/e2e install + - run: + name: Generate licenses csv files and send csv data to google sheet + command: | + npm i -g license-checker + + echo "$GOOGLE_ACCOUNT_SERVICE_KEY_BASE64" | base64 -id > gasKey.json + SPREADSHEET_ID=$GOOGLE_SPREADSHEET_DEPENDENCIES_ID node .circleci/deps-licenses-report.js + - store_artifacts: + path: licenses + destination: licenses + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/configuration-reference/#workflows +workflows: + license-checker: + jobs: + - licenses-check: + name: Run License checker + filters: + branches: + only: + - /^license.*/ + + frontend-tests: + <<: *ignore-for-manual-build + jobs: + - unit-tests-ui: + name: Run Unit Tests + filters: + branches: + only: + - /^feature.*/ + + e2e-tests: + jobs: + - approve: + name: Start E2E Tests + type: approval + filters: + branches: + only: + - /^e2e/feature.*/ + - /^e2e/bugfix.*/ + + - linux: + name: Build extension - Linux (stage) + env: stage + filters: + branches: + only: + - /^e2e/feature.*/ + - /^e2e/bugfix.*/ + requires: + - Start E2E Tests + + - e2e-linux: + name: E2ETest (linux) + parallelism: 2 + requires: + - Build extension - Linux (stage) + + # Manual builds using web UI + manual-build-linux: + when: << pipeline.parameters.linux >> + jobs: + - manual-build-validate: + name: Validating build parameters + os: linux + target: << pipeline.parameters.linux >> + - linux: + name: Build extension - Linux (stage) + env: stage + target: << pipeline.parameters.linux >> + requires: + - Validating build parameters + - store-build-artifacts: + name: Store build artifacts (stage) + requires: + - Build extension - Linux (stage) + + manual-build-mac: + when: << pipeline.parameters.mac >> + jobs: + - manual-build-validate: + name: Validating build parameters + os: mac + target: << pipeline.parameters.mac >> + - macosx: + name: Build extension - MacOS (stage) + env: stage + target: << pipeline.parameters.mac >> + requires: + - Validating build parameters + - store-build-artifacts: + name: Store build artifacts (stage) + requires: + - Build extension - MacOS (stage) + + manual-build-windows: + when: << pipeline.parameters.windows >> + jobs: + - manual-build-validate: + name: Validating build parameters + os: windows + target: << pipeline.parameters.windows >> + - windows: + name: Build extension - Windows (stage) + env: stage + target: << pipeline.parameters.windows >> + requires: + - Validating build parameters + - store-build-artifacts: + name: Store build artifacts (stage) + requires: + - Build extension - Windows (stage) + + # build vscode extension (dev) from "build" branches + build: + <<: *ignore-for-manual-build + jobs: + - linux: + name: Build extension - Linux (dev) + env: dev + <<: *buildFilter + - macosx: + name: Build extension - MacOS (dev) + env: dev + <<: *buildFilter + - windows: + name: Build extension - Windows (dev) + env: dev + <<: *buildFilter + - store-build-artifacts: + name: Store build artifacts (dev) + requires: + - Build extension - Linux (dev) + - Build extension - MacOS (dev) + - Build extension - Windows (dev) + + # Main workflow for release/* and latest branches only + release: + <<: *ignore-for-manual-build + jobs: + # unit tests (on any commit) + - unit-tests-ui: + name: Run Unit Tests + filters: &releaseAndLatestFilter + branches: + only: + - /^release.*/ + - latest + + # ================== STAGE ================== + # build extensions (stage) + - linux: + name: Build extension - Linux (stage) + <<: *stageFilter + - macosx: + name: Build extension - MacOS (stage) + <<: *stageFilter + - windows: + name: Build extension - Windows (stage) + <<: *stageFilter + # e2e tests on linux build + - e2e-linux: + name: E2ETest (linux) + parallelism: 2 + requires: + - Build extension - Linux (stage) + + - store-build-artifacts: + name: Store build artifacts (stage) + requires: + - Build extension - Linux (stage) + - Build extension - MacOS (stage) + - Build extension - Windows (stage) + + # Needs approval from QA team that build was tested before merging to latest + - qa-approve: + name: Approved by QA team + type: approval + requires: + - Build extension - Linux (stage) + - Build extension - MacOS (stage) + - Build extension - Windows (stage) + + # ================== PROD ================== + # build and release vscode extension (prod) + - linux: + name: Build extension - Linux (prod) + env: prod + <<: *prodFilter + - macosx: + name: Build extension - MacOS (prod) + env: prod + <<: *prodFilter + - windows: + name: Build extension - Windows (prod) + env: prod + <<: *prodFilter + + # e2e tests on linux build + - e2e-linux: + name: E2ETest (Linux) + parallelism: 2 + requires: + - Build extension - Linux (prod) + + # upload release to prerelease AWS folder + - release-aws-private: + name: Release AWS S3 Private (prod) + requires: + - Build extension - Linux (prod) + - Build extension - MacOS (prod) + - Build extension - Windows (prod) + + # Manual approve for publish release + - approve-publish: + name: Approve Publish Release (prod) + type: approval + requires: + - Release AWS S3 Private (prod) + # # Publish release + # - publish-prod-aws: + # name: Publish AWS S3 + # requires: + # - Approve Publish Release (prod) + # <<: *prodFilter # double check for "latest" + + weekly: + triggers: + - schedule: + cron: '0 0 * * 1' + filters: + branches: + only: + - main + jobs: + # Process all licenses + - licenses-check: + name: Process licenses of packages + diff --git a/.env b/.env index 25ddeaf5..a89c5dce 100644 --- a/.env +++ b/.env @@ -8,7 +8,7 @@ RI_APP_PORT=5541 RI_APP_VERSION='1.0.0' RI_APP_PREFIX='api' RI_APP_FOLDER_NAME='.redis-for-vscode' -RI_CDN_PATH='https://s3.amazonaws.com/redisinsight.download/public/releases/2.54.1/web-mini' +RI_CDN_PATH='https://s3.us-east-1.amazonaws.com/redisinsight.test/public/zalenski/vscode/web-mini' RI_WITHOUT_BACKEND=false # RI_WITHOUT_BACKEND=true RI_STDOUT_LOGGER=false diff --git a/.eslintrc.js b/.eslintrc.js index 849d7afa..0ba652ab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -86,6 +86,8 @@ module.exports = { 'function-paren-newline': 'off', 'prefer-regex-literals': 'off', 'react/display-name': 'off', + 'react/jsx-indent-props': [2, 2], + 'react/jsx-indent': [2, 2], 'no-promise-executor-return': 'off', 'import/order': [ 1, diff --git a/.gitignore b/.gitignore index f82dd99e..65db26ac 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ reports # App packaged release dist +report distWeb dll vendor diff --git a/.vscode/settings.json b/.vscode/settings.json index bb1001f5..9653f327 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,9 @@ "source.fixAll.eslint": "explicit" }, "eslint.validate": ["javascript", "typescript"], + "eslint.workingDirectories": [ + { "directory": "./", "changeProcessCWD": true } + ], "files.associations": { "*.css": "postcss", "*.scss": "postcss" @@ -35,6 +38,7 @@ ".eslintcache": true, "bower_components": true, "release": true, + "src/webviews/public": true, "npm-debug.log.*": true, "tests/**/__snapshots__": true, "yarn.lock": true, @@ -44,7 +48,13 @@ "**/pnpm-lock.yaml": true, "**/test-extensions": true }, - "cSpell.words": ["githubocto", "tailwindcss", "webviews", "zustand"], + "cSpell.words": [ + "githubocto", + "keyspace", + "tailwindcss", + "webviews", + "zustand" + ], "testing.automaticallyOpenPeekView": "never", "[typescriptreact]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" diff --git a/.vscodeignore b/.vscodeignore index 033ae5f3..51fba043 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -28,6 +28,7 @@ vite.config.mjs # test coverage test +report tests test-workspace .vscode-test diff --git a/package.json b/package.json index e921933c..d829405c 100644 --- a/package.json +++ b/package.json @@ -194,9 +194,9 @@ "@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react-swc": "^3.6.0", "@vitest/coverage-v8": "^1.3.1", - "@vitest/ui": "^1.3.1", + "@vitest/ui": "^1.6.0", "@vscode/l10n-dev": "^0.0.35", - "@vscode/vsce": "^3.0.0", + "@vscode/vsce": "^3.2.1", "cross-env": "^7.0.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.5.1", diff --git a/src/extension.ts b/src/extension.ts index 02f0d3de..6bd9af33 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -164,7 +164,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('RedisForVSCode.editDatabaseClose', (args) => { WebviewPanel.getInstance({ viewId: ViewId.EditDatabase }).dispose() - sidebarProvider.view?.webview.postMessage({ action: 'RefreshTree', data: args }) + sidebarProvider.view?.webview.postMessage({ action: 'UpdateDatabaseInList', data: args }) const keyDetailsWebview = WebviewPanel.instances[ViewId.Key] if (keyDetailsWebview) { diff --git a/src/webviews/src/actions/processCliAction.ts b/src/webviews/src/actions/processCliAction.ts index 305a3a69..4ebdf0ba 100644 --- a/src/webviews/src/actions/processCliAction.ts +++ b/src/webviews/src/actions/processCliAction.ts @@ -4,9 +4,11 @@ import { useDatabasesStore } from 'uiSrc/store' export const processCliAction = (message: CliAction) => { const prevDatabaseId = useDatabasesStore.getState().connectedDatabase?.id + const prevDatabaseIndex = useDatabasesStore.getState().connectedDatabase?.db const database = message?.data?.database + const dbIndex = database?.db ?? 0 - if (prevDatabaseId === database?.id) { + if (prevDatabaseId! + prevDatabaseIndex === database?.id + dbIndex) { return } window.ri.database = database diff --git a/src/webviews/src/components/database-form/DbIndex.tsx b/src/webviews/src/components/database-form/DbIndex.tsx deleted file mode 100644 index c9a87cbd..00000000 --- a/src/webviews/src/components/database-form/DbIndex.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { ChangeEvent, useId } from 'react' -import cx from 'classnames' -import { FormikProps } from 'formik' -import * as l10n from '@vscode/l10n' -import { VSCodeDivider } from '@vscode/webview-ui-toolkit/react' - -import { validateNumber } from 'uiSrc/utils' -import { DbConnectionInfo } from 'uiSrc/interfaces' -import { Checkbox, InputText, CheckboxChangeEvent } from 'uiSrc/ui' - -export interface Props { - - formik: FormikProps -} - -const DbIndex = (props: Props) => { - const { formik } = props - - const id = useId() - - const handleChangeDbIndexCheckbox = (e: CheckboxChangeEvent): void => { - const isChecked = e.target.checked - if (!isChecked) { - // Reset db field to initial value - formik.setFieldValue('db', null) - } - formik.setFieldValue('showDb', isChecked) - } - - return ( - <> -
- -
- - {formik.values.showDb && ( -
- ) => { - formik.setFieldValue( - e.target.name, - validateNumber(e.target.value.trim()), - ) - }} - type="text" - min={0} - /> -
- )} - - ) -} - -export { DbIndex } diff --git a/src/webviews/src/components/database-form/index.ts b/src/webviews/src/components/database-form/index.ts index c08784f4..0ee78fad 100644 --- a/src/webviews/src/components/database-form/index.ts +++ b/src/webviews/src/components/database-form/index.ts @@ -1,6 +1,5 @@ export { DbInfo } from './DbInfo' export { MessageStandalone } from './Messages' -export { DbIndex } from './DbIndex' export { DbCompressor } from './DbCompressor' export { TlsDetails } from './TlsDetails' export { DatabaseForm } from './DatabaseForm' diff --git a/src/webviews/src/components/no-keys-message/NoKeysMessage.tsx b/src/webviews/src/components/no-keys-message/NoKeysMessage.tsx index 837403a4..bfdf45b4 100644 --- a/src/webviews/src/components/no-keys-message/NoKeysMessage.tsx +++ b/src/webviews/src/components/no-keys-message/NoKeysMessage.tsx @@ -3,7 +3,7 @@ import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' import React, { FC } from 'react' import { VscodeMessageAction } from 'uiSrc/constants' -import { Nullable } from 'uiSrc/interfaces' +import { Maybe, Nullable } from 'uiSrc/interfaces' import { vscodeApi } from 'uiSrc/services' import { Database } from 'uiSrc/store' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/utils' @@ -13,12 +13,14 @@ import styles from './styles.module.scss' export interface Props { total: Nullable database: Database + dbIndex: Maybe } export const NoKeysMessage: FC = (props) => { const { total, database, + dbIndex, } = props const handleAddKey = () => { @@ -26,7 +28,10 @@ export const NoKeysMessage: FC = (props) => { event: TelemetryEvent.TREE_VIEW_KEY_ADD_BUTTON_CLICKED, eventData: { databaseId: database.id }, }) - vscodeApi.postMessage({ action: VscodeMessageAction.AddKey, data: { database } }) + vscodeApi.postMessage({ + action: VscodeMessageAction.AddKey, + data: { database: { ...database, db: dbIndex } }, + }) } // TODO: will be implemented in the future diff --git a/src/webviews/src/components/scan-more/styles.module.scss b/src/webviews/src/components/scan-more/styles.module.scss index c3b660ea..062163c7 100644 --- a/src/webviews/src/components/scan-more/styles.module.scss +++ b/src/webviews/src/components/scan-more/styles.module.scss @@ -1,5 +1,5 @@ .container { - @apply flex flex-row pl-[15px]; + @apply flex flex-row pl-[23px]; &:hover { background: var(--vscode-merge-commonHeaderBackground); diff --git a/src/webviews/src/constants/cli/cliOutputComponents.spec.tsx b/src/webviews/src/constants/cli/cliOutputComponents.spec.tsx new file mode 100644 index 00000000..b96c3488 --- /dev/null +++ b/src/webviews/src/constants/cli/cliOutputComponents.spec.tsx @@ -0,0 +1,37 @@ +import { describe, it, expect } from 'vitest' +import React from 'react' + +import { render } from 'testSrc/helpers' +import { InitOutputText, cliTexts } from './cliOutputComponents' + +describe('InitOutputText', () => { + it('should render correct output with valid inputs', () => { + const host = '127.0.0.1' + const port = 6379 + const dbIndex = 1 + const { container } = render(<>{InitOutputText(host, port, dbIndex, false)}) + + expect(container).toHaveTextContent('Connecting...') + expect(container).toHaveTextContent('Pinging Redis server on ') + expect(container).toHaveTextContent(`${host}:${port}`) + }) +}) + +describe('cliTexts', () => { + it('CLI_UNSUPPORTED_COMMANDS should format correctly', () => { + const commandLine = 'INFO' + const commands = 'AUTH, CONFIG' + const result = cliTexts.CLI_UNSUPPORTED_COMMANDS(commandLine, commands) + + expect(result).toBe( + `${`${commandLine} is not supported by the Redis CLI. The list of all unsupported commands: ${commands}`}`, + ) + }) + + it('CLI_ERROR_MESSAGE should render with the correct message', () => { + const message = 'An error occurred' + const result = cliTexts.CLI_ERROR_MESSAGE(message) + const { getByText } = render(<>{result}) + expect(getByText(message)).toHaveClass('text-vscode-errorForeground') + }) +}) diff --git a/src/webviews/src/constants/core/storage.ts b/src/webviews/src/constants/core/storage.ts index 11f24235..6a54081f 100644 --- a/src/webviews/src/constants/core/storage.ts +++ b/src/webviews/src/constants/core/storage.ts @@ -30,6 +30,7 @@ enum StorageItem { OAuthAgreement = 'OAuthAgreement', cliDatabase = 'cliDatabase', databaseId = 'databaseId', + openTreeNode = 'openTreeNode', } export { StorageItem } diff --git a/src/webviews/src/constants/vscode/vscode.ts b/src/webviews/src/constants/vscode/vscode.ts index 437d406c..aa679e85 100644 --- a/src/webviews/src/constants/vscode/vscode.ts +++ b/src/webviews/src/constants/vscode/vscode.ts @@ -27,6 +27,7 @@ export enum VscodeMessageAction { SaveAppInfo = 'SaveAppInfo', ShowEula = 'ShowEula', CloseEula = 'CloseEula', + UpdateDatabaseInList = 'UpdateDatabaseInList', } export enum VscodeStateItem { diff --git a/src/webviews/src/index.tsx b/src/webviews/src/index.tsx index 7e798f7a..5c15a07d 100644 --- a/src/webviews/src/index.tsx +++ b/src/webviews/src/index.tsx @@ -6,6 +6,7 @@ import { useSelectedKeyStore, fetchEditedDatabase, fetchCerts, + useDatabasesStore, } from 'uiSrc/store' import { Config } from 'uiSrc/modules' import { AppRoutes } from 'uiSrc/Routes' @@ -53,6 +54,9 @@ document.addEventListener('DOMContentLoaded', () => { case VscodeMessageAction.RefreshTree: refreshTreeAction(message) break + case VscodeMessageAction.UpdateDatabaseInList: + useDatabasesStore.getState().setDatabaseToList(message.data?.database) + break case VscodeMessageAction.AddDatabase: addDatabaseAction(message) break diff --git a/src/webviews/src/interfaces/vscode/api.ts b/src/webviews/src/interfaces/vscode/api.ts index 6108ce52..a3fc9a37 100644 --- a/src/webviews/src/interfaces/vscode/api.ts +++ b/src/webviews/src/interfaces/vscode/api.ts @@ -34,6 +34,7 @@ export interface SetDatabaseAction { | VscodeMessageAction.SetDatabase | VscodeMessageAction.CloseAddDatabase | VscodeMessageAction.AddDatabase + | VscodeMessageAction.UpdateDatabaseInList data: { database: Database } diff --git a/src/webviews/src/modules/keys-tree/KeysTree.tsx b/src/webviews/src/modules/keys-tree/KeysTree.tsx index 57b81f62..0a571824 100644 --- a/src/webviews/src/modules/keys-tree/KeysTree.tsx +++ b/src/webviews/src/modules/keys-tree/KeysTree.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react' -import * as l10n from '@vscode/l10n' import cx from 'classnames' -import { isUndefined } from 'lodash' +import { isString, isUndefined } from 'lodash' import { KeyInfo, Nullable, RedisString } from 'uiSrc/interfaces' import { AllKeyTypes, VscodeMessageAction } from 'uiSrc/constants' @@ -36,6 +35,7 @@ export const KeysTree = ({ database }: Props) => { const keysState = useKeysInContext((state) => state.data) const loading = useKeysInContext((state) => state.loading) + const dbIndex = useKeysInContext((state) => state.databaseIndex ?? 0) const keysApi = useKeysApi() const contextApi = useContextApi() @@ -58,8 +58,8 @@ export const KeysTree = ({ database }: Props) => { // open all parents for selected key const openSelectedKey = (selectedKeyName: Nullable = '') => { - if (selectedKeyName) { - const parts = selectedKeyName.split(delimiter) + if (selectedKeyName && isString(selectedKeyName)) { + const parts = selectedKeyName?.split(delimiter) const parents = parts.map((_, index) => parts.slice(0, index + 1).join(delimiter) + delimiter) // remove key name from parents @@ -111,11 +111,11 @@ export const KeysTree = ({ database }: Props) => { if (isUndefined(type)) { return } - fetchKeyInfo({ key: name, databaseId: database.id }, false, () => { + fetchKeyInfo({ key: name, databaseId: database.id, dbIndex: dbIndex! }, false, () => { vscodeApi.postMessage({ action: VscodeMessageAction.SelectKey, data: { - database, + database: { ...database, db: dbIndex! }, keyInfo: { key: name, keyString, keyType: type, displayedKeyType: getGroupTypeDisplay(type) }, }, }) diff --git a/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.spec.tsx b/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.spec.tsx index fb134dcc..ea593c2e 100644 --- a/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.spec.tsx +++ b/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.spec.tsx @@ -30,8 +30,7 @@ const deleteKeyFromTreeMock = vi.fn(); (vi.spyOn(useKeys, 'useKeysApi') as Mock).mockImplementation(() => ({ fetchPatternKeysAction: fnMock, setDatabaseId: fnMock, - addKeyIntoTree: addKeyIntoTreeMock, - deleteKeyFromTree: deleteKeyFromTreeMock, + setDatabaseIndex: fnMock, })) const setKeysTreeSortMock = vi.fn() const resetKeysTreeMock = vi.fn(); @@ -57,6 +56,15 @@ describe('DatabaseWrapper', () => { expect(useKeys.useKeysApi().fetchPatternKeysAction).toBeCalled() }) + it('should render logical databases', async () => { + const { queryByTestId } = render() + + fireEvent.click(queryByTestId(`database-${mockDatabase.id}`)!) + await waitForStack() + + expect(queryByTestId(`logical-database-${mockDatabase.id}-0`)!).toBeInTheDocument() + }) + describe('selectedKeyAction', () => { const setSelectedKeyActionMock = vi.fn() const setSelectedKeyMock = vi.fn() @@ -83,15 +91,6 @@ describe('DatabaseWrapper', () => { vi.restoreAllMocks() }) - it('should call deleteKeyFromTree and setSelectedKeyAction action after if selected key action is Removed', async () => { - render() - - await waitForStack() - - expect(setSelectedKeyActionMock).toBeCalledWith(null) - expect(deleteKeyFromTreeMock).toBeCalledWith(constants.KEY_NAME_1) - }) - it('should not call any mocks if database is not equal', async () => { render() @@ -111,28 +110,5 @@ describe('DatabaseWrapper', () => { expect(deleteKeyFromTreeMock).not.toBeCalled() expect(addKeyIntoTreeMock).not.toBeCalled() }) - - it('should call addKeyIntoTree action after if selected key action is Added', async () => { - const spySelectedKey = vi.spyOn(useSelectedKeyStore, 'useSelectedKeyStore') as Mock - - const setSelectedKeyActionMock = vi.fn() - const setSelectedKeyMock = vi.fn() - - spySelectedKey.mockImplementation(() => ({ - selectedKeyAction: { - ...selectedKeyAction, - type: SelectedKeyActionType.Added, - }, - setSelectedKeyAction: setSelectedKeyActionMock, - setSelectedKey: setSelectedKeyMock, - })) - - render() - - expect(setSelectedKeyMock).toBeCalledWith({ name: constants.KEY_NAME_1 }) - expect(setSelectedKeyActionMock).toBeCalledWith(null) - expect(deleteKeyFromTreeMock).not.toBeCalled() - expect(addKeyIntoTreeMock).not.toBeCalled() - }) }) }) diff --git a/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx b/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx index bb6b5c81..20d810f6 100644 --- a/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx +++ b/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx @@ -1,73 +1,40 @@ -import React, { useEffect, useState } from 'react' -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' +import React, { useState } from 'react' import cx from 'classnames' -import { VscChevronRight, VscChevronDown, VscTerminal, VscEdit } from 'react-icons/vsc' +import { VscChevronRight, VscChevronDown, VscEdit } from 'react-icons/vsc' +import { isUndefined, toNumber } from 'lodash' import * as l10n from '@vscode/l10n' -import { useShallow } from 'zustand/react/shallow' -import { set } from 'lodash' +import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' -import { vscodeApi } from 'uiSrc/services' -import { POPOVER_WINDOW_BORDER_WIDTH, SelectedKeyActionType, VscodeMessageAction } from 'uiSrc/constants' import { TelemetryEvent, formatLongName, - getDbIndex, getRedisModulesSummary, sendEventTelemetry, } from 'uiSrc/utils' -import { Database, checkConnectToDatabase, deleteDatabases, useSelectedKeyStore } from 'uiSrc/store' +import { ContextStoreProvider, Database, DatabaseOverview, checkConnectToDatabase, deleteDatabases } from 'uiSrc/store' import DatabaseOfflineIconSvg from 'uiSrc/assets/database/database_icon_offline.svg?react' import DatabaseActiveIconSvg from 'uiSrc/assets/database/database_icon_active.svg?react' +import { Tooltip } from 'uiSrc/ui' import { PopoverDelete } from 'uiSrc/components' -import { RefreshBtn, Tooltip } from 'uiSrc/ui' -import { useKeysApi, useKeysInContext } from '../../hooks/useKeys' +import { POPOVER_WINDOW_BORDER_WIDTH, VscodeMessageAction } from 'uiSrc/constants' +import { vscodeApi } from 'uiSrc/services' +import { Maybe } from 'uiSrc/interfaces' +import { LogicalDatabaseWrapper } from '../logical-database-wrapper' +import { KeysTreeHeader } from '../keys-tree-header' +import { KeysStoreProvider } from '../../hooks/useKeys' +import { KeysTree } from '../../KeysTree' import styles from './styles.module.scss' export interface Props { database: Database - children: React.ReactNode } -export const DatabaseWrapper = ({ children, database }: Props) => { +export const DatabaseWrapper = React.memo(({ database }: Props) => { const { id, name } = database - const lastRefreshTime = useKeysInContext((state) => state.data.lastRefreshTime) - const { selectedKeyAction, setSelectedKeyAction, setSelectedKey } = useSelectedKeyStore(useShallow((state) => ({ - selectedKeyAction: state.action, - setSelectedKeyAction: state.setSelectedKeyAction, - setSelectedKey: state.processSelectedKeySuccess, - }))) - const [showTree, setShowTree] = useState(false) - - const keysApi = useKeysApi() - - useEffect(() => { - const { type, keyInfo, database: databaseAction } = selectedKeyAction || {} - const { key, keyType, newKey } = keyInfo || {} - const { id: databaseId } = databaseAction || {} - - if (!type || databaseId !== database.id) { - return - } - - switch (type) { - case SelectedKeyActionType.Added: - keysApi.addKeyIntoTree(key!, keyType!) - setSelectedKey({ name: key! }) - break - case SelectedKeyActionType.Removed: - keysApi.deleteKeyFromTree(key!) - break - case SelectedKeyActionType.Renamed: - keysApi.editKeyName(key!, newKey!) - break - default: - break - } - setSelectedKeyAction(null) - }, [selectedKeyAction]) + const [totalKeysPerDb, setTotalKeysPerDb] = useState>>(undefined) const handleCheckConnectToDatabase = ({ id, provider, modules }: Database) => { if (showTree) { @@ -87,16 +54,12 @@ export const DatabaseWrapper = ({ children, database }: Props) => { checkConnectToDatabase(id, connectToInstance) } - const connectToInstance = (database: Database) => { - keysApi.setDatabaseId(database.id) - - // todo: fix for cli first open - set(window, 'ri.database', database) + const connectToInstance = (database: Database, overview: DatabaseOverview) => { + // TODO: fix for cli first open + // TODO: remove after tests + // set(window, 'ri.database', database) setShowTree(!showTree) - } - - const openCliClickHandle = () => { - vscodeApi.postMessage({ action: VscodeMessageAction.AddCli, data: { database } }) + setTotalKeysPerDb(overview?.totalKeysPerDb) } const editHandle = () => { @@ -123,9 +86,36 @@ export const DatabaseWrapper = ({ children, database }: Props) => { }) } - const refreshHandle = () => { - keysApi.fetchPatternKeysAction() - } + const Chevron = () => (showTree ? ( + + ) : ( + + )) + + const DatabaseIcon = () => (showTree ? ( + + ) : ( + + )) + + const LogicalDatabase = ( + { database, open, dbTotal }: + { database: Database, open?: boolean, dbTotal?: number }, + ) => ( + + + + + + + + + + ) return (
@@ -137,10 +127,8 @@ export const DatabaseWrapper = ({ children, database }: Props) => { className={styles.databaseNameWrapper} data-testid={`database-${id}`} > - {showTree && ()} - {showTree && ()} - {!showTree && ()} - {!showTree && ()} + {} + {} { >
{name}
-
{getDbIndex(database.db)}
- {showTree && ( - - )} { handleButtonClick={() => clickDeleteDatabaseHandle()} testid={`delete-database-${id}`} /> - {showTree && ( - - - - )}
- {showTree && children} + {showTree && (<> + {!isUndefined(totalKeysPerDb) && Object.keys(totalKeysPerDb!).map((databaseIndex) => ( + + ))} + {isUndefined(totalKeysPerDb) && ( + + )} + )} ) -} +}) diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx index 0a8c83aa..a3765599 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx @@ -14,8 +14,10 @@ const mockedProps: Props = { database: mockDatabase, total: 1, scanned: 1, + showTree: true, resultsLength: 1, loading: false, + toggleShowTree: () => {}, } vi.spyOn(utils, 'sendEventTelemetry') diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx index a6fe035e..196c61a2 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx @@ -3,16 +3,18 @@ import * as l10n from '@vscode/l10n' import cx from 'classnames' import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' import { BiSortDown, BiSortUp } from 'react-icons/bi' -import { VscAdd } from 'react-icons/vsc' +import { VscAdd, VscChevronDown, VscChevronRight, VscDatabase, VscTerminal } from 'react-icons/vsc' +import { isUndefined } from 'lodash' import { TelemetryEvent, nullableNumberWithSpaces, numberWithSpaces, sendEventTelemetry } from 'uiSrc/utils' import { vscodeApi } from 'uiSrc/services' import { SortOrder, VscodeMessageAction } from 'uiSrc/constants' -import { Database, useContextApi, useContextInContext } from 'uiSrc/store' +import { checkDatabaseIndexAction, Database, useContextApi, useContextInContext } from 'uiSrc/store' import { Nullable } from 'uiSrc/interfaces' -import { Tooltip } from 'uiSrc/ui' +import { RefreshBtn, Tooltip } from 'uiSrc/ui' import { KeyTreeFilter } from '../keys-tree-filter' +import { useKeysApi, useKeysInContext } from '../../hooks/useKeys' import styles from './styles.module.scss' export interface Props { @@ -21,13 +23,19 @@ export interface Props { resultsLength: number loading: boolean total: Nullable + showTree: boolean + dbIndex: number + toggleShowTree: (value?: boolean) => void } export const KeysSummary = (props: Props) => { - const { loading, total, scanned, resultsLength, database } = props + const { loading, total, scanned, resultsLength, database, showTree, dbIndex, toggleShowTree } = props const sorting = useContextInContext((state) => state.dbConfig.treeViewSort) + const lastRefreshTime = useKeysInContext((state) => state.data.lastRefreshTime) + const isMultiDbIndex = !isUndefined(dbIndex) const contextApi = useContextApi() + const keysApi = useKeysApi() const isSortingASC = sorting === SortOrder.ASC @@ -36,7 +44,10 @@ export const KeysSummary = (props: Props) => { event: TelemetryEvent.TREE_VIEW_KEY_ADD_BUTTON_CLICKED, eventData: { databaseId: database.id }, }) - vscodeApi.postMessage({ action: VscodeMessageAction.AddKey, data: { database } }) + vscodeApi.postMessage({ + action: VscodeMessageAction.AddKey, + data: { database: { ...database, db: dbIndex! } }, + }) } const changeSortHandle = () => { @@ -53,19 +64,82 @@ export const KeysSummary = (props: Props) => { }) } - return ( -
-
+ const handleToggleShowTree = () => { + if (!isMultiDbIndex) { + return + } + if (!showTree) { + checkDatabaseIndexAction(database.id, dbIndex!, () => toggleShowTree()) + return + } + toggleShowTree() + } + + const openCliClickHandle = () => { + vscodeApi.postMessage({ + action: VscodeMessageAction.AddCli, + data: { database: { ...database, db: dbIndex! } }, + }) + } + + const refreshHandle = () => { + keysApi.fetchPatternKeysAction() + } + + const Chevron = () => { + if (!isMultiDbIndex) return null + return showTree ? ( + + ) : ( + + ) + } + + const DbIndex = () => { + if (!isMultiDbIndex) return null + return ( + <> + + {dbIndex} + + ) + } + + const Summary = () => { + if (!showTree) { + return ( - ( - {numberWithSpaces(resultsLength)} - {' / '} + {'('} {nullableNumberWithSpaces(total)} - ) - + {')'} + ) + } + + return ( + + {'('} + {numberWithSpaces(resultsLength)} + {' / '} + {nullableNumberWithSpaces(total)} + {')'} + + + ) + } + + return ( +
+
+ {} + {} + {}
{/* {loading && !total && !isNull(total) && (
@@ -73,7 +147,7 @@ export const KeysSummary = (props: Props) => {
)} */} -
+
{ + + + +
) diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/styles.module.scss b/src/webviews/src/modules/keys-tree/components/keys-summary/styles.module.scss index c21fa0bb..2438c2d1 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/styles.module.scss +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/styles.module.scss @@ -1,3 +1,10 @@ +.container { + @apply h-[22px] flex flex-row justify-between pl-6; + &:hover { + background-color: var(--vscode-merge-commonHeaderBackground); + } +} + .content { - @apply flex-grow-0 text-vscode-foreground opacity-80 pl-3; + @apply flex flex-row items-center flex-grow text-vscode-foreground opacity-80 pl-1; } diff --git a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx index b001bb01..e32515e9 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx @@ -1,18 +1,23 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import * as l10n from '@vscode/l10n' import { ScanMore } from 'uiSrc/components' -import { SCAN_TREE_COUNT_DEFAULT } from 'uiSrc/constants' -import { isDisableScanMore, isShowScanMore, numberWithSpaces } from 'uiSrc/utils' +import { SCAN_TREE_COUNT_DEFAULT, StorageItem } from 'uiSrc/constants' +import { isDisableScanMore, isShowScanMore, numberWithSpaces, sendEventTelemetry, TelemetryEvent } from 'uiSrc/utils' import { Database } from 'uiSrc/store' +import { sessionStorageService } from 'uiSrc/services' import { KeysSummary } from '../keys-summary' import { useKeysApi, useKeysInContext } from '../../hooks/useKeys' export interface Props { database: Database + open?: boolean + dbTotal?: number + children: React.ReactNode } -export const KeysTreeHeader = ({ database }: Props) => { +export const KeysTreeHeader = ({ database, open, dbTotal, children }: Props) => { + const { id: dbId, db: dbIndex } = database const { loading, total, scanned, nextCursor, resultsLength } = useKeysInContext((state) => ({ loading: state.loading, scanned: state.data.scanned, @@ -21,12 +26,33 @@ export const KeysTreeHeader = ({ database }: Props) => { resultsLength: state.data.keys?.length, })) + const showTreeInit = open || sessionStorageService.get(`${StorageItem.openTreeNode + dbId + dbIndex}`) + const [showTree, setShowTree] = useState(showTreeInit) + const keysApi = useKeysApi() const loadMoreItems = () => { keysApi.fetchMorePatternKeysAction(nextCursor, SCAN_TREE_COUNT_DEFAULT) } + useEffect(() => { + if (showTree) { + sendEventTelemetry({ + event: TelemetryEvent.BROWSER_DATABASE_INDEX_CHANGED, + eventData: { + databaseId: dbIndex, + }, + }) + } + }, [showTree]) + + const handleToggleShowTree = (value?: boolean) => { + const newShowTree = value ?? !showTree + sessionStorageService.set(`${StorageItem.openTreeNode + dbId + dbIndex}`, newShowTree) + + setShowTree(newShowTree) + } + const scannedDisplay = resultsLength > scanned ? resultsLength : scanned const notAccurateScanned = total && scanned >= total @@ -40,8 +66,11 @@ export const KeysTreeHeader = ({ database }: Props) => { database={database} loading={loading} scanned={scanned} - total={total} + total={dbTotal ?? total} + dbIndex={dbIndex!} resultsLength={resultsLength} + showTree={showTree} + toggleShowTree={handleToggleShowTree} /> {isShowScanMore(scanned, total, nextCursor) && ( { text={l10n.t('({0}{1} Scanned)', notAccurateScanned, numberWithSpaces(scannedDisplay))} /> )} + {showTree && children} ) } diff --git a/src/webviews/src/modules/keys-tree/components/node/styles.module.scss b/src/webviews/src/modules/keys-tree/components/node/styles.module.scss index a6b52916..42d6d8d8 100644 --- a/src/webviews/src/modules/keys-tree/components/node/styles.module.scss +++ b/src/webviews/src/modules/keys-tree/components/node/styles.module.scss @@ -1,5 +1,5 @@ .nodeContainer { - @apply pl-3; + @apply pl-[20px]; border-left: 3px solid transparent; &:hover { diff --git a/src/webviews/src/modules/keys-tree/hooks/interface.ts b/src/webviews/src/modules/keys-tree/hooks/interface.ts index 0b1bb182..08f9cd50 100644 --- a/src/webviews/src/modules/keys-tree/hooks/interface.ts +++ b/src/webviews/src/modules/keys-tree/hooks/interface.ts @@ -4,6 +4,7 @@ import { ZSetMember } from 'uiSrc/modules/key-details/components/zset-details/ho export interface KeysStore { databaseId: Nullable + databaseIndex: Nullable loading: boolean deleting: boolean isFiltered: boolean @@ -28,10 +29,10 @@ export interface KeysActions { // Add key addKey: () => void addKeyFinal: () => void - addKeySuccess: (data: KeysStoreData) => void addKeyToTree: (key: RedisString, keyType: KeyTypes) => void resetAddKey: () => void setDatabaseId: (databaseId: string) => void + setDatabaseIndex: (databaseIndex: number) => void setFilterAndSearch: (filter: Nullable, search: string) => void } diff --git a/src/webviews/src/modules/keys-tree/hooks/tests/useKeys.spec.ts b/src/webviews/src/modules/keys-tree/hooks/tests/useKeys.spec.ts index e859b14e..4652845d 100644 --- a/src/webviews/src/modules/keys-tree/hooks/tests/useKeys.spec.ts +++ b/src/webviews/src/modules/keys-tree/hooks/tests/useKeys.spec.ts @@ -1,5 +1,3 @@ -import { cleanup } from '@testing-library/react' -import { cloneDeep } from 'lodash' import { Mock, SpyInstance } from 'vitest' import { createStore } from 'zustand' import * as utils from 'uiSrc/utils' @@ -362,6 +360,9 @@ describe('useKeys', () => { apiService.post = apiServiceMock const controller = new AbortController() + const dbIndexMock = 2 + useKeysStore.setState((state) => ({ ...state, databaseIndex: dbIndexMock })) + // Act useKeysStore.getState().fetchKeysMetadataTree( data.map(({ name }, i) => ([i, name])) as any, @@ -374,7 +375,7 @@ describe('useKeys', () => { expect(apiServiceMock).toBeCalledWith( `/databases/null/keys/get-metadata`, { keys: data.map(({ name }) => (name)), type: undefined }, - { params: { encoding: 'buffer' }, signal: controller.signal }, + { headers: { "ri-db-index": dbIndexMock }, signal: controller.signal }, ) expect(onSuccessMock).toBeCalledWith(data) diff --git a/src/webviews/src/modules/keys-tree/hooks/useKeysActions.ts b/src/webviews/src/modules/keys-tree/hooks/useKeysActions.ts index 1b941b16..0494e78a 100644 --- a/src/webviews/src/modules/keys-tree/hooks/useKeysActions.ts +++ b/src/webviews/src/modules/keys-tree/hooks/useKeysActions.ts @@ -7,6 +7,7 @@ import { KeysStore, KeysActions } from './interface' export const initialKeysState: KeysStore = { databaseId: null, + databaseIndex: null, deleting: false, loading: false, filter: null, @@ -93,14 +94,6 @@ KeysStore & KeysActions addKey: () => set({ loading: true }), addKeyFinal: () => set({ loading: false }), - addKeySuccess: (data) => set((state) => ({ - data: { - ...state.data, - previousResultCount: data.keys?.length, - lastRefreshTime: Date.now(), - }, - })), - addKeyToTree: (key, type) => set((state) => { state.data?.keys.unshift({ name: key, type }) @@ -116,6 +109,8 @@ KeysStore & KeysActions setDatabaseId: (databaseId) => set({ databaseId }), + setDatabaseIndex: (databaseIndex) => set({ databaseIndex }), + setFilterAndSearch: (filter, search = DEFAULT_SEARCH_MATCH) => set({ filter, search }), }) diff --git a/src/webviews/src/modules/keys-tree/hooks/useKeysThunks.ts b/src/webviews/src/modules/keys-tree/hooks/useKeysThunks.ts index ba80da4e..b63b777d 100644 --- a/src/webviews/src/modules/keys-tree/hooks/useKeysThunks.ts +++ b/src/webviews/src/modules/keys-tree/hooks/useKeysThunks.ts @@ -3,11 +3,10 @@ import { StateCreator } from 'zustand' import { KeyInfo, Nullable, RedisString } from 'uiSrc/interfaces' import { apiService, sessionStorageService } from 'uiSrc/services' -import { DEFAULT_SEARCH_MATCH, ApiEndpoints, KeyTypes, successMessages, SCAN_TREE_COUNT_DEFAULT, ENDPOINT_BASED_ON_KEY_TYPE, EndpointBasedOnKeyType, StorageItem } from 'uiSrc/constants' +import { DEFAULT_SEARCH_MATCH, ApiEndpoints, KeyTypes, successMessages, SCAN_TREE_COUNT_DEFAULT, ENDPOINT_BASED_ON_KEY_TYPE, EndpointBasedOnKeyType, StorageItem, CustomHeaders } from 'uiSrc/constants' import { TelemetryEvent, getApiErrorMessage, - getEncoding, getMatchType, isStatusSuccessful, sendEventTelemetry, @@ -22,7 +21,7 @@ import { GetKeysWithDetailsResponse, KeysStore, KeysActions, SetStringWithExpire import { parseKeysListResponse } from '../utils' // eslint-disable-next-line import/no-mutable-exports -export let sourceKeysFetch: Nullable = null +export const sourceKeysFetchStack: Record> = {} export const createKeysThunksSlice: StateCreator< KeysStore & KeysActions & KeysThunks, @@ -34,18 +33,19 @@ KeysThunks fetchPatternKeysAction: async ( cursor: string = '0', count: number = SCAN_TREE_COUNT_DEFAULT, - telemetryProperties: { [key: string]: any } = {}, + telemetryProperties: Record = {}, onSuccess?: (data: GetKeysWithDetailsResponse[]) => void, onFailed?: () => void, ) => { get().loadKeys() try { - sourceKeysFetch?.cancel?.() const { CancelToken } = axios - sourceKeysFetch = CancelToken.source() + const { search: match, filter: type, databaseId, databaseIndex } = get() + const sourceKeysFetchKey = databaseId! + databaseIndex! - const { search: match, filter: type, databaseId } = get() + sourceKeysFetchStack[sourceKeysFetchKey]?.cancel?.() + sourceKeysFetchStack[sourceKeysFetchKey] = CancelToken.source() const { data, status } = await apiService.post( getDatabaseUrl(databaseId, ApiEndpoints.KEYS), @@ -53,12 +53,12 @@ KeysThunks cursor, count, type, match: match || DEFAULT_SEARCH_MATCH, keysInfo: false, }, { - params: { encoding: getEncoding() }, - cancelToken: sourceKeysFetch.token, + headers: { [CustomHeaders.DbIndex]: databaseIndex }, + cancelToken: sourceKeysFetchStack[sourceKeysFetchKey]?.token, }, ) - sourceKeysFetch = null + sourceKeysFetchStack[sourceKeysFetchKey] = null if (isStatusSuccessful(status)) { get().loadKeysSuccess( parseKeysListResponse({}, data), @@ -105,24 +105,25 @@ KeysThunks get().loadKeys() try { - sourceKeysFetch?.cancel?.() - const { CancelToken } = axios - sourceKeysFetch = CancelToken.source() + const { search: match, filter: type, databaseId, databaseIndex } = get() + const sourceKeysFetchKey = databaseId! + databaseIndex! + sourceKeysFetchStack[sourceKeysFetchKey]?.cancel?.() + + sourceKeysFetchStack[sourceKeysFetchKey] = CancelToken.source() - const { search: match, filter: type, databaseId } = get() const { data, status } = await apiService.post( getDatabaseUrl(databaseId, ApiEndpoints.KEYS), { cursor, count, type, match: match || DEFAULT_SEARCH_MATCH, keysInfo: false, }, { - params: { encoding: getEncoding() }, - cancelToken: sourceKeysFetch.token, + headers: { [CustomHeaders.DbIndex]: databaseIndex }, + cancelToken: sourceKeysFetchStack[sourceKeysFetchKey]?.token, }, ) - sourceKeysFetch = null + sourceKeysFetchStack[sourceKeysFetchKey] = null if (isStatusSuccessful(status)) { const newKeysData = parseKeysListResponse( get().data.shardsMeta, @@ -159,7 +160,9 @@ KeysThunks const { data } = await apiService.post( getDatabaseUrl(get().databaseId, ApiEndpoints.KEYS_METADATA), { keys: keys.map(([,nameBuffer]) => nameBuffer), type: get().filter || undefined }, - { params: { encoding: getEncoding() }, signal }, + { + headers: { [CustomHeaders.DbIndex]: get().databaseIndex }, signal, + }, ) const newData = data.map((key, i) => ({ ...key, path: keys[i][0] || 0 })) as KeyInfo[] @@ -184,7 +187,7 @@ KeysThunks getDatabaseUrl(get().databaseId, ApiEndpoints.KEYS), { data: { keyNames: [key] }, - params: { encoding: getEncoding() }, + headers: { [CustomHeaders.DbIndex]: get().databaseIndex }, }, ) diff --git a/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionForm.tsx b/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionForm.tsx index 615d04df..528df562 100644 --- a/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionForm.tsx +++ b/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionForm.tsx @@ -7,17 +7,13 @@ import { VSCodeButton, VSCodeDivider } from '@vscode/webview-ui-toolkit/react' import { VscInfo } from 'react-icons/vsc' import { Keys, - validationErrors, fieldDisplayNames, - SubmitBtnText, ConnectionType, } from 'uiSrc/constants' import { DbConnectionInfo, ISubmitButton } from 'uiSrc/interfaces' import { getFormErrors, getRequiredFieldsText } from 'uiSrc/utils' import { - DbIndex, DbInfo, - MessageStandalone, TlsDetails, DatabaseForm, DbCompressor, @@ -247,9 +243,6 @@ const ManualConnectionForm = (props: Props) => { onHostNamePaste={onHostNamePaste} showFields={{ host: true, alias: true, port: true, timeout: true }} /> - @@ -293,11 +286,6 @@ const ManualConnectionForm = (props: Props) => { autoFocus={!isCloneMode && isEditMode} onHostNamePaste={onHostNamePaste} /> - {isCloneMode && ( - - )} diff --git a/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx b/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx index 4252a75c..c24ac5a6 100644 --- a/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx +++ b/src/webviews/src/modules/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx @@ -227,97 +227,6 @@ describe('DatabaseForm', () => { ) }) - it('should change Database Index checkbox', async () => { - const handleSubmit = vi.fn() - const handleTestConnection = vi.fn() - render( -
- -
, - ) - await waitFor(() => { - fireEvent.click(screen.getByTestId('showDb')) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await waitFor(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // showDb: true, - // }), - // ) - await waitFor(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - showDb: true, - }), - ) - }) - - it('should change db checkbox and value', async () => { - const handleSubmit = vi.fn() - const handleTestConnection = vi.fn() - render( -
- -
, - ) - await waitFor(() => { - fireEvent.click(screen.getByTestId('showDb')) - }) - - await waitFor(() => { - fireEvent.change(screen.getByTestId('db'), { - target: { value: '12' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - - // await waitFor(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // showDb: true, - // db: '12', - // }), - // ) - await waitFor(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - showDb: true, - db: '12', - }), - ) - }) - it('should change "Use SNI" with prepopulated with host', async () => { const handleSubmit = vi.fn() const handleTestConnection = vi.fn() @@ -751,24 +660,6 @@ describe('DatabaseForm', () => { }) }) - it('should render selected logical database with proper db index', () => { - render( - , - ) - expect(screen.getByTestId('showDb')).toBeChecked() - expect(screen.getByTestId('db')).toHaveValue('5') - }) - it('should render proper database alias', () => { render( = () => { @@ -20,14 +19,7 @@ export const SidebarPage: FC = () => { return (
{databases.map((database) => ( - - - - - - - - + ))} {!databases.length && }
diff --git a/src/webviews/src/services/apiService.ts b/src/webviews/src/services/apiService.ts index 1008d355..4fa292e0 100644 --- a/src/webviews/src/services/apiService.ts +++ b/src/webviews/src/services/apiService.ts @@ -1,7 +1,7 @@ import axios, { AxiosRequestConfig } from 'axios' import { isNumber } from 'lodash' -import { sessionStorageService } from 'uiSrc/services' -import { StorageItem, CustomHeaders, BASE_URL } from 'uiSrc/constants' +import { CustomHeaders, BASE_URL } from 'uiSrc/constants' +import { getEncoding } from 'uiSrc/utils' const axiosInstance = axios.create({ baseURL: BASE_URL, @@ -12,11 +12,19 @@ export const requestInterceptor = (config: AxiosRequestConfig): any => { const databaseId = /databases\/([\w-]+)\/?.*/.exec(config.url || '')?.[1] if (databaseId) { - const dbIndex = sessionStorageService.get(`${StorageItem.dbIndex}${databaseId}`) + const dbIndex = window.ri?.database?.db + const encoding = getEncoding() if (isNumber(dbIndex)) { config.headers[CustomHeaders.DbIndex] = dbIndex } + + if (encoding) { + config.params = { + encoding, + ...config.params, + } + } } // TODO: security windowId diff --git a/src/webviews/src/services/tests/apiService.spec.ts b/src/webviews/src/services/tests/apiService.spec.ts index bfe88285..283bc8bc 100644 --- a/src/webviews/src/services/tests/apiService.spec.ts +++ b/src/webviews/src/services/tests/apiService.spec.ts @@ -1,10 +1,11 @@ import { AxiosRequestConfig } from 'axios' -import { sessionStorageService } from 'uiSrc/services' import { requestInterceptor } from 'uiSrc/services/apiService' +const mockDbIndex = 5 +vi.stubGlobal('ri', { database: { db: mockDbIndex } }) + describe('requestInterceptor', () => { it('should properly set db-index to headers', () => { - sessionStorageService.get = vi.fn().mockReturnValue(5) const config: AxiosRequestConfig = { headers: {}, @@ -12,11 +13,10 @@ describe('requestInterceptor', () => { } requestInterceptor(config) - expect(config?.headers?.['ri-db-index']).toEqual(5) + expect(config?.headers?.['ri-db-index']).toEqual(mockDbIndex) }) it('should not set db-index to headers with url not related to database', () => { - sessionStorageService.get = vi.fn().mockReturnValue(5) const config: AxiosRequestConfig = { headers: {}, diff --git a/src/webviews/src/store/hooks/use-app-info-store/interface.ts b/src/webviews/src/store/hooks/use-app-info-store/interface.ts index 3c2472f8..e283a2fe 100644 --- a/src/webviews/src/store/hooks/use-app-info-store/interface.ts +++ b/src/webviews/src/store/hooks/use-app-info-store/interface.ts @@ -54,6 +54,7 @@ export interface GetServerInfoResponse { encryptionStrategies: string[] sessionId: number controlNumber: number + databaseCount: number controlGroup: string } diff --git a/src/webviews/src/store/hooks/use-databases-store/interface.ts b/src/webviews/src/store/hooks/use-databases-store/interface.ts index 00a3f748..35ee7396 100644 --- a/src/webviews/src/store/hooks/use-databases-store/interface.ts +++ b/src/webviews/src/store/hooks/use-databases-store/interface.ts @@ -60,6 +60,7 @@ export interface DatabaseOverview { networkInKbps?: Nullable networkOutKbps?: Nullable cpuUsagePercentage?: Nullable + totalKeysPerDb?: Record } export interface AdditionalRedisModule { diff --git a/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.spec.ts b/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.spec.ts index 6e0b4f2f..7363f9fb 100644 --- a/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.spec.ts +++ b/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.spec.ts @@ -15,6 +15,7 @@ import { deleteDatabases, fetchDatabaseOverview, fetchDatabaseById, + checkDatabaseIndexAction, } from './useDatabasesStore' let databases: any[] @@ -428,5 +429,22 @@ describe('useDatabasesStore', () => { expect(utils.showErrorMessage).toBeCalled() }) }) + + describe('checkDatabaseIndexAction', () => { + it('should set loading', async () => { + // Arrange + const responseGetPayload = { status: 200 } + + apiService.get = vi.fn().mockResolvedValue(responseGetPayload) + + // Act + checkDatabaseIndexAction('databaseId', 1) + expect(useDatabasesStore.getState().loading).toEqual(true) + + await waitForStack() + + expect(useDatabasesStore.getState().loading).toEqual(false) + }) + }) }) }) diff --git a/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.ts b/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.ts index 1d597b22..1aa37877 100644 --- a/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.ts +++ b/src/webviews/src/store/hooks/use-databases-store/useDatabasesStore.ts @@ -28,6 +28,7 @@ export const initialDatabasesState: DatabasesStore = { connectedDatabase: null, databaseOverview: { version: '', + totalKeysPerDb: {}, }, } @@ -48,15 +49,9 @@ export const useDatabasesStore = create()( }), setDatabaseToList: (databaseFull: Database) => set((state) => { - const databases = state.data.map((database) => { - if (database.id === databaseFull.id) { - return { ...database, ...databaseFull } - } + const dbIndex = state.data.findIndex(({ id }) => id === databaseFull.id) - return database - }) - - state.data = databases + state.data[dbIndex] = databaseFull }), addDatabaseToList: (database: Database) => set((state) => { @@ -181,6 +176,23 @@ export const fetchDatabaseById = async (databaseId: string, onSuccess?: (data: D return null } +export const fetchDatabaseOverviewById = async (databaseId: string): Promise> => { + try { + const { data, status } = await apiService.get( + `${ApiEndpoints.DATABASES}/${databaseId}/overview`, + { params: { keyspace: 'full' }, + }) + + if (isStatusSuccessful(status)) { + return data + } + } catch (error) { + showErrorMessage(getApiErrorMessage(error as AxiosError)) + } + + return null +} + export const updateDatabase = ({ id, ...payload }: Partial, onSuccess?: (database: Database) => void) => { useDatabasesStore.setState(async (state) => { state.processDatabase() @@ -201,7 +213,7 @@ export const updateDatabase = ({ id, ...payload }: Partial, onSuccess? export function checkConnectToDatabase( id: string = '', - onSuccessAction?: (database: Database) => void, + onSuccessAction?: (database: Database, overview: DatabaseOverview) => void, onFailAction?: () => void, resetInstance: boolean = true, ) { @@ -213,8 +225,10 @@ export function checkConnectToDatabase( if (isStatusSuccessful(status)) { const database = await fetchDatabaseById(id) + const overview = await fetchDatabaseOverviewById(id) state.setDatabaseToList(database!) - onSuccessAction?.(database!) + + onSuccessAction?.(database!, overview!) } } catch (error) { showErrorMessage(getApiErrorMessage(error as AxiosError)) @@ -245,16 +259,42 @@ export const deleteDatabases = (databases: Database[], onSuccess?: () => void) = }) } -export const fetchDatabaseOverview = () => { +export const fetchDatabaseOverview = (onSuccess?: (data: DatabaseOverview) => void) => { useDatabasesStore.setState(async (state) => { try { const { data, status } = await apiService.get(getUrl(ApiEndpoints.OVERVIEW)) if (isStatusSuccessful(status)) { state.getDatabaseOverviewSuccess(data) + onSuccess?.(data) } } catch (error) { showErrorMessage(getApiErrorMessage(error as AxiosError)) } }) } + +export function checkDatabaseIndexAction( + id: string, + index: number, + onSuccessAction?: () => void, + onFailAction?: () => void, +) { + useDatabasesStore.setState(async (state) => { + state.processDatabase() + try { + const { status } = await apiService.get( + `${ApiEndpoints.DATABASES}/${id}/db/${index}`, + ) + + if (isStatusSuccessful(status)) { + onSuccessAction?.() + } + } catch (error) { + showErrorMessage(getApiErrorMessage(error as AxiosError)) + onFailAction?.() + } finally { + state.processDatabaseFinal() + } + }) +} diff --git a/src/webviews/src/store/hooks/use-selected-key-store/useSelectedKeyStore.ts b/src/webviews/src/store/hooks/use-selected-key-store/useSelectedKeyStore.ts index 628d5409..40a36c36 100644 --- a/src/webviews/src/store/hooks/use-selected-key-store/useSelectedKeyStore.ts +++ b/src/webviews/src/store/hooks/use-selected-key-store/useSelectedKeyStore.ts @@ -7,6 +7,7 @@ import { KeyInfo, RedisString } from 'uiSrc/interfaces' import { apiService } from 'uiSrc/services' import { ApiEndpoints, + CustomHeaders, DEFAULT_SEARCH_MATCH, KeyTypes, SCAN_COUNT_DEFAULT, @@ -60,7 +61,7 @@ export const useSelectedKeyStore = create // Asynchronous thunk action export const fetchKeyInfo = ( - { key, databaseId }: { key: RedisString, databaseId?: string }, + { key, databaseId, dbIndex }: { key: RedisString, databaseId?: string, dbIndex?: number }, fetchKeyValue = true, onSuccess?: (data: KeyInfo) => void, ) => { @@ -70,7 +71,7 @@ export const fetchKeyInfo = ( const { data, status } = await apiService.post( databaseId ? getDatabaseUrl(databaseId, ApiEndpoints.KEY_INFO) : getUrl(ApiEndpoints.KEY_INFO), { keyName: key }, - { params: { encoding: getEncoding() } }, + { headers: { [CustomHeaders.DbIndex]: dbIndex } }, ) if (isStatusSuccessful(status)) { diff --git a/tests/e2e/.mocharc.js b/tests/e2e/.mocharc.js index 474de534..423e8015 100644 --- a/tests/e2e/.mocharc.js +++ b/tests/e2e/.mocharc.js @@ -11,7 +11,7 @@ module.exports = { reporterOptions: { reporterEnabled: 'mochawesome, mocha-junit-reporter', mochawesomeReporterOptions: { - reportFilename: '[status]_[datetime]-[name]-report', + reportFilename: 'index', quiet: true, }, mochaJunitReporterReporterOptions: { diff --git a/vite.config.mjs b/vite.config.mjs index 546f7015..901cfd35 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -82,6 +82,9 @@ export default defineConfig({ testTimeout: 20000, setupFiles: ['./src/webviews/test/setup.ts'], coverage: { + enabled: true, + reporter: 'html', + reportsDirectory: './report/coverage', include: ['src/webviews/src/**'], exclude: [ 'src/webviews/src/**/index.ts', @@ -103,9 +106,9 @@ export default defineConfig({ reporters: [ 'default', [ - 'junit', + 'html', { - outputFile: './reports/junit.xml', + outputFile: './report/index.html', }, ], ], diff --git a/yarn.lock b/yarn.lock index 8f6182a6..db4c9b65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2024,12 +2024,12 @@ dependencies: tinyspy "^2.2.0" -"@vitest/ui@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.3.1.tgz#89e6a049f3fa68f6dad804318f0b8477cd5392da" - integrity sha512-2UrFLJ62c/eJGPHcclstMKlAR7E1WB1ITe1isuowEPJJHi3HfqofvsUqQ1cGrEF7kitG1DJuwURUA3HLDtQkXA== +"@vitest/ui@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.6.0.tgz#ffcc97ebcceca7fec840c29ab68632d0cd01db93" + integrity sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA== dependencies: - "@vitest/utils" "1.3.1" + "@vitest/utils" "1.6.0" fast-glob "^3.3.2" fflate "^0.8.1" flatted "^3.2.9" @@ -2037,20 +2037,20 @@ picocolors "^1.0.0" sirv "^2.0.4" -"@vitest/utils@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.3.1.tgz#7b05838654557544f694a372de767fcc9594d61a" - integrity sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ== +"@vitest/utils@1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.5.1.tgz#b373a1f5274b2b8b52ce8d6023db5e006f92d770" + integrity sha512-92pE17bBXUxA0Y7goPcvnATMCuq4NQLOmqsG0e2BtzRi7KLwZB5jpiELi/8ybY8IQNWemKjSD5rMoO7xTdv8ug== dependencies: diff-sequences "^29.6.3" estree-walker "^3.0.3" loupe "^2.3.7" pretty-format "^29.7.0" -"@vitest/utils@1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.5.1.tgz#b373a1f5274b2b8b52ce8d6023db5e006f92d770" - integrity sha512-92pE17bBXUxA0Y7goPcvnATMCuq4NQLOmqsG0e2BtzRi7KLwZB5jpiELi/8ybY8IQNWemKjSD5rMoO7xTdv8ug== +"@vitest/utils@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1" + integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw== dependencies: diff-sequences "^29.6.3" estree-walker "^3.0.3" @@ -2138,10 +2138,10 @@ "@vscode/vsce-sign-win32-arm64" "2.0.2" "@vscode/vsce-sign-win32-x64" "2.0.2" -"@vscode/vsce@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@vscode/vsce/-/vsce-3.0.0.tgz#79fa4a263eb9477d49efb2931d45204e3efbcb78" - integrity sha512-UKYcC7fcSw6AUK6nlfm8A8WSOA41H3DRAwafCMp9CQpg3K9COAQKkrgQ+dKPhwkFTP7SPZNEiulDpPLwvr5QQQ== +"@vscode/vsce@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@vscode/vsce/-/vsce-3.2.1.tgz#7bfa869ea43fe7d787f09e164f0f0e239df8fb1d" + integrity sha512-AY9vBjwExakK1c0cI/3NN2Ey0EgiKLBye/fxl/ue+o4q6RZ7N+xzd1jAD6eI6eBeMVANi617+V2rxIAkDPco2Q== dependencies: "@azure/identity" "^4.1.0" "@vscode/vsce-sign" "^2.0.0" @@ -2155,7 +2155,7 @@ hosted-git-info "^4.0.2" jsonc-parser "^3.2.0" leven "^3.1.0" - markdown-it "^12.3.2" + markdown-it "^14.1.0" mime "^1.3.4" minimatch "^3.0.3" parse-semver "^1.1.1" @@ -3639,11 +3639,6 @@ entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -5679,13 +5674,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkify-it@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" - integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== - dependencies: - uc.micro "^1.0.1" - linkify-it@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" @@ -5895,17 +5883,6 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - markdown-it@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.0.0.tgz#b4b2ddeb0f925e88d981f84c183b59bac9e3741b" @@ -5918,6 +5895,18 @@ markdown-it@^14.0.0: punycode.js "^2.3.1" uc.micro "^2.0.0" +markdown-it@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -5927,11 +5916,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - mdurl@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" @@ -8473,12 +8457,7 @@ typescript@^5.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -uc.micro@^2.0.0: +uc.micro@^2.0.0, uc.micro@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== From ca02933e9d7f6eb1f596c5d2114f55d6e35849d5 Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 12:34:24 +0100 Subject: [PATCH 02/19] #RI-31 - Logical databases --- .../LogicalDatabaseWrapper.spec.tsx | 127 ++++++++++++++++++ .../LogicalDatabaseWrapper.tsx | 68 ++++++++++ .../logical-database-wrapper/index.ts | 1 + .../styles.module.scss | 15 +++ 4 files changed, 211 insertions(+) create mode 100644 src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.spec.tsx create mode 100644 src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.tsx create mode 100644 src/webviews/src/modules/keys-tree/components/logical-database-wrapper/index.ts create mode 100644 src/webviews/src/modules/keys-tree/components/logical-database-wrapper/styles.module.scss diff --git a/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.spec.tsx b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.spec.tsx new file mode 100644 index 00000000..03bbc8f3 --- /dev/null +++ b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.spec.tsx @@ -0,0 +1,127 @@ +import React from 'react' +import { mock } from 'ts-mockito' +import { Mock } from 'vitest' + +import { KeyTypes, SelectedKeyActionType } from 'uiSrc/constants' +import * as utils from 'uiSrc/utils' +import { apiService, vscodeApi } from 'uiSrc/services' +import * as useContext from 'uiSrc/store/hooks/use-context/useContext' +import * as useSelectedKeyStore from 'uiSrc/store/hooks/use-selected-key-store/useSelectedKeyStore' +import { Database } from 'uiSrc/store' +import { constants, fireEvent, render, waitForStack } from 'testSrc/helpers' +import { LogicalDatabaseWrapper, Props } from './LogicalDatabaseWrapper' +import * as useKeys from '../../hooks/useKeys' + +const mockDatabase = constants.DATABASE +const mockedProps = { + ...mock(
), + database: mockDatabase, +} +beforeEach(() => { + apiService.get = vi.fn().mockResolvedValue({ status: 200, data: {} }) +}) + +vi.spyOn(utils, 'sendEventTelemetry') +vi.spyOn(vscodeApi, 'postMessage') + +const fnMock = vi.fn() +const addKeyIntoTreeMock = vi.fn() +const deleteKeyFromTreeMock = vi.fn(); +(vi.spyOn(useKeys, 'useKeysApi') as Mock).mockImplementation(() => ({ + fetchPatternKeysAction: fnMock, + setDatabaseId: fnMock, + setDatabaseIndex: fnMock, + addKeyIntoTree: addKeyIntoTreeMock, + deleteKeyFromTree: deleteKeyFromTreeMock, +})) +const setKeysTreeSortMock = vi.fn() +const resetKeysTreeMock = vi.fn(); +(vi.spyOn(useContext, 'useContextApi') as Mock).mockImplementation(() => ({ + setKeysTreeSort: setKeysTreeSortMock, + resetKeysTree: resetKeysTreeMock, +})) + +describe('LogicalDatabaseWrapper', () => { + it('should render', () => { + expect(render()).toBeTruthy() + }) + + describe('selectedKeyAction', () => { + const setSelectedKeyActionMock = vi.fn() + const setSelectedKeyMock = vi.fn() + const spySelectedKey = vi.spyOn(useSelectedKeyStore, 'useSelectedKeyStore') as Mock + + const selectedKeyAction = { + type: SelectedKeyActionType.Removed, + database: { + id: constants.DATABASE_ID, + }, + keyInfo: { + key: constants.KEY_NAME_1, + keyType: KeyTypes.Hash, + }, + } + + spySelectedKey.mockImplementation(() => ({ + setSelectedKeyAction: setSelectedKeyActionMock, + setSelectedKey: setSelectedKeyMock, + selectedKeyAction, + })) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should call deleteKeyFromTree and setSelectedKeyAction action after if selected key action is Removed', async () => { + render() + + await waitForStack() + + expect(setSelectedKeyActionMock).toBeCalledWith(null) + expect(deleteKeyFromTreeMock).toBeCalledWith(constants.KEY_NAME_1) + }) + + it('should not call any mocks if database is not equal', async () => { + render() + + await waitForStack() + + expect(setSelectedKeyActionMock).not.toBeCalled() + expect(deleteKeyFromTreeMock).not.toBeCalled() + expect(addKeyIntoTreeMock).not.toBeCalled() + }) + + it('should not call any mocks if type is not defined', async () => { + render() + + await waitForStack() + + expect(setSelectedKeyActionMock).not.toBeCalled() + expect(deleteKeyFromTreeMock).not.toBeCalled() + expect(addKeyIntoTreeMock).not.toBeCalled() + }) + + it('should call addKeyIntoTree action after if selected key action is Added', async () => { + const spySelectedKey = vi.spyOn(useSelectedKeyStore, 'useSelectedKeyStore') as Mock + + const setSelectedKeyActionMock = vi.fn() + const setSelectedKeyMock = vi.fn() + + spySelectedKey.mockImplementation(() => ({ + selectedKeyAction: { + ...selectedKeyAction, + type: SelectedKeyActionType.Added, + }, + setSelectedKeyAction: setSelectedKeyActionMock, + setSelectedKey: setSelectedKeyMock, + })) + + render() + + expect(setSelectedKeyMock).toBeCalledWith({ name: constants.KEY_NAME_1 }) + expect(setSelectedKeyActionMock).toBeCalledWith(null) + expect(deleteKeyFromTreeMock).not.toBeCalled() + expect(addKeyIntoTreeMock).not.toBeCalled() + }) + }) +}) diff --git a/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.tsx b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.tsx new file mode 100644 index 00000000..3fa0350d --- /dev/null +++ b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/LogicalDatabaseWrapper.tsx @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from 'react' +import cx from 'classnames' +import { useShallow } from 'zustand/react/shallow' + +import { SelectedKeyActionType } from 'uiSrc/constants' +import { Database, useSelectedKeyStore } from 'uiSrc/store' +import { useKeysApi } from '../../hooks/useKeys' + +export interface Props { + database: Database + dbIndex?: number + children: React.ReactNode +} + +export const LogicalDatabaseWrapper = ({ children, database }: Props) => { + const { id: dbId, db: dbIndex } = database + const { selectedKeyAction, setSelectedKeyAction, setSelectedKey } = useSelectedKeyStore(useShallow((state) => ({ + selectedKeyAction: state.action, + setSelectedKeyAction: state.setSelectedKeyAction, + setSelectedKey: state.processSelectedKeySuccess, + }))) + + const [renderChildren, setRenderChildren] = useState(false) + + const keysApi = useKeysApi() + + useEffect(() => { + keysApi.setDatabaseId(dbId) + keysApi.setDatabaseIndex(dbIndex ?? 0) + + setRenderChildren(true) + }, []) + + useEffect(() => { + const { type, keyInfo, database: databaseAction } = selectedKeyAction || {} + const { key, keyType, newKey } = keyInfo || {} + const { id: databaseId, db: databaseIndex } = databaseAction || {} + + if (!type || (databaseId! + databaseIndex !== dbId + dbIndex)) { + return + } + + switch (type) { + case SelectedKeyActionType.Added: + keysApi.addKeyIntoTree(key!, keyType!) + setSelectedKey({ name: key! }) + break + case SelectedKeyActionType.Removed: + keysApi.deleteKeyFromTree(key!) + break + case SelectedKeyActionType.Renamed: + keysApi.editKeyName(key!, newKey!) + break + default: + break + } + setSelectedKeyAction(null) + }, [selectedKeyAction]) + + return ( +
+ {renderChildren && children} +
+ ) +} diff --git a/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/index.ts b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/index.ts new file mode 100644 index 00000000..d7fa653f --- /dev/null +++ b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/index.ts @@ -0,0 +1 @@ +export { LogicalDatabaseWrapper } from './LogicalDatabaseWrapper' diff --git a/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/styles.module.scss b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/styles.module.scss new file mode 100644 index 00000000..1666ca36 --- /dev/null +++ b/src/webviews/src/modules/keys-tree/components/logical-database-wrapper/styles.module.scss @@ -0,0 +1,15 @@ +.databaseNameWrapper { + @apply flex pl-3.5 cursor-pointer text-ellipsis whitespace-nowrap overflow-hidden items-center; +} + +.databaseName { + @apply text-ellipsis whitespace-nowrap overflow-hidden flex items-center; +} + +.icon { + @apply mr-2 w-[14px] h-[14px] min-w-[14px] ml-[3px]; + + &Nested { + @apply m-0; + } +} From 3f481a880350ebe53b39d7ca4e0979fb2f29ad10 Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 12:34:40 +0100 Subject: [PATCH 03/19] #RI-31 - Logical databases --- .github/actions/install-all-build-libs/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/install-all-build-libs/action.yml b/.github/actions/install-all-build-libs/action.yml index 3d8142dc..3ea1fa54 100644 --- a/.github/actions/install-all-build-libs/action.yml +++ b/.github/actions/install-all-build-libs/action.yml @@ -8,7 +8,7 @@ runs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '20.15' + node-version: '20.18.0' - name: Install dependencies for root package.js shell: bash From 2c91e153a3716fd726eda07c8d2753ed819b86e8 Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 12:42:56 +0100 Subject: [PATCH 04/19] #RI-31 - Logical databases --- .../keys-summary/KeysSummary.spec.tsx | 1 + .../keys-tree-header/KeysTreeHeader.spec.tsx | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx index a3765599..3eed8992 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx @@ -14,6 +14,7 @@ const mockedProps: Props = { database: mockDatabase, total: 1, scanned: 1, + dbIndex: 0, showTree: true, resultsLength: 1, loading: false, diff --git a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx index e1f62610..6c7587ff 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx @@ -1,21 +1,32 @@ import React from 'react' -import { render } from 'testSrc/helpers' +import { render, constants } from 'testSrc/helpers' import { KeysTreeHeader } from './KeysTreeHeader' import * as useKeys from '../../hooks/useKeys' const useKeysInContextMock = vi.spyOn(useKeys, 'useKeysInContext') +const mockDatabase = constants.DATABASE +const mockedProps: Props = { + database: mockDatabase, + total: 1, + scanned: 1, + showTree: true, + resultsLength: 1, + loading: false, + toggleShowTree: () => {}, +} + describe('KeysTreeHeaders', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it.skip('should hide Scan more button when totalItemsCount < scanned', () => { const initialState = { scanned: 2, total: 1 } useKeysInContextMock.mockImplementation(() => initialState) - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId('scan-more')).not.toBeInTheDocument() }) @@ -25,7 +36,7 @@ describe('KeysTreeHeaders', () => { useKeysInContextMock.mockImplementation(() => initialState) - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId('scan-more')).toBeInTheDocument() }) @@ -35,7 +46,7 @@ describe('KeysTreeHeaders', () => { useKeysInContextMock.mockImplementation(() => initialState) - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId('scan-more')).toBeInTheDocument() }) @@ -45,7 +56,7 @@ describe('KeysTreeHeaders', () => { useKeysInContextMock.mockImplementation(() => initialState) - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId('scan-more')).toBeInTheDocument() }) From d39a5839cf02db5e287e2168f8a5f3bdb3eb732b Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 12:53:48 +0100 Subject: [PATCH 05/19] #RI-31 - Logical databases --- .../keys-tree/components/keys-summary/KeysSummary.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx index 3eed8992..7f547b0d 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.spec.tsx @@ -79,12 +79,12 @@ describe('KeysSummary', () => { }) it('should call sendEventTelemetry and postMessage actions after click on Add Key icon', async () => { - const { queryByTestId } = render() + const { queryByTestId } = render() fireEvent.click(queryByTestId('add-key-button')!) expect(vscodeApi.postMessage).toBeCalledWith({ - action: VscodeMessageAction.AddKey, data: { database: mockDatabase }, + action: VscodeMessageAction.AddKey, data: { database: { ...mockDatabase } }, }) expect(utils.sendEventTelemetry).toBeCalledWith({ From 7c69e8d91d5b611cc219b292360cef2b6e3c3d4f Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 13:00:44 +0100 Subject: [PATCH 06/19] #RI-31 - Logical databases --- vite.config.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vite.config.mjs b/vite.config.mjs index 901cfd35..089f2493 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -105,6 +105,12 @@ export default defineConfig({ }, reporters: [ 'default', + [ + 'junit', + { + outputFile: './reports/junit.xml', + }, + ], [ 'html', { From cc61be2b68b5fa60aa31b39ef76e9d9db16bd150 Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 13:03:19 +0100 Subject: [PATCH 07/19] #RI-31 - Logical databases --- vite.config.mjs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/vite.config.mjs b/vite.config.mjs index 089f2493..d1979f0b 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -103,21 +103,11 @@ export default defineConfig({ inline: ['rawproto', 'react-monaco-editor'], }, }, - reporters: [ - 'default', - [ - 'junit', - { - outputFile: './reports/junit.xml', - }, - ], - [ - 'html', - { - outputFile: './report/index.html', - }, - ], - ], + reporters: ['default', 'junit', 'html'], + outputFile: { + junit: './reports/junit.xml', + html: './report/index.html', + }, }, }) From 2da5cdcdabcfc86d272f45325b75c0e11e489548 Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 14:22:01 +0100 Subject: [PATCH 08/19] #RI-31 - fix pr comments --- .../src/modules/keys-tree/KeysTree.tsx | 6 ++-- .../database-wrapper/DatabaseWrapper.tsx | 29 ++++--------------- .../components/keys-summary/KeysSummary.tsx | 25 +++++----------- .../keys-tree-header/KeysTreeHeader.spec.tsx | 11 +++---- .../keys-tree-header/KeysTreeHeader.tsx | 2 +- .../src/ui/icons/chevron/Chevron.spec.tsx | 13 +++++++++ src/webviews/src/ui/icons/chevron/Chevron.tsx | 20 +++++++++++++ .../src/ui/icons/chevron/styles.module.scss | 7 +++++ .../ui/icons/database/DatabaseIcon.spec.tsx | 13 +++++++++ .../src/ui/icons/database/DatabaseIcon.tsx | 20 +++++++++++++ .../src/ui/icons/database/styles.module.scss | 7 +++++ src/webviews/src/ui/index.ts | 2 ++ 12 files changed, 104 insertions(+), 51 deletions(-) create mode 100644 src/webviews/src/ui/icons/chevron/Chevron.spec.tsx create mode 100644 src/webviews/src/ui/icons/chevron/Chevron.tsx create mode 100644 src/webviews/src/ui/icons/chevron/styles.module.scss create mode 100644 src/webviews/src/ui/icons/database/DatabaseIcon.spec.tsx create mode 100644 src/webviews/src/ui/icons/database/DatabaseIcon.tsx create mode 100644 src/webviews/src/ui/icons/database/styles.module.scss diff --git a/src/webviews/src/modules/keys-tree/KeysTree.tsx b/src/webviews/src/modules/keys-tree/KeysTree.tsx index 0a571824..7b2c4f0a 100644 --- a/src/webviews/src/modules/keys-tree/KeysTree.tsx +++ b/src/webviews/src/modules/keys-tree/KeysTree.tsx @@ -111,11 +111,11 @@ export const KeysTree = ({ database }: Props) => { if (isUndefined(type)) { return } - fetchKeyInfo({ key: name, databaseId: database.id, dbIndex: dbIndex! }, false, () => { + fetchKeyInfo({ key: name, databaseId: database.id, dbIndex }, false, () => { vscodeApi.postMessage({ action: VscodeMessageAction.SelectKey, data: { - database: { ...database, db: dbIndex! }, + database: { ...database, db: dbIndex }, keyInfo: { key: name, keyString, keyType: type, displayedKeyType: getGroupTypeDisplay(type) }, }, }) @@ -152,7 +152,7 @@ export const KeysTree = ({ database }: Props) => { return (
- +
) } diff --git a/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx b/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx index 20d810f6..1ab0a070 100644 --- a/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx +++ b/src/webviews/src/modules/keys-tree/components/database-wrapper/DatabaseWrapper.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import cx from 'classnames' -import { VscChevronRight, VscChevronDown, VscEdit } from 'react-icons/vsc' +import { VscEdit } from 'react-icons/vsc' import { isUndefined, toNumber } from 'lodash' import * as l10n from '@vscode/l10n' import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' @@ -12,9 +12,7 @@ import { sendEventTelemetry, } from 'uiSrc/utils' import { ContextStoreProvider, Database, DatabaseOverview, checkConnectToDatabase, deleteDatabases } from 'uiSrc/store' -import DatabaseOfflineIconSvg from 'uiSrc/assets/database/database_icon_offline.svg?react' -import DatabaseActiveIconSvg from 'uiSrc/assets/database/database_icon_active.svg?react' -import { Tooltip } from 'uiSrc/ui' +import { Chevron, DatabaseIcon, Tooltip } from 'uiSrc/ui' import { PopoverDelete } from 'uiSrc/components' import { POPOVER_WINDOW_BORDER_WIDTH, VscodeMessageAction } from 'uiSrc/constants' import { vscodeApi } from 'uiSrc/services' @@ -86,18 +84,6 @@ export const DatabaseWrapper = React.memo(({ database }: Props) => { }) } - const Chevron = () => (showTree ? ( - - ) : ( - - )) - - const DatabaseIcon = () => (showTree ? ( - - ) : ( - - )) - const LogicalDatabase = ( { database, open, dbTotal }: { database: Database, open?: boolean, dbTotal?: number }, @@ -127,8 +113,8 @@ export const DatabaseWrapper = React.memo(({ database }: Props) => { className={styles.databaseNameWrapper} data-testid={`database-${id}`} > - {} - {} + {} + {} {
{showTree && (<> - {!isUndefined(totalKeysPerDb) && Object.keys(totalKeysPerDb!).map((databaseIndex) => ( + {!isUndefined(totalKeysPerDb) && Object.keys(totalKeysPerDb).map((databaseIndex) => ( ))} {isUndefined(totalKeysPerDb) && ( diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx index 196c61a2..9473171e 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx @@ -3,15 +3,15 @@ import * as l10n from '@vscode/l10n' import cx from 'classnames' import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' import { BiSortDown, BiSortUp } from 'react-icons/bi' -import { VscAdd, VscChevronDown, VscChevronRight, VscDatabase, VscTerminal } from 'react-icons/vsc' +import { VscAdd, VscDatabase, VscTerminal } from 'react-icons/vsc' import { isUndefined } from 'lodash' import { TelemetryEvent, nullableNumberWithSpaces, numberWithSpaces, sendEventTelemetry } from 'uiSrc/utils' import { vscodeApi } from 'uiSrc/services' import { SortOrder, VscodeMessageAction } from 'uiSrc/constants' import { checkDatabaseIndexAction, Database, useContextApi, useContextInContext } from 'uiSrc/store' -import { Nullable } from 'uiSrc/interfaces' -import { RefreshBtn, Tooltip } from 'uiSrc/ui' +import { Maybe, Nullable } from 'uiSrc/interfaces' +import { Chevron, RefreshBtn, Tooltip } from 'uiSrc/ui' import { KeyTreeFilter } from '../keys-tree-filter' import { useKeysApi, useKeysInContext } from '../../hooks/useKeys' @@ -24,7 +24,7 @@ export interface Props { loading: boolean total: Nullable showTree: boolean - dbIndex: number + dbIndex: Maybe toggleShowTree: (value?: boolean) => void } @@ -46,7 +46,7 @@ export const KeysSummary = (props: Props) => { }) vscodeApi.postMessage({ action: VscodeMessageAction.AddKey, - data: { database: { ...database, db: dbIndex! } }, + data: { database: { ...database, db: dbIndex } }, }) } @@ -69,7 +69,7 @@ export const KeysSummary = (props: Props) => { return } if (!showTree) { - checkDatabaseIndexAction(database.id, dbIndex!, () => toggleShowTree()) + checkDatabaseIndexAction(database.id, dbIndex, () => toggleShowTree()) return } toggleShowTree() @@ -78,7 +78,7 @@ export const KeysSummary = (props: Props) => { const openCliClickHandle = () => { vscodeApi.postMessage({ action: VscodeMessageAction.AddCli, - data: { database: { ...database, db: dbIndex! } }, + data: { database: { ...database, db: dbIndex } }, }) } @@ -86,15 +86,6 @@ export const KeysSummary = (props: Props) => { keysApi.fetchPatternKeysAction() } - const Chevron = () => { - if (!isMultiDbIndex) return null - return showTree ? ( - - ) : ( - - ) - } - const DbIndex = () => { if (!isMultiDbIndex) return null return ( @@ -137,7 +128,7 @@ export const KeysSummary = (props: Props) => { onClick={handleToggleShowTree} data-testid="keys-summary" > - {} + {} {} {}
diff --git a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx index 6c7587ff..87b6e9b7 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.spec.tsx @@ -1,7 +1,7 @@ import React from 'react' import { render, constants } from 'testSrc/helpers' -import { KeysTreeHeader } from './KeysTreeHeader' +import { KeysTreeHeader, Props } from './KeysTreeHeader' import * as useKeys from '../../hooks/useKeys' const useKeysInContextMock = vi.spyOn(useKeys, 'useKeysInContext') @@ -9,12 +9,9 @@ const useKeysInContextMock = vi.spyOn(useKeys, 'useKeysInContext') const mockDatabase = constants.DATABASE const mockedProps: Props = { database: mockDatabase, - total: 1, - scanned: 1, - showTree: true, - resultsLength: 1, - loading: false, - toggleShowTree: () => {}, + dbTotal: 1, + open: true, + children:
, } describe('KeysTreeHeaders', () => { diff --git a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx index e32515e9..75fa5f68 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-tree-header/KeysTreeHeader.tsx @@ -67,7 +67,7 @@ export const KeysTreeHeader = ({ database, open, dbTotal, children }: Props) => loading={loading} scanned={scanned} total={dbTotal ?? total} - dbIndex={dbIndex!} + dbIndex={dbIndex} resultsLength={resultsLength} showTree={showTree} toggleShowTree={handleToggleShowTree} diff --git a/src/webviews/src/ui/icons/chevron/Chevron.spec.tsx b/src/webviews/src/ui/icons/chevron/Chevron.spec.tsx new file mode 100644 index 00000000..050e9c1d --- /dev/null +++ b/src/webviews/src/ui/icons/chevron/Chevron.spec.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' + +import { render } from 'testSrc/helpers' +import { Chevron, Props } from './Chevron' + +const mockedProps = mock() + +describe('Chevron', () => { + it('should render', async () => { + expect(render()).toBeTruthy() + }) +}) diff --git a/src/webviews/src/ui/icons/chevron/Chevron.tsx b/src/webviews/src/ui/icons/chevron/Chevron.tsx new file mode 100644 index 00000000..5ece0c31 --- /dev/null +++ b/src/webviews/src/ui/icons/chevron/Chevron.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import cx from 'classnames' + +import { VscChevronDown, VscChevronRight } from 'react-icons/vsc' +import styles from './styles.module.scss' + +export interface Props { + open?: boolean + display?: boolean +} + +export const Chevron = ({ open = false, display = true }: Props) => { + if (!display) return null + + return open ? ( + + ) : ( + + ) +} diff --git a/src/webviews/src/ui/icons/chevron/styles.module.scss b/src/webviews/src/ui/icons/chevron/styles.module.scss new file mode 100644 index 00000000..f9963a39 --- /dev/null +++ b/src/webviews/src/ui/icons/chevron/styles.module.scss @@ -0,0 +1,7 @@ +.icon { + @apply mr-2 w-[14px] h-[14px] min-w-[14px] ml-[3px]; + + &Nested { + @apply m-0; + } +} diff --git a/src/webviews/src/ui/icons/database/DatabaseIcon.spec.tsx b/src/webviews/src/ui/icons/database/DatabaseIcon.spec.tsx new file mode 100644 index 00000000..8febc609 --- /dev/null +++ b/src/webviews/src/ui/icons/database/DatabaseIcon.spec.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' + +import { render } from 'testSrc/helpers' +import { DatabaseIcon, Props } from './DatabaseIcon' + +const mockedProps = mock() + +describe('DatabaseIcon', () => { + it('should render', async () => { + expect(render()).toBeTruthy() + }) +}) diff --git a/src/webviews/src/ui/icons/database/DatabaseIcon.tsx b/src/webviews/src/ui/icons/database/DatabaseIcon.tsx new file mode 100644 index 00000000..d29b5076 --- /dev/null +++ b/src/webviews/src/ui/icons/database/DatabaseIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react' + +import DatabaseOfflineIconSvg from 'uiSrc/assets/database/database_icon_offline.svg?react' +import DatabaseActiveIconSvg from 'uiSrc/assets/database/database_icon_active.svg?react' +import styles from './styles.module.scss' + +export interface Props { + open?: boolean + display?: boolean +} + +export const DatabaseIcon = ({ open = false, display = true }: Props) => { + if (!display) return null + + return open ? ( + + ) : ( + + ) +} diff --git a/src/webviews/src/ui/icons/database/styles.module.scss b/src/webviews/src/ui/icons/database/styles.module.scss new file mode 100644 index 00000000..f9963a39 --- /dev/null +++ b/src/webviews/src/ui/icons/database/styles.module.scss @@ -0,0 +1,7 @@ +.icon { + @apply mr-2 w-[14px] h-[14px] min-w-[14px] ml-[3px]; + + &Nested { + @apply m-0; + } +} diff --git a/src/webviews/src/ui/index.ts b/src/webviews/src/ui/index.ts index 1dbba620..83539ae5 100644 --- a/src/webviews/src/ui/index.ts +++ b/src/webviews/src/ui/index.ts @@ -12,6 +12,8 @@ export { Spacer } from './spacer/Spacer' export { PromoLink } from './promo-link/PromoLink' export { Tooltip } from './tooltip/Tooltip' export { RiButton } from './button/RiButton' +export { Chevron } from './icons/chevron/Chevron' +export { DatabaseIcon } from './icons/database/DatabaseIcon' export type { RadioGroupOption } from './radio-group/RadioGroup' export type { SelectOption } from './select/Select' From 1fddabf2b903dc5b53d5d4290e03192930d3195c Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 14:23:22 +0100 Subject: [PATCH 09/19] #RI-31 - fix pr comments --- .gitignore | 2 +- .vscodeignore | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 65db26ac..97518b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ coverage /.nyc_output **/coverage reports +report # IDEs and editors /.idea @@ -38,7 +39,6 @@ reports # App packaged release dist -report distWeb dll vendor diff --git a/.vscodeignore b/.vscodeignore index 51fba043..0fe0f708 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -29,6 +29,7 @@ vite.config.mjs coverage test report +reports tests test-workspace .vscode-test From 73460be3c0f5193abe7258660d4704ccdd5a65ea Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Tue, 10 Dec 2024 14:41:05 +0100 Subject: [PATCH 10/19] #RI-31 - fix pr comments --- .../components/keys-summary/KeysSummary.tsx | 2 +- src/webviews/src/ui/icons/chevron/Chevron.tsx | 22 ++++++++++++------- .../src/ui/icons/chevron/styles.module.scss | 7 ------ .../src/ui/icons/database/DatabaseIcon.tsx | 22 +++++++++++++------ .../src/ui/icons/database/styles.module.scss | 7 ------ src/webviews/vscode.css | 8 +++++++ 6 files changed, 38 insertions(+), 30 deletions(-) delete mode 100644 src/webviews/src/ui/icons/chevron/styles.module.scss delete mode 100644 src/webviews/src/ui/icons/database/styles.module.scss diff --git a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx index 9473171e..f6fdb101 100644 --- a/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx +++ b/src/webviews/src/modules/keys-tree/components/keys-summary/KeysSummary.tsx @@ -128,7 +128,7 @@ export const KeysSummary = (props: Props) => { onClick={handleToggleShowTree} data-testid="keys-summary" > - {} + {
diff --git a/src/webviews/src/ui/icons/chevron/Chevron.tsx b/src/webviews/src/ui/icons/chevron/Chevron.tsx index 5ece0c31..a988e896 100644 --- a/src/webviews/src/ui/icons/chevron/Chevron.tsx +++ b/src/webviews/src/ui/icons/chevron/Chevron.tsx @@ -1,20 +1,26 @@ import React from 'react' import cx from 'classnames' - import { VscChevronDown, VscChevronRight } from 'react-icons/vsc' -import styles from './styles.module.scss' +import { IconBaseProps } from 'react-icons/lib' -export interface Props { +export interface Props extends IconBaseProps { open?: boolean - display?: boolean + hidden?: boolean + className?: string } -export const Chevron = ({ open = false, display = true }: Props) => { - if (!display) return null +export const Chevron = ({ open = false, hidden = false, className = '', ...props }: Props) => { + if (hidden) return null return open ? ( - + ) : ( - + ) } diff --git a/src/webviews/src/ui/icons/chevron/styles.module.scss b/src/webviews/src/ui/icons/chevron/styles.module.scss deleted file mode 100644 index f9963a39..00000000 --- a/src/webviews/src/ui/icons/chevron/styles.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.icon { - @apply mr-2 w-[14px] h-[14px] min-w-[14px] ml-[3px]; - - &Nested { - @apply m-0; - } -} diff --git a/src/webviews/src/ui/icons/database/DatabaseIcon.tsx b/src/webviews/src/ui/icons/database/DatabaseIcon.tsx index d29b5076..63dcd4bd 100644 --- a/src/webviews/src/ui/icons/database/DatabaseIcon.tsx +++ b/src/webviews/src/ui/icons/database/DatabaseIcon.tsx @@ -1,20 +1,28 @@ import React from 'react' +import cx from 'classnames' +import { IconBaseProps } from 'react-icons/lib' import DatabaseOfflineIconSvg from 'uiSrc/assets/database/database_icon_offline.svg?react' import DatabaseActiveIconSvg from 'uiSrc/assets/database/database_icon_active.svg?react' -import styles from './styles.module.scss' -export interface Props { +export interface Props extends IconBaseProps { open?: boolean - display?: boolean + hidden?: boolean + className?: string } -export const DatabaseIcon = ({ open = false, display = true }: Props) => { - if (!display) return null +export const DatabaseIcon = ({ open = false, hidden = false, className = '', ...props }: Props) => { + if (hidden) return null return open ? ( - + ) : ( - + ) } diff --git a/src/webviews/src/ui/icons/database/styles.module.scss b/src/webviews/src/ui/icons/database/styles.module.scss deleted file mode 100644 index f9963a39..00000000 --- a/src/webviews/src/ui/icons/database/styles.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.icon { - @apply mr-2 w-[14px] h-[14px] min-w-[14px] ml-[3px]; - - &Nested { - @apply m-0; - } -} diff --git a/src/webviews/vscode.css b/src/webviews/vscode.css index e5effbeb..2522e082 100644 --- a/src/webviews/vscode.css +++ b/src/webviews/vscode.css @@ -53,6 +53,14 @@ vscode-button::part(control) { @apply border-none; } } + + .sidebar-icon { + @apply mr-2 w-[14px] h-[14px] min-w-[14px] ml-[3px]; + + &-nested { + @apply m-0; + } + } } .table-loading:before { From 5b2b3f37acd6b8f8563040c4b658635c0b35807e Mon Sep 17 00:00:00 2001 From: Egor Zalenski Date: Thu, 12 Dec 2024 09:27:00 +0100 Subject: [PATCH 11/19] #RIVS-299 - Add the changes from the main project --- l10n/bundle.l10n.json | 5 +- package.json | 3 +- .../components/database-form/TlsDetails.tsx | 103 ++++++++----- src/webviews/src/components/index.ts | 4 + .../popover-delete/PopoverDelete.tsx | 1 + .../super-select/SuperSelect.spec.tsx | 43 ++++++ .../components/super-select/SuperSelect.tsx | 51 +++++++ .../removable-option/RemovableOption.spec.tsx | 13 ++ .../removable-option/RemovableOption.tsx | 63 ++++++++ .../super-select/styles.module.scss | 38 +++++ src/webviews/src/constants/database/form.ts | 6 +- src/webviews/src/interfaces/database/form.ts | 2 +- .../rejson-details/RejsonDetailsWrapper.tsx | 7 +- .../EditEntireItemAction.tsx | 1 + .../rejson-details/hooks/useRejsonStore.ts | 6 +- .../components/rejson-details/interfaces.ts | 2 +- .../rejson-object/RejsonObject.tsx | 6 +- .../rejson-scalar/RejsonScalar.spec.tsx | 40 +++++ .../rejson-scalar/RejsonScalar.tsx | 5 +- .../rejson-details/utils/utils.spec.ts | 143 +++++++++++++++++- .../components/rejson-details/utils/utils.ts | 114 +++++++++++++- .../database-wrapper/DatabaseWrapper.tsx | 2 +- .../manual-connection/ManualConnection.tsx | 23 ++- .../ManualConnectionFrom.spec.tsx | 15 +- .../useCertificatesStore.spec.ts | 24 ++- .../useCertificatesStore.ts | 20 +++ src/webviews/src/ui/button/RiButton.tsx | 1 + src/webviews/src/utils/comparisons/diff.ts | 2 +- .../src/utils/comparisons/tests/diff.spec.ts | 2 +- src/webviews/src/utils/database/form.tsx | 29 ++-- .../src/utils/formatters/bufferFormatters.ts | 7 + src/webviews/src/utils/formatters/javaDate.ts | 42 +++++ .../formatters/tests/bufferFormatters.spec.ts | 8 + .../formatters/tests/valueFormatters.spec.ts | 4 +- .../src/utils/formatters/valueFormatters.ts | 4 +- src/webviews/src/utils/telemetry/events.ts | 1 + .../handlers/database/certificatesHandlers.ts | 4 + src/webviews/test/helpers/constants.ts | 9 ++ yarn.lock | 8 +- 39 files changed, 769 insertions(+), 92 deletions(-) create mode 100644 src/webviews/src/components/super-select/SuperSelect.spec.tsx create mode 100644 src/webviews/src/components/super-select/SuperSelect.tsx create mode 100644 src/webviews/src/components/super-select/components/removable-option/RemovableOption.spec.tsx create mode 100644 src/webviews/src/components/super-select/components/removable-option/RemovableOption.tsx create mode 100644 src/webviews/src/components/super-select/styles.module.scss create mode 100644 src/webviews/src/utils/formatters/javaDate.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 2d2ba56d..2d260197 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -41,7 +41,7 @@ "Key Name": "Key Name", " will be deleted.": " will be deleted.", "Delete": "Delete", - "will be deleted from Redis for VS Code.": "will be deleted from Redis for VS Code.", + "will be removed from Redis for VS Code.": "will be removed from Redis for VS Code.", "Key Size": "Key Size", "Key Size: ": "Key Size: ", "Length": "Length", @@ -295,9 +295,6 @@ "Host:": "Host:", "Database Index:": "Database Index:", "Modules:": "Modules:", - "Select Logical Database": "Select Logical Database", - "Database Index": "Database Index", - "Enter Database Index": "Enter Database Index", "No decompression": "No decompression", "Enable automatic data decompression": "Enable automatic data decompression", "Decompression format": "Decompression format", diff --git a/package.json b/package.json index d829405c..00c17e55 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "download:backend": "tsc ./scripts/downloadBackend.ts && node ./scripts/downloadBackend.js", "dev": "vite dev", "dev:key": "cross-env RI_DATA_ROUTE=main/key vite dev", + "dev:database": "cross-env RI_DATA_ROUTE=main/add_database vite dev", "dev:sidebar": "cross-env RI_DATA_ROUTE=sidebar vite dev", "l10n:collect": "npx @vscode/l10n-dev export -o ./l10n ./src", "watch": "tsc -watch -p ./", @@ -286,7 +287,7 @@ "react-inlinesvg": "^4.1.1", "react-monaco-editor": "^0.55.0", "react-router-dom": "^6.17.0", - "react-select": "^5.8.0", + "react-select": "^5.8.3", "react-spinners": "^0.13.8", "react-virtualized": "^9.22.5", "react-virtualized-auto-sizer": "^1.0.20", diff --git a/src/webviews/src/components/database-form/TlsDetails.tsx b/src/webviews/src/components/database-form/TlsDetails.tsx index ddd70cd6..d4398d89 100644 --- a/src/webviews/src/components/database-form/TlsDetails.tsx +++ b/src/webviews/src/components/database-form/TlsDetails.tsx @@ -2,40 +2,61 @@ import React, { ChangeEvent, useId } from 'react' import cx from 'classnames' import { FormikProps } from 'formik' import * as l10n from '@vscode/l10n' -import { VSCodeDivider } from '@vscode/webview-ui-toolkit/react' import { CheckboxChangeEvent } from 'rc-checkbox' +import { find } from 'lodash' +import { MenuListProps } from 'react-select' -import { validateCertName, validateField } from 'uiSrc/utils' +import { sendEventTelemetry, TelemetryEvent, validateCertName, validateField } from 'uiSrc/utils' import { ADD_NEW, ADD_NEW_CA_CERT, - ADD_NEW_CA_CERT_LABEL, - ADD_NEW_LABEL, + ApiEndpoints, NO_CA_CERT, - NO_CA_CERT_LABEL, } from 'uiSrc/constants' -import { DbConnectionInfo } from 'uiSrc/interfaces' -import { Checkbox, InputText, Select, TextArea } from 'uiSrc/ui' +import { DbConnectionInfo, RedisString } from 'uiSrc/interfaces' +import { Checkbox, InputText, TextArea } from 'uiSrc/ui' +import { removeCertAction } from 'uiSrc/store' +import { SuperSelectRemovableOption, SuperSelect, SuperSelectOption } from 'uiSrc/components' import styles from './styles.module.scss' +const suffix = '_tls_details' + export interface Props { formik: FormikProps caCertificates?: { id: string, name: string }[] certificates?: { id: string, name: string }[] } + const TlsDetails = (props: Props) => { const { formik, caCertificates, certificates } = props const id = useId() - const optionsCertsCA = [ - { - value: NO_CA_CERT, - label: NO_CA_CERT_LABEL, - }, - { - value: ADD_NEW_CA_CERT, - label: ADD_NEW_CA_CERT_LABEL, - }, + const handleDeleteCaCert = (id: RedisString, onSuccess?: () => void) => { + removeCertAction(id, ApiEndpoints.CA_CERTIFICATES, () => { + onSuccess?.() + handleClickDeleteCert('CA') + }) + } + + const handleDeleteClientCert = (id: RedisString, onSuccess?: () => void) => { + removeCertAction(id, ApiEndpoints.CLIENT_CERTIFICATES, () => { + onSuccess?.() + handleClickDeleteCert('Client') + }) + } + + const handleClickDeleteCert = (certificateType: 'Client' | 'CA') => { + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_CERTIFICATE_REMOVED, + eventData: { + certificateType, + }, + }) + } + + const optionsCertsCA: SuperSelectOption[] = [ + NO_CA_CERT, + ADD_NEW_CA_CERT, ] caCertificates?.forEach((cert) => { @@ -45,12 +66,7 @@ const TlsDetails = (props: Props) => { }) }) - const optionsCertsClient = [ - { - value: ADD_NEW, - label: ADD_NEW_LABEL, - }, - ] + const optionsCertsClient: SuperSelectOption[] = [ADD_NEW] certificates?.forEach((cert) => { optionsCertsClient.push({ @@ -130,17 +146,22 @@ const TlsDetails = (props: Props) => {
{`${l10n.t('CA Certificate')}${formik.values.verifyServerTlsCert ? '*' : ''}`}
-