diff --git a/.circleci/config.yml b/.circleci/config.yml index 2db7d0a781..8eff6f505b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -432,7 +432,7 @@ jobs: exit 0; fi - SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:<< parameters.env >> + UPGRADES_LINK=$UPGRADES_LINK_STAGE SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:<< parameters.env >> - persist_to_workspace: root: . paths: @@ -474,7 +474,7 @@ jobs: exit 0; fi - SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:<< parameters.env >> + UPGRADES_LINK=$UPGRADES_LINK_STAGE SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:<< parameters.env >> rm -rf release/mac no_output_timeout: 15m - persist_to_workspace: @@ -514,7 +514,7 @@ jobs: exit 0; fi - SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:<< parameters.env >> + UPGRADES_LINK=$UPGRADES_LINK_STAGE SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:<< parameters.env >> rm -rf release/win-unpacked shell: bash.exe no_output_timeout: 20m diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..0c9660b8dd --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main, latest, release/*, codeql ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '37 11 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/configs/webpack.config.main.prod.babel.js b/configs/webpack.config.main.prod.babel.js index 0c679cfb97..76aa522547 100644 --- a/configs/webpack.config.main.prod.babel.js +++ b/configs/webpack.config.main.prod.babel.js @@ -1,7 +1,6 @@ import path from 'path'; import webpack from 'webpack'; import { merge } from 'webpack-merge'; -import TerserPlugin from 'terser-webpack-plugin'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import baseConfig from './webpack.config.base'; import DeleteSourceMaps from '../scripts/DeleteSourceMaps'; diff --git a/package.json b/package.json index 2d66972d89..da40a03423 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "build:main": "webpack --config ./configs/webpack.config.main.prod.babel.js", "build:main:stage": "webpack --config ./configs/webpack.config.main.stage.babel.js", "build:web": "webpack --config ./configs/webpack.config.web.prod.babel.js", - "build:statics": "sh ./scripts/build-statics.sh", - "build:statics:win": "./scripts/build-statics.cmd", + "build:defaults": "yarn --cwd redisinsight/api build:defaults", + "build:statics": "yarn build:defaults & sh ./scripts/build-statics.sh", + "build:statics:win": "yarn build:defaults & ./scripts/build-statics.cmd", "build:renderer": "webpack --config ./configs/webpack.config.renderer.prod.babel.js", "build:renderer:stage": "webpack --config ./configs/webpack.config.renderer.stage.babel.js", "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir redisinsight/ui", diff --git a/redisinsight/api/.gitignore b/redisinsight/api/.gitignore index 0fc65df77f..690310b2bf 100644 --- a/redisinsight/api/.gitignore +++ b/redisinsight/api/.gitignore @@ -1,7 +1,8 @@ # compiled output /dist /node_modules -/src/static +/static +/defaults # Logs logs diff --git a/redisinsight/api/config/default.ts b/redisinsight/api/config/default.ts index 2fe1da099e..3ec628c9a6 100644 --- a/redisinsight/api/config/default.ts +++ b/redisinsight/api/config/default.ts @@ -6,15 +6,21 @@ const staticDir = process.env.BUILD_TYPE === 'ELECTRON' && process['resourcesPat ? join(process['resourcesPath'], 'static') : join(__dirname, '..', 'static'); +const defaultsDir = process.env.BUILD_TYPE === 'ELECTRON' && process['resourcesPath'] + ? join(process['resourcesPath'], 'defaults') + : join(__dirname, '..', 'defaults'); + export default { dir_path: { homedir, staticDir, + defaultsDir, logs: join(homedir, 'logs'), defaultPlugins: join(staticDir, 'plugins'), customPlugins: join(homedir, 'plugins'), pluginsAssets: join(staticDir, 'resources', 'plugins'), commands: join(homedir, 'commands'), + defaultCommandsDir: join(defaultsDir, 'commands'), caCertificates: join(homedir, 'ca_certificates'), clientCertificates: join(homedir, 'client_certificates'), }, @@ -72,18 +78,36 @@ export default { omitSensitiveData: process.env.LOGGER_OMIT_DATA ? process.env.LOGGER_OMIT_DATA === 'true' : true, pipelineSummaryLimit: parseInt(process.env.LOGGER_PIPELINE_SUMMARY_LIMIT, 10) || 5, }, - commands: { - mainUrl: process.env.COMMANDS_MAIN_URL - || 'https://raw.githubusercontent.com/redis/redis-doc/master/commands.json', - redisearchUrl: process.env.COMMANDS_REDISEARCH_URL - || 'https://raw.githubusercontent.com/RediSearch/RediSearch/master/commands.json', - redijsonUrl: process.env.COMMANDS_REDIJSON_URL - || 'https://raw.githubusercontent.com/RedisJSON/RedisJSON/master/commands.json', - redistimeseriesUrl: process.env.COMMANDS_REDISTIMESERIES_URL - || 'https://raw.githubusercontent.com/RedisTimeSeries/RedisTimeSeries/master/commands.json', - redisaiUrl: process.env.COMMANDS_REDISAI_URL - || 'https://raw.githubusercontent.com/RedisAI/RedisAI/master/commands.json', - redisgraphUrl: process.env.COMMANDS_REDISGRAPH_URL - || 'https://raw.githubusercontent.com/RedisGraph/RedisGraph/master/commands.json', - }, + commands: [ + { + name: 'main', + url: process.env.COMMANDS_MAIN_URL + || 'https://raw.githubusercontent.com/redis/redis-doc/master/commands.json', + }, + { + name: 'redisearch', + url: process.env.COMMANDS_REDISEARCH_URL + || 'https://raw.githubusercontent.com/RediSearch/RediSearch/master/commands.json', + }, + { + name: 'redijson', + url: process.env.COMMANDS_REDIJSON_URL + || 'https://raw.githubusercontent.com/RedisJSON/RedisJSON/master/commands.json', + }, + { + name: 'redistimeseries', + url: process.env.COMMANDS_REDISTIMESERIES_URL + || 'https://raw.githubusercontent.com/RedisTimeSeries/RedisTimeSeries/master/commands.json', + }, + { + name: 'redisai', + url: process.env.COMMANDS_REDISAI_URL + || 'https://raw.githubusercontent.com/RedisAI/RedisAI/master/commands.json', + }, + { + name: 'redisgraph', + url: process.env.COMMANDS_REDISGRAPH_URL + || 'https://raw.githubusercontent.com/RedisGraph/RedisGraph/master/commands.json', + }, + ], }; diff --git a/redisinsight/api/config/production.ts b/redisinsight/api/config/production.ts index eb991c0698..5422411465 100644 --- a/redisinsight/api/config/production.ts +++ b/redisinsight/api/config/production.ts @@ -1,6 +1,6 @@ import { join } from 'path'; -const homedir = join(require('os').homedir(), process.env.APP_FOLDER_NAME || '.redisinsight-v2.0'); +const homedir = join(require('os').homedir(), process.env.APP_FOLDER_NAME || '.redisinsight-preview'); export default { dir_path: { diff --git a/redisinsight/api/nest-cli.json b/redisinsight/api/nest-cli.json index 316a787c6a..4a840a3e32 100644 --- a/redisinsight/api/nest-cli.json +++ b/redisinsight/api/nest-cli.json @@ -3,7 +3,14 @@ "sourceRoot": "src", "compilerOptions": { "assets": [ - "static/**/*" + { + "include": "../static/**/*", + "outDir": "dist/static" + }, + { + "include": "../defaults/**/*", + "outDir": "dist/defaults" + } ] } } diff --git a/redisinsight/api/package.json b/redisinsight/api/package.json index 4be212dac8..9b806cbc4d 100644 --- a/redisinsight/api/package.json +++ b/redisinsight/api/package.json @@ -9,6 +9,8 @@ "url": "https://redis.com/redis-enterprise/redis-insight" }, "scripts": { + "build:defaults:commands": "ts-node ./scripts/default-commands.ts", + "build:defaults": "yarn build:defaults:commands", "prebuild": "rimraf dist", "build": "nest build", "build:prod": "rimraf dist && nest build -p ./tsconfig.build.prod.json && cross-env NODE_ENV=production", @@ -46,6 +48,7 @@ "class-transformer": "^0.2.3", "class-validator": "^0.12.2", "express": "^4.17.1", + "fs-extra": "^10.0.0", "ioredis": "^4.27.1", "is-glob": "^4.0.1", "jsonpath": "^1.1.1", diff --git a/redisinsight/api/scripts/default-commands.ts b/redisinsight/api/scripts/default-commands.ts new file mode 100644 index 0000000000..46674239c7 --- /dev/null +++ b/redisinsight/api/scripts/default-commands.ts @@ -0,0 +1,40 @@ +import axios from 'axios'; +import * as fs from 'fs'; +import * as path from 'path'; +import { get } from '../src/utils/config'; + +const PATH_CONFIG = get('dir_path'); +const COMMANDS_CONFIG = get('commands'); + +async function init() { + try { + await Promise.all(COMMANDS_CONFIG.map(async ({ name, url }) => { + try { + console.log(`Trying to get ${name} commands...`); + const { data } = await axios.get(url, { + responseType: 'text', + transformResponse: [(raw) => raw], + }); + + if (!fs.existsSync(PATH_CONFIG.defaultCommandsDir)) { + fs.mkdirSync(PATH_CONFIG.defaultCommandsDir, { recursive: true }); + } + + fs.writeFileSync( + path.join(PATH_CONFIG.defaultCommandsDir, `${name}.json`), + JSON.stringify(JSON.parse(data)), // check that we received proper json object + ); + console.log(`Successfully generated default ${name} commands`); + } catch (error) { + console.error(`Unable to update ${name} commands`, error); + } + })); + + process.exit(0); + } catch (e) { + console.error('Something went wrong trying to get default commands jsons', e); + process.exit(1); + } +} + +init(); diff --git a/redisinsight/api/src/__mocks__/commands.ts b/redisinsight/api/src/__mocks__/commands.ts index bf3004064a..237afec8a0 100644 --- a/redisinsight/api/src/__mocks__/commands.ts +++ b/redisinsight/api/src/__mocks__/commands.ts @@ -165,5 +165,7 @@ export const mockRedisgraphCommands = { }; export const mockCommandsJsonProvider = () => ({ + updateLatestJson: jest.fn(), getCommands: jest.fn(), + getDefaultCommands: jest.fn(), }); diff --git a/redisinsight/api/src/constants/commands/main.json b/redisinsight/api/src/constants/commands/main.json deleted file mode 100644 index 45cb3d0a78..0000000000 --- a/redisinsight/api/src/constants/commands/main.json +++ /dev/null @@ -1,5901 +0,0 @@ -{ - "ACL LOAD": { - "summary": "Reload the ACLs from the configured ACL file", - "complexity": "O(N). Where N is the number of configured users.", - "since": "6.0.0", - "group": "server" - }, - "ACL SAVE": { - "summary": "Save the current ACL rules in the configured ACL file", - "complexity": "O(N). Where N is the number of configured users.", - "since": "6.0.0", - "group": "server" - }, - "ACL LIST": { - "summary": "List the current ACL rules in ACL config file format", - "complexity": "O(N). Where N is the number of configured users.", - "since": "6.0.0", - "group": "server" - }, - "ACL USERS": { - "summary": "List the username of all the configured ACL rules", - "complexity": "O(N). Where N is the number of configured users.", - "since": "6.0.0", - "group": "server" - }, - "ACL GETUSER": { - "summary": "Get the rules for a specific ACL user", - "complexity": "O(N). Where N is the number of password, command and pattern rules that the user has.", - "arguments": [ - { - "name": "username", - "type": "string" - } - ], - "since": "6.0.0", - "group": "server" - }, - "ACL SETUSER": { - "summary": "Modify or create the rules for a specific ACL user", - "complexity": "O(N). Where N is the number of rules provided.", - "arguments": [ - { - "name": "username", - "type": "string" - }, - { - "name": "rule", - "type": "string", - "multiple": true, - "optional": true - } - ], - "since": "6.0.0", - "group": "server" - }, - "ACL DELUSER": { - "summary": "Remove the specified ACL users and the associated rules", - "complexity": "O(1) amortized time considering the typical user.", - "arguments": [ - { - "name": "username", - "type": "string", - "multiple": true - } - ], - "since": "6.0.0", - "group": "server" - }, - "ACL CAT": { - "summary": "List the ACL categories or the commands inside a category", - "complexity": "O(1) since the categories and commands are a fixed set.", - "arguments": [ - { - "name": "categoryname", - "type": "string", - "optional": true - } - ], - "since": "6.0.0", - "group": "server" - }, - "ACL GENPASS": { - "summary": "Generate a pseudorandom secure password to use for ACL users", - "complexity": "O(1)", - "arguments": [ - { - "name": "bits", - "type": "integer", - "optional": true - } - ], - "since": "6.0.0", - "group": "server" - }, - "ACL WHOAMI": { - "summary": "Return the name of the user associated to the current connection", - "complexity": "O(1)", - "since": "6.0.0", - "group": "server" - }, - "ACL LOG": { - "summary": "List latest events denied because of ACLs in place", - "complexity": "O(N) with N being the number of entries shown.", - "arguments": [ - { - "name": "count or RESET", - "type": "string", - "optional": true - } - ], - "since": "6.0.0", - "group": "server" - }, - "ACL HELP": { - "summary": "Show helpful text about the different subcommands", - "complexity": "O(1)", - "since": "6.0.0", - "group": "server" - }, - "APPEND": { - "summary": "Append a value to a key", - "complexity": "O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "2.0.0", - "group": "string" - }, - "ASKING": { - "summary": "Sent by cluster clients after an -ASK redirect", - "complexity": "O(1)", - "arguments": [], - "since": "3.0.0", - "group": "cluster" - }, - "AUTH": { - "summary": "Authenticate to the server", - "arguments": [ - { - "name": "username", - "type": "string", - "optional": true - }, - { - "name": "password", - "type": "string" - } - ], - "since": "1.0.0", - "group": "connection" - }, - "BGREWRITEAOF": { - "summary": "Asynchronously rewrite the append-only file", - "since": "1.0.0", - "group": "server" - }, - "BGSAVE": { - "summary": "Asynchronously save the dataset to disk", - "arguments": [ - { - "name": "schedule", - "type": "enum", - "enum": [ - "SCHEDULE" - ], - "optional": true - } - ], - "since": "1.0.0", - "group": "server" - }, - "BITCOUNT": { - "summary": "Count set bits in a string", - "complexity": "O(N)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": [ - "start", - "end" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - } - ], - "since": "2.6.0", - "group": "bitmap" - }, - "BITFIELD": { - "summary": "Perform arbitrary bitfield integer operations on strings", - "complexity": "O(1) for each subcommand specified", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "GET", - "name": [ - "type", - "offset" - ], - "type": [ - "type", - "integer" - ], - "optional": true - }, - { - "command": "SET", - "name": [ - "type", - "offset", - "value" - ], - "type": [ - "type", - "integer", - "integer" - ], - "optional": true - }, - { - "command": "INCRBY", - "name": [ - "type", - "offset", - "increment" - ], - "type": [ - "type", - "integer", - "integer" - ], - "optional": true - }, - { - "command": "OVERFLOW", - "type": "enum", - "enum": [ - "WRAP", - "SAT", - "FAIL" - ], - "optional": true - } - ], - "since": "3.2.0", - "group": "bitmap" - }, - "BITFIELD_RO": { - "summary": "Perform arbitrary bitfield integer operations on strings. Read-only variant of BITFIELD", - "complexity": "O(1) for each subcommand specified", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "GET", - "name": [ - "type", - "offset" - ], - "type": [ - "type", - "integer" - ] - } - ], - "since": "6.2.0", - "group": "bitmap" - }, - "BITOP": { - "summary": "Perform bitwise operations between strings", - "complexity": "O(N)", - "arguments": [ - { - "name": "operation", - "type": "string" - }, - { - "name": "destkey", - "type": "key" - }, - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "2.6.0", - "group": "bitmap" - }, - "BITPOS": { - "summary": "Find first bit set or clear in a string", - "complexity": "O(N)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "bit", - "type": "integer" - }, - { - "name": "index", - "type": "block", - "optional": true, - "block": [ - { - "name": "start", - "type": "integer" - }, - { - "name": "end", - "type": "integer", - "optional": true - } - ] - } - ], - "since": "2.8.7", - "group": "bitmap" - }, - "BLPOP": { - "summary": "Remove and get the first element in a list, or block until one is available", - "complexity": "O(N) where N is the number of provided keys.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "timeout", - "type": "double" - } - ], - "since": "2.0.0", - "group": "list" - }, - "BRPOP": { - "summary": "Remove and get the last element in a list, or block until one is available", - "complexity": "O(N) where N is the number of provided keys.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "timeout", - "type": "double" - } - ], - "since": "2.0.0", - "group": "list" - }, - "BRPOPLPUSH": { - "summary": "Pop an element from a list, push it to another list and return it; or block until one is available", - "complexity": "O(1)", - "arguments": [ - { - "name": "source", - "type": "key" - }, - { - "name": "destination", - "type": "key" - }, - { - "name": "timeout", - "type": "double" - } - ], - "since": "2.2.0", - "group": "list" - }, - "BLMOVE": { - "summary": "Pop an element from a list, push it to another list and return it; or block until one is available", - "complexity": "O(1)", - "arguments": [ - { - "name": "source", - "type": "key" - }, - { - "name": "destination", - "type": "key" - }, - { - "name": "wherefrom", - "type": "enum", - "enum": [ - "LEFT", - "RIGHT" - ] - }, - { - "name": "whereto", - "type": "enum", - "enum": [ - "LEFT", - "RIGHT" - ] - }, - { - "name": "timeout", - "type": "double" - } - ], - "since": "6.2.0", - "group": "list" - }, - "LMPOP": { - "summary": "Pop elements from a list", - "complexity": "O(N+M) where N is the number of provided keys and M is the number of elements returned.", - "arguments": [ - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "optional": true, - "multiple": true - }, - { - "name": "where", - "type": "enum", - "enum": [ - "LEFT", - "RIGHT" - ] - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "7.0.0", - "group": "list" - }, - "BLMPOP": { - "summary": "Pop elements from a list, or block until one is available", - "complexity": "O(N+M) where N is the number of provided keys and M is the number of elements returned.", - "arguments": [ - { - "name": "timeout", - "type": "double" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "optional": true, - "multiple": true - }, - { - "name": "where", - "type": "enum", - "enum": [ - "LEFT", - "RIGHT" - ] - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "7.0.0", - "group": "list" - }, - "BZPOPMIN": { - "summary": "Remove and return the member with the lowest score from one or more sorted sets, or block until one is available", - "complexity": "O(log(N)) with N being the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "timeout", - "type": "double" - } - ], - "since": "5.0.0", - "group": "sorted_set" - }, - "BZPOPMAX": { - "summary": "Remove and return the member with the highest score from one or more sorted sets, or block until one is available", - "complexity": "O(log(N)) with N being the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "timeout", - "type": "double" - } - ], - "since": "5.0.0", - "group": "sorted_set" - }, - "CLIENT CACHING": { - "summary": "Instruct the server about tracking or not keys in the next request", - "complexity": "O(1)", - "arguments": [ - { - "name": "mode", - "type": "enum", - "enum": [ - "YES", - "NO" - ] - } - ], - "since": "6.0.0", - "group": "connection" - }, - "CLIENT ID": { - "summary": "Returns the client ID for the current connection", - "complexity": "O(1)", - "since": "5.0.0", - "group": "connection" - }, - "CLIENT INFO": { - "summary": "Returns information about the current client connection.", - "complexity": "O(1)", - "since": "6.2.0", - "group": "connection" - }, - "CLIENT KILL": { - "summary": "Kill the connection of a client", - "complexity": "O(N) where N is the number of client connections", - "arguments": [ - { - "name": "ip:port", - "type": "string", - "optional": true - }, - { - "command": "ID", - "name": "client-id", - "type": "integer", - "optional": true - }, - { - "command": "TYPE", - "type": "enum", - "enum": [ - "normal", - "master", - "slave", - "pubsub" - ], - "optional": true - }, - { - "command": "USER", - "name": "username", - "type": "string", - "optional": true - }, - { - "command": "ADDR", - "name": "ip:port", - "type": "string", - "optional": true - }, - { - "command": "LADDR", - "name": "ip:port", - "type": "string", - "optional": true - }, - { - "command": "SKIPME", - "name": "yes/no", - "type": "string", - "optional": true - } - ], - "since": "2.4.0", - "group": "connection" - }, - "CLIENT LIST": { - "summary": "Get the list of client connections", - "complexity": "O(N) where N is the number of client connections", - "arguments": [ - { - "command": "TYPE", - "type": "enum", - "enum": [ - "normal", - "master", - "replica", - "pubsub" - ], - "optional": true - }, - { - "name": "id", - "type": "block", - "block": [ - { - "command": "ID" - }, - { - "name": "client-id", - "type": "integer", - "multiple": true - } - ], - "optional": true - } - ], - "since": "2.4.0", - "group": "connection" - }, - "CLIENT GETNAME": { - "summary": "Get the current connection name", - "complexity": "O(1)", - "since": "2.6.9", - "group": "connection" - }, - "CLIENT GETREDIR": { - "summary": "Get tracking notifications redirection client ID if any", - "complexity": "O(1)", - "since": "6.0.0", - "group": "connection" - }, - "CLIENT UNPAUSE": { - "summary": "Resume processing of clients that were paused", - "complexity": "O(N) Where N is the number of paused clients", - "since": "6.2.0", - "group": "connection" - }, - "CLIENT PAUSE": { - "summary": "Stop processing commands from clients for some time", - "complexity": "O(1)", - "arguments": [ - { - "name": "timeout", - "type": "integer" - }, - { - "name": "mode", - "type": "enum", - "optional": true, - "enum": [ - "WRITE", - "ALL" - ] - } - ], - "since": "2.9.50", - "group": "connection" - }, - "CLIENT REPLY": { - "summary": "Instruct the server whether to reply to commands", - "complexity": "O(1)", - "arguments": [ - { - "name": "reply-mode", - "type": "enum", - "enum": [ - "ON", - "OFF", - "SKIP" - ] - } - ], - "since": "3.2.0", - "group": "connection" - }, - "CLIENT SETNAME": { - "summary": "Set the current connection name", - "complexity": "O(1)", - "since": "2.6.9", - "arguments": [ - { - "name": "connection-name", - "type": "string" - } - ], - "group": "connection" - }, - "CLIENT TRACKING": { - "summary": "Enable or disable server assisted client side caching support", - "complexity": "O(1). Some options may introduce additional complexity.", - "arguments": [ - { - "name": "status", - "type": "enum", - "enum": [ - "ON", - "OFF" - ] - }, - { - "command": "REDIRECT", - "name": "client-id", - "type": "integer", - "optional": true - }, - { - "command": "PREFIX", - "name": "prefix", - "type": "string", - "optional": true, - "multiple": true - }, - { - "name": "BCAST", - "type": "enum", - "enum": [ - "BCAST" - ], - "optional": true - }, - { - "name": "OPTIN", - "type": "enum", - "enum": [ - "OPTIN" - ], - "optional": true - }, - { - "name": "OPTOUT", - "type": "enum", - "enum": [ - "OPTOUT" - ], - "optional": true - }, - { - "name": "NOLOOP", - "type": "enum", - "enum": [ - "NOLOOP" - ], - "optional": true - } - ], - "since": "6.0.0", - "group": "connection" - }, - "CLIENT TRACKINGINFO": { - "summary": "Return information about server assisted client side caching for the current connection", - "complexity": "O(1)", - "since": "6.2.0", - "group": "connection" - }, - "CLIENT UNBLOCK": { - "summary": "Unblock a client blocked in a blocking command from a different connection", - "complexity": "O(log N) where N is the number of client connections", - "arguments": [ - { - "name": "client-id", - "type": "integer" - }, - { - "name": "unblock-type", - "type": "enum", - "enum": [ - "TIMEOUT", - "ERROR" - ], - "optional": true - } - ], - "since": "5.0.0", - "group": "connection" - }, - "CLIENT NO-EVICT": { - "summary": "Set client eviction mode for the current connection", - "complexity": "O(1)", - "since": "7.0.0", - "arguments": [ - { - "name": "enabled", - "type": "enum", - "enum": [ - "ON", - "OFF" - ] - } - ], - "group": "connection" - }, - "CLUSTER ADDSLOTS": { - "summary": "Assign new hash slots to receiving node", - "complexity": "O(N) where N is the total number of hash slot arguments", - "arguments": [ - { - "name": "slot", - "type": "integer", - "multiple": true - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER BUMPEPOCH": { - "summary": "Advance the cluster config epoch", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER COUNT-FAILURE-REPORTS": { - "summary": "Return the number of failure reports active for a given node", - "complexity": "O(N) where N is the number of failure reports", - "arguments": [ - { - "name": "node-id", - "type": "string" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER COUNTKEYSINSLOT": { - "summary": "Return the number of local keys in the specified hash slot", - "complexity": "O(1)", - "arguments": [ - { - "name": "slot", - "type": "integer" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER DELSLOTS": { - "summary": "Set hash slots as unbound in receiving node", - "complexity": "O(N) where N is the total number of hash slot arguments", - "arguments": [ - { - "name": "slot", - "type": "integer", - "multiple": true - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER FAILOVER": { - "summary": "Forces a replica to perform a manual failover of its master.", - "complexity": "O(1)", - "arguments": [ - { - "name": "options", - "type": "enum", - "enum": [ - "FORCE", - "TAKEOVER" - ], - "optional": true - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER FLUSHSLOTS": { - "summary": "Delete a node's own slots information", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER FORGET": { - "summary": "Remove a node from the nodes table", - "complexity": "O(1)", - "arguments": [ - { - "name": "node-id", - "type": "string" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER GETKEYSINSLOT": { - "summary": "Return local key names in the specified hash slot", - "complexity": "O(log(N)) where N is the number of requested keys", - "arguments": [ - { - "name": "slot", - "type": "integer" - }, - { - "name": "count", - "type": "integer" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER INFO": { - "summary": "Provides info about Redis Cluster node state", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER KEYSLOT": { - "summary": "Returns the hash slot of the specified key", - "complexity": "O(N) where N is the number of bytes in the key", - "arguments": [ - { - "name": "key", - "type": "string" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER MEET": { - "summary": "Force a node cluster to handshake with another node", - "complexity": "O(1)", - "arguments": [ - { - "name": "ip", - "type": "string" - }, - { - "name": "port", - "type": "integer" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER MYID": { - "summary": "Return the node id", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER NODES": { - "summary": "Get Cluster config for the node", - "complexity": "O(N) where N is the total number of Cluster nodes", - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER REPLICATE": { - "summary": "Reconfigure a node as a replica of the specified master node", - "complexity": "O(1)", - "arguments": [ - { - "name": "node-id", - "type": "string" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER RESET": { - "summary": "Reset a Redis Cluster node", - "complexity": "O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.", - "arguments": [ - { - "name": "reset-type", - "type": "enum", - "enum": [ - "HARD", - "SOFT" - ], - "optional": true - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER SAVECONFIG": { - "summary": "Forces the node to save cluster state on disk", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER SET-CONFIG-EPOCH": { - "summary": "Set the configuration epoch in a new node", - "complexity": "O(1)", - "arguments": [ - { - "name": "config-epoch", - "type": "integer" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER SETSLOT": { - "summary": "Bind a hash slot to a specific node", - "complexity": "O(1)", - "arguments": [ - { - "name": "slot", - "type": "integer" - }, - { - "name": "subcommand", - "type": "enum", - "enum": [ - "IMPORTING", - "MIGRATING", - "STABLE", - "NODE" - ] - }, - { - "name": "node-id", - "type": "string", - "optional": true - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER SLAVES": { - "summary": "List replica nodes of the specified master node", - "complexity": "O(1)", - "arguments": [ - { - "name": "node-id", - "type": "string" - } - ], - "since": "3.0.0", - "group": "cluster" - }, - "CLUSTER REPLICAS": { - "summary": "List replica nodes of the specified master node", - "complexity": "O(1)", - "arguments": [ - { - "name": "node-id", - "type": "string" - } - ], - "since": "5.0.0", - "group": "cluster" - }, - "CLUSTER SLOTS": { - "summary": "Get array of Cluster slot to node mappings", - "complexity": "O(N) where N is the total number of Cluster nodes", - "since": "3.0.0", - "group": "cluster" - }, - "COMMAND": { - "summary": "Get array of Redis command details", - "complexity": "O(N) where N is the total number of Redis commands", - "since": "2.8.13", - "group": "server" - }, - "COMMAND COUNT": { - "summary": "Get total number of Redis commands", - "complexity": "O(1)", - "since": "2.8.13", - "group": "server" - }, - "COMMAND GETKEYS": { - "summary": "Extract keys given a full Redis command", - "complexity": "O(N) where N is the number of arguments to the command", - "since": "2.8.13", - "group": "server" - }, - "COMMAND INFO": { - "summary": "Get array of specific Redis command details", - "complexity": "O(N) when N is number of commands to look up", - "since": "2.8.13", - "arguments": [ - { - "name": "command-name", - "type": "string", - "multiple": true - } - ], - "group": "server" - }, - "CONFIG GET": { - "summary": "Get the value of a configuration parameter", - "arguments": [ - { - "name": "parameter", - "type": "string" - } - ], - "since": "2.0.0", - "group": "server" - }, - "CONFIG REWRITE": { - "summary": "Rewrite the configuration file with the in memory configuration", - "since": "2.8.0", - "group": "server" - }, - "CONFIG SET": { - "summary": "Set a configuration parameter to the given value", - "arguments": [ - { - "name": "parameter", - "type": "string" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "2.0.0", - "group": "server" - }, - "CONFIG RESETSTAT": { - "summary": "Reset the stats returned by INFO", - "complexity": "O(1)", - "since": "2.0.0", - "group": "server" - }, - "COPY": { - "summary": "Copy a key", - "complexity": "O(N) worst case for collections, where N is the number of nested items. O(1) for string values.", - "since": "6.2.0", - "arguments": [ - { - "name": "source", - "type": "key" - }, - { - "name": "destination", - "type": "key" - }, - { - "command": "DB", - "name": "destination-db", - "type": "integer", - "optional": true - }, - { - "name": "replace", - "type": "enum", - "enum": [ - "REPLACE" - ], - "optional": true - } - ], - "group": "generic" - }, - "DBSIZE": { - "summary": "Return the number of keys in the selected database", - "since": "1.0.0", - "group": "server" - }, - "DEBUG OBJECT": { - "summary": "Get debugging information about a key", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "server" - }, - "DEBUG SEGFAULT": { - "summary": "Make the server crash", - "since": "1.0.0", - "group": "server" - }, - "DECR": { - "summary": "Decrement the integer value of a key by one", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "string" - }, - "DECRBY": { - "summary": "Decrement the integer value of a key by the given number", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "decrement", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "string" - }, - "DEL": { - "summary": "Delete a key", - "complexity": "O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "generic" - }, - "DISCARD": { - "summary": "Discard all commands issued after MULTI", - "since": "2.0.0", - "group": "transactions" - }, - "DUMP": { - "summary": "Return a serialized version of the value stored at the specified key.", - "complexity": "O(1) to access the key and additional O(N*M) to serialize it, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1).", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.6.0", - "group": "generic" - }, - "ECHO": { - "summary": "Echo the given string", - "arguments": [ - { - "name": "message", - "type": "string" - } - ], - "since": "1.0.0", - "group": "connection" - }, - "EVAL": { - "summary": "Execute a Lua script server side", - "complexity": "Depends on the script that is executed.", - "arguments": [ - { - "name": "script", - "type": "string" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "optional": true, - "multiple": true - }, - { - "name": "arg", - "type": "string", - "optional": true, - "multiple": true - } - ], - "since": "2.6.0", - "group": "scripting" - }, - "EVAL_RO": { - "summary": "Execute a read-only Lua script server side", - "complexity": "Depends on the script that is executed.", - "arguments": [ - { - "name": "script", - "type": "string" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "arg", - "type": "string", - "multiple": true - } - ], - "since": "7.0.0", - "group": "scripting" - }, - "EVALSHA": { - "summary": "Execute a Lua script server side", - "complexity": "Depends on the script that is executed.", - "arguments": [ - { - "name": "sha1", - "type": "string" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "optional": true, - "multiple": true - }, - { - "name": "arg", - "type": "string", - "optional": true, - "multiple": true - } - ], - "since": "2.6.0", - "group": "scripting" - }, - "EVALSHA_RO": { - "summary": "Execute a read-only Lua script server side", - "complexity": "Depends on the script that is executed.", - "arguments": [ - { - "name": "sha1", - "type": "string" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "arg", - "type": "string", - "multiple": true - } - ], - "since": "7.0.0", - "group": "scripting" - }, - "EXEC": { - "summary": "Execute all commands issued after MULTI", - "since": "1.2.0", - "group": "transactions" - }, - "EXISTS": { - "summary": "Determine if a key exists", - "complexity": "O(N) where N is the number of keys to check.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "generic" - }, - "EXPIRE": { - "summary": "Set a key's time to live in seconds", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "seconds", - "type": "integer" - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX", - "GT", - "LT" - ], - "optional": true - } - ], - "since": "1.0.0", - "group": "generic" - }, - "EXPIREAT": { - "summary": "Set the expiration for a key as a UNIX timestamp", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "timestamp", - "type": "posix time" - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX", - "GT", - "LT" - ], - "optional": true - } - ], - "since": "1.2.0", - "group": "generic" - }, - "EXPIRETIME": { - "summary": "Get the expiration Unix timestamp for a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "7.0.0", - "group": "generic" - }, - "FAILOVER": { - "summary": "Start a coordinated failover between this server and one of its replicas.", - "arguments": [ - { - "name": "target", - "type": "block", - "optional": true, - "block": [ - { - "command": "TO" - }, - { - "name": "host", - "type": "string" - }, - { - "name": "port", - "type": "integer" - }, - { - "command": "FORCE", - "optional": true - } - ] - }, - { - "command": "ABORT", - "optional": true - }, - { - "command": "TIMEOUT", - "name": "milliseconds", - "type": "integer", - "optional": true - } - ], - "since": "6.2.0", - "group": "server" - }, - "FLUSHALL": { - "summary": "Remove all keys from all databases", - "complexity": "O(N) where N is the total number of keys in all databases", - "arguments": [ - { - "name": "async", - "type": "enum", - "enum": [ - "ASYNC", - "SYNC" - ], - "optional": true - } - ], - "since": "1.0.0", - "group": "server" - }, - "FLUSHDB": { - "summary": "Remove all keys from the current database", - "complexity": "O(N) where N is the number of keys in the selected database", - "arguments": [ - { - "name": "async", - "type": "enum", - "enum": [ - "ASYNC", - "SYNC" - ], - "optional": true - } - ], - "since": "1.0.0", - "group": "server" - }, - "GEOADD": { - "summary": "Add one or more geospatial items in the geospatial index represented using a sorted set", - "complexity": "O(log(N)) for each item added, where N is the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX" - ], - "optional": true - }, - { - "name": "change", - "type": "enum", - "enum": [ - "CH" - ], - "optional": true - }, - { - "name": [ - "longitude", - "latitude", - "member" - ], - "type": [ - "double", - "double", - "string" - ], - "multiple": true - } - ], - "since": "3.2.0", - "group": "geo" - }, - "GEOHASH": { - "summary": "Returns members of a geospatial index as standard geohash strings", - "complexity": "O(log(N)) for each member requested, where N is the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "3.2.0", - "group": "geo" - }, - "GEOPOS": { - "summary": "Returns longitude and latitude of members of a geospatial index", - "complexity": "O(N) where N is the number of members requested.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "3.2.0", - "group": "geo" - }, - "GEODIST": { - "summary": "Returns the distance between two members of a geospatial index", - "complexity": "O(log(N))", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member1", - "type": "string" - }, - { - "name": "member2", - "type": "string" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ], - "optional": true - } - ], - "since": "3.2.0", - "group": "geo" - }, - "GEORADIUS": { - "summary": "Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point", - "complexity": "O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "longitude", - "type": "double" - }, - { - "name": "latitude", - "type": "double" - }, - { - "name": "radius", - "type": "double" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ] - }, - { - "name": "withcoord", - "type": "enum", - "enum": [ - "WITHCOORD" - ], - "optional": true - }, - { - "name": "withdist", - "type": "enum", - "enum": [ - "WITHDIST" - ], - "optional": true - }, - { - "name": "withhash", - "type": "enum", - "enum": [ - "WITHHASH" - ], - "optional": true - }, - { - "type": "block", - "name": "count", - "block": [ - { - "name": "count", - "command": "COUNT", - "type": "integer" - }, - { - "name": "any", - "type": "enum", - "enum": [ - "ANY" - ], - "optional": true - } - ], - "optional": true - }, - { - "name": "order", - "type": "enum", - "enum": [ - "ASC", - "DESC" - ], - "optional": true - }, - { - "command": "STORE", - "name": "key", - "type": "key", - "optional": true - }, - { - "command": "STOREDIST", - "name": "key", - "type": "key", - "optional": true - } - ], - "since": "3.2.0", - "group": "geo" - }, - "GEORADIUSBYMEMBER": { - "summary": "Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member", - "complexity": "O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string" - }, - { - "name": "radius", - "type": "double" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ] - }, - { - "name": "withcoord", - "type": "enum", - "enum": [ - "WITHCOORD" - ], - "optional": true - }, - { - "name": "withdist", - "type": "enum", - "enum": [ - "WITHDIST" - ], - "optional": true - }, - { - "name": "withhash", - "type": "enum", - "enum": [ - "WITHHASH" - ], - "optional": true - }, - { - "type": "block", - "name": "count", - "block": [ - { - "name": "count", - "command": "COUNT", - "type": "integer" - }, - { - "name": "any", - "type": "enum", - "enum": [ - "ANY" - ], - "optional": true - } - ], - "optional": true - }, - { - "name": "order", - "type": "enum", - "enum": [ - "ASC", - "DESC" - ], - "optional": true - }, - { - "command": "STORE", - "name": "key", - "type": "key", - "optional": true - }, - { - "command": "STOREDIST", - "name": "key", - "type": "key", - "optional": true - } - ], - "since": "3.2.0", - "group": "geo" - }, - "GEOSEARCH": { - "summary": "Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle.", - "complexity": "O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "FROMMEMBER", - "name": "member", - "type": "string", - "optional": true - }, - { - "command": "FROMLONLAT", - "name": [ - "longitude", - "latitude" - ], - "type": [ - "double", - "double" - ], - "optional": true - }, - { - "type": "block", - "name": "circle", - "block": [ - { - "name": "radius", - "command": "BYRADIUS", - "type": "double" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ] - } - ], - "optional": true - }, - { - "type": "block", - "name": "box", - "block": [ - { - "name": "width", - "command": "BYBOX", - "type": "double" - }, - { - "name": "height", - "type": "double" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ] - } - ], - "optional": true - }, - { - "name": "order", - "type": "enum", - "enum": [ - "ASC", - "DESC" - ], - "optional": true - }, - { - "type": "block", - "name": "count", - "block": [ - { - "name": "count", - "command": "COUNT", - "type": "integer" - }, - { - "name": "any", - "type": "enum", - "enum": [ - "ANY" - ], - "optional": true - } - ], - "optional": true - }, - { - "name": "withcoord", - "type": "enum", - "enum": [ - "WITHCOORD" - ], - "optional": true - }, - { - "name": "withdist", - "type": "enum", - "enum": [ - "WITHDIST" - ], - "optional": true - }, - { - "name": "withhash", - "type": "enum", - "enum": [ - "WITHHASH" - ], - "optional": true - } - ], - "since": "6.2", - "group": "geo" - }, - "GEOSEARCHSTORE": { - "summary": "Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle, and store the result in another key.", - "complexity": "O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "source", - "type": "key" - }, - { - "command": "FROMMEMBER", - "name": "member", - "type": "string", - "optional": true - }, - { - "command": "FROMLONLAT", - "name": [ - "longitude", - "latitude" - ], - "type": [ - "double", - "double" - ], - "optional": true - }, - { - "type": "block", - "name": "circle", - "block": [ - { - "name": "radius", - "command": "BYRADIUS", - "type": "double" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ] - } - ], - "optional": true - }, - { - "type": "block", - "name": "box", - "block": [ - { - "name": "width", - "command": "BYBOX", - "type": "double" - }, - { - "name": "height", - "type": "double" - }, - { - "name": "unit", - "type": "enum", - "enum": [ - "m", - "km", - "ft", - "mi" - ] - } - ], - "optional": true - }, - { - "name": "order", - "type": "enum", - "enum": [ - "ASC", - "DESC" - ], - "optional": true - }, - { - "type": "block", - "name": "count", - "block": [ - { - "name": "count", - "command": "COUNT", - "type": "integer" - }, - { - "name": "any", - "type": "enum", - "enum": [ - "ANY" - ], - "optional": true - } - ], - "optional": true - }, - { - "name": "storedist", - "type": "enum", - "enum": [ - "STOREDIST" - ], - "optional": true - } - ], - "since": "6.2", - "group": "geo" - }, - "GET": { - "summary": "Get the value of a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "string" - }, - "GETBIT": { - "summary": "Returns the bit value at offset in the string value stored at key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "offset", - "type": "integer" - } - ], - "since": "2.2.0", - "group": "bitmap" - }, - "GETDEL": { - "summary":"Get the value of a key and delete the key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "6.2.0", - "group": "string" - }, - "GETEX": { - "summary": "Get the value of a key and optionally set its expiration", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "expiration", - "type": "enum", - "enum": [ - "EX seconds", - "PX milliseconds", - "EXAT timestamp", - "PXAT milliseconds-timestamp", - "PERSIST" - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "string" - }, - "GETRANGE": { - "summary": "Get a substring of the string stored at a key", - "complexity": "O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "start", - "type": "integer" - }, - { - "name": "end", - "type": "integer" - } - ], - "since": "2.4.0", - "group": "string" - }, - "GETSET": { - "summary": "Set the string value of a key and return its old value", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "1.0.0", - "group": "string" - }, - "HDEL": { - "summary": "Delete one or more hash fields", - "complexity": "O(N) where N is the number of fields to be removed.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string", - "multiple": true - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HELLO": { - "summary": "Handshake with Redis", - "complexity": "O(1)", - "arguments": [ - { - "name": "arguments", - "type": "block", - "block": [ - { - "name": "protover", - "type": "integer" - }, - { - "command": "AUTH", - "name": [ - "username", - "password" - ], - "type": [ - "string", - "string" - ], - "optional": true - }, - { - "command": "SETNAME", - "name": "clientname", - "type": "string", - "optional": true - } - ], - "optional": true - } - ], - "since": "6.0.0", - "group": "connection" - }, - "HEXISTS": { - "summary": "Determine if a hash field exists", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HGET": { - "summary": "Get the value of a hash field", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HGETALL": { - "summary": "Get all the fields and values in a hash", - "complexity": "O(N) where N is the size of the hash.", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HINCRBY": { - "summary": "Increment the integer value of a hash field by the given number", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string" - }, - { - "name": "increment", - "type": "integer" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HINCRBYFLOAT": { - "summary": "Increment the float value of a hash field by the given amount", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string" - }, - { - "name": "increment", - "type": "double" - } - ], - "since": "2.6.0", - "group": "hash" - }, - "HKEYS": { - "summary": "Get all the fields in a hash", - "complexity": "O(N) where N is the size of the hash.", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HLEN": { - "summary": "Get the number of fields in a hash", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HMGET": { - "summary": "Get the values of all the given hash fields", - "complexity": "O(N) where N is the number of fields being requested.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string", - "multiple": true - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HMSET": { - "summary": "Set multiple hash fields to multiple values", - "complexity": "O(N) where N is the number of fields being set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": [ - "field", - "value" - ], - "type": [ - "string", - "string" - ], - "multiple": true - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HSET": { - "summary": "Set the string value of a hash field", - "complexity": "O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": [ - "field", - "value" - ], - "type": [ - "string", - "string" - ], - "multiple": true - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HSETNX": { - "summary": "Set the value of a hash field, only if the field does not exist", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "HRANDFIELD": { - "summary": "Get one or multiple random fields from a hash", - "complexity": "O(N) where N is the number of fields returned", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "options", - "type": "block", - "block": [ - { - "name": "count", - "type": "integer" - }, - { - "name": "withvalues", - "type": "enum", - "enum": [ - "WITHVALUES" - ], - "optional": true - } - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "hash" - }, - "HSTRLEN": { - "summary": "Get the length of the value of a hash field", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "field", - "type": "string" - } - ], - "since": "3.2.0", - "group": "hash" - }, - "HVALS": { - "summary": "Get all the values in a hash", - "complexity": "O(N) where N is the size of the hash.", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.0.0", - "group": "hash" - }, - "INCR": { - "summary": "Increment the integer value of a key by one", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "string" - }, - "INCRBY": { - "summary": "Increment the integer value of a key by the given amount", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "increment", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "string" - }, - "INCRBYFLOAT": { - "summary": "Increment the float value of a key by the given amount", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "increment", - "type": "double" - } - ], - "since": "2.6.0", - "group": "string" - }, - "INFO": { - "summary": "Get information and statistics about the server", - "arguments": [ - { - "name": "section", - "type": "string", - "optional": true - } - ], - "since": "1.0.0", - "group": "server" - }, - "LOLWUT": { - "summary": "Display some computer art and the Redis version", - "arguments": [ - { - "command": "VERSION", - "name": "version", - "type": "integer", - "optional": true - } - ], - "since": "5.0.0", - "group": "server" - }, - "KEYS": { - "summary": "Find all keys matching the given pattern", - "complexity": "O(N) with N being the number of keys in the database, under the assumption that the key names in the database and the given pattern have limited length.", - "arguments": [ - { - "name": "pattern", - "type": "pattern" - } - ], - "since": "1.0.0", - "group": "generic" - }, - "LASTSAVE": { - "summary": "Get the UNIX time stamp of the last successful save to disk", - "since": "1.0.0", - "group": "server" - }, - "LINDEX": { - "summary": "Get an element from a list by its index", - "complexity": "O(N) where N is the number of elements to traverse to get to the element at index. This makes asking for the first or the last element of the list O(1).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "index", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "list" - }, - "LINSERT": { - "summary": "Insert an element before or after another element in a list", - "complexity": "O(N) where N is the number of elements to traverse before seeing the value pivot. This means that inserting somewhere on the left end on the list (head) can be considered O(1) and inserting somewhere on the right end (tail) is O(N).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "where", - "type": "enum", - "enum": [ - "BEFORE", - "AFTER" - ] - }, - { - "name": "pivot", - "type": "string" - }, - { - "name": "element", - "type": "string" - } - ], - "since": "2.2.0", - "group": "list" - }, - "LLEN": { - "summary": "Get the length of a list", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "list" - }, - "LPOP": { - "summary": "Remove and get the first elements in a list", - "complexity": "O(N) where N is the number of elements returned", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "1.0.0", - "group": "list" - }, - "LPOS": { - "summary": "Return the index of matching elements on a list", - "complexity": "O(N) where N is the number of elements in the list, for the average case. When searching for elements near the head or the tail of the list, or when the MAXLEN option is provided, the command may run in constant time.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "element", - "type": "string" - }, - { - "command": "RANK", - "name": "rank", - "type": "integer", - "optional": true - }, - { - "command": "COUNT", - "name": "num-matches", - "type": "integer", - "optional": true - }, - { - "command": "MAXLEN", - "name": "len", - "type": "integer", - "optional": true - } - ], - "since": "6.0.6", - "group": "list" - }, - "LPUSH": { - "summary": "Prepend one or multiple elements to a list", - "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "element", - "type": "string", - "multiple": true - } - ], - "since": "1.0.0", - "group": "list" - }, - "LPUSHX": { - "summary": "Prepend an element to a list, only if the list exists", - "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "element", - "type": "string", - "multiple": true - } - ], - "since": "2.2.0", - "group": "list" - }, - "LRANGE": { - "summary": "Get a range of elements from a list", - "complexity": "O(S+N) where S is the distance of start offset from HEAD for small lists, from nearest end (HEAD or TAIL) for large lists; and N is the number of elements in the specified range.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "start", - "type": "integer" - }, - { - "name": "stop", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "list" - }, - "LREM": { - "summary": "Remove elements from a list", - "complexity": "O(N+M) where N is the length of the list and M is the number of elements removed.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer" - }, - { - "name": "element", - "type": "string" - } - ], - "since": "1.0.0", - "group": "list" - }, - "LSET": { - "summary": "Set the value of an element in a list by its index", - "complexity": "O(N) where N is the length of the list. Setting either the first or the last element of the list is O(1).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "index", - "type": "integer" - }, - { - "name": "element", - "type": "string" - } - ], - "since": "1.0.0", - "group": "list" - }, - "LTRIM": { - "summary": "Trim a list to the specified range", - "complexity": "O(N) where N is the number of elements to be removed by the operation.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "start", - "type": "integer" - }, - { - "name": "stop", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "list" - }, - "MEMORY DOCTOR": { - "summary": "Outputs memory problems report", - "since": "4.0.0", - "group": "server" - }, - "MEMORY HELP": { - "summary": "Show helpful text about the different subcommands", - "since": "4.0.0", - "group": "server" - }, - "MEMORY MALLOC-STATS": { - "summary": "Show allocator internal stats", - "since": "4.0.0", - "group": "server" - }, - "MEMORY PURGE": { - "summary": "Ask the allocator to release memory", - "since": "4.0.0", - "group": "server" - }, - "MEMORY STATS": { - "summary": "Show memory usage details", - "since": "4.0.0", - "group": "server" - }, - "MEMORY USAGE": { - "summary": "Estimate the memory usage of a key", - "complexity": "O(N) where N is the number of samples.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "SAMPLES", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "4.0.0", - "group": "server" - }, - "MGET": { - "summary": "Get the values of all the given keys", - "complexity": "O(N) where N is the number of keys to retrieve.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "string" - }, - "MIGRATE": { - "summary": "Atomically transfer a key from a Redis instance to another one.", - "complexity": "This command actually executes a DUMP+DEL in the source instance, and a RESTORE in the target instance. See the pages of these commands for time complexity. Also an O(N) data transfer between the two instances is performed.", - "arguments": [ - { - "name": "host", - "type": "string" - }, - { - "name": "port", - "type": "string" - }, - { - "name": "key", - "type": "enum", - "enum": [ - "key", - "\"\"" - ] - }, - { - "name": "destination-db", - "type": "integer" - }, - { - "name": "timeout", - "type": "integer" - }, - { - "name": "copy", - "type": "enum", - "enum": [ - "COPY" - ], - "optional": true - }, - { - "name": "replace", - "type": "enum", - "enum": [ - "REPLACE" - ], - "optional": true - }, - { - "command": "AUTH", - "name": "password", - "type": "string", - "optional": true - }, - { - "command": "AUTH2", - "name": "username password", - "type": "string", - "optional": true - }, - { - "name": "key", - "command": "KEYS", - "type": "key", - "variadic": true, - "optional": true - } - ], - "since": "2.6.0", - "group": "generic" - }, - "MODULE LIST": { - "summary": "List all modules loaded by the server", - "complexity": "O(N) where N is the number of loaded modules.", - "since": "4.0.0", - "group": "server" - }, - "MODULE LOAD": { - "summary": "Load a module", - "complexity": "O(1)", - "arguments": [ - { - "name": "path", - "type": "string" - }, - { - "name": "arg", - "type": "string", - "variadic": true, - "optional": true - } - ], - "since": "4.0.0", - "group": "server" - }, - "MODULE UNLOAD": { - "summary": "Unload a module", - "complexity": "O(1)", - "arguments": [ - { - "name": "name", - "type": "string" - } - ], - "since": "4.0.0", - "group": "server" - }, - "MONITOR": { - "summary": "Listen for all requests received by the server in real time", - "since": "1.0.0", - "group": "server" - }, - "MOVE": { - "summary": "Move a key to another database", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "db", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "generic" - }, - "MSET": { - "summary": "Set multiple keys to multiple values", - "complexity": "O(N) where N is the number of keys to set.", - "arguments": [ - { - "name": [ - "key", - "value" - ], - "type": [ - "key", - "string" - ], - "multiple": true - } - ], - "since": "1.0.1", - "group": "string" - }, - "MSETNX": { - "summary": "Set multiple keys to multiple values, only if none of the keys exist", - "complexity": "O(N) where N is the number of keys to set.", - "arguments": [ - { - "name": [ - "key", - "value" - ], - "type": [ - "key", - "string" - ], - "multiple": true - } - ], - "since": "1.0.1", - "group": "string" - }, - "MULTI": { - "summary": "Mark the start of a transaction block", - "since": "1.2.0", - "group": "transactions" - }, - "OBJECT": { - "summary": "Inspect the internals of Redis objects", - "complexity": "O(1) for all the currently implemented subcommands.", - "since": "2.2.3", - "group": "generic", - "arguments": [ - { - "name": "subcommand", - "type": "string" - }, - { - "name": "arguments", - "type": "string", - "optional": true, - "multiple": true - } - ] - }, - "PERSIST": { - "summary": "Remove the expiration from a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.2.0", - "group": "generic" - }, - "PEXPIRE": { - "summary": "Set a key's time to live in milliseconds", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "milliseconds", - "type": "integer" - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX", - "GT", - "LT" - ], - "optional": true - } - ], - "since": "2.6.0", - "group": "generic" - }, - "PEXPIREAT": { - "summary": "Set the expiration for a key as a UNIX timestamp specified in milliseconds", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "milliseconds-timestamp", - "type": "posix time" - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX", - "GT", - "LT" - ], - "optional": true - } - ], - "since": "2.6.0", - "group": "generic" - }, - "PEXPIRETIME": { - "summary": "Get the expiration Unix timestamp for a key in milliseconds", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "7.0.0", - "group": "generic" - }, - "PFADD": { - "summary": "Adds the specified elements to the specified HyperLogLog.", - "complexity": "O(1) to add every element.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "element", - "type": "string", - "optional": true, - "multiple": true - } - ], - "since": "2.8.9", - "group": "hyperloglog" - }, - "PFCOUNT": { - "summary": "Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).", - "complexity": "O(1) with a very small average constant time when called with a single key. O(N) with N being the number of keys, and much bigger constant times, when called with multiple keys.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "2.8.9", - "group": "hyperloglog" - }, - "PFMERGE": { - "summary": "Merge N different HyperLogLogs into a single one.", - "complexity": "O(N) to merge N HyperLogLogs, but with high constant times.", - "arguments": [ - { - "name": "destkey", - "type": "key" - }, - { - "name": "sourcekey", - "type": "key", - "multiple": true - } - ], - "since": "2.8.9", - "group": "hyperloglog" - }, - "PING": { - "summary": "Ping the server", - "arguments": [ - { - "name": "message", - "type": "string", - "optional": true - } - ], - "since": "1.0.0", - "group": "connection" - }, - "PSETEX": { - "summary": "Set the value and expiration in milliseconds of a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "milliseconds", - "type": "integer" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "2.6.0", - "group": "string" - }, - "PSUBSCRIBE": { - "summary": "Listen for messages published to channels matching the given patterns", - "complexity": "O(N) where N is the number of patterns the client is already subscribed to.", - "arguments": [ - { - "name": [ - "pattern" - ], - "type": [ - "pattern" - ], - "multiple": true - } - ], - "since": "2.0.0", - "group": "pubsub" - }, - "PUBSUB": { - "summary": "Inspect the state of the Pub/Sub subsystem", - "complexity": "O(N) for the CHANNELS subcommand, where N is the number of active channels, and assuming constant time pattern matching (relatively short channels and patterns). O(N) for the NUMSUB subcommand, where N is the number of requested channels. O(1) for the NUMPAT subcommand.", - "arguments": [ - { - "name": "subcommand", - "type": "string" - }, - { - "name": "argument", - "type": "string", - "optional": true, - "multiple": true - } - ], - "since": "2.8.0", - "group": "pubsub" - }, - "PTTL": { - "summary": "Get the time to live for a key in milliseconds", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.6.0", - "group": "generic" - }, - "PUBLISH": { - "summary": "Post a message to a channel", - "complexity": "O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).", - "arguments": [ - { - "name": "channel", - "type": "string" - }, - { - "name": "message", - "type": "string" - } - ], - "since": "2.0.0", - "group": "pubsub" - }, - "PUNSUBSCRIBE": { - "summary": "Stop listening for messages posted to channels matching the given patterns", - "complexity": "O(N+M) where N is the number of patterns the client is already subscribed and M is the number of total patterns subscribed in the system (by any client).", - "arguments": [ - { - "name": "pattern", - "type": "pattern", - "optional": true, - "multiple": true - } - ], - "since": "2.0.0", - "group": "pubsub" - }, - "QUIT": { - "summary": "Close the connection", - "since": "1.0.0", - "group": "connection" - }, - "RANDOMKEY": { - "summary": "Return a random key from the keyspace", - "complexity": "O(1)", - "since": "1.0.0", - "group": "generic" - }, - "READONLY": { - "summary": "Enables read queries for a connection to a cluster replica node", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "READWRITE": { - "summary": "Disables read queries for a connection to a cluster replica node", - "complexity": "O(1)", - "since": "3.0.0", - "group": "cluster" - }, - "RENAME": { - "summary": "Rename a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "newkey", - "type": "key" - } - ], - "since": "1.0.0", - "group": "generic" - }, - "RENAMENX": { - "summary": "Rename a key, only if the new key does not exist", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "newkey", - "type": "key" - } - ], - "since": "1.0.0", - "group": "generic" - }, - "RESET": { - "summary": "Reset the connection", - "since": "6.2", - "group": "connection" - }, - "RESTORE": { - "summary": "Create a key using the provided serialized value, previously obtained using DUMP.", - "complexity": "O(1) to create the new key and additional O(N*M) to reconstruct the serialized value, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1). However for sorted set values the complexity is O(N*M*log(N)) because inserting values into sorted sets is O(log(N)).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "ttl", - "type": "integer" - }, - { - "name": "serialized-value", - "type": "string" - }, - { - "name": "replace", - "type": "enum", - "enum": [ - "REPLACE" - ], - "optional": true - }, - { - "name": "absttl", - "type": "enum", - "enum": [ - "ABSTTL" - ], - "optional": true - }, - { - "command": "IDLETIME", - "name": "seconds", - "type": "integer", - "optional": true - }, - { - "command": "FREQ", - "name": "frequency", - "type": "integer", - "optional": true - } - ], - "since": "2.6.0", - "group": "generic" - }, - "ROLE": { - "summary": "Return the role of the instance in the context of replication", - "since": "2.8.12", - "group": "server" - }, - "RPOP": { - "summary": "Remove and get the last elements in a list", - "complexity": "O(N) where N is the number of elements returned", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "1.0.0", - "group": "list" - }, - "RPOPLPUSH": { - "summary": "Remove the last element in a list, prepend it to another list and return it", - "complexity": "O(1)", - "arguments": [ - { - "name": "source", - "type": "key" - }, - { - "name": "destination", - "type": "key" - } - ], - "since": "1.2.0", - "group": "list" - }, - "LMOVE": { - "summary": "Pop an element from a list, push it to another list and return it", - "complexity": "O(1)", - "arguments": [ - { - "name": "source", - "type": "key" - }, - { - "name": "destination", - "type": "key" - }, - { - "name": "wherefrom", - "type": "enum", - "enum": [ - "LEFT", - "RIGHT" - ] - }, - { - "name": "whereto", - "type": "enum", - "enum": [ - "LEFT", - "RIGHT" - ] - } - ], - "since": "6.2.0", - "group": "list" - }, - "RPUSH": { - "summary": "Append one or multiple elements to a list", - "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "element", - "type": "string", - "multiple": true - } - ], - "since": "1.0.0", - "group": "list" - }, - "RPUSHX": { - "summary": "Append an element to a list, only if the list exists", - "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "element", - "type": "string", - "multiple": true - } - ], - "since": "2.2.0", - "group": "list" - }, - "SADD": { - "summary": "Add one or more members to a set", - "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SAVE": { - "summary": "Synchronously save the dataset to disk", - "since": "1.0.0", - "group": "server" - }, - "SCARD": { - "summary": "Get the number of members in a set", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "set" - }, - "SCRIPT DEBUG": { - "summary": "Set the debug mode for executed scripts.", - "complexity": "O(1)", - "arguments": [ - { - "name": "mode", - "type": "enum", - "enum": [ - "YES", - "SYNC", - "NO" - ] - } - ], - "since": "3.2.0", - "group": "scripting" - }, - "SCRIPT EXISTS": { - "summary": "Check existence of scripts in the script cache.", - "complexity": "O(N) with N being the number of scripts to check (so checking a single script is an O(1) operation).", - "arguments": [ - { - "name": "sha1", - "type": "string", - "multiple": true - } - ], - "since": "2.6.0", - "group": "scripting" - }, - "SCRIPT FLUSH": { - "summary": "Remove all the scripts from the script cache.", - "arguments": [ - { - "name": "async", - "type": "enum", - "enum": [ - "ASYNC", - "SYNC" - ], - "optional": true - } - ], - "complexity": "O(N) with N being the number of scripts in cache", - "since": "2.6.0", - "group": "scripting" - }, - "SCRIPT KILL": { - "summary": "Kill the script currently in execution.", - "complexity": "O(1)", - "since": "2.6.0", - "group": "scripting" - }, - "SCRIPT LOAD": { - "summary": "Load the specified Lua script into the script cache.", - "complexity": "O(N) with N being the length in bytes of the script body.", - "arguments": [ - { - "name": "script", - "type": "string" - } - ], - "since": "2.6.0", - "group": "scripting" - }, - "SDIFF": { - "summary": "Subtract multiple sets", - "complexity": "O(N) where N is the total number of elements in all given sets.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SDIFFSTORE": { - "summary": "Subtract multiple sets and store the resulting set in a key", - "complexity": "O(N) where N is the total number of elements in all given sets.", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SELECT": { - "summary": "Change the selected database for the current connection", - "arguments": [ - { - "name": "index", - "type": "integer" - } - ], - "since": "1.0.0", - "group": "connection" - }, - "SET": { - "summary": "Set the string value of a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "value", - "type": "string" - }, - { - "name": "expiration", - "type": "enum", - "enum": [ - "EX seconds", - "PX milliseconds", - "EXAT timestamp", - "PXAT milliseconds-timestamp", - "KEEPTTL" - ], - "optional": true - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX" - ], - "optional": true - }, - { - "name": "get", - "type": "enum", - "enum": [ - "GET" - ], - "optional": true - } - ], - "since": "1.0.0", - "group": "string" - }, - "SETBIT": { - "summary": "Sets or clears the bit at offset in the string value stored at key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "offset", - "type": "integer" - }, - { - "name": "value", - "type": "integer" - } - ], - "since": "2.2.0", - "group": "bitmap" - }, - "SETEX": { - "summary": "Set the value and expiration of a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "seconds", - "type": "integer" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "2.0.0", - "group": "string" - }, - "SETNX": { - "summary": "Set the value of a key, only if the key does not exist", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "1.0.0", - "group": "string" - }, - "SETRANGE": { - "summary": "Overwrite part of a string at key starting at the specified offset", - "complexity": "O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "offset", - "type": "integer" - }, - { - "name": "value", - "type": "string" - } - ], - "since": "2.2.0", - "group": "string" - }, - "SHUTDOWN": { - "summary": "Synchronously save the dataset to disk and then shut down the server", - "arguments": [ - { - "name": "save-mode", - "type": "enum", - "enum": [ - "NOSAVE", - "SAVE" - ], - "optional": true - } - ], - "since": "1.0.0", - "group": "server" - }, - "SINTER": { - "summary": "Intersect multiple sets", - "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SINTERCARD": { - "summary": "Intersect multiple sets and return the cardinality of the result", - "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "7.0.0", - "group": "set" - }, - "SINTERSTORE": { - "summary": "Intersect multiple sets and store the resulting set in a key", - "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SISMEMBER": { - "summary": "Determine if a given value is a member of a set", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string" - } - ], - "since": "1.0.0", - "group": "set" - }, - "SMISMEMBER": { - "summary": "Returns the membership associated with the given elements for a set", - "complexity": "O(N) where N is the number of elements being checked for membership", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "6.2.0", - "group": "set" - }, - "SLAVEOF": { - "summary": "Make the server a replica of another instance, or promote it as master. Deprecated starting with Redis 5. Use REPLICAOF instead.", - "arguments": [ - { - "name": "host", - "type": "string" - }, - { - "name": "port", - "type": "string" - } - ], - "since": "1.0.0", - "group": "server" - }, - "REPLICAOF": { - "summary": "Make the server a replica of another instance, or promote it as master.", - "arguments": [ - { - "name": "host", - "type": "string" - }, - { - "name": "port", - "type": "string" - } - ], - "since": "5.0.0", - "group": "server" - }, - "SLOWLOG": { - "summary": "Manages the Redis slow queries log", - "arguments": [ - { - "name": "subcommand", - "type": "string" - }, - { - "name": "argument", - "type": "string", - "optional": true - } - ], - "since": "2.2.12", - "group": "server" - }, - "SMEMBERS": { - "summary": "Get all the members in a set", - "complexity": "O(N) where N is the set cardinality.", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "set" - }, - "SMOVE": { - "summary": "Move a member from one set to another", - "complexity": "O(1)", - "arguments": [ - { - "name": "source", - "type": "key" - }, - { - "name": "destination", - "type": "key" - }, - { - "name": "member", - "type": "string" - } - ], - "since": "1.0.0", - "group": "set" - }, - "SORT": { - "summary": "Sort the elements in a list, set or sorted set", - "complexity": "O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is O(N).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "BY", - "name": "pattern", - "type": "pattern", - "optional": true - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - }, - { - "command": "GET", - "name": "pattern", - "type": "string", - "optional": true, - "multiple": true - }, - { - "name": "order", - "type": "enum", - "enum": [ - "ASC", - "DESC" - ], - "optional": true - }, - { - "name": "sorting", - "type": "enum", - "enum": [ - "ALPHA" - ], - "optional": true - }, - { - "command": "STORE", - "name": "destination", - "type": "key", - "optional": true - } - ], - "since": "1.0.0", - "group": "generic" - }, - "SORT_RO": { - "summary": "Sort the elements in a list, set or sorted set. Read-only variant of SORT.", - "complexity": "O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is O(N).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "BY", - "name": "pattern", - "type": "pattern", - "optional": true - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - }, - { - "command": "GET", - "name": "pattern", - "type": "string", - "optional": true, - "multiple": true - }, - { - "name": "order", - "type": "enum", - "enum": [ - "ASC", - "DESC" - ], - "optional": true - }, - { - "name": "sorting", - "type": "enum", - "enum": [ - "ALPHA" - ], - "optional": true - } - ], - "since": "7.0.0", - "group": "generic" - }, - "SPOP": { - "summary": "Remove and return one or multiple random members from a set", - "complexity": "Without the count argument O(1), otherwise O(N) where N is the value of the passed count.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SRANDMEMBER": { - "summary": "Get one or multiple random members from a set", - "complexity": "Without the count argument O(1), otherwise O(N) where N is the absolute value of the passed count.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SREM": { - "summary": "Remove one or more members from a set", - "complexity": "O(N) where N is the number of members to be removed.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "STRALGO": { - "summary": "Run algorithms (currently LCS) against strings", - "complexity": "For LCS O(strlen(s1)*strlen(s2))", - "arguments": [ - { - "name": "algorithm", - "type": "enum", - "enum": [ - "LCS" - ] - }, - { - "name": "algo-specific-argument", - "type": "string", - "multiple": true - } - ], - "since": "6.0.0", - "group": "string" - }, - "STRLEN": { - "summary": "Get the length of the value stored in a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "2.2.0", - "group": "string" - }, - "SUBSCRIBE": { - "summary": "Listen for messages published to the given channels", - "complexity": "O(N) where N is the number of channels to subscribe to.", - "arguments": [ - { - "name": "channel", - "type": "string", - "multiple": true - } - ], - "since": "2.0.0", - "group": "pubsub" - }, - "SUNION": { - "summary": "Add multiple sets", - "complexity": "O(N) where N is the total number of elements in all given sets.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SUNIONSTORE": { - "summary": "Add multiple sets and store the resulting set in a key", - "complexity": "O(N) where N is the total number of elements in all given sets.", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "1.0.0", - "group": "set" - }, - "SWAPDB": { - "summary": "Swaps two Redis databases", - "complexity": "O(N) where N is the count of clients watching or blocking on keys from both databases.", - "arguments": [ - { - "name": "index1", - "type": "integer" - }, - { - "name": "index2", - "type": "integer" - } - ], - "since": "4.0.0", - "group": "server" - }, - "SYNC": { - "summary": "Internal command used for replication", - "since": "1.0.0", - "group": "server" - }, - "PSYNC": { - "summary": "Internal command used for replication", - "arguments": [ - { - "name": "replicationid", - "type": "integer" - }, - { - "name": "offset", - "type": "integer" - } - ], - "since": "2.8.0", - "group": "server" - }, - "TIME": { - "summary": "Return the current server time", - "complexity": "O(1)", - "since": "2.6.0", - "group": "server" - }, - "TOUCH": { - "summary": "Alters the last access time of a key(s). Returns the number of existing keys specified.", - "complexity": "O(N) where N is the number of keys that will be touched.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "3.2.1", - "group": "generic" - }, - "TTL": { - "summary": "Get the time to live for a key in seconds", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "generic" - }, - "TYPE": { - "summary": "Determine the type stored at key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.0.0", - "group": "generic" - }, - "UNSUBSCRIBE": { - "summary": "Stop listening for messages posted to the given channels", - "complexity": "O(N) where N is the number of clients already subscribed to a channel.", - "arguments": [ - { - "name": "channel", - "type": "string", - "optional": true, - "multiple": true - } - ], - "since": "2.0.0", - "group": "pubsub" - }, - "UNLINK": { - "summary": "Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.", - "complexity": "O(1) for each key removed regardless of its size. Then the command does O(N) work in a different thread in order to reclaim memory, where N is the number of allocations the deleted objects where composed of.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "4.0.0", - "group": "generic" - }, - "UNWATCH": { - "summary": "Forget about all watched keys", - "complexity": "O(1)", - "since": "2.2.0", - "group": "transactions" - }, - "WAIT": { - "summary": "Wait for the synchronous replication of all the write commands sent in the context of the current connection", - "complexity": "O(1)", - "arguments": [ - { - "name": "numreplicas", - "type": "integer" - }, - { - "name": "timeout", - "type": "integer" - } - ], - "since": "3.0.0", - "group": "generic" - }, - "WATCH": { - "summary": "Watch the given keys to determine execution of the MULTI/EXEC block", - "complexity": "O(1) for every key.", - "arguments": [ - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "2.2.0", - "group": "transactions" - }, - "ZADD": { - "summary": "Add one or more members to a sorted set, or update its score if it already exists", - "complexity": "O(log(N)) for each item added, where N is the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "condition", - "type": "enum", - "enum": [ - "NX", - "XX" - ], - "optional": true - }, - { - "name": "comparison", - "type": "enum", - "enum": [ - "GT", - "LT" - ], - "optional": true - }, - { - "name": "change", - "type": "enum", - "enum": [ - "CH" - ], - "optional": true - }, - { - "name": "increment", - "type": "enum", - "enum": [ - "INCR" - ], - "optional": true - }, - { - "name": [ - "score", - "member" - ], - "type": [ - "double", - "string" - ], - "multiple": true - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZCARD": { - "summary": "Get the number of members in a sorted set", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZCOUNT": { - "summary": "Count the members in a sorted set with scores within the given values", - "complexity": "O(log(N)) with N being the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "double" - }, - { - "name": "max", - "type": "double" - } - ], - "since": "2.0.0", - "group": "sorted_set" - }, - "ZDIFF": { - "summary": "Subtract multiple sorted sets", - "complexity": "O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.", - "arguments": [ - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZDIFFSTORE": { - "summary": "Subtract multiple sorted sets and store the resulting sorted set in a new key", - "complexity": "O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZINCRBY": { - "summary": "Increment the score of a member in a sorted set", - "complexity": "O(log(N)) where N is the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "increment", - "type": "integer" - }, - { - "name": "member", - "type": "string" - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZINTER": { - "summary": "Intersect multiple sorted sets", - "complexity": "O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.", - "arguments": [ - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "command": "WEIGHTS", - "name": "weight", - "type": "integer", - "variadic": true, - "optional": true - }, - { - "command": "AGGREGATE", - "name": "aggregate", - "type": "enum", - "enum": [ - "SUM", - "MIN", - "MAX" - ], - "optional": true - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZINTERCARD": { - "summary": "Intersect multiple sorted sets and return the cardinality of the result", - "complexity": "O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets.", - "arguments": [ - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - } - ], - "since": "7.0.0", - "group": "sorted_set" - }, - "ZINTERSTORE": { - "summary": "Intersect multiple sorted sets and store the resulting sorted set in a new key", - "complexity": "O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "command": "WEIGHTS", - "name": "weight", - "type": "integer", - "variadic": true, - "optional": true - }, - { - "command": "AGGREGATE", - "name": "aggregate", - "type": "enum", - "enum": [ - "SUM", - "MIN", - "MAX" - ], - "optional": true - } - ], - "since": "2.0.0", - "group": "sorted_set" - }, - "ZLEXCOUNT": { - "summary": "Count the number of members in a sorted set between a given lexicographical range", - "complexity": "O(log(N)) with N being the number of elements in the sorted set.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "string" - }, - { - "name": "max", - "type": "string" - } - ], - "since": "2.8.9", - "group": "sorted_set" - }, - "ZPOPMAX": { - "summary": "Remove and return members with the highest scores in a sorted set", - "complexity": "O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "5.0.0", - "group": "sorted_set" - }, - "ZPOPMIN": { - "summary": "Remove and return members with the lowest scores in a sorted set", - "complexity": "O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "5.0.0", - "group": "sorted_set" - }, - "ZRANDMEMBER": { - "summary": "Get one or multiple random elements from a sorted set", - "complexity": "O(N) where N is the number of elements returned", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "options", - "type": "block", - "block": [ - { - "name": "count", - "type": "integer" - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - } - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZRANGESTORE": { - "summary": "Store a range of members from sorted set into another key", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements stored into the destination key.", - "arguments": [ - { - "name": "dst", - "type": "key" - }, - { - "name": "src", - "type": "key" - }, - { - "name": "min", - "type": "string" - }, - { - "name": "max", - "type": "string" - }, - { - "name": "sortby", - "type": "enum", - "enum": [ - "BYSCORE", - "BYLEX" - ], - "optional": true - }, - { - "name": "rev", - "type": "enum", - "enum": [ - "REV" - ], - "optional": true - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZRANGE": { - "summary": "Return a range of members in a sorted set", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "string" - }, - { - "name": "max", - "type": "string" - }, - { - "name": "sortby", - "type": "enum", - "enum": [ - "BYSCORE", - "BYLEX" - ], - "optional": true - }, - { - "name": "rev", - "type": "enum", - "enum": [ - "REV" - ], - "optional": true - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZRANGEBYLEX": { - "summary": "Return a range of members in a sorted set, by lexicographical range", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "string" - }, - { - "name": "max", - "type": "string" - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - } - ], - "since": "2.8.9", - "group": "sorted_set" - }, - "ZREVRANGEBYLEX": { - "summary": "Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "max", - "type": "string" - }, - { - "name": "min", - "type": "string" - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - } - ], - "since": "2.8.9", - "group": "sorted_set" - }, - "ZRANGEBYSCORE": { - "summary": "Return a range of members in a sorted set, by score", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "double" - }, - { - "name": "max", - "type": "double" - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - } - ], - "since": "1.0.5", - "group": "sorted_set" - }, - "ZRANK": { - "summary": "Determine the index of a member in a sorted set", - "complexity": "O(log(N))", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string" - } - ], - "since": "2.0.0", - "group": "sorted_set" - }, - "ZREM": { - "summary": "Remove one or more members from a sorted set", - "complexity": "O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZREMRANGEBYLEX": { - "summary": "Remove all members in a sorted set between the given lexicographical range", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "string" - }, - { - "name": "max", - "type": "string" - } - ], - "since": "2.8.9", - "group": "sorted_set" - }, - "ZREMRANGEBYRANK": { - "summary": "Remove all members in a sorted set within the given indexes", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "start", - "type": "integer" - }, - { - "name": "stop", - "type": "integer" - } - ], - "since": "2.0.0", - "group": "sorted_set" - }, - "ZREMRANGEBYSCORE": { - "summary": "Remove all members in a sorted set within the given scores", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "min", - "type": "double" - }, - { - "name": "max", - "type": "double" - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZREVRANGE": { - "summary": "Return a range of members in a sorted set, by index, with scores ordered from high to low", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "start", - "type": "integer" - }, - { - "name": "stop", - "type": "integer" - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZREVRANGEBYSCORE": { - "summary": "Return a range of members in a sorted set, by score, with scores ordered from high to low", - "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "max", - "type": "double" - }, - { - "name": "min", - "type": "double" - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - }, - { - "command": "LIMIT", - "name": [ - "offset", - "count" - ], - "type": [ - "integer", - "integer" - ], - "optional": true - } - ], - "since": "2.2.0", - "group": "sorted_set" - }, - "ZREVRANK": { - "summary": "Determine the index of a member in a sorted set, with scores ordered from high to low", - "complexity": "O(log(N))", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string" - } - ], - "since": "2.0.0", - "group": "sorted_set" - }, - "ZSCORE": { - "summary": "Get the score associated with the given member in a sorted set", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string" - } - ], - "since": "1.2.0", - "group": "sorted_set" - }, - "ZUNION": { - "summary": "Add multiple sorted sets", - "complexity": "O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.", - "arguments": [ - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "command": "WEIGHTS", - "name": "weight", - "type": "integer", - "variadic": true, - "optional": true - }, - { - "command": "AGGREGATE", - "name": "aggregate", - "type": "enum", - "enum": [ - "SUM", - "MIN", - "MAX" - ], - "optional": true - }, - { - "name": "withscores", - "type": "enum", - "enum": [ - "WITHSCORES" - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZMSCORE": { - "summary": "Get the score associated with the given members in a sorted set", - "complexity": "O(N) where N is the number of members being requested.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "member", - "type": "string", - "multiple": true - } - ], - "since": "6.2.0", - "group": "sorted_set" - }, - "ZUNIONSTORE": { - "summary": "Add multiple sorted sets and store the resulting sorted set in a new key", - "complexity": "O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.", - "arguments": [ - { - "name": "destination", - "type": "key" - }, - { - "name": "numkeys", - "type": "integer" - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "command": "WEIGHTS", - "name": "weight", - "type": "integer", - "variadic": true, - "optional": true - }, - { - "command": "AGGREGATE", - "name": "aggregate", - "type": "enum", - "enum": [ - "SUM", - "MIN", - "MAX" - ], - "optional": true - } - ], - "since": "2.0.0", - "group": "sorted_set" - }, - "SCAN": { - "summary": "Incrementally iterate the keys space", - "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", - "arguments": [ - { - "name": "cursor", - "type": "integer" - }, - { - "command": "MATCH", - "name": "pattern", - "type": "pattern", - "optional": true - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - }, - { - "command": "TYPE", - "name": "type", - "type": "string", - "optional": true - } - ], - "since": "2.8.0", - "group": "generic" - }, - "SSCAN": { - "summary": "Incrementally iterate Set elements", - "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection..", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "cursor", - "type": "integer" - }, - { - "command": "MATCH", - "name": "pattern", - "type": "pattern", - "optional": true - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "2.8.0", - "group": "set" - }, - "HSCAN": { - "summary": "Incrementally iterate hash fields and associated values", - "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection..", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "cursor", - "type": "integer" - }, - { - "command": "MATCH", - "name": "pattern", - "type": "pattern", - "optional": true - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "2.8.0", - "group": "hash" - }, - "ZSCAN": { - "summary": "Incrementally iterate sorted sets elements and associated scores", - "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection..", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "cursor", - "type": "integer" - }, - { - "command": "MATCH", - "name": "pattern", - "type": "pattern", - "optional": true - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "2.8.0", - "group": "sorted_set" - }, - "XINFO": { - "summary": "Get information on streams and consumer groups", - "complexity": "O(N) with N being the number of returned items for the subcommands CONSUMERS and GROUPS. The STREAM subcommand is O(log N) with N being the number of items in the stream.", - "arguments": [ - { - "command": "CONSUMERS", - "name": [ - "key", - "groupname" - ], - "type": [ - "key", - "string" - ], - "optional": true - }, - { - "command": "GROUPS", - "name": "key", - "type": "key", - "optional": true - }, - { - "command": "STREAM", - "name": "key", - "type": "key", - "optional": true - }, - { - "name": "help", - "type": "enum", - "enum": [ - "HELP" - ], - "optional": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XADD": { - "summary": "Appends a new entry to a stream", - "complexity": "O(1) when adding a new entry, O(N) when trimming where N being the number of entires evicted.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "command": "NOMKSTREAM", - "optional": true - }, - { - "name": "trim", - "type": "block", - "optional": true, - "block": [ - { - "name": "strategy", - "type": "enum", - "enum": [ - "MAXLEN", - "MINID" - ] - }, - { - "name": "operator", - "type": "enum", - "enum": [ - "=", - "~" - ], - "optional": true - }, - { - "name": "threshold", - "type": "string" - }, - { - "command": "LIMIT", - "name": "count", - "type": "integer", - "optional": true - } - ] - }, - { - "type": "enum", - "enum": [ - "*", - "ID" - ] - }, - { - "name": [ - "field", - "value" - ], - "type": [ - "string", - "string" - ], - "multiple": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XTRIM": { - "summary": "Trims the stream to (approximately if '~' is passed) a certain size", - "complexity": "O(N), with N being the number of evicted entries. Constant times are very small however, since entries are organized in macro nodes containing multiple entries that can be released with a single deallocation.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "trim", - "type": "block", - "block": [ - { - "name": "strategy", - "type": "enum", - "enum": [ - "MAXLEN", - "MINID" - ] - }, - { - "name": "operator", - "type": "enum", - "enum": [ - "=", - "~" - ], - "optional": true - }, - { - "name": "threshold", - "type": "string" - }, - { - "command": "LIMIT", - "name": "count", - "type": "integer", - "optional": true - } - ] - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XDEL": { - "summary": "Removes the specified entries from the stream. Returns the number of items actually deleted, that may be different from the number of IDs passed in case certain IDs do not exist.", - "complexity": "O(1) for each single item to delete in the stream, regardless of the stream size.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "ID", - "type": "string", - "multiple": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XRANGE": { - "summary": "Return a range of elements in a stream, with IDs matching the specified IDs interval", - "complexity": "O(N) with N being the number of elements being returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "start", - "type": "string" - }, - { - "name": "end", - "type": "string" - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XREVRANGE": { - "summary": "Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE", - "complexity": "O(N) with N being the number of elements returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "end", - "type": "string" - }, - { - "name": "start", - "type": "string" - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XLEN": { - "summary": "Return the number of entries in a stream", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XREAD": { - "summary": "Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.", - "complexity": "For each stream mentioned: O(N) with N being the number of elements being returned, it means that XREAD-ing with a fixed COUNT is O(1). Note that when the BLOCK option is used, XADD will pay O(M) time in order to serve the M clients blocked on the stream getting new data.", - "arguments": [ - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - }, - { - "command": "BLOCK", - "name": "milliseconds", - "type": "integer", - "optional": true - }, - { - "name": "streams", - "type": "enum", - "enum": [ - "STREAMS" - ] - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "ID", - "type": "string", - "multiple": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XGROUP": { - "summary": "Create, destroy, and manage consumer groups.", - "complexity": "O(1) for all the subcommands, with the exception of the DESTROY subcommand which takes an additional O(M) time in order to delete the M entries inside the consumer group pending entries list (PEL).", - "arguments": [ - { - "name": "create", - "type": "block", - "block": [ - { - "command": "CREATE", - "name": [ - "key", - "groupname" - ], - "type": [ - "key", - "string" - ] - }, - { - "name": "id", - "type": "enum", - "enum": [ - "ID", - "$" - ] - }, - { - "command": "MKSTREAM", - "optional": true - } - ], - "optional": true - }, - { - "name": "setid", - "type": "block", - "block": [ - { - "command": "SETID", - "name": [ - "key", - "groupname" - ], - "type": [ - "key", - "string" - ] - }, - { - "name": "id", - "type": "enum", - "enum": [ - "ID", - "$" - ] - } - ], - "optional": true - }, - { - "command": "DESTROY", - "name": [ - "key", - "groupname" - ], - "type": [ - "key", - "string" - ], - "optional": true - }, - { - "command": "CREATECONSUMER", - "name": [ - "key", - "groupname", - "consumername" - ], - "type": [ - "key", - "string", - "string" - ], - "optional": true - }, - { - "command": "DELCONSUMER", - "name": [ - "key", - "groupname", - "consumername" - ], - "type": [ - "key", - "string", - "string" - ], - "optional": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XREADGROUP": { - "summary": "Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.", - "complexity": "For each stream mentioned: O(M) with M being the number of elements returned. If M is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1). On the other side when XREADGROUP blocks, XADD will pay the O(N) time in order to serve the N clients blocked on the stream getting new data.", - "arguments": [ - { - "command": "GROUP", - "name": [ - "group", - "consumer" - ], - "type": [ - "string", - "string" - ] - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - }, - { - "command": "BLOCK", - "name": "milliseconds", - "type": "integer", - "optional": true - }, - { - "name": "noack", - "type": "enum", - "enum": [ - "NOACK" - ], - "optional": true - }, - { - "name": "streams", - "type": "enum", - "enum": [ - "STREAMS" - ] - }, - { - "name": "key", - "type": "key", - "multiple": true - }, - { - "name": "ID", - "type": "string", - "multiple": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XACK": { - "summary": "Marks a pending message as correctly processed, effectively removing it from the pending entries list of the consumer group. Return value of the command is the number of messages successfully acknowledged, that is, the IDs we were actually able to resolve in the PEL.", - "complexity": "O(1) for each message ID processed.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "group", - "type": "string" - }, - { - "name": "ID", - "type": "string", - "multiple": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XCLAIM": { - "summary": "Changes (or acquires) ownership of a message in a consumer group, as if the message was delivered to the specified consumer.", - "complexity": "O(log N) with N being the number of messages in the PEL of the consumer group.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "group", - "type": "string" - }, - { - "name": "consumer", - "type": "string" - }, - { - "name": "min-idle-time", - "type": "string" - }, - { - "name": "ID", - "type": "string", - "multiple": true - }, - { - "command": "IDLE", - "name": "ms", - "type": "integer", - "optional": true - }, - { - "command": "TIME", - "name": "ms-unix-time", - "type": "integer", - "optional": true - }, - { - "command": "RETRYCOUNT", - "name": "count", - "type": "integer", - "optional": true - }, - { - "name": "force", - "enum": [ - "FORCE" - ], - "optional": true - }, - { - "name": "justid", - "enum": [ - "JUSTID" - ], - "optional": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "XAUTOCLAIM": { - "summary": "Changes (or acquires) ownership of messages in a consumer group, as if the messages were delivered to the specified consumer.", - "complexity": "O(1) if COUNT is small.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "group", - "type": "string" - }, - { - "name": "consumer", - "type": "string" - }, - { - "name": "min-idle-time", - "type": "string" - }, - { - "name": "start", - "type": "string" - }, - { - "command": "COUNT", - "name": "count", - "type": "integer", - "optional": true - }, - { - "name": "justid", - "enum": [ - "JUSTID" - ], - "optional": true - } - ], - "since": "6.2.0", - "group": "stream" - }, - "XPENDING": { - "summary": "Return information and entries from a stream consumer group pending entries list, that are messages fetched but never acknowledged.", - "complexity": "O(N) with N being the number of elements returned, so asking for a small fixed number of entries per call is O(1). O(M), where M is the total number of entries scanned when used with the IDLE filter. When the command returns just the summary and the list of consumers is small, it runs in O(1) time; otherwise, an additional O(N) time for iterating every consumer.", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "group", - "type": "string" - }, - { - "type": "block", - "name": "filters", - "block": [ - { - "command": "IDLE", - "name": "min-idle-time", - "type": "integer", - "optional": true - }, - { - "name": "start", - "type": "string" - }, - { - "name": "end", - "type": "string" - }, - { - "name": "count", - "type": "integer" - }, - { - "name": "consumer", - "type": "string", - "optional": true - } - ], - "optional": true - } - ], - "since": "5.0.0", - "group": "stream" - }, - "LATENCY DOCTOR": { - "summary": "Return a human readable latency analysis report.", - "since": "2.8.13", - "group": "server" - }, - "LATENCY GRAPH": { - "summary": "Return a latency graph for the event.", - "arguments": [ - { - "name": "event", - "type": "string" - } - ], - "since": "2.8.13", - "group": "server" - }, - "LATENCY HISTORY": { - "summary": "Return timestamp-latency samples for the event.", - "arguments": [ - { - "name": "event", - "type": "string" - } - ], - "since": "2.8.13", - "group": "server" - }, - "LATENCY LATEST": { - "summary": "Return the latest latency samples for all events.", - "since": "2.8.13", - "group": "server" - }, - "LATENCY RESET": { - "summary": "Reset latency data for one or more events.", - "arguments": [ - { - "name": "event", - "type": "string", - "optional": true, - "multiple": true - } - ], - "since": "2.8.13", - "group": "server" - }, - "LATENCY HELP": { - "summary": "Show helpful text about the different subcommands.", - "since": "2.8.13", - "group": "server" - } -} diff --git a/redisinsight/api/src/constants/commands/redijson.json b/redisinsight/api/src/constants/commands/redijson.json deleted file mode 100644 index 6d3d8466cc..0000000000 --- a/redisinsight/api/src/constants/commands/redijson.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "JSON.DEL": { - "summary": "Deletes a value", - "complexity": "O(N), where N is the size of the deleted value", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "path", - "type": "json path string", - "optional": true - } - ], - "since": "1.0.0", - "group": "json" - }, - "JSON.GET": { - "summary": "Gets the value at one or more paths in JSON serialized form", - "complexity": "O(N), where N is the size of the value", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "indent", - "type": "string", - "optional": true - }, - { - "name": "newline", - "type": "string", - "optional": true - }, - { - "name": "space", - "type": "string", - "optional": true - }, - { - "name": "escape", - "type": "enum", - "enum": [ - "NOESCAPE" - ], - "optional": true - }, - { - "name": "paths", - "type": "json path string", - "optional": true - } - ], - "since": "1.0.0", - "group": "json" - } -} diff --git a/redisinsight/api/src/constants/commands/redisai.json b/redisinsight/api/src/constants/commands/redisai.json deleted file mode 100644 index 8eb9eeafc7..0000000000 --- a/redisinsight/api/src/constants/commands/redisai.json +++ /dev/null @@ -1,420 +0,0 @@ -{ - "AI.TENSORSET": - { - "summary": "stores a tensor as the value of a key.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "type", - "type": "enum", - "enum": [ - "FLOAT" , "DOUBLE" , "INT8" , "INT16" , "INT32" , "INT64" , "UINT8", "UINT16", "STRING", "BOOL" - ] - }, - { - "name": "shape", - "type": "integer", - "multiple": true - }, - { - "name": "blob", - "command": "BLOB", - "type": "string", - "optional": true - }, - { - "name": "value", - "command": "VALUES", - "type": "string", - "multiple": true, - "optional": true - } - - ], - "since": "1.2.5", - "group": "tensor" - }, - "AI.TENSORGET": - { - "summary": "returns a tensor stored as key's value.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "meta", - "type": "enum", - "enum": [ - "META" - ] - }, - { - "name": "format", - "type": "enum", - "enum": [ - "BLOB", "VALUES" - ], - "optional": true - } - - ], - "since": "1.2.5", - "group": "tensor" - }, - "AI.MODELSETORE": - { - "summary": "stores a model as the value of a key", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "backend", - "type": "enum", - "enum":["TF", "TORCH", "ONNX"] - }, - { - "name": "device", - "type": "enum", - "enum":["CPU", "GPU"] - }, - { - "name": "tag", - "command": "TAG", - "type": "string", - "optional": true - }, - { - "name": "batchsize", - "command": "BATCHSIZE ", - "type": "integer", - "optional": true - }, - { - "name": "minbatchsize", - "command": "BATCHSIZE ", - "type": "integer", - "optional": true - }, - { - "name": "minbatchtimeout", - "command": "MINBATCHTIMEOUT ", - "type": "integer", - "optional": true - }, - { - "type": "block", - "optional": true, - "block": [ - { - "name": "input_count", - "type": "integer", - "command":"INPUTS" - }, - { - "name": "input", - "type": "string", - "multiple": true - } - - ] - }, - { - "type": "block", - "optional": true, - "block": [ - { - "name": "output_count", - "type": "integer", - "command":"OUTPUTS" - }, - { - "name": "output", - "type": "string", - "multiple": true - } - - ] - }, - { - "name": "blob", - "command": "BLOB", - "type": "string", - "optional": true - } - - ], - "since": "1.2.5", - "group": "model" - }, - "AI.MODELGET": { - "summary": "returns a model's metadata and blob stored as a key's value.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "meta", - "type": "enum", - "enum": [ - "META" - ], - "optional": true - }, - { - "name": "blob", - "type": "enum", - "enum": [ - "BLOB" - ], - "optional": true - } - ], - "since": "1.2.5", - "group": "model" - }, - "AI.MODELDEL": - { - "summary": "deletes a model stored as a key's value.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.2.5", - "group": "model" - }, - "AI.MODELEXECUTE": - { - "summary": "runs a model stored as a key's value using its specified backend and device. It accepts one or more input tensors and store output tensors.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "type": "block", - "block": [ - { - "name": "input_count", - "type": "integer", - "command":"INPUTS" - }, - { - "name": "input", - "type": "string", - "multiple": true - } - - ] - }, - { - "type": "block", - "block": [ - { - "name": "output_count", - "type": "integer", - "command":"OUTPUTS" - }, - { - "name": "output", - "type": "string", - "multiple": true - } - - ] - }, - { - "name": "timeout", - "command": "TIMEOUT", - "type": "integer", - "optional": true - } - ], - "since": "1.2.5", - "group": "inference" - }, - "AI.SCRIPTSTORE": { - "summary": "stores a TorchScript as the value of a key.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "device", - "type": "enum", - "enum":["CPU", "GPU"] - }, - { - "name": "tag", - "command": "TAG", - "type": "string", - "optional": true - }, - { - "type": "block", - "block": [ - { - "name": "entry_point_count", - "type": "integer", - "command":"ENTRY_POINTS" - }, - { - "name": "entry_point", - "type": "string", - "multiple": true - } - - ] - } - ], - "since": "1.2.5", - "group": "script" - }, - "AI.SCRIPTGET": { - "summary": "returns the TorchScript stored as a key's value.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "meta", - "type": "enum", - "enum": [ - "META" - ], - "optional": true - }, - { - "name": "source", - "type": "enum", - "enum": [ - "SOURCE" - ], - "optional": true - } - ], - "since": "1.2.5", - "group": "script" - }, - "AI.SCRIPTDEL": { - "summary": "deletes a script stored as a key's value.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - } - ], - "since": "1.2.5", - "group": "script" - }, - "AI.SCRIPTEXECUTE": - { - "summary": "command runs a script stored as a key's value on its specified device.", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "name": "function", - "type": "string" - }, - { - "type": "block", - "optional": true, - "block": [ - { - "name": "key_count", - "type": "integer", - "command":"KEYS" - }, - { - "name": "key", - "type": "string", - "multiple": true - } - - ] - }, - { - "type": "block", - "optional": true, - "block": [ - { - "name": "input_count", - "type": "integer", - "command":"INPUTS" - }, - { - "name": "input", - "type": "string", - "multiple": true - } - - ] - }, - { - "type": "block", - "optional": true, - "block": [ - { - "name": "arg_count", - "type": "integer", - "command":"ARGS" - }, - { - "name": "arg", - "type": "string", - "multiple": true - } - - ] - }, - { - "type": "block", - "optional": true, - "block": [ - { - "name": "output_count", - "type": "integer", - "command":"OUTPUTS" - }, - { - "name": "output", - "type": "string", - "multiple": true - } - - ] - }, - { - "name": "timeout", - "command": "TIMEOUT", - "type": "integer", - "optional": true - } - ], - "since": "1.2.5", - "group": "inference" - } -} diff --git a/redisinsight/api/src/constants/commands/redisearch.json b/redisinsight/api/src/constants/commands/redisearch.json deleted file mode 100644 index 9f0c53e26c..0000000000 --- a/redisinsight/api/src/constants/commands/redisearch.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "FT.CREATE": { - "summary": "Creates an index with the given spec", - "complexity": "O(1)", - "arguments": [ - { - "name": "index", - "type": "key" - } - ], - "since": "1.0.0", - "group": "search" - }, - "FT.DROPINDEX": { - "summary": "Deletes the index", - "complexity": "O(N)", - "arguments": [ - { - "name": "index", - "type": "key" - }, - { - "name": "deletedocs", - "type": "enum", - "enum": [ - "DD" - ], - "optional": true - } - ], - "since": "2.0.0", - "group": "search" - } -} diff --git a/redisinsight/api/src/constants/commands/redisgraph.json b/redisinsight/api/src/constants/commands/redisgraph.json deleted file mode 100644 index d684adce66..0000000000 --- a/redisinsight/api/src/constants/commands/redisgraph.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "GRAPH.QUERY": { - "summary": "Queries the graph", - "arguments": [ - { - "name": "graph", - "type": "key" - }, - { - "name": "query", - "type": "string" - } - ], - "since": "1.0.0", - "group": "graph" - }, - "GRAPH.EXPLAIN": { - "summary": "Produce execution plan for query", - "arguments": [ - { - "name": "graph", - "type": "key" - }, - { - "name": "query", - "type": "string" - } - ], - "since": "2.0.0", - "group": "graph" - } -} diff --git a/redisinsight/api/src/constants/commands/redistimeseries.json b/redisinsight/api/src/constants/commands/redistimeseries.json deleted file mode 100644 index d5c85080e5..0000000000 --- a/redisinsight/api/src/constants/commands/redistimeseries.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "TS.CREATE": { - "summary": "Create a new time-series", - "complexity": "O(1)", - "arguments": [ - { - "name": "key", - "type": "key" - }, - { - "type": "integer", - "command": "RETENTION", - "name": "retentionTime", - "optional": true - }, - { - "type": "enum", - "command": "ENCODING", - "enum": [ - "UNCOMPRESSED", - "COMPRESSED" - ], - "optional": true - }, - { - "type": "integer", - "command": "CHUNK_SIZE", - "name": "size", - "optional": true - }, - { - "type": "enum", - "command": "DUPLICATE_POLICY", - "name": "policy", - "enum": [ - "BLOCK", - "FIRST", - "LAST", - "MIN", - "MAX", - "SUM" - ], - "optional": true - }, - { - "command": "LABELS", - "name": [ - "label", - "value" - ], - "type": [ - "string", - "string" - ], - "multiple": true, - "optional": true - } - ], - "since": "1.0.0", - "group": "timeseries" - }, - "TS.ADD": { - "summary": "Append a new sample to the series. If the series has not been created yet with TS.CREATE it will be automatically created.", - "complexity": "O(M) when M is the amount of compaction rules or O(1) with no compaction", - "arguments": [{ - "name": "key", - "type": "key" - }, - { - "name": "timestamp", - "type": "integer" - }, - { - "name": "value", - "type": "double" - }, - { - "type": "integer", - "command": "RETENTION", - "name": "retentionTime", - "optional": true - }, - { - "type": "enum", - "command": "ENCODING", - "enum": [ - "UNCOMPRESSED", - "COMPRESSED" - ], - "optional": true - }, - { - "type": "integer", - "command": "CHUNK_SIZE", - "name": "size", - "optional": true - }, - { - "type": "enum", - "command": "ON_DUPLICATE", - "name": "policy", - "enum": [ - "BLOCK", - "FIRST", - "LAST", - "MIN", - "MAX", - "SUM" - ], - "optional": true - }, - { - "command": "LABELS", - "name": [ - "label", - "value" - ], - "type": [ - "string", - "string" - ], - "multiple": true, - "optional": true - } - ], - "since": "1.0.0", - "group": "timeseries" - } -} diff --git a/redisinsight/api/src/modules/browser/dto/keys.dto.ts b/redisinsight/api/src/modules/browser/dto/keys.dto.ts index 256c1a7f43..1eec253706 100644 --- a/redisinsight/api/src/modules/browser/dto/keys.dto.ts +++ b/redisinsight/api/src/modules/browser/dto/keys.dto.ts @@ -201,7 +201,7 @@ export class UpdateKeyTtlDto { @ApiProperty({ type: Number, description: - 'Set a timeout on key in seconds. After the timeout has expired, the key will automatically be deleted.' + 'Set a timeout on key in seconds. After the timeout has expired, the key will automatically be deleted. ' + 'If the property has value of -1, then the key timeout will be removed.', maximum: MAX_TTL_NUMBER, }) @@ -216,7 +216,7 @@ export class KeyTtlResponse { type: Number, description: 'The remaining time to live of a key that has a timeout. ' - + 'If value equals -2 then the key does not exist or has deleted.' + + 'If value equals -2 then the key does not exist or has deleted. ' + 'If value equals -1 then the key has no associated expire (No limit).', maximum: MAX_TTL_NUMBER, }) diff --git a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts index ec568c0b93..0e57810885 100644 --- a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts +++ b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts @@ -12,15 +12,11 @@ import { GetKeysWithDetailsResponse, RedisDataType, } from 'src/modules/browser/dto'; -import ERROR_MESSAGES from 'src/constants/error-messages'; +import { parseClusterCursor } from 'src/modules/browser/utils/clusterCursor'; import { ISettingsProvider } from 'src/modules/core/models/settings-provider.interface'; import { AbstractStrategy } from './abstract.strategy'; import { IGetNodeKeysResult } from '../scanner.interface'; -const NODES_SEPARATOR = '||'; -const CURSOR_SEPARATOR = '@'; -// Correct format 172.17.0.1:7001@-1||172.17.0.1:7002@33 -const CLUSTER_CURSOR_REGEX = /^(([a-z0-9.])+:[0-9]+(@-?\d+))+((\|\|)?([a-z0-9.])+:[0-9]+(@-?\d+))*$/; const REDIS_SCAN_CONFIG = config.get('redis_scan'); export class ClusterStrategy extends AbstractStrategy { @@ -100,7 +96,7 @@ export class ClusterStrategy extends AbstractStrategy { initialCursor: string, ): Promise { if (Number.isNaN(toNumber(initialCursor))) { - return this.getNodesFromClusterCursor(initialCursor); + return parseClusterCursor(initialCursor); } const clusterNodes = await this.redisManager.getNodes( @@ -118,35 +114,6 @@ export class ClusterStrategy extends AbstractStrategy { })); } - /** - * Parses composed custom cursor from FE and returns nodes - * Format: 172.17.0.1:7001@22||172.17.0.1:7002@33 - */ - private getNodesFromClusterCursor(cursor: string): IGetNodeKeysResult[] { - const isCorrectFormat = CLUSTER_CURSOR_REGEX.test(cursor); - if (!isCorrectFormat) { - throw new Error(ERROR_MESSAGES.INCORRECT_CLUSTER_CURSOR_FORMAT); - } - const nodeStrings = cursor.split(NODES_SEPARATOR); - const nodes = []; - - nodeStrings.forEach((item: string) => { - const [address, nextCursor] = item.split(CURSOR_SEPARATOR); - const [host, port] = address.split(':'); - if (parseInt(nextCursor, 10) >= 0) { - nodes.push({ - total: 0, - scanned: 0, - host, - port: parseInt(port, 10), - cursor: parseInt(nextCursor, 10), - keys: [], - }); - } - }); - return nodes; - } - private async calculateNodesTotalKeys( clientOptions, nodes: IGetNodeKeysResult[], diff --git a/redisinsight/api/src/modules/browser/utils/clusterCursor.spec.ts b/redisinsight/api/src/modules/browser/utils/clusterCursor.spec.ts new file mode 100644 index 0000000000..2de97689dc --- /dev/null +++ b/redisinsight/api/src/modules/browser/utils/clusterCursor.spec.ts @@ -0,0 +1,85 @@ +import ERROR_MESSAGES from 'src/constants/error-messages'; +import { isClusterCursorValid, parseClusterCursor } from './clusterCursor'; + +const isClusterCursorValidTests = [ + { input: '172.17.0.1:7001@22||172.17.0.1:7002@33', expected: true }, + { input: '172.17.0.1:7001@-1||172.17.0.1:7002@-1', expected: true }, + { + input: '172.17.0.1:7001@10' + + '||172.17.0.1:7002@10' + + '||172.17.0.1:7003@10' + + '||172.17.0.1:7004@10' + + '||172.17.0.1:7005@10' + + '||172.17.0.1:7006@10', + expected: true, + }, + { input: '172.17.0.1:7001@-1', expected: true }, + { input: 'domain.com:7001@-1', expected: true }, + { input: '172.17.0.1:7001@1228822', expected: true }, + { input: '172.17.0.1:7001@', expected: false }, + { input: '172.17.0.1:7001@text', expected: false }, + { input: '172,17,0,1:7001@-1', expected: false }, + { input: 'plain text', expected: false }, + { input: 'text@text||text@text', expected: false }, + { input: 'text@text', expected: false }, + { input: '', expected: false }, +]; + +describe('isClusterCursorValid', () => { + it.each(isClusterCursorValidTests)('%j', ({ input, expected }) => { + expect(isClusterCursorValid(input)).toBe(expected); + }); +}); + +const defaultNodeScanResult = { + total: 0, scanned: 0, host: '172.17.0.1', port: 0, cursor: 0, keys: [], +}; +const parsingError = new Error(ERROR_MESSAGES.INCORRECT_CLUSTER_CURSOR_FORMAT); +const parseClusterCursorTests = [ + { + input: '172.17.0.1:7001@22||172.17.0.1:7002@33', + expected: [ + { ...defaultNodeScanResult, port: 7001, cursor: 22 }, + { ...defaultNodeScanResult, port: 7002, cursor: 33 }, + ], + }, + { + input: '172.17.0.1:7001@-1' + + '||172.17.0.1:7002@10' + + '||172.17.0.1:7003@-1' + + '||172.17.0.1:7004@10' + + '||172.17.0.1:7005@-1' + + '||172.17.0.1:7006@10', + expected: [ + { ...defaultNodeScanResult, port: 7002, cursor: 10 }, + { ...defaultNodeScanResult, port: 7004, cursor: 10 }, + { ...defaultNodeScanResult, port: 7006, cursor: 10 }, + ], + }, + { + input: '172.17.0.1:7001@-1||172.17.0.1:7002@-1', + expected: [], + }, + { input: '172.17.0.1:7001@', expected: parsingError }, + { input: '172.17.0.1:7001@text', expected: parsingError }, + { input: '172,17,0,1:7001@-1', expected: parsingError }, + { input: 'plain text', expected: parsingError }, + { input: 'text@text||text@text', expected: parsingError }, + { input: 'text@text', expected: parsingError }, + { input: '', expected: parsingError }, + { input: '', expected: parsingError }, +]; + +describe('parseClusterCursor', () => { + it.each(parseClusterCursorTests)('%j', ({ input, expected }) => { + if (expected instanceof Error) { + try { + parseClusterCursor(input); + } catch (e) { + expect(e.message).toEqual(expected.message); + } + } else { + expect(parseClusterCursor(input)).toEqual(expected); + } + }); +}); diff --git a/redisinsight/api/src/modules/browser/utils/clusterCursor.ts b/redisinsight/api/src/modules/browser/utils/clusterCursor.ts new file mode 100644 index 0000000000..14ab10b620 --- /dev/null +++ b/redisinsight/api/src/modules/browser/utils/clusterCursor.ts @@ -0,0 +1,39 @@ +import ERROR_MESSAGES from 'src/constants/error-messages'; +import { IGetNodeKeysResult } from 'src/modules/browser/services/keys-business/scanner/scanner.interface'; + +const NODES_SEPARATOR = '||'; +const CURSOR_SEPARATOR = '@'; +// Correct format 172.17.0.1:7001@-1||172.17.0.1:7002@33 +const CLUSTER_CURSOR_REGEX = /^(([a-z0-9.])+:[0-9]+(@-?\d+)(?:\|{2}(?!$)|$))+$/; + +export const isClusterCursorValid = (cursor) => CLUSTER_CURSOR_REGEX.test(cursor); + +/** + * Parses composed custom cursor from FE and returns nodes + * Format: 172.17.0.1:7001@22||172.17.0.1:7002@33 + */ +export const parseClusterCursor = (cursor: string): IGetNodeKeysResult[] => { + if (!isClusterCursorValid(cursor)) { + throw new Error(ERROR_MESSAGES.INCORRECT_CLUSTER_CURSOR_FORMAT); + } + const nodeStrings = cursor.split(NODES_SEPARATOR); + const nodes = []; + + nodeStrings.forEach((item: string) => { + const [address, nextCursor] = item.split(CURSOR_SEPARATOR); + const [host, port] = address.split(':'); + + // ignore nodes with cursor -1 (fully scanned) + if (parseInt(nextCursor, 10) >= 0) { + nodes.push({ + total: 0, + scanned: 0, + host, + port: parseInt(port, 10), + cursor: parseInt(nextCursor, 10), + keys: [], + }); + } + }); + return nodes; +}; diff --git a/redisinsight/api/src/modules/commands/commands-json.provider.spec.ts b/redisinsight/api/src/modules/commands/commands-json.provider.spec.ts index c861fe54a5..28600cb35e 100644 --- a/redisinsight/api/src/modules/commands/commands-json.provider.spec.ts +++ b/redisinsight/api/src/modules/commands/commands-json.provider.spec.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import { Test, TestingModule } from '@nestjs/testing'; import { mockMainCommands, @@ -10,76 +10,73 @@ import { CommandsJsonProvider } from 'src/modules/commands/commands-json.provide jest.mock('axios'); const mockedAxios = axios as jest.Mocked; -jest.mock('fs'); +jest.mock('fs-extra'); const mockedFs = fs as jest.Mocked; describe('CommandsJsonProvider', () => { let service: CommandsJsonProvider; - let updateLatestJsonSpy; beforeEach(async () => { - jest.mock('fs', () => mockedFs); + jest.mock('fs-extra', () => mockedFs); - mockedFs.existsSync.mockReturnValue(true); - mockedFs.mkdirSync.mockReturnValue(''); - mockedFs.writeFileSync.mockReturnValue(undefined); mockedAxios.get.mockResolvedValue({ data: JSON.stringify(mockMainCommands) }); const module: TestingModule = await Test.createTestingModule({ providers: [ { provide: 'service', - useFactory: () => new CommandsJsonProvider('name', 'someurl', mockMainCommands), + useFactory: () => new CommandsJsonProvider('name', 'someurl'), }, ], }).compile(); service = module.get('service'); - updateLatestJsonSpy = jest.spyOn(service, 'updateLatestJson'); - }); - - describe('onModuleInit', () => { - it('should trigger updateLatestJson function', async () => { - await service.onModuleInit(); - - expect(updateLatestJsonSpy).toHaveBeenCalled(); - }); }); describe('updateLatestJson', () => { - it('Should create dir and save proper json', async () => { - mockedFs.existsSync.mockReturnValueOnce(false); - - await service.onModuleInit(); - - // todo: uncomment after enable esModuleInterop in the tsconfig - // expect(mockedFs.mkdirSync).toHaveBeenCalled(); - // expect(mockedFs.writeFileSync).toHaveBeenCalled(); - }); it('should not fail when incorrect data retrieved', async () => { - mockedAxios.get.mockResolvedValueOnce('incorrect json'); - await service.onModuleInit(); + mockedAxios.get.mockResolvedValueOnce('json'); + await service.updateLatestJson(); - // todo: uncomment after enable esModuleInterop in the tsconfig - // expect(mockedFs.writeFileSync).not.toHaveBeenCalled(); + expect(mockedFs.writeFile).not.toHaveBeenCalled(); }); }); describe('getCommands', () => { it('should return default config when file was not found', async () => { - mockedFs.readFileSync.mockImplementationOnce(() => { throw new Error('No file'); }); + mockedFs.readFile.mockRejectedValueOnce(new Error('No file')); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from(JSON.stringify(mockMainCommands))); expect(await service.getCommands()).toEqual(mockMainCommands); }); it('should return default config when incorrect json received from file', async () => { - mockedFs.readFileSync.mockReturnValue('incorrect json'); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from('incorrect json')); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from(JSON.stringify(mockMainCommands))); expect(await service.getCommands()).toEqual(mockMainCommands); }); it('should return latest commands', async () => { - mockedFs.readFileSync.mockReturnValue(JSON.stringify(mockRedijsonCommands)); + mockedFs.readFile.mockResolvedValue(Buffer.from(JSON.stringify(mockRedijsonCommands))); expect(await service.getCommands()).toEqual(mockRedijsonCommands); }); }); + + describe('getDefaultCommands', () => { + it('should return empty object when file was not found', async () => { + mockedFs.readFile.mockRejectedValue(new Error('No file')); + + expect(await service.getDefaultCommands()).toEqual({}); + }); + it('should return empty object when incorrect json received from file', async () => { + mockedFs.readFile.mockResolvedValue(Buffer.from('incorrect json')); + + expect(await service.getDefaultCommands()).toEqual({}); + }); + it('should return default commands', async () => { + mockedFs.readFile.mockResolvedValue(Buffer.from(JSON.stringify(mockRedijsonCommands))); + + expect(await service.getDefaultCommands()).toEqual(mockRedijsonCommands); + }); + }); }); diff --git a/redisinsight/api/src/modules/commands/commands-json.provider.ts b/redisinsight/api/src/modules/commands/commands-json.provider.ts index cc5381c892..9babb04289 100644 --- a/redisinsight/api/src/modules/commands/commands-json.provider.ts +++ b/redisinsight/api/src/modules/commands/commands-json.provider.ts @@ -1,41 +1,29 @@ -import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import axios from 'axios'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import * as path from 'path'; import config from 'src/utils/config'; const PATH_CONFIG = config.get('dir_path'); @Injectable() -export class CommandsJsonProvider implements OnModuleInit { +export class CommandsJsonProvider { private readonly logger: Logger; private readonly name: string; private readonly url: string; - private readonly defaultCommands: Record; - - constructor(name, url, defaultCommands) { + constructor(name, url) { this.name = name; this.url = url; - this.defaultCommands = defaultCommands; - this.logger = new Logger(this.name); - } - - /** - * Updates latest json on startup - */ - async onModuleInit() { - // async operation to not wait for it and not block user in case when no internet connection - this.updateLatestJson(); + this.logger = new Logger(`CommandsJsonProvider:${this.name}`); } /** * Get latest json from external resource and save it locally - * @private */ - private async updateLatestJson() { + async updateLatestJson() { try { this.logger.log(`Trying to update ${this.name} commands...`); const { data } = await axios.get(this.url, { @@ -43,11 +31,9 @@ export class CommandsJsonProvider implements OnModuleInit { transformResponse: [(raw) => raw], }); - if (!fs.existsSync(PATH_CONFIG.commands)) { - fs.mkdirSync(PATH_CONFIG.commands); - } + await fs.ensureDir(PATH_CONFIG.commands); - fs.writeFileSync( + await fs.writeFile( path.join(PATH_CONFIG.commands, `${this.name}.json`), JSON.stringify(JSON.parse(data)), // check that we received proper json object ); @@ -63,13 +49,29 @@ export class CommandsJsonProvider implements OnModuleInit { */ async getCommands() { try { - return JSON.parse(fs.readFileSync( + return JSON.parse(await fs.readFile( path.join(PATH_CONFIG.commands, `${this.name}.json`), 'utf8', )); } catch (error) { - this.logger.error(`Unable to get latest ${this.name} commands. Return default.`, error); - return this.defaultCommands; + this.logger.warn(`Unable to get latest ${this.name} commands. Return default.`, error); + return this.getDefaultCommands(); + } + } + + /** + * Try to get default json that was delivered with build + * In case when no default data we will return empty object to not fail api call + */ + async getDefaultCommands() { + try { + return JSON.parse(await fs.readFile( + path.join(PATH_CONFIG.defaultCommandsDir, `${this.name}.json`), + 'utf8', + )); + } catch (error) { + this.logger.error(`Unable to get default ${this.name} commands.`, error); + return {}; } } } diff --git a/redisinsight/api/src/modules/commands/commands.module.ts b/redisinsight/api/src/modules/commands/commands.module.ts index a2cd32cc37..7d63648306 100644 --- a/redisinsight/api/src/modules/commands/commands.module.ts +++ b/redisinsight/api/src/modules/commands/commands.module.ts @@ -3,65 +3,16 @@ import { CommandsController } from 'src/modules/commands/commands.controller'; import { CommandsService } from 'src/modules/commands/commands.service'; import { CommandsJsonProvider } from 'src/modules/commands/commands-json.provider'; import config from 'src/utils/config'; -import * as defaultMainCommands from 'src/constants/commands/main.json'; -import * as defaultRedisearchCommands from 'src/constants/commands/redisearch.json'; -import * as defaultRedijsonCommands from 'src/constants/commands/redijson.json'; -import * as defaultRedistimeseriesCommands from 'src/constants/commands/redistimeseries.json'; -import * as defaultRedisaiCommands from 'src/constants/commands/redisai.json'; -import * as defaultRedisgraphCommands from 'src/constants/commands/redisgraph.json'; -const COMMANDS_CONFIG = config.get('commands'); +const COMMANDS_CONFIGS = config.get('commands'); @Module({ controllers: [CommandsController], providers: [ - CommandsService, { - provide: 'mainCommandsProvider', - useFactory: () => new CommandsJsonProvider( - 'main', - COMMANDS_CONFIG.mainUrl, - defaultMainCommands, - ), - }, - { - provide: 'redisearchCommandsProvider', - useFactory: () => new CommandsJsonProvider( - 'redisearch', - COMMANDS_CONFIG.redisearchUrl, - defaultRedisearchCommands, - ), - }, - { - provide: 'redijsonCommandsProvider', - useFactory: () => new CommandsJsonProvider( - 'redijson', - COMMANDS_CONFIG.redijsonUrl, - defaultRedijsonCommands, - ), - }, - { - provide: 'redistimeseriesCommandsProvider', - useFactory: () => new CommandsJsonProvider( - 'redistimeseries', - COMMANDS_CONFIG.redistimeseriesUrl, - defaultRedistimeseriesCommands, - ), - }, - { - provide: 'redisaiCommandsProvider', - useFactory: () => new CommandsJsonProvider( - 'redisai', - COMMANDS_CONFIG.redisaiUrl, - defaultRedisaiCommands, - ), - }, - { - provide: 'redisgraphCommandsProvider', - useFactory: () => new CommandsJsonProvider( - 'redisgraph', - COMMANDS_CONFIG.redisgraphUrl, - defaultRedisgraphCommands, + provide: CommandsService, + useFactory: () => new CommandsService( + COMMANDS_CONFIGS.map(({ name, url }) => new CommandsJsonProvider(name, url)), ), }, ], diff --git a/redisinsight/api/src/modules/commands/commands.service.spec.ts b/redisinsight/api/src/modules/commands/commands.service.spec.ts index 115c2b5ab9..2a03f20531 100644 --- a/redisinsight/api/src/modules/commands/commands.service.spec.ts +++ b/redisinsight/api/src/modules/commands/commands.service.spec.ts @@ -14,52 +14,37 @@ import { CommandsJsonProvider } from 'src/modules/commands/commands-json.provide describe('CommandsService', () => { let service: CommandsService; - let mainCommandsProvider: MockType; - let redisearchCommandsProvider: MockType; - let redijsonCommandsProvider: MockType; - let redistimeseriesCommandsProvider: MockType; - let redisaiCommandsProvider: MockType; - let redisgraphCommandsProvider: MockType; + + const mainCommandsProvider: MockType = mockCommandsJsonProvider(); + const redisearchCommandsProvider: MockType = mockCommandsJsonProvider(); + const redijsonCommandsProvider: MockType = mockCommandsJsonProvider(); + const redistimeseriesCommandsProvider: MockType = mockCommandsJsonProvider(); + const redisaiCommandsProvider: MockType = mockCommandsJsonProvider(); + const redisgraphCommandsProvider: MockType = mockCommandsJsonProvider(); beforeEach(async () => { jest.clearAllMocks(); + + const commandsProviders = [ + mainCommandsProvider, + redisearchCommandsProvider, + redijsonCommandsProvider, + redistimeseriesCommandsProvider, + redisaiCommandsProvider, + redisgraphCommandsProvider, + ]; + const module: TestingModule = await Test.createTestingModule({ providers: [ - CommandsService, - { - provide: 'mainCommandsProvider', - useFactory: mockCommandsJsonProvider, - }, - { - provide: 'redisearchCommandsProvider', - useFactory: mockCommandsJsonProvider, - }, - { - provide: 'redijsonCommandsProvider', - useFactory: mockCommandsJsonProvider, - }, { - provide: 'redistimeseriesCommandsProvider', - useFactory: mockCommandsJsonProvider, - }, - { - provide: 'redisaiCommandsProvider', - useFactory: mockCommandsJsonProvider, - }, - { - provide: 'redisgraphCommandsProvider', - useFactory: mockCommandsJsonProvider, + provide: CommandsService, + // @ts-ignore + useFactory: () => new CommandsService(commandsProviders), }, ], }).compile(); service = module.get(CommandsService); - mainCommandsProvider = module.get('mainCommandsProvider'); - redisearchCommandsProvider = module.get('redisearchCommandsProvider'); - redijsonCommandsProvider = module.get('redijsonCommandsProvider'); - redistimeseriesCommandsProvider = module.get('redistimeseriesCommandsProvider'); - redisaiCommandsProvider = module.get('redisaiCommandsProvider'); - redisgraphCommandsProvider = module.get('redisgraphCommandsProvider'); mainCommandsProvider.getCommands.mockResolvedValue(mockMainCommands); redisearchCommandsProvider.getCommands.mockResolvedValue(mockRedisearchCommands); @@ -69,6 +54,19 @@ describe('CommandsService', () => { redisgraphCommandsProvider.getCommands.mockResolvedValue(mockRedisgraphCommands); }); + describe('onModuleInit', () => { + it('should trigger updateLatestJson function', async () => { + await service.onModuleInit(); + + expect(mainCommandsProvider.updateLatestJson).toHaveBeenCalled(); + expect(redisearchCommandsProvider.updateLatestJson).toHaveBeenCalled(); + expect(redijsonCommandsProvider.updateLatestJson).toHaveBeenCalled(); + expect(redistimeseriesCommandsProvider.updateLatestJson).toHaveBeenCalled(); + expect(redisaiCommandsProvider.updateLatestJson).toHaveBeenCalled(); + expect(redisgraphCommandsProvider.updateLatestJson).toHaveBeenCalled(); + }); + }); + describe('getAll', () => { it('Should return merged commands into one', async () => { expect(await service.getAll()).toEqual({ diff --git a/redisinsight/api/src/modules/commands/commands.service.ts b/redisinsight/api/src/modules/commands/commands.service.ts index 787b5281f8..a403a9f3c5 100644 --- a/redisinsight/api/src/modules/commands/commands.service.ts +++ b/redisinsight/api/src/modules/commands/commands.service.ts @@ -1,34 +1,30 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { assign } from 'lodash'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { CommandsJsonProvider } from 'src/modules/commands/commands-json.provider'; @Injectable() -export class CommandsService { - constructor( - @Inject('redisearchCommandsProvider') - private redisearchCommandsProvider: CommandsJsonProvider, - @Inject('redijsonCommandsProvider') - private redijsonCommandsProvider: CommandsJsonProvider, - @Inject('redistimeseriesCommandsProvider') - private redistimeseriesCommandsProvider: CommandsJsonProvider, - @Inject('redisaiCommandsProvider') - private redisaiCommandsProvider: CommandsJsonProvider, - @Inject('redisgraphCommandsProvider') - private redisgraphCommandsProvider: CommandsJsonProvider, - @Inject('mainCommandsProvider') - private mainCommandsProvider: CommandsJsonProvider, - ) {} +export class CommandsService implements OnModuleInit { + private commandsProviders; + + constructor(commandsProviders: CommandsJsonProvider[] = []) { + this.commandsProviders = commandsProviders; + } + + /** + * Updates latest jsons on startup + */ + async onModuleInit() { + // async operation to not wait for it and not block user in case when no internet connection + Promise.all(this.commandsProviders.map((provider) => provider.updateLatestJson())); + } /** * Get all commands merged into single object */ async getAll(): Promise> { - return { - ...(await this.redisearchCommandsProvider.getCommands()), - ...(await this.redijsonCommandsProvider.getCommands()), - ...(await this.redistimeseriesCommandsProvider.getCommands()), - ...(await this.redisaiCommandsProvider.getCommands()), - ...(await this.redisgraphCommandsProvider.getCommands()), - ...(await this.mainCommandsProvider.getCommands()), - }; + return assign( + {}, + ...(await Promise.all(this.commandsProviders.map((provider) => provider.getCommands()))), + ); } } diff --git a/redisinsight/api/src/utils/hosting-provider-helper.ts b/redisinsight/api/src/utils/hosting-provider-helper.ts index 5df786bc7b..8608a89870 100644 --- a/redisinsight/api/src/utils/hosting-provider-helper.ts +++ b/redisinsight/api/src/utils/hosting-provider-helper.ts @@ -1,6 +1,9 @@ import { HostingProvider } from 'src/modules/core/models/database-instance.entity'; import { IP_ADDRESS_REGEX, PRIVATE_IP_ADDRESS_REGEX } from 'src/constants'; +// Ignore LGTM [js/incomplete-url-substring-sanitization] alert. +// Because we do not bind potentially dangerous logic to this. +// We define a hosting provider for telemetry only. export const getHostingProvider = (host: string): HostingProvider => { // Tries to detect the hosting provider from the hostname. if (host === '0.0.0.0' || host === 'localhost') { @@ -9,12 +12,13 @@ export const getHostingProvider = (host: string): HostingProvider => { if (IP_ADDRESS_REGEX.test(host) && PRIVATE_IP_ADDRESS_REGEX.test(host)) { return HostingProvider.LOCALHOST; } - if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com')) { + if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com')) { // lgtm[js/incomplete-url-substring-sanitization] return HostingProvider.RE_CLOUD; } - if (host.endsWith('cache.amazonaws.com')) { + if (host.endsWith('cache.amazonaws.com')) { // lgtm[js/incomplete-url-substring-sanitization] return HostingProvider.AWS; } + // lgtm[js/incomplete-url-substring-sanitization] if (host.endsWith('cache.windows.net')) { return HostingProvider.AZURE; } diff --git a/redisinsight/api/test/test-runs/re-clu/Dockerfile b/redisinsight/api/test/test-runs/re-clu/Dockerfile index 136d43fbc3..8af7097d05 100644 --- a/redisinsight/api/test/test-runs/re-clu/Dockerfile +++ b/redisinsight/api/test/test-runs/re-clu/Dockerfile @@ -1,18 +1,9 @@ -FROM redislabs/redis:6.0.8-28.bionic +FROM redislabs/redis:6.2.8-50 -# Change user to root to install pip -USER root -RUN set -ex \ - && apt-get update \ - && apt-get install -y python3-pip \ - && pip3 install requests -# Change user back to redislabs -USER redislabs - -# Set the env var to instruct RE to create a cluster on startup +## Set the env var to instruct RE to create a cluster on startup ENV BOOTSTRAP_ACTION create_cluster ENV BOOTSTRAP_CLUSTER_FQDN cluster.local -COPY run_re_and_create_db.sh create_dbs.py cert.pem ./ +COPY entrypoint.sh db.json ./ -ENTRYPOINT [ "bash", "./run_re_and_create_db.sh" ] +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/redisinsight/api/test/test-runs/re-clu/README.md b/redisinsight/api/test/test-runs/re-clu/README.md deleted file mode 100644 index 6b65582c5a..0000000000 --- a/redisinsight/api/test/test-runs/re-clu/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# docker-redisenterprise-testdb -A Docker container that creates test databases on a Redis Enterprise cluster - - -## Databases - -Environment variable control which dbs are created. By default, no db is created. -- `CREATE_SIMPLE_DB`: Single-shard simple database on port 12000 -- `CREATE_CLUSTER_DB`: Database-clustering enabled, with 3 shards on port 12010 -- `CREATE_TLS_DB`: Single-shard TLS database on port 12443 -- `CREATE_TLS_MUTUAL_AUTH_DB`: Single-shard TLS client authentication enabled database on port 12465 -- `CREATE_MODULES_DB`: Single-shard db with modules: RedisGraph, RediSearch and RedisTimeSeries on port 12003 -- `CREATE_CRDB`: CRDT database on port 12005. `CRDB_INSTANCES` env var should also be set to a space-separated list of the FQDNs of participating clusters. - - -## References - -- [Redis Enterprise REST API Docs](https://storage.googleapis.com/rlecrestapi/rest-html/http_rest_api.html) diff --git a/redisinsight/api/test/test-runs/re-clu/cert.pem b/redisinsight/api/test/test-runs/re-clu/cert.pem deleted file mode 100644 index 3dfb16abf7..0000000000 --- a/redisinsight/api/test/test-runs/re-clu/cert.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFfzCCA2egAwIBAgIURYhz7wsPwNGHxFoINaEB6ysJyEYwDQYJKoZIhvcNAQEL -BQAwTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRgwFgYDVQQKDA9SZWRpc0xh -YnMsIEluYy4xGTAXBgNVBAMMEHJlZGlzaW5zaWdodC5jb20wHhcNMjAxMjI4MTA1 -NzU1WhcNMjExMjI4MTA1NzU1WjBPMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0Ex -GDAWBgNVBAoMD1JlZGlzTGFicywgSW5jLjEZMBcGA1UEAwwQcmVkaXNpbnNpZ2h0 -LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANS1OFxNjFn1JQJO -HounllmdHA0hMCoZ5DGO1rur+ppZBZFh9Z4NT01neEVMi9vZkvsmeGQ6xwOibbGv -EClNXrFS/pqBg91AdGJzz6lH08VHgAtu+2P6kDewTXQyN+Gu+3qlss4t3di9jQAd -oYXkbPT5ZPnaJtkWq+rrw8P7hCC0OARETzysg/IVukyYYIGjgpeCOWOWeF4oGurO -EQtZnuVncMC4ZvobGlGtXk0Rk46j1uwLDNgovLkajnMHViGxS/kCOSZB2UJvhve/ -YmKK14kxc/mFoNu1+ING5bGEcprVUe8wKXb+TuTRp2YxIB1GIG8+vQwcYFS5l0kH -BsRGwBKS2ESaSQ1eSyIVd2wdXVHqlILlmy2Zvi9DM/kMX/OtoBjIDhWx9mStTxtz -DjHIooT/FeFQzC2ah1bP+/KYabCHScEXpXxubpK9saXLtj4Vk/RcfZ42+0eeVxBf -Dttln7MHP79VyyCZpT9OSu8q4qU6dVDlz3fczC6fkE6b2kPVQnSLz9Wmr47syawg -Argv96d6wcNiiNzOyHZNCaxHwsVFx0zJOuRiyMwJp4JvrAb2glkKgCzVNjCMO+8v -HuXv6TTUHKvLwqnuqe04VRQazIDUPzQma+whdgIMkAJBanm7U+fmZ/LX0d1XLCMv -E5k/zzu9uOXUNM46Pvz20Rko8W4fAgMBAAGjUzBRMB0GA1UdDgQWBBSbd9FdsIfw -AUWS9kE93p5MGmv4WTAfBgNVHSMEGDAWgBSbd9FdsIfwAUWS9kE93p5MGmv4WTAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCvXOzCaEGf6xoA8A7S -U5GXv5rGof1TW9+FogiUc1GyvEJJa4spo2MEASBCkqN0e5ac4tE3XiMsPXgoMCNt -XFRa71rI1a61an+02+i5hgG6fBTftTLRaMOWKXswiF/GN+Jc2aYPNy9KZtwxowre -g28a47vlAUT3O4ZdpdR4TVD6Zvy/EtA/TfuP9XPBA1TaAp6jEUArZFU3H0VM6nn3 -1GKH8rxqv54jLovA7ASs6CKU8PtgU0RDypB69lYVWjbVdrSA5Jh6o54N60tNizCk -LzaEbSGijI6qeKwouFoqKqx+Kr9aYPnGnA1rJXWVvI1Z8XIsWmb9fFnl9nPspqxF -A6vuvc2W9TIFLvwZIe/QC4kKq07rA7zCiF/dsjqU1VdeoJZXp0109t7Ua0tlUO/J -XIIY8sQHyJ2kZAiL9ghCHvgO5ciezVvu6ru1E6M8FD0OaopPa2jM4V562sZ/Ztdi -7IPlQ160zsHuYq3Q4uYNalWJ7gLaxbXFaok/fZo39GRVzjk/FN8zuXuGjMchhoaB -XrWDF0H6NM92cvH/8cgSg7JXvBlCdGqV9XPnqYm312MqeFZ9Lg2H8wTfHuAzNrOZ -eV1Cp5mBepvYtsFX3ATdDg7+QgAVhLdnAyUmMNY2cW1GTdfQV4L3fIm0S7RKayC6 -eeFbQeTv19tELZiXhalV3YuJMQ== ------END CERTIFICATE----- diff --git a/redisinsight/api/test/test-runs/re-clu/create_dbs.py b/redisinsight/api/test/test-runs/re-clu/create_dbs.py deleted file mode 100644 index 71b306ff86..0000000000 --- a/redisinsight/api/test/test-runs/re-clu/create_dbs.py +++ /dev/null @@ -1,218 +0,0 @@ -import os -import pprint -import subprocess - -import requests - - -# Suppress "Unverified HTTPS request" warnings -# See https://github.com/influxdata/influxdb-python/issues/240#issuecomment-140003499 -# pylint: disable=no-member -requests.packages.urllib3.disable_warnings() - - -CREATE_SIMPLE_DB = bool(os.environ.get("CREATE_SIMPLE_DB", "")) -CREATE_CLUSTER_DB = bool(os.environ.get("CREATE_CLUSTER_DB", "")) -CREATE_TLS_DB = bool(os.environ.get("CREATE_TLS_DB", "")) -CREATE_TLS_MUTUAL_AUTH_DB = bool(os.environ.get("CREATE_TLS_MUTUAL_AUTH_DB", "")) -CREATE_MODULES_DB = bool(os.environ.get("CREATE_MODULES_DB", "")) -CREATE_CRDB = bool(os.environ.get("CREATE_CRDB", "")) -CRDB_INSTANCES = os.environ.get("CRDB_INSTANCES", "") - - -USERNAME = 'demo@redislabs.com' -PASSWORD = '123456' - - -RLEC_API_BASE_URL = 'https://localhost:9443/v1' - - -COMMON_REQ_PARAMS = dict(auth=(USERNAME, PASSWORD), - verify=False,) - - -def get_module_data() -> dict: - """ - Returns a dict of module name to a dict containing: - - module_id - - module_name - - module_args - - semantic_version - """ - resp = requests.get(url=f'{RLEC_API_BASE_URL}/modules', **COMMON_REQ_PARAMS) - if not resp.ok: - raise Exception(f"Failed to get modules info: {resp.status_code}: {resp.text}") - data = resp.json() - module_dict = {} - for m in data: - module_dict[m['module_name']] = { - "module_id": m["uid"], - "module_name": m["module_name"], - "module_args": m["command_line_args"], - "semantic_version": m["semantic_version"], - } - return module_dict - - -def create_db(body: dict) -> dict: - """ - Create a bdb and return the response from the API. - """ - resp = requests.post(url=f'{RLEC_API_BASE_URL}/bdbs', - json=body, - **COMMON_REQ_PARAMS) - if not resp.ok: - raise Exception(f"Failed to create db: {resp.status_code}: {resp.text}") - data = resp.json() - return data - - -def create_simple_db() -> dict: - body = { - "name": "testdb", - "type": "redis", - "memory_size": 100000, - "port": 12000 - } - return create_db(body) - - -def create_cluster_db() -> dict: - body = { - "name": "testdb", - "type": "redis", - "memory_size": 1024* 1024 * 1024, # 1GB - "port": 12010, - "sharding": True, - "shards_count": 3, - "proxy_policy": "all-master-shards", - "oss_cluster": True, -# "oss_sharding": True, - # Default OSS Redis Cluster-like hashing policy. - # These regexes are taken from the RLEC REST API docs: - # https://storage.googleapis.com/rlecrestapi/rest-html/http_rest_api.html#bdb (see the 'shard_key_regex' attribute) - "shard_key_regex": [ - {"regex": ".*\\{(?.*)\\}.*" }, - {"regex": "(?.*)" } - ], - } - return create_db(body) - - -def create_tls_db() -> dict: - body = { - "name": "testtlsdb", - "type": "redis", - "memory_size": 100000, - "port": 12443, - "tls_mode": "enabled", - "enforce_client_authentication": "disabled" - } - return create_db(body) - - -def create_tls_mutual_auth_db() -> dict: - with open('./cert.pem') as f: - cert_str = f.read() - body = { - "name": "testtlsclientauthdb", - "type": "redis", - "memory_size": 100000, - "port": 12465, - "tls_mode": "enabled", - "enforce_client_authentication": "enabled", - "authentication_ssl_client_certs": [{ - "client_cert": cert_str, - }] - } - return create_db(body) - - -def create_modules_db(module_info: dict) -> dict: - body = { - "name": "modulesdb", - "type": "redis", - "memory_size": 100000, - "port": 12003, - "module_list": [ - module_info['ft'], - module_info['graph'], - module_info['timeseries'], - ] - } - return create_db(body) - - -def create_crdb() -> dict: - cluster_fqdns = CRDB_INSTANCES.split() - assert len(cluster_fqdns) >= 2, f"At least two clusters are needed for a CRDB, got {cluster_fqdns}" - crdb_cli_instances_args = (f"--instance fqdn={fqdn},username={USERNAME},password={PASSWORD}" - for fqdn in cluster_fqdns) - crdb_cli_instances_args = " ".join(crdb_cli_instances_args) - crdb_cli_command = f"/opt/redislabs/bin/crdb-cli crdb create --name mycrdb --memory-size 10mb --port 12005 --replication false --shards-count 1 {crdb_cli_instances_args}" - print("Running the following command:") - print(crdb_cli_command) - subprocess.run(crdb_cli_command.split()) - - -def main(): - - if CREATE_SIMPLE_DB: - print("Creating simple db...") - bdb = create_simple_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping simple db") - - if CREATE_CLUSTER_DB: - print("Creating cluster db...") - bdb = create_cluster_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping cluster db") - - if CREATE_TLS_DB: - print("Creating TLS db...") - bdb = create_tls_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping TLS db") - - if CREATE_TLS_MUTUAL_AUTH_DB: - print("Creating TLS mutual auth db...") - bdb = create_tls_mutual_auth_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping TLS mutual auth db") - - if CREATE_MODULES_DB: - print("Getting modules info...") - module_info = get_module_data() - print('done') - print("Creating modules db...") - bdb = create_modules_db(module_info) - print('done') - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping modules db") - - if CREATE_CRDB: - print("Creating CRDB...") - create_crdb() - print('done') - print("\n\n") - else: - print("Skipping CRDB") - - -if __name__ == '__main__': - main() diff --git a/redisinsight/api/test/test-runs/re-clu/db.json b/redisinsight/api/test/test-runs/re-clu/db.json new file mode 100644 index 0000000000..0ceba7ee65 --- /dev/null +++ b/redisinsight/api/test/test-runs/re-clu/db.json @@ -0,0 +1,18 @@ +{ + "name": "testdb", + "type": "redis", + "memory_size": 1073741824, + "port": 12010, + "sharding": true, + "shards_count": 3, + "proxy_policy": "all-master-shards", + "oss_cluster": true, + "shard_key_regex": [ + { + "regex": ".*\\{(?.*)\\}.*" + }, + { + "regex": "(?.*)" + } + ] +} diff --git a/redisinsight/api/test/test-runs/re-clu/docker-compose.yml b/redisinsight/api/test/test-runs/re-clu/docker-compose.yml index b0e04aeb61..b7be56fc94 100644 --- a/redisinsight/api/test/test-runs/re-clu/docker-compose.yml +++ b/redisinsight/api/test/test-runs/re-clu/docker-compose.yml @@ -9,9 +9,5 @@ services: build: ./re-clu cap_add: - sys_resource - environment: - - CREATE_CLUSTER_DB=1 -# ports: -# - 12010:12010 -# - 8443:8443 -# - 9443:9443 + env_file: + - ./re-clu/.env diff --git a/redisinsight/api/test/test-runs/re-st/run_re_and_create_db.sh b/redisinsight/api/test/test-runs/re-clu/entrypoint.sh similarity index 70% rename from redisinsight/api/test/test-runs/re-st/run_re_and_create_db.sh rename to redisinsight/api/test/test-runs/re-clu/entrypoint.sh index 1f8c28f883..70b5c44bdb 100644 --- a/redisinsight/api/test/test-runs/re-st/run_re_and_create_db.sh +++ b/redisinsight/api/test/test-runs/re-clu/entrypoint.sh @@ -1,5 +1,8 @@ #! /bin/bash +TEST_RE_USER=${TEST_RE_USER:-"demo@redislabs.com"} +TEST_RE_PASS=${TEST_RE_PASS:-"123456"} + set -e # enable job control @@ -8,7 +11,7 @@ set -m /opt/start.sh & # This command queries the REST API and outputs the status code -CURL_CMD="curl --silent --fail --output /dev/null -i -w %{http_code} -u demo@redislabs.com:123456 -k https://localhost:9443/v1/nodes" +CURL_CMD="curl --silent --fail --output /dev/null -i -w %{http_code} -u $TEST_RE_USER:$TEST_RE_PASS -k https://localhost:9443/v1/nodes" # Wait to get 2 consecutive 200 responses from the REST API while true @@ -30,9 +33,9 @@ do fi done -echo "Running Python script to create databases..." -python3 create_dbs.py +echo "Creating databases..." +curl -k -u "$TEST_RE_USER:$TEST_RE_PASS" --request POST --url "https://localhost:9443/v1/bdbs" --header 'content-type: application/json' --data-binary "@db.json" # now we bring the primary process back into the foreground # and leave it there diff --git a/redisinsight/api/test/test-runs/re-st/Dockerfile b/redisinsight/api/test/test-runs/re-st/Dockerfile index 136d43fbc3..8af7097d05 100644 --- a/redisinsight/api/test/test-runs/re-st/Dockerfile +++ b/redisinsight/api/test/test-runs/re-st/Dockerfile @@ -1,18 +1,9 @@ -FROM redislabs/redis:6.0.8-28.bionic +FROM redislabs/redis:6.2.8-50 -# Change user to root to install pip -USER root -RUN set -ex \ - && apt-get update \ - && apt-get install -y python3-pip \ - && pip3 install requests -# Change user back to redislabs -USER redislabs - -# Set the env var to instruct RE to create a cluster on startup +## Set the env var to instruct RE to create a cluster on startup ENV BOOTSTRAP_ACTION create_cluster ENV BOOTSTRAP_CLUSTER_FQDN cluster.local -COPY run_re_and_create_db.sh create_dbs.py cert.pem ./ +COPY entrypoint.sh db.json ./ -ENTRYPOINT [ "bash", "./run_re_and_create_db.sh" ] +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/redisinsight/api/test/test-runs/re-st/README.md b/redisinsight/api/test/test-runs/re-st/README.md deleted file mode 100644 index 6b65582c5a..0000000000 --- a/redisinsight/api/test/test-runs/re-st/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# docker-redisenterprise-testdb -A Docker container that creates test databases on a Redis Enterprise cluster - - -## Databases - -Environment variable control which dbs are created. By default, no db is created. -- `CREATE_SIMPLE_DB`: Single-shard simple database on port 12000 -- `CREATE_CLUSTER_DB`: Database-clustering enabled, with 3 shards on port 12010 -- `CREATE_TLS_DB`: Single-shard TLS database on port 12443 -- `CREATE_TLS_MUTUAL_AUTH_DB`: Single-shard TLS client authentication enabled database on port 12465 -- `CREATE_MODULES_DB`: Single-shard db with modules: RedisGraph, RediSearch and RedisTimeSeries on port 12003 -- `CREATE_CRDB`: CRDT database on port 12005. `CRDB_INSTANCES` env var should also be set to a space-separated list of the FQDNs of participating clusters. - - -## References - -- [Redis Enterprise REST API Docs](https://storage.googleapis.com/rlecrestapi/rest-html/http_rest_api.html) diff --git a/redisinsight/api/test/test-runs/re-st/cert.pem b/redisinsight/api/test/test-runs/re-st/cert.pem deleted file mode 100644 index 3dfb16abf7..0000000000 --- a/redisinsight/api/test/test-runs/re-st/cert.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFfzCCA2egAwIBAgIURYhz7wsPwNGHxFoINaEB6ysJyEYwDQYJKoZIhvcNAQEL -BQAwTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRgwFgYDVQQKDA9SZWRpc0xh -YnMsIEluYy4xGTAXBgNVBAMMEHJlZGlzaW5zaWdodC5jb20wHhcNMjAxMjI4MTA1 -NzU1WhcNMjExMjI4MTA1NzU1WjBPMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0Ex -GDAWBgNVBAoMD1JlZGlzTGFicywgSW5jLjEZMBcGA1UEAwwQcmVkaXNpbnNpZ2h0 -LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANS1OFxNjFn1JQJO -HounllmdHA0hMCoZ5DGO1rur+ppZBZFh9Z4NT01neEVMi9vZkvsmeGQ6xwOibbGv -EClNXrFS/pqBg91AdGJzz6lH08VHgAtu+2P6kDewTXQyN+Gu+3qlss4t3di9jQAd -oYXkbPT5ZPnaJtkWq+rrw8P7hCC0OARETzysg/IVukyYYIGjgpeCOWOWeF4oGurO -EQtZnuVncMC4ZvobGlGtXk0Rk46j1uwLDNgovLkajnMHViGxS/kCOSZB2UJvhve/ -YmKK14kxc/mFoNu1+ING5bGEcprVUe8wKXb+TuTRp2YxIB1GIG8+vQwcYFS5l0kH -BsRGwBKS2ESaSQ1eSyIVd2wdXVHqlILlmy2Zvi9DM/kMX/OtoBjIDhWx9mStTxtz -DjHIooT/FeFQzC2ah1bP+/KYabCHScEXpXxubpK9saXLtj4Vk/RcfZ42+0eeVxBf -Dttln7MHP79VyyCZpT9OSu8q4qU6dVDlz3fczC6fkE6b2kPVQnSLz9Wmr47syawg -Argv96d6wcNiiNzOyHZNCaxHwsVFx0zJOuRiyMwJp4JvrAb2glkKgCzVNjCMO+8v -HuXv6TTUHKvLwqnuqe04VRQazIDUPzQma+whdgIMkAJBanm7U+fmZ/LX0d1XLCMv -E5k/zzu9uOXUNM46Pvz20Rko8W4fAgMBAAGjUzBRMB0GA1UdDgQWBBSbd9FdsIfw -AUWS9kE93p5MGmv4WTAfBgNVHSMEGDAWgBSbd9FdsIfwAUWS9kE93p5MGmv4WTAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCvXOzCaEGf6xoA8A7S -U5GXv5rGof1TW9+FogiUc1GyvEJJa4spo2MEASBCkqN0e5ac4tE3XiMsPXgoMCNt -XFRa71rI1a61an+02+i5hgG6fBTftTLRaMOWKXswiF/GN+Jc2aYPNy9KZtwxowre -g28a47vlAUT3O4ZdpdR4TVD6Zvy/EtA/TfuP9XPBA1TaAp6jEUArZFU3H0VM6nn3 -1GKH8rxqv54jLovA7ASs6CKU8PtgU0RDypB69lYVWjbVdrSA5Jh6o54N60tNizCk -LzaEbSGijI6qeKwouFoqKqx+Kr9aYPnGnA1rJXWVvI1Z8XIsWmb9fFnl9nPspqxF -A6vuvc2W9TIFLvwZIe/QC4kKq07rA7zCiF/dsjqU1VdeoJZXp0109t7Ua0tlUO/J -XIIY8sQHyJ2kZAiL9ghCHvgO5ciezVvu6ru1E6M8FD0OaopPa2jM4V562sZ/Ztdi -7IPlQ160zsHuYq3Q4uYNalWJ7gLaxbXFaok/fZo39GRVzjk/FN8zuXuGjMchhoaB -XrWDF0H6NM92cvH/8cgSg7JXvBlCdGqV9XPnqYm312MqeFZ9Lg2H8wTfHuAzNrOZ -eV1Cp5mBepvYtsFX3ATdDg7+QgAVhLdnAyUmMNY2cW1GTdfQV4L3fIm0S7RKayC6 -eeFbQeTv19tELZiXhalV3YuJMQ== ------END CERTIFICATE----- diff --git a/redisinsight/api/test/test-runs/re-st/create_dbs.py b/redisinsight/api/test/test-runs/re-st/create_dbs.py deleted file mode 100644 index 422f57b832..0000000000 --- a/redisinsight/api/test/test-runs/re-st/create_dbs.py +++ /dev/null @@ -1,215 +0,0 @@ -import os -import pprint -import subprocess - -import requests - - -# Suppress "Unverified HTTPS request" warnings -# See https://github.com/influxdata/influxdb-python/issues/240#issuecomment-140003499 -# pylint: disable=no-member -requests.packages.urllib3.disable_warnings() - - -CREATE_SIMPLE_DB = bool(os.environ.get("CREATE_SIMPLE_DB", "")) -CREATE_CLUSTER_DB = bool(os.environ.get("CREATE_CLUSTER_DB", "")) -CREATE_TLS_DB = bool(os.environ.get("CREATE_TLS_DB", "")) -CREATE_TLS_MUTUAL_AUTH_DB = bool(os.environ.get("CREATE_TLS_MUTUAL_AUTH_DB", "")) -CREATE_MODULES_DB = bool(os.environ.get("CREATE_MODULES_DB", "")) -CREATE_CRDB = bool(os.environ.get("CREATE_CRDB", "")) -CRDB_INSTANCES = os.environ.get("CRDB_INSTANCES", "") - - -USERNAME = 'demo@redislabs.com' -PASSWORD = '123456' - - -RLEC_API_BASE_URL = 'https://localhost:9443/v1' - - -COMMON_REQ_PARAMS = dict(auth=(USERNAME, PASSWORD), - verify=False,) - - -def get_module_data() -> dict: - """ - Returns a dict of module name to a dict containing: - - module_id - - module_name - - module_args - - semantic_version - """ - resp = requests.get(url=f'{RLEC_API_BASE_URL}/modules', **COMMON_REQ_PARAMS) - if not resp.ok: - raise Exception(f"Failed to get modules info: {resp.status_code}: {resp.text}") - data = resp.json() - module_dict = {} - for m in data: - module_dict[m['module_name']] = { - "module_id": m["uid"], - "module_name": m["module_name"], - "module_args": m["command_line_args"], - "semantic_version": m["semantic_version"], - } - return module_dict - - -def create_db(body: dict) -> dict: - """ - Create a bdb and return the response from the API. - """ - resp = requests.post(url=f'{RLEC_API_BASE_URL}/bdbs', - json=body, - **COMMON_REQ_PARAMS) - if not resp.ok: - raise Exception(f"Failed to create db: {resp.status_code}: {resp.text}") - data = resp.json() - return data - - -def create_simple_db() -> dict: - body = { - "name": "testdb", - "type": "redis", - "memory_size": 1024 * 1024 * 1024, # 1GB - "port": 12000 - } - return create_db(body) - - -def create_cluster_db() -> dict: - body = { - "name": "testdb", - "type": "redis", - "memory_size": 1024 * 1024 * 1024, # 1GB - "port": 12010, - "sharding": True, - "shards_count": 3, - # Default OSS Redis Cluster-like hashing policy. - # These regexes are taken from the RLEC REST API docs: - # https://storage.googleapis.com/rlecrestapi/rest-html/http_rest_api.html#bdb (see the 'shard_key_regex' attribute) - "shard_key_regex": [ - {"regex": ".*\\{(?.*)\\}.*" }, - {"regex": "(?.*)" } - ], - } - return create_db(body) - - -def create_tls_db() -> dict: - body = { - "name": "testtlsdb", - "type": "redis", - "memory_size": 100000, - "port": 12443, - "tls_mode": "enabled", - "enforce_client_authentication": "disabled" - } - return create_db(body) - - -def create_tls_mutual_auth_db() -> dict: - with open('./cert.pem') as f: - cert_str = f.read() - body = { - "name": "testtlsclientauthdb", - "type": "redis", - "memory_size": 100000, - "port": 12465, - "tls_mode": "enabled", - "enforce_client_authentication": "enabled", - "authentication_ssl_client_certs": [{ - "client_cert": cert_str, - }] - } - return create_db(body) - - -def create_modules_db(module_info: dict) -> dict: - body = { - "name": "modulesdb", - "type": "redis", - "memory_size": 100000, - "port": 12003, - "module_list": [ - module_info['ft'], - module_info['graph'], - module_info['timeseries'], - ] - } - return create_db(body) - - -def create_crdb() -> dict: - cluster_fqdns = CRDB_INSTANCES.split() - assert len(cluster_fqdns) >= 2, f"At least two clusters are needed for a CRDB, got {cluster_fqdns}" - crdb_cli_instances_args = (f"--instance fqdn={fqdn},username={USERNAME},password={PASSWORD}" - for fqdn in cluster_fqdns) - crdb_cli_instances_args = " ".join(crdb_cli_instances_args) - crdb_cli_command = f"/opt/redislabs/bin/crdb-cli crdb create --name mycrdb --memory-size 10mb --port 12005 --replication false --shards-count 1 {crdb_cli_instances_args}" - print("Running the following command:") - print(crdb_cli_command) - subprocess.run(crdb_cli_command.split()) - - -def main(): - - if CREATE_SIMPLE_DB: - print("Creating simple db...") - bdb = create_simple_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping simple db") - - if CREATE_CLUSTER_DB: - print("Creating cluster db...") - bdb = create_cluster_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping cluster db") - - if CREATE_TLS_DB: - print("Creating TLS db...") - bdb = create_tls_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping TLS db") - - if CREATE_TLS_MUTUAL_AUTH_DB: - print("Creating TLS mutual auth db...") - bdb = create_tls_mutual_auth_db() - print("done") - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping TLS mutual auth db") - - if CREATE_MODULES_DB: - print("Getting modules info...") - module_info = get_module_data() - print('done') - print("Creating modules db...") - bdb = create_modules_db(module_info) - print('done') - pprint.pprint(bdb) - print("\n\n") - else: - print("Skipping modules db") - - if CREATE_CRDB: - print("Creating CRDB...") - create_crdb() - print('done') - print("\n\n") - else: - print("Skipping CRDB") - - -if __name__ == '__main__': - main() diff --git a/redisinsight/api/test/test-runs/re-st/db.json b/redisinsight/api/test/test-runs/re-st/db.json new file mode 100644 index 0000000000..750c740eb5 --- /dev/null +++ b/redisinsight/api/test/test-runs/re-st/db.json @@ -0,0 +1,6 @@ +{ + "name": "testdb", + "type": "redis", + "memory_size": 1073741824, + "port": 12000 +} diff --git a/redisinsight/api/test/test-runs/re-st/docker-compose.yml b/redisinsight/api/test/test-runs/re-st/docker-compose.yml index e1f508e749..150b4ba1fd 100644 --- a/redisinsight/api/test/test-runs/re-st/docker-compose.yml +++ b/redisinsight/api/test/test-runs/re-st/docker-compose.yml @@ -9,9 +9,5 @@ services: build: ./re-st cap_add: - sys_resource - environment: - - CREATE_SIMPLE_DB=true -# ports: -# - 12000:12000 -# - 8443:8443 -# - 9443:9443 + env_file: + - ./re-st/.env diff --git a/tests/e2e/rte/redis-enterprise/run_re_and_create_db.sh b/redisinsight/api/test/test-runs/re-st/entrypoint.sh similarity index 70% rename from tests/e2e/rte/redis-enterprise/run_re_and_create_db.sh rename to redisinsight/api/test/test-runs/re-st/entrypoint.sh index bc72dca13d..70b5c44bdb 100644 --- a/tests/e2e/rte/redis-enterprise/run_re_and_create_db.sh +++ b/redisinsight/api/test/test-runs/re-st/entrypoint.sh @@ -1,5 +1,8 @@ #! /bin/bash +TEST_RE_USER=${TEST_RE_USER:-"demo@redislabs.com"} +TEST_RE_PASS=${TEST_RE_PASS:-"123456"} + set -e # enable job control @@ -8,7 +11,7 @@ set -m /opt/start.sh & # This command queries the REST API and outputs the status code -CURL_CMD="curl --silent --fail --output /dev/null -i -w %{http_code} -u demo@redislabs.com:123456 -k https://redis-enterprise:9443/v1/nodes" +CURL_CMD="curl --silent --fail --output /dev/null -i -w %{http_code} -u $TEST_RE_USER:$TEST_RE_PASS -k https://localhost:9443/v1/nodes" # Wait to get 2 consecutive 200 responses from the REST API while true @@ -30,9 +33,9 @@ do fi done -echo "Running Python script to create databases..." -python3 create_dbs.py +echo "Creating databases..." +curl -k -u "$TEST_RE_USER:$TEST_RE_PASS" --request POST --url "https://localhost:9443/v1/bdbs" --header 'content-type: application/json' --data-binary "@db.json" # now we bring the primary process back into the foreground # and leave it there diff --git a/redisinsight/api/yarn.lock b/redisinsight/api/yarn.lock index 9ebe25d550..846879c23a 100644 --- a/redisinsight/api/yarn.lock +++ b/redisinsight/api/yarn.lock @@ -3686,6 +3686,15 @@ fs-extra@9.1.0, fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" diff --git a/redisinsight/main.dev.ts b/redisinsight/main.dev.ts index bda37fd4ff..1195c401ef 100644 --- a/redisinsight/main.dev.ts +++ b/redisinsight/main.dev.ts @@ -44,14 +44,14 @@ if (process.env.NODE_ENV !== 'production') { log.info('App starting.....'); export default class AppUpdater { - constructor() { + constructor(url: string = '') { log.info('AppUpdater initialization'); log.transports.file.level = 'info'; try { autoUpdater.setFeedURL({ provider: 'generic', - url: process.env.MANUAL_UPGRADES_LINK || process.env.UPGRADES_LINK, + url, }); } catch (error) { log.error(error); @@ -119,8 +119,10 @@ const bootstrap = async () => { trayInstance = tray.buildTray(); } - if (process.env.NODE_ENV === 'production') { - new AppUpdater(); + const upgradeUrl = process.env.MANUAL_UPGRADES_LINK || process.env.UPGRADES_LINK; + + if (upgradeUrl) { + new AppUpdater(upgradeUrl); } app.setName('RedisInsight'); @@ -183,11 +185,10 @@ export const createWindow = async (splash: BrowserWindow | null = null) => { webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true, - webSecurity: false, + webSecurity: true, contextIsolation: false, spellcheck: true, - allowRunningInsecureContent: true, - enableRemoteModule: true, + allowRunningInsecureContent: false, scrollBounce: true, }, }); diff --git a/redisinsight/ui/src/components/ContentEditable.tsx b/redisinsight/ui/src/components/ContentEditable.tsx index 1526593c18..335f363936 100644 --- a/redisinsight/ui/src/components/ContentEditable.tsx +++ b/redisinsight/ui/src/components/ContentEditable.tsx @@ -28,9 +28,9 @@ export const parseMultilineContentEditableChangeHtml = (text: string = '') => export const parseContentEditableHtml = (text: string = '') => text .replace(/ /gi, ' ') - .replace(/&/gi, '&') .replace(/</gi, '<') .replace(/>/gi, '>') + .replace(/&/gi, '&') const onPaste = (e: React.ClipboardEvent) => { e.preventDefault() diff --git a/redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.spec.tsx b/redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.spec.tsx new file mode 100644 index 0000000000..fdf0c0296d --- /dev/null +++ b/redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.spec.tsx @@ -0,0 +1,62 @@ +import { cloneDeep } from 'lodash' +import React from 'react' + +import { cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils' +import { toggleCli, toggleCliHelper } from 'uiSrc/slices/cli/cli-settings' + +import BottomGroupComponents from './BottomGroupComponents' + +jest.mock('uiSrc/slices/cli/cli-settings', () => ({ + ...jest.requireActual('uiSrc/slices/cli/cli-settings'), + cliSettingsSelector: jest.fn().mockReturnValue({ + isShowCli: true, + isShowHelper: true + }), +})) + +let store: typeof mockedStore +beforeEach(() => { + cleanup() + store = cloneDeep(mockedStore) + store.clearActions() +}) + +const commandHelperId = 'command-helper' +const cliId = 'cli' + +describe('BottomGroupComponents', () => { + it('should render', () => { + expect( + render() + ).toBeTruthy() + }) + + it('should render Cli when isShowCli truthy', () => { + render() + expect(screen.getByTestId(cliId)).toBeInTheDocument() + }) + + it('should render Command Helper when isShowHelper truthy', () => { + render() + expect(screen.getByTestId(commandHelperId)).toBeInTheDocument() + }) + + it('should not to close command helper after closing cli', () => { + render() + fireEvent.click(screen.getByTestId('collapse-cli')) + const expectedActions = [toggleCli()] + expect(store.getActions()).toEqual(expect.arrayContaining(expectedActions)) + + expect(screen.getByTestId('command-helper')).toBeInTheDocument() + }) + + it('should not to close cli after closing command-helper', () => { + render() + fireEvent.click(screen.getByTestId('close-command-helper')) + + const expectedActions = [toggleCliHelper()] + expect(store.getActions()).toEqual(expect.arrayContaining(expectedActions)) + + expect(screen.getByTestId('cli')).toBeInTheDocument() + }) +}) diff --git a/redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.tsx b/redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.tsx new file mode 100644 index 0000000000..d24d1f4129 --- /dev/null +++ b/redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { useSelector } from 'react-redux' +import cx from 'classnames' +import { cliSettingsSelector } from 'uiSrc/slices/cli/cli-settings' +import CliWrapper from 'uiSrc/components/cli/CliWrapper' +import CommandHelperWrapper from 'uiSrc/components/command-helper/CommandHelperWrapper' +import BottomGroupMinimized from './components/bottom-group-minimized/BottomGroupMinimized' + +import styles from './styles.module.scss' + +const BottomGroupComponents = () => { + const { isShowCli, isShowHelper } = useSelector(cliSettingsSelector) + + return ( +
+
+ {isShowCli && } + {isShowHelper && ( +
+ +
+ )} +
+ +
+ ) +} + +export default BottomGroupComponents diff --git a/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.spec.tsx b/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.spec.tsx new file mode 100644 index 0000000000..a42e8f22cc --- /dev/null +++ b/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.spec.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import { cloneDeep } from 'lodash' + +import { toggleCli, toggleCliHelper } from 'uiSrc/slices/cli/cli-settings' +import { cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils' +import BottomGroupMinimized from './BottomGroupMinimized' + +let store: typeof mockedStore +beforeEach(() => { + cleanup() + store = cloneDeep(mockedStore) + store.clearActions() +}) + +describe('BottomGroupMinimized', () => { + it('should render', () => { + expect( + render() + ).toBeTruthy() + }) + + it('should "toggleCli" action be called after click "expand-cli" button', () => { + render() + fireEvent.click(screen.getByTestId('expand-cli')) + + const expectedActions = [toggleCli()] + expect(store.getActions()).toEqual(expectedActions) + }) + + it('should "toggleCliHelper" action be called after click "expand-command-helper" button', () => { + render() + fireEvent.click(screen.getByTestId('expand-command-helper')) + + const expectedActions = [toggleCliHelper()] + expect(store.getActions()).toEqual(expectedActions) + }) +}) diff --git a/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.tsx b/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.tsx new file mode 100644 index 0000000000..c540f6368a --- /dev/null +++ b/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import cx from 'classnames' +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui' +import { useDispatch, useSelector } from 'react-redux' +import { useParams } from 'react-router-dom' + +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { toggleCli, toggleCliHelper, cliSettingsSelector } from 'uiSrc/slices/cli/cli-settings' + +import styles from '../../styles.module.scss' + +const BottomGroupMinimized = () => { + const { isShowHelper, isShowCli } = useSelector(cliSettingsSelector) + const { instanceId = '' } = useParams<{ instanceId: string }>() + const dispatch = useDispatch() + + const handleExpandCli = () => { + sendEventTelemetry({ + event: TelemetryEvent.CLI_OPENED, + eventData: { + databaseId: instanceId + } + }) + dispatch(toggleCli()) + } + + const handleExpandHelper = () => { + sendEventTelemetry({ + event: isShowHelper ? TelemetryEvent.COMMAND_HELPER_COLLAPSED : TelemetryEvent.COMMAND_HELPER_EXPANDED, + eventData: { + databaseId: instanceId + } + }) + dispatch(toggleCliHelper()) + } + + return ( +
+ + + + + CLI + + + + + + Command Helper + + + +
+ ) +} + +export default BottomGroupMinimized diff --git a/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/index.ts b/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/index.ts new file mode 100644 index 0000000000..7249cdcf76 --- /dev/null +++ b/redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/index.ts @@ -0,0 +1,3 @@ +import BottomGroupMinimized from './BottomGroupMinimized' + +export default BottomGroupMinimized diff --git a/redisinsight/ui/src/components/bottom-group-components/styles.module.scss b/redisinsight/ui/src/components/bottom-group-components/styles.module.scss new file mode 100644 index 0000000000..d49005f83d --- /dev/null +++ b/redisinsight/ui/src/components/bottom-group-components/styles.module.scss @@ -0,0 +1,63 @@ +.groupComponentsWrapper { + flex-grow: 1; + height: 100%; + padding: 0 16px; +} + +.groupComponents { + display: flex; + flex-grow: 1; + height: calc(100% - 26px); +} + +.containerMinimized { + height: 26px; + line-height: 26px; + border: 1px solid var(--euiColorLightShade); +} + +.componentBadgeItem { + margin: 0 2px; + cursor: pointer; + user-select: none; + + :global { + .euiBadge__text, .euiBadge__content { + cursor: pointer !important; + } + + .euiBadge__text { + display: flex; + align-items: center; + } + + .euiIcon { + margin-right: 4px; + } + } +} + +.componentBadge { + background-color: transparent !important; + color: var(--euiTextSubduedColor) !important; + height: 18px !important; + border: none !important; + + &:hover { + background-color: var(--buttonIconPrimaryHover) !important; + } + + &.active { + background-color: var(--hoverInListColorDarken) !important; + color: var(--euiColorPrimary) !important; + } +} + + +.helperWrapper { + width: 100%; + max-width: 360px; + &.fullWidth { + max-width: 100%; + } +} diff --git a/redisinsight/ui/src/components/cli/Cli/Cli.tsx b/redisinsight/ui/src/components/cli/Cli/Cli.tsx index ba03471815..edaf369a4f 100644 --- a/redisinsight/ui/src/components/cli/Cli/Cli.tsx +++ b/redisinsight/ui/src/components/cli/Cli/Cli.tsx @@ -5,7 +5,7 @@ import CliBodyWrapper from 'uiSrc/components/cli/components/cli-body' import styles from './styles.module.scss' const CLI = () => ( -
+
diff --git a/redisinsight/ui/src/components/cli/Cli/styles.module.scss b/redisinsight/ui/src/components/cli/Cli/styles.module.scss index d10e657e75..92f31f1508 100644 --- a/redisinsight/ui/src/components/cli/Cli/styles.module.scss +++ b/redisinsight/ui/src/components/cli/Cli/styles.module.scss @@ -5,8 +5,6 @@ .container { height: 100%; width: 100%; - padding-left: 16px; - padding-right: 16px; } .main { diff --git a/redisinsight/ui/src/components/cli/CliWrapper.spec.tsx b/redisinsight/ui/src/components/cli/CliWrapper.spec.tsx index 08655bc584..2d94d17571 100644 --- a/redisinsight/ui/src/components/cli/CliWrapper.spec.tsx +++ b/redisinsight/ui/src/components/cli/CliWrapper.spec.tsx @@ -1,6 +1,6 @@ import { cloneDeep } from 'lodash' import React from 'react' -import { clearSearchingCommand, setCliEnteringCommand } from 'uiSrc/slices/cli/cli-settings' +import { processCliClient, setCliEnteringCommand } from 'uiSrc/slices/cli/cli-settings' import { cleanup, mockedStore, render } from 'uiSrc/utils/test-utils' import CliWrapper from './CliWrapper' @@ -35,7 +35,7 @@ describe('CliWrapper', () => { unmount() - const expectedActions = [clearSearchingCommand(), setCliEnteringCommand()] + const expectedActions = [processCliClient(), setCliEnteringCommand()] expect(store.getActions().slice(-2)).toEqual(expectedActions) }) }) diff --git a/redisinsight/ui/src/components/cli/components/cli-body/CliBody/styles.module.scss b/redisinsight/ui/src/components/cli/components/cli-body/CliBody/styles.module.scss index 168b934237..3350192da3 100644 --- a/redisinsight/ui/src/components/cli/components/cli-body/CliBody/styles.module.scss +++ b/redisinsight/ui/src/components/cli/components/cli-body/CliBody/styles.module.scss @@ -23,7 +23,6 @@ color: var(--textColorShade); border-top: 1px solid var(--euiColorLightShade); - border-right: 1px solid var(--euiColorLightShade); z-index: 10; diff --git a/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.spec.tsx b/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.spec.tsx index 9c8e134ab4..7cf849842c 100644 --- a/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.spec.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.spec.tsx @@ -110,12 +110,6 @@ describe('CliBodyWrapper', () => { ) }) - it('CliHelper should be opened by default', () => { - render() - - expect(screen.getByTestId('cli-helper')).toBeInTheDocument() - }) - // It's not possible to simulate events on contenteditable with testing-react-library, // or any testing library that uses js - dom, because of a limitation on js - dom itself. // https://github.com/testing-library/dom-testing-library/pull/235 diff --git a/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.tsx b/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.tsx index ad9a15fcf9..0cd53f5866 100644 --- a/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.tsx @@ -29,10 +29,9 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances' import { checkUnsupportedCommand, clearOutput } from 'uiSrc/utils/cliHelper' import { InitOutputText, ConnectionSuccessOutputText } from 'uiSrc/constants/cliOutput' import { SendClusterCommandDto } from 'apiSrc/modules/cli/dto/cli.dto' - import CliBody from './CliBody' + import styles from './CliBody/styles.module.scss' -import CliHelperWrapper from '../cli-helper' const CliBodyWrapper = () => { const cliClientUuid = sessionStorageService.get(BrowserStorageItem.cliClientUuid) ?? '' @@ -45,7 +44,6 @@ const CliBodyWrapper = () => { const { errorClient: error, unsupportedCommands, - isShowHelper, isEnteringCommand, isSearching, matchedCommand @@ -147,7 +145,6 @@ const CliBodyWrapper = () => { setCommand={setCommand} onSubmit={handleSubmit} /> - {isShowHelper && } ) } diff --git a/redisinsight/ui/src/components/cli/components/cli-command-info/index.ts b/redisinsight/ui/src/components/cli/components/cli-command-info/index.ts deleted file mode 100644 index 0e9667fe8b..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-command-info/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliCommandInfo from './CliCommandInfo' - -export default CliCommandInfo diff --git a/redisinsight/ui/src/components/cli/components/cli-header-minimized/CliHeaderMinimized.spec.tsx b/redisinsight/ui/src/components/cli/components/cli-header-minimized/CliHeaderMinimized.spec.tsx deleted file mode 100644 index 7be5f7f5a9..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-header-minimized/CliHeaderMinimized.spec.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import { cloneDeep } from 'lodash' -import { instance, mock } from 'ts-mockito' - -import { toggleCli, clearSearchingCommand } from 'uiSrc/slices/cli/cli-settings' -import { fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils' -import CliHeaderMinimized, { Props } from './CliHeaderMinimized' - -const mockedProps = mock() - -describe('CliHeaderMinimized', () => { - it('should render', () => { - expect( - render() - ).toBeTruthy() - }) - - it('should "toggleCli" & "clearSearchingCommand" actions be called after click "expand-cli" button', () => { - const store = cloneDeep(mockedStore) - - render() - fireEvent.click(screen.getByTestId('expand-cli')) - - const expectedActions = [toggleCli(), clearSearchingCommand()] - expect(store.getActions()).toEqual(expectedActions) - }) -}) diff --git a/redisinsight/ui/src/components/cli/components/cli-header-minimized/CliHeaderMinimized.tsx b/redisinsight/ui/src/components/cli/components/cli-header-minimized/CliHeaderMinimized.tsx deleted file mode 100644 index 3a475d3714..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-header-minimized/CliHeaderMinimized.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react' -import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiText, EuiToolTip } from '@elastic/eui' -import { useDispatch } from 'react-redux' -import { useParams } from 'react-router-dom' - -import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { toggleCli, clearSearchingCommand } from 'uiSrc/slices/cli/cli-settings' - -import styles from '../cli-header/styles.module.scss' - -const CliHeaderMinimized = () => { - const { instanceId = '' } = useParams<{ instanceId: string }>() - const dispatch = useDispatch() - - const handleExpandCli = () => { - sendEventTelemetry({ - event: TelemetryEvent.CLI_OPENED, - eventData: { - databaseId: instanceId - } - }) - dispatch(toggleCli()) - dispatch(clearSearchingCommand()) - } - - return ( -
- - - CLI - - - - - {}} - /> - - - -
- ) -} - -export default CliHeaderMinimized diff --git a/redisinsight/ui/src/components/cli/components/cli-header-minimized/index.ts b/redisinsight/ui/src/components/cli/components/cli-header-minimized/index.ts deleted file mode 100644 index 7e207404d1..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-header-minimized/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliHeaderMinimized from './CliHeaderMinimized' - -export default CliHeaderMinimized diff --git a/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.spec.tsx b/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.spec.tsx index b03f77b94e..08cf905635 100644 --- a/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.spec.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.spec.tsx @@ -1,6 +1,5 @@ import { cloneDeep } from 'lodash' import React from 'react' -import { instance, mock } from 'ts-mockito' import { cleanup, @@ -12,12 +11,10 @@ import { waitFor, } from 'uiSrc/utils/test-utils' import { BrowserStorageItem } from 'uiSrc/constants' -import { processCliClient, toggleCli, toggleCliHelper } from 'uiSrc/slices/cli/cli-settings' +import { processCliClient, toggleCli } from 'uiSrc/slices/cli/cli-settings' import { connectedInstanceSelector } from 'uiSrc/slices/instances' import { sessionStorageService } from 'uiSrc/services' -import CliHeader, { Props } from './CliHeader' - -const mockedProps = mock() +import CliHeader from './CliHeader' let store: typeof mockedStore beforeEach(() => { @@ -44,11 +41,11 @@ jest.mock('uiSrc/services', () => ({ describe('CliHeader', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should "toggleCli" action be called after click "collapse-cli" button', () => { - render() + render() fireEvent.click(screen.getByTestId('collapse-cli')) const expectedActions = [toggleCli()] @@ -59,7 +56,7 @@ describe('CliHeader', () => { const mockUuid = 'test-uuid' sessionStorageMock.getItem = jest.fn().mockReturnValue(mockUuid) - render() + render() await waitFor(() => { fireEvent.click(screen.getByTestId('collapse-cli')) @@ -69,25 +66,11 @@ describe('CliHeader', () => { expect(store.getActions()).toEqual(expectedActions) }) - it('should "toggleCliHelper" action be called after click "collapse-cli-helper" button', async () => { - const mockUuid = 'test-uuid' - sessionStorageMock.getItem = jest.fn().mockReturnValue(mockUuid) - - render() - - await waitFor(() => { - fireEvent.click(screen.getByTestId('collapse-cli-helper')) - }) - - const expectedActions = [toggleCliHelper()] - expect(store.getActions().slice(0, expectedActions.length)).toEqual(expectedActions) - }) - it('should "processCliClient" action be called after unmount with mocked sessionStorage item ', () => { const mockUuid = 'test-uuid' sessionStorageService.get = jest.fn().mockReturnValue(mockUuid) - const { unmount } = render() + const { unmount } = render() unmount() @@ -108,7 +91,7 @@ describe('CliHeader', () => { port, })) - const { queryByTestId } = render() + const { queryByTestId } = render() const endpointEl = queryByTestId(mockEndpoint) diff --git a/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx b/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx index 67b702431f..c336a8b96f 100644 --- a/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx @@ -10,13 +10,12 @@ import { EuiText, EuiToolTip, EuiTextColor, + EuiIcon, } from '@elastic/eui' import { - cliSettingsSelector, deleteCliClientAction, toggleCli, - toggleCliHelper, } from 'uiSrc/slices/cli/cli-settings' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { BrowserStorageItem } from 'uiSrc/constants' @@ -30,7 +29,6 @@ const CliHeader = () => { const { instanceId = '' } = useParams<{ instanceId: string }>() - const { isShowHelper } = useSelector(cliSettingsSelector) const { host, port } = useSelector(connectedInstanceSelector) const endpoint = `${host}:${port}` @@ -58,27 +56,17 @@ const CliHeader = () => { dispatch(toggleCli()) } - const handleCollapseCliHelper = (event: React.MouseEvent) => { - event.stopPropagation() - sendEventTelemetry({ - event: isShowHelper ? TelemetryEvent.COMMAND_HELPER_COLLAPSED : TelemetryEvent.COMMAND_HELPER_EXPANDED, - eventData: { - databaseId: instanceId - } - }) - dispatch(toggleCliHelper()) - } - return ( -
+
- + + CLI @@ -102,37 +90,19 @@ const CliHeader = () => { - - - - - {}} + onClick={handleCollapseCli} /> diff --git a/redisinsight/ui/src/components/cli/components/cli-header/styles.module.scss b/redisinsight/ui/src/components/cli/components/cli-header/styles.module.scss index 489dbe54af..851bee1741 100644 --- a/redisinsight/ui/src/components/cli/components/cli-header/styles.module.scss +++ b/redisinsight/ui/src/components/cli/components/cli-header/styles.module.scss @@ -1,5 +1,4 @@ -.container, -.containerMinimized { +.container { height: 34px; line-height: 34px; width: 100%; @@ -11,22 +10,10 @@ z-index: 10; } -.containerMinimized { - margin-left: 16px; - cursor: pointer; - width: calc(100% - 32px); - border: 1px solid var(--euiColorLightShade); -} - .icon { margin-left: 5px; } -.iconHelper svg { - width: 24px; - height: 24px; -} - .endpointContainer { cursor: default; font: normal normal normal 12px/15px Graphik, sans-serif !important; @@ -45,3 +32,15 @@ text-overflow: ellipsis; padding-left: 5px; } + +.title { + display: flex; + flex-direction: row !important; + align-items: center; + :global { + .euiIcon { + color: var(--euiColorPrimary); + margin-right: 8px; + } + } +} diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/index.ts b/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/index.ts deleted file mode 100644 index 358da01d4f..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliHelper from './CliHelper' - -export default CliHelper diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/index.ts b/redisinsight/ui/src/components/cli/components/cli-helper/index.ts deleted file mode 100644 index 0808ce3c8f..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-helper/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliHelperWrapper from './CliHelperWrapper' - -export default CliHelperWrapper diff --git a/redisinsight/ui/src/components/cli/components/cli-search-output/index.ts b/redisinsight/ui/src/components/cli/components/cli-search-output/index.ts deleted file mode 100644 index 12ac491dd0..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-search-output/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliSearchOutput from './CliSearchOutput' - -export default CliSearchOutput diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/index.ts b/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/index.ts deleted file mode 100644 index adc57ae766..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliSearchFilter from './CliSearchFilter' - -export default CliSearchFilter diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/index.ts b/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/index.ts deleted file mode 100644 index e624864d52..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliSearchInput from './CliSearchInput' - -export default CliSearchInput diff --git a/redisinsight/ui/src/components/cli/components/cli-search/index.ts b/redisinsight/ui/src/components/cli/components/cli-search/index.ts deleted file mode 100644 index cfc80c5c7a..0000000000 --- a/redisinsight/ui/src/components/cli/components/cli-search/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CliSearchWrapper from './CliSearchWrapper' - -export default CliSearchWrapper diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/CliHelper.spec.tsx b/redisinsight/ui/src/components/command-helper/CommandHelper/CommandHelper.spec.tsx similarity index 77% rename from redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/CliHelper.spec.tsx rename to redisinsight/ui/src/components/command-helper/CommandHelper/CommandHelper.spec.tsx index 1ba02539fb..ee6ad80d1f 100644 --- a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/CliHelper.spec.tsx +++ b/redisinsight/ui/src/components/command-helper/CommandHelper/CommandHelper.spec.tsx @@ -2,7 +2,7 @@ import { cloneDeep } from 'lodash' import React from 'react' import { instance, mock } from 'ts-mockito' import { cleanup, mockedStore, render, screen } from 'uiSrc/utils/test-utils' -import CliHelper, { Props } from './CliHelper' +import CommandHelper, { Props } from './CommandHelper' const mockedProps = mock() let store: typeof mockedStore @@ -33,11 +33,11 @@ const mockedSearchedCommands = ['HSET', 'SET'] describe('CliHelper', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('Cli Helper should be in the Document', () => { - render() + render() const cliHelper = screen.queryByTestId('cli-helper') @@ -45,7 +45,7 @@ describe('CliHelper', () => { }) it('Default text component should be in the Document by default', () => { - render() + render() const cliHelperDefault = screen.queryByTestId('cli-helper-default') @@ -54,7 +54,7 @@ describe('CliHelper', () => { it('Default text component should not be in the Document when Command is matched', () => { const { queryByTestId } = render( - + ) const cliHelperDefault = queryByTestId('cli-helper-default') @@ -63,7 +63,7 @@ describe('CliHelper', () => { }) it('Cli Helper search should be in the Document', () => { - render() + render() const cliHelperSearch = screen.queryByTestId('cli-helper-search') @@ -72,7 +72,7 @@ describe('CliHelper', () => { it('Title text component should be in the Document when Command is matched', () => { const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-title') @@ -82,7 +82,7 @@ describe('CliHelper', () => { it('Summary text component should be in the Document when Command is matched and summary exists', () => { const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-summary') @@ -92,7 +92,7 @@ describe('CliHelper', () => { it('Complexity badge text component should be in the Document when Command is matched and complexity exists', () => { const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-complexity-short') @@ -102,7 +102,7 @@ describe('CliHelper', () => { it('Complexity text component should be in the Document when Command is matched and complexity exists', () => { const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-complexity') @@ -112,7 +112,7 @@ describe('CliHelper', () => { it('Complexity text component should not be in the Document when Command is matched and complexity exists and ComplexityShort detected', () => { const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-complexity') @@ -122,7 +122,7 @@ describe('CliHelper', () => { it('Since text component should be in the Document when Command is matched and since exists', () => { const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-since') @@ -133,7 +133,7 @@ describe('CliHelper', () => { it('Arguments component should be in the Document when Command is matched and argList exists', () => { const argList = ['key', 'field'].map((field, i) =>
{field}
) const { queryByTestId } = render( - + ) const cliHelperTitle = queryByTestId('cli-helper-arguments') @@ -142,7 +142,7 @@ describe('CliHelper', () => { }) it('Search results should be in the Document when Command is matched', () => { - render() + render() const cliHelperSearchResultsTitle = screen.queryAllByTestId(/cli-helper-output-title/) expect(cliHelperSearchResultsTitle).toHaveLength(2) diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/CliHelper.tsx b/redisinsight/ui/src/components/command-helper/CommandHelper/CommandHelper.tsx similarity index 87% rename from redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/CliHelper.tsx rename to redisinsight/ui/src/components/command-helper/CommandHelper/CommandHelper.tsx index fa3896c2c9..5f66faa5a5 100644 --- a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/CliHelper.tsx +++ b/redisinsight/ui/src/components/command-helper/CommandHelper/CommandHelper.tsx @@ -3,9 +3,10 @@ import { EuiLink, EuiText, EuiTextColor } from '@elastic/eui' import { CommandGroup } from 'uiSrc/constants' import { getDocUrlForCommand } from 'uiSrc/utils' -import CliCommandInfo from '../../cli-command-info' -import CliSearchWrapper from '../../cli-search' -import CliSearchOutput from '../../cli-search-output' +import CHCommandInfo from '../components/command-helper-info' +import CHSearchWrapper from '../components/command-helper-search' +import CHSearchOutput from '../components/command-helper-search-output' + import styles from './styles.module.scss' export interface Props { @@ -21,7 +22,7 @@ export interface Props { since: string; } -const CliHelper = (props: Props) => { +const CommandHelper = (props: Props) => { const { commandLine = '', isSearching = false, @@ -54,17 +55,17 @@ const CliHelper = (props: Props) => { return (
- +
{isSearching && ( - + )} {!isSearching && ( <> {commandLine && (
- + {summary && ( {summary} @@ -114,4 +115,4 @@ const CliHelper = (props: Props) => { ) } -export default CliHelper +export default CommandHelper diff --git a/redisinsight/ui/src/components/command-helper/CommandHelper/index.ts b/redisinsight/ui/src/components/command-helper/CommandHelper/index.ts new file mode 100644 index 0000000000..28eb4f29f7 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/CommandHelper/index.ts @@ -0,0 +1,3 @@ +import CommandHelper from './CommandHelper' + +export default CommandHelper diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/styles.module.scss b/redisinsight/ui/src/components/command-helper/CommandHelper/styles.module.scss similarity index 81% rename from redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/styles.module.scss rename to redisinsight/ui/src/components/command-helper/CommandHelper/styles.module.scss index 2b1cdbf5d2..abbc5080c8 100644 --- a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelper/styles.module.scss +++ b/redisinsight/ui/src/components/command-helper/CommandHelper/styles.module.scss @@ -4,11 +4,9 @@ .container { - height: 100%; + height: calc(100% - 34px); position: relative; - - width: 360px; - min-width: 360px; + width: 100%; background-color: var(--browserTableRowEven); text-align: left; @@ -31,7 +29,7 @@ flex: 1; padding: 0 10px 10px 10px; - width: 360px; + width: 100%; min-width: 360px; overflow: auto; word-break: break-word; @@ -50,7 +48,7 @@ .summary { font: normal normal normal 13px/18px Graphik, sans-serif !important; - letter-spacing: -0.13px; + letter-spacing: -0.13px !important; padding: 10px 0 5px; } @@ -76,5 +74,14 @@ } .badge { background-color: var(--badgeBackgroundColor) !important; - margin-left: 5px; + margin-right: 18px; + min-width: 68px; + text-align: center !important; +} + +.commandHelperWrapper { + height: 100%; + border-top: 1px solid var(--euiColorLightShade); + border-left: 1px solid var(--euiColorLightShade); + border-right: 1px solid var(--euiColorLightShade); } diff --git a/redisinsight/ui/src/components/command-helper/CommandHelperHeader/CommandHelperHeader.spec.tsx b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/CommandHelperHeader.spec.tsx new file mode 100644 index 0000000000..9a1692407f --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/CommandHelperHeader.spec.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { cloneDeep } from 'lodash' +import { + cleanup, + fireEvent, mockedStore, + render, + screen, +} from 'uiSrc/utils/test-utils' +import { toggleCliHelper } from 'uiSrc/slices/cli/cli-settings' +import CommandHelperHeader from './CommandHelperHeader' + +let store: typeof mockedStore +beforeEach(() => { + cleanup() + store = cloneDeep(mockedStore) + store.clearActions() +}) + +describe('CommandHelperHeader', () => { + it('should render', () => { + expect(render()).toBeTruthy() + }) + + it('should "toggleCli" action be called after click "close-command-helper" button', () => { + render() + fireEvent.click(screen.getByTestId('close-command-helper')) + + const expectedActions = [toggleCliHelper()] + expect(store.getActions()).toEqual(expectedActions) + }) +}) diff --git a/redisinsight/ui/src/components/command-helper/CommandHelperHeader/CommandHelperHeader.tsx b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/CommandHelperHeader.tsx new file mode 100644 index 0000000000..cc3ed4a063 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/CommandHelperHeader.tsx @@ -0,0 +1,70 @@ +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useParams } from 'react-router-dom' + +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonIcon, + EuiText, + EuiToolTip, + EuiIcon, +} from '@elastic/eui' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { cliSettingsSelector, toggleCliHelper } from 'uiSrc/slices/cli/cli-settings' + +import styles from './styles.module.scss' + +const CommandHelperHeader = () => { + const { isShowHelper } = useSelector(cliSettingsSelector) + const { instanceId = '' } = useParams<{ instanceId: string }>() + const dispatch = useDispatch() + + const handleExpandHelper = () => { + sendEventTelemetry({ + event: isShowHelper ? TelemetryEvent.COMMAND_HELPER_COLLAPSED : TelemetryEvent.COMMAND_HELPER_EXPANDED, + eventData: { + databaseId: instanceId + } + }) + dispatch(toggleCliHelper()) + } + + return ( +
+ + + + Command Helper + + + + + + + + +
+ ) +} + +export default CommandHelperHeader diff --git a/redisinsight/ui/src/components/command-helper/CommandHelperHeader/index.ts b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/index.ts new file mode 100644 index 0000000000..4f1b432837 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/index.ts @@ -0,0 +1,3 @@ +import CommandHelperHeader from './CommandHelperHeader' + +export default CommandHelperHeader diff --git a/redisinsight/ui/src/components/command-helper/CommandHelperHeader/styles.module.scss b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/styles.module.scss new file mode 100644 index 0000000000..0b351a2a2c --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/CommandHelperHeader/styles.module.scss @@ -0,0 +1,27 @@ +.container { + height: 34px; + line-height: 34px; + width: 100%; + overflow: hidden; + background-color: var(--browserTableRowEven); + + padding-left: 18px; + padding-right: 18px; + z-index: 10; +} + +.icon { + margin-left: 5px; +} + +.title { + display: flex; + flex-direction: row !important; + align-items: center; + :global { + .euiIcon { + color: var(--euiColorPrimary); + margin-right: 8px; + } + } +} diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelperWrapper.spec.tsx b/redisinsight/ui/src/components/command-helper/CommandHelperWrapper.spec.tsx similarity index 79% rename from redisinsight/ui/src/components/cli/components/cli-helper/CliHelperWrapper.spec.tsx rename to redisinsight/ui/src/components/command-helper/CommandHelperWrapper.spec.tsx index 43c5b03b07..94811e5bba 100644 --- a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelperWrapper.spec.tsx +++ b/redisinsight/ui/src/components/command-helper/CommandHelperWrapper.spec.tsx @@ -3,7 +3,7 @@ import React from 'react' import { cliSettingsSelector } from 'uiSrc/slices/cli/cli-settings' import { cleanup, mockedStore, render, screen } from 'uiSrc/utils/test-utils' import { ICommands, MOCK_COMMANDS_SPEC } from 'uiSrc/constants' -import CliHelperWrapper from './CliHelperWrapper' +import CommandHelperWrapper from './CommandHelperWrapper' const ALL_REDIS_COMMANDS: ICommands = MOCK_COMMANDS_SPEC const redisCommandsPath = 'uiSrc/slices/app/redis-commands' @@ -53,49 +53,49 @@ const mockedCommands: IMockedCommands[] = [ argStr: 'XGROUP [CREATE key groupname ID|$ [MKSTREAM]] [SETID key groupname ID|$] [DESTROY key groupname] [CREATECONSUMER key groupname consumername] [DELCONSUMER key groupname consumername]', argListText: - 'Arguments:[CREATE key groupname id [MKSTREAM]]Optional[SETID key groupname id]Optional[DESTROY key groupname]Optional[CREATECONSUMER key groupname consumername]Optional[DELCONSUMER key groupname consumername]Optional', + 'Arguments:Optional[CREATE key groupname id [MKSTREAM]]Optional[SETID key groupname id]Optional[DESTROY key groupname]Optional[CREATECONSUMER key groupname consumername]Optional[DELCONSUMER key groupname consumername]', }, { matchedCommand: 'hset', argStr: 'HSET key field value [field value ...]', - argListText: 'Arguments:keyRequiredfield valueMultiple', + argListText: 'Arguments:RequiredkeyMultiplefield value', }, { matchedCommand: 'acl setuser', argStr: 'ACL SETUSER username [rule [rule ...]]', - argListText: 'Arguments:usernameRequired[rule]Multiple', + argListText: 'Arguments:RequiredusernameMultiple[rule]', }, { matchedCommand: 'bitfield', argStr: 'BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]', argListText: - 'Arguments:keyRequired[GET type offset]Optional[SET type offset value]Optional[INCRBY type offset increment]Optional[OVERFLOW WRAP|SAT|FAIL]Optional', + 'Arguments:RequiredkeyOptional[GET type offset]Optional[SET type offset value]Optional[INCRBY type offset increment]Optional[OVERFLOW WRAP|SAT|FAIL]', }, { matchedCommand: 'client kill', argStr: 'CLIENT KILL [ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [LADDR ip:port] [SKIPME yes/no]', argListText: - 'Arguments:[ip:port]Optional[ID client-id]Optional[TYPE normal|master|slave|pubsub]Optional[USER username]Optional[ADDR ip:port]Optional[LADDR ip:port]Optional[SKIPME yes/no]Optional', + 'Arguments:Optional[ip:port]Optional[ID client-id]Optional[TYPE normal|master|slave|pubsub]Optional[USER username]Optional[ADDR ip:port]Optional[LADDR ip:port]Optional[SKIPME yes/no]', }, { matchedCommand: 'geoadd', argStr: 'GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]', argListText: - 'Arguments:keyRequired[condition]Optional[change]Optionallongitude latitude memberMultiple', + 'Arguments:RequiredkeyOptional[condition]Optional[change]Multiplelongitude latitude member', }, { matchedCommand: 'zadd', argStr: 'ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]', argListText: - 'Arguments:keyRequired[condition]Optional[comparison]Optional[change]Optional[increment]Optionalscore memberMultiple', + 'Arguments:RequiredkeyOptional[condition]Optional[comparison]Optional[change]Optional[increment]Multiplescore member', }, ] describe('CliBodyWrapper', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('Title should be rendered according mocked data', () => { @@ -107,7 +107,7 @@ describe('CliBodyWrapper', () => { isEnteringCommand: true, })) - const { unmount } = render() + const { unmount } = render() expect(screen.getByTestId(cliHelperTestId)).toBeInTheDocument() expect(screen.getByTestId(titleArgsId)).toHaveTextContent(argStr) @@ -125,7 +125,7 @@ describe('CliBodyWrapper', () => { isEnteringCommand: true, })) - const { unmount } = render() + const { unmount } = render() expect(screen.getByTestId(cliHelperTestId)).toBeInTheDocument() expect(screen.getByTestId(argsId)).toHaveTextContent(argListText) @@ -145,7 +145,7 @@ describe('CliBodyWrapper', () => { isEnteringCommand: true, })) - const { unmount } = render() + const { unmount } = render() expect(screen.getByTestId(cliHelperTestId)).toBeInTheDocument() expect(screen.getByTestId(sinceId)).toHaveTextContent(since) @@ -165,7 +165,7 @@ describe('CliBodyWrapper', () => { isEnteringCommand: true, })) - const { unmount } = render() + const { unmount } = render() expect(screen.getByTestId(cliHelperTestId)).toBeInTheDocument() @@ -185,7 +185,7 @@ describe('CliBodyWrapper', () => { searchedCommand: '', isSearching: true, })) - const { unmount } = render() + const { unmount } = render() expect( screen.getByTestId(`cli-helper-output-title-${matchedCommand.toUpperCase()}`) ).toBeInTheDocument() diff --git a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelperWrapper.tsx b/redisinsight/ui/src/components/command-helper/CommandHelperWrapper.tsx similarity index 78% rename from redisinsight/ui/src/components/cli/components/cli-helper/CliHelperWrapper.tsx rename to redisinsight/ui/src/components/command-helper/CommandHelperWrapper.tsx index 43cccbbfe1..d4b2fe3a98 100644 --- a/redisinsight/ui/src/components/cli/components/cli-helper/CliHelperWrapper.tsx +++ b/redisinsight/ui/src/components/command-helper/CommandHelperWrapper.tsx @@ -12,14 +12,15 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { cliSettingsSelector } from 'uiSrc/slices/cli/cli-settings' import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands' import { generateArgs, generateArgsNames, getComplexityShortNotation } from 'uiSrc/utils' -import CliHelper from './CliHelper' +import CommandHelper from './CommandHelper' +import CommandHelperHeader from './CommandHelperHeader' -import styles from './CliHelper/styles.module.scss' +import styles from './CommandHelper/styles.module.scss' -const CliHelperWrapper = () => { +const CommandHelperWrapper = () => { const { - matchedCommand, - searchedCommand, + matchedCommand = '', + searchedCommand = '', isSearching, isEnteringCommand, searchingCommand, @@ -76,7 +77,6 @@ const CliHelperWrapper = () => { className={styles.arg} key={i} > - {arg.generatedName} @@ -84,24 +84,29 @@ const CliHelperWrapper = () => { + {arg.generatedName} ) } return ( - generateArgData(obj, i))} - /> +
+ + generateArgData(obj, i))} + /> +
+ ) } -export default React.memo(CliHelperWrapper) +export default React.memo(CommandHelperWrapper) diff --git a/redisinsight/ui/src/components/cli/components/cli-command-info/CliCommandInfo.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-info/CHCommandInfo.tsx similarity index 92% rename from redisinsight/ui/src/components/cli/components/cli-command-info/CliCommandInfo.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-info/CHCommandInfo.tsx index be61529517..1157b3e3dd 100644 --- a/redisinsight/ui/src/components/cli/components/cli-command-info/CliCommandInfo.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-info/CHCommandInfo.tsx @@ -11,7 +11,7 @@ export interface Props { complexity: string; } -const CliCommandInfo = (props: Props) => { +const CHCommandInfo = (props: Props) => { const { args = '', group = CommandGroup.Generic, complexity = '' } = props return ( @@ -31,4 +31,4 @@ const CliCommandInfo = (props: Props) => { ) } -export default CliCommandInfo +export default CHCommandInfo diff --git a/redisinsight/ui/src/components/command-helper/components/command-helper-info/index.ts b/redisinsight/ui/src/components/command-helper/components/command-helper-info/index.ts new file mode 100644 index 0000000000..3dfb392785 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-info/index.ts @@ -0,0 +1,3 @@ +import CHCommandInfo from './CHCommandInfo' + +export default CHCommandInfo diff --git a/redisinsight/ui/src/components/cli/components/cli-command-info/styles.module.scss b/redisinsight/ui/src/components/command-helper/components/command-helper-info/styles.module.scss similarity index 100% rename from redisinsight/ui/src/components/cli/components/cli-command-info/styles.module.scss rename to redisinsight/ui/src/components/command-helper/components/command-helper-info/styles.module.scss diff --git a/redisinsight/ui/src/components/cli/components/cli-search-output/CliSearchOutput.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/CHSearchOutput.tsx similarity index 97% rename from redisinsight/ui/src/components/cli/components/cli-search-output/CliSearchOutput.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search-output/CHSearchOutput.tsx index 3cc88d3b8a..438405b32c 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search-output/CliSearchOutput.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/CHSearchOutput.tsx @@ -15,7 +15,7 @@ export interface Props { searchedCommands: string[]; } -const CliSearchOutput = ({ searchedCommands }: Props) => { +const CHSearchOutput = ({ searchedCommands }: Props) => { const { instanceId = '' } = useParams<{ instanceId: string }>() const dispatch = useDispatch() const { spec: ALL_REDIS_COMMANDS } = useSelector(appRedisCommandsSelector) @@ -100,4 +100,4 @@ const CliSearchOutput = ({ searchedCommands }: Props) => { ) } -export default CliSearchOutput +export default CHSearchOutput diff --git a/redisinsight/ui/src/components/cli/components/cli-search-output/CliSearchOutput.spec.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/CliSearchOutput.spec.tsx similarity index 86% rename from redisinsight/ui/src/components/cli/components/cli-search-output/CliSearchOutput.spec.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search-output/CliSearchOutput.spec.tsx index f42df356b0..736813b61b 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search-output/CliSearchOutput.spec.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/CliSearchOutput.spec.tsx @@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash' import { cleanup, mockedStore, render, screen, fireEvent } from 'uiSrc/utils/test-utils' import { setSearchedCommand } from 'uiSrc/slices/cli/cli-settings' -import CliSearchOutput from './CliSearchOutput' +import CHSearchOutput from './CHSearchOutput' const redisCommandsPath = 'uiSrc/slices/app/redis-commands' let store: typeof mockedStore @@ -52,19 +52,19 @@ jest.mock(redisCommandsPath, () => { } }) -describe('CliSearchOutput', () => { +describe('CHSearchOutput', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should render no results', () => { - render() + render() expect(screen.getByTestId('search-cmds-no-results')).toBeInTheDocument() }) it('should render searched commands results', () => { const searchedCommands = mockedCommands.map((command) => command.matchedCommand) - render() + render() searchedCommands.forEach((command) => { expect(screen.getByTestId(`cli-helper-output-title-${command}`)).toBeInTheDocument() }) @@ -72,7 +72,7 @@ describe('CliSearchOutput', () => { it('should render searched commands results with proper args or summary', () => { const searchedCommands = mockedCommands.map((command) => command.matchedCommand) - render() + render() mockedCommands.forEach((command) => { if (command.argStr) { expect( @@ -89,7 +89,7 @@ describe('CliSearchOutput', () => { it('should call setSearchedCommand after click any command', () => { const searchedCommands = mockedCommands.map((command) => command.matchedCommand) const anySearchCommand = searchedCommands[0] - render() + render() fireEvent.click(screen.getByTestId(`cli-helper-output-title-${anySearchCommand}`)) expect(store.getActions()).toEqual([setSearchedCommand(anySearchCommand)]) }) diff --git a/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/index.ts b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/index.ts new file mode 100644 index 0000000000..e47b4359d4 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/index.ts @@ -0,0 +1,3 @@ +import CHSearchOutput from './CHSearchOutput' + +export default CHSearchOutput diff --git a/redisinsight/ui/src/components/cli/components/cli-search-output/styles.module.scss b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/styles.module.scss similarity index 88% rename from redisinsight/ui/src/components/cli/components/cli-search-output/styles.module.scss rename to redisinsight/ui/src/components/command-helper/components/command-helper-search-output/styles.module.scss index 624140096b..347b0051c0 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search-output/styles.module.scss +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search-output/styles.module.scss @@ -20,5 +20,5 @@ } .summary, .summary div { - color: var(--inputPlaceHolderColor) !important; + color: var(--inputPlaceholderColor) !important; } diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/CliSearchFilter.spec.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/CHSearchFilter.spec.tsx similarity index 79% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/CliSearchFilter.spec.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/CHSearchFilter.spec.tsx index 9158a27c7c..7406febf99 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/CliSearchFilter.spec.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/CHSearchFilter.spec.tsx @@ -1,7 +1,7 @@ import React from 'react' import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' import { GROUP_TYPES_DISPLAY } from 'uiSrc/constants' -import CliSearchFilter from './CliSearchFilter' +import CHSearchFilter from './CHSearchFilter' const redisCommandsPath = 'uiSrc/slices/app/redis-commands' @@ -18,14 +18,14 @@ jest.mock(redisCommandsPath, () => { } }) -describe('CliSearchFilter', () => { +describe('CHSearchFilter', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should call submitFilter after choose options', () => { const submitFilter = jest.fn() - const { queryByText } = render() + const { queryByText } = render() const testGroup = commandGroupsMock[0] fireEvent.click(screen.getByTestId('select-filter-group-type')) fireEvent.click(queryByText((GROUP_TYPES_DISPLAY as any)[testGroup]) || document) diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/CliSearchFilter.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/CHSearchFilter.tsx similarity index 96% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/CliSearchFilter.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/CHSearchFilter.tsx index 2e2b772a01..fbde734c11 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/CliSearchFilter.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/CHSearchFilter.tsx @@ -20,7 +20,7 @@ export interface Props { isLoading?: boolean; } -const CliSearchFilter = ({ submitFilter, isLoading }: Props) => { +const CHSearchFilter = ({ submitFilter, isLoading }: Props) => { const { commandGroups = [] } = useSelector(appRedisCommandsSelector) const [isSelectOpen, setIsSelectOpen] = useState(false) @@ -103,4 +103,4 @@ const CliSearchFilter = ({ submitFilter, isLoading }: Props) => { ) } -export default CliSearchFilter +export default CHSearchFilter diff --git a/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/index.ts b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/index.ts new file mode 100644 index 0000000000..e15a0e5c6e --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/index.ts @@ -0,0 +1,3 @@ +import CHSearchFilter from './CHSearchFilter' + +export default CHSearchFilter diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/styles.module.scss b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/styles.module.scss similarity index 100% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchFilter/styles.module.scss rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchFilter/styles.module.scss diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/CliSearchInput.spec.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/CHSearchInput.spec.tsx similarity index 64% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/CliSearchInput.spec.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/CHSearchInput.spec.tsx index 78e63dee6e..a0fefd72be 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/CliSearchInput.spec.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/CHSearchInput.spec.tsx @@ -1,16 +1,16 @@ import React from 'react' import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' -import CliSearchInput from './CliSearchInput' +import CHSearchInput from './CHSearchInput' -describe('CliSearchInput', () => { +describe('CHSearchInput', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should call submitSearch with after typing', () => { const submitSearch = jest.fn() - render() + render() fireEvent.change( screen.getByTestId('cli-helper-search'), { target: { value: 'set' } } diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/CliSearchInput.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/CHSearchInput.tsx similarity index 92% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/CliSearchInput.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/CHSearchInput.tsx index c78b380914..83ec24b325 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/CliSearchInput.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/CHSearchInput.tsx @@ -11,7 +11,7 @@ export interface Props { isLoading?: boolean; } -const CliSearchInput = ({ submitSearch, isLoading = false }: Props) => { +const CHSearchInput = ({ submitSearch, isLoading = false }: Props) => { const [searchValue, setSearchValue] = useState('') const { isEnteringCommand, matchedCommand } = useSelector(cliSettingsSelector) @@ -48,4 +48,4 @@ const CliSearchInput = ({ submitSearch, isLoading = false }: Props) => { ) } -export default CliSearchInput +export default CHSearchInput diff --git a/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/index.ts b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/index.ts new file mode 100644 index 0000000000..4f3ef83de3 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/index.ts @@ -0,0 +1,3 @@ +import CHSearchInput from './CHSearchInput' + +export default CHSearchInput diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/styles.module.scss b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/styles.module.scss similarity index 100% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchInput/styles.module.scss rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchInput/styles.module.scss diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchWrapper.spec.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchWrapper.spec.tsx similarity index 87% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchWrapper.spec.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchWrapper.spec.tsx index d8a250f77c..943d3277cd 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchWrapper.spec.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchWrapper.spec.tsx @@ -2,7 +2,7 @@ import React from 'react' import { cloneDeep } from 'lodash' import { render, screen, fireEvent, mockedStore, cleanup } from 'uiSrc/utils/test-utils' import { clearSearchingCommand, setSearchingCommand, setCliEnteringCommand } from 'uiSrc/slices/cli/cli-settings' -import CliSearchWrapper from './CliSearchWrapper' +import CHSearchWrapper from './CHSearchWrapper' let store: typeof mockedStore const redisCommandsPath = 'uiSrc/slices/app/redis-commands' @@ -23,13 +23,13 @@ jest.mock(redisCommandsPath, () => { } }) -describe('CliSearchInput', () => { +describe('CHSearchInput', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should call search action after typing', () => { - render() + render() fireEvent.change( screen.getByTestId('cli-helper-search'), { target: { value: 'set' } } @@ -39,7 +39,7 @@ describe('CliSearchInput', () => { }) it('should call clear search action after clear input', () => { - render() + render() const searchInput = screen.getByTestId('cli-helper-search') fireEvent.change( searchInput, diff --git a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchWrapper.tsx b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchWrapper.tsx similarity index 84% rename from redisinsight/ui/src/components/cli/components/cli-search/CliSearchWrapper.tsx rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchWrapper.tsx index 237cde17bd..47938a1484 100644 --- a/redisinsight/ui/src/components/cli/components/cli-search/CliSearchWrapper.tsx +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/CHSearchWrapper.tsx @@ -11,12 +11,12 @@ import { } from 'uiSrc/slices/cli/cli-settings' import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands' -import CliSearchInput from './CliSearchInput' -import CliSearchFilter from './CliSearchFilter' +import CHSearchInput from './CHSearchInput' +import CHSearchFilter from './CHSearchFilter' import styles from './styles.module.scss' -const CliSearchWrapper = () => { +const CHSearchWrapper = () => { const { instanceId = '' } = useParams<{ instanceId: string }>() const [filterType, setFilterType] = useState('') const [searchValue, setSearchValue] = useState('') @@ -62,10 +62,10 @@ const CliSearchWrapper = () => { return (
- - + +
) } -export default CliSearchWrapper +export default CHSearchWrapper diff --git a/redisinsight/ui/src/components/command-helper/components/command-helper-search/index.ts b/redisinsight/ui/src/components/command-helper/components/command-helper-search/index.ts new file mode 100644 index 0000000000..d4a761d636 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/components/command-helper-search/index.ts @@ -0,0 +1,3 @@ +import CHSearchWrapper from './CHSearchWrapper' + +export default CHSearchWrapper diff --git a/redisinsight/ui/src/components/cli/components/cli-search/styles.module.scss b/redisinsight/ui/src/components/command-helper/components/command-helper-search/styles.module.scss similarity index 100% rename from redisinsight/ui/src/components/cli/components/cli-search/styles.module.scss rename to redisinsight/ui/src/components/command-helper/components/command-helper-search/styles.module.scss diff --git a/redisinsight/ui/src/components/command-helper/index.ts b/redisinsight/ui/src/components/command-helper/index.ts new file mode 100644 index 0000000000..2fce4b3ce2 --- /dev/null +++ b/redisinsight/ui/src/components/command-helper/index.ts @@ -0,0 +1,3 @@ +import CommandHelperWrapper from './CommandHelperWrapper' + +export default CommandHelperWrapper diff --git a/redisinsight/ui/src/components/page-breadcrumbs/styles.module.scss b/redisinsight/ui/src/components/page-breadcrumbs/styles.module.scss index b5a0a91cbd..705ebc10c8 100644 --- a/redisinsight/ui/src/components/page-breadcrumbs/styles.module.scss +++ b/redisinsight/ui/src/components/page-breadcrumbs/styles.module.scss @@ -6,6 +6,7 @@ :global(.euiBreadcrumb) { margin-bottom: 0; font-size: 13px; + letter-spacing: -0.13px; font-weight: 500; color: var(--euiTextSubduedColor) !important; diff --git a/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx b/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx index 2e27f0f2af..c29f7b1fbb 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardCliPlugin/QueryCardCliPlugin.tsx @@ -45,8 +45,6 @@ const QueryCardCliPlugin = (props: Props) => { const generatedIframeNameRef = useRef('') const { theme } = useContext(ThemeContext) - const dispatch = useDispatch() - const sendMessageToPlugin = (data = {}) => { const event: any = document.createEvent('Event') event.initEvent('message', false, false) @@ -63,21 +61,6 @@ const QueryCardCliPlugin = (props: Props) => { }) } - const sendRedisCommand = (command: string, requestId: string) => { - dispatch( - sendPluginCommandAction({ - command, - onSuccessAction: (response) => { - sendMessageToPlugin({ - event: 'executeRedisCommand', - requestId, - data: response - }) - } - }) - ) - } - useEffect(() => { if (currentView === null) return pluginApi.onEvent(generatedIframeNameRef.current, PluginEvents.heightChanged, (height: string) => { diff --git a/redisinsight/ui/src/pages/browser/components/add-key/AddKeyCommonFields/AddKeyCommonFields.tsx b/redisinsight/ui/src/pages/browser/components/add-key/AddKeyCommonFields/AddKeyCommonFields.tsx index 3196494016..ff5f8060a4 100644 --- a/redisinsight/ui/src/pages/browser/components/add-key/AddKeyCommonFields/AddKeyCommonFields.tsx +++ b/redisinsight/ui/src/pages/browser/components/add-key/AddKeyCommonFields/AddKeyCommonFields.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, Fragment } from 'react' +import React, { ChangeEvent } from 'react' import { toNumber } from 'lodash' import { EuiFieldText, EuiFormRow } from '@elastic/eui' diff --git a/redisinsight/ui/src/pages/browser/components/rejson-details/JSONArray/JSONArray.tsx b/redisinsight/ui/src/pages/browser/components/rejson-details/JSONArray/JSONArray.tsx index a6c1b45af7..87a102907c 100644 --- a/redisinsight/ui/src/pages/browser/components/rejson-details/JSONArray/JSONArray.tsx +++ b/redisinsight/ui/src/pages/browser/components/rejson-details/JSONArray/JSONArray.tsx @@ -228,8 +228,9 @@ class JSONArrayComponent extends Component { } onClickSetKVPair = () => { + const { addNewKeyValuePair } = this.state this.setState({ - addNewKeyValuePair: !this.state.addNewKeyValuePair, + addNewKeyValuePair: !addNewKeyValuePair, newValue: '', error: '' }) @@ -299,11 +300,12 @@ class JSONArrayComponent extends Component { const { shouldRejsonDataBeDownloaded, handleFetchVisualisationResults, + value } = this.props if (!shouldRejsonDataBeDownloaded) { this.setState({ - value: this.props.value, + value, openIndex: true, }) return diff --git a/redisinsight/ui/src/pages/browser/components/rejson-details/JSONObject/JSONObject.tsx b/redisinsight/ui/src/pages/browser/components/rejson-details/JSONObject/JSONObject.tsx index 67bfe8ae9e..41fd740f83 100644 --- a/redisinsight/ui/src/pages/browser/components/rejson-details/JSONObject/JSONObject.tsx +++ b/redisinsight/ui/src/pages/browser/components/rejson-details/JSONObject/JSONObject.tsx @@ -179,8 +179,9 @@ class JSONObject extends React.Component { } onClickSetKVPair = () => { + const { addNewKeyValuePair } = this.state this.setState({ - addNewKeyValuePair: !this.state.addNewKeyValuePair, + addNewKeyValuePair: !addNewKeyValuePair, newKey: '', newValue: '', }) @@ -286,11 +287,11 @@ class JSONObject extends React.Component { onJSONKeyExpandAndCollapse(!openIndex, path) if (!openIndex) { - const { shouldRejsonDataBeDownloaded } = this.props + const { shouldRejsonDataBeDownloaded, value } = this.props if (!shouldRejsonDataBeDownloaded) { this.setState({ - value: this.props.value, + value, openIndex: true, }) return diff --git a/redisinsight/ui/src/pages/browser/components/rejson-details/RejsonDetails/RejsonDetails.tsx b/redisinsight/ui/src/pages/browser/components/rejson-details/RejsonDetails/RejsonDetails.tsx index ad78762f2a..ec40ba439f 100644 --- a/redisinsight/ui/src/pages/browser/components/rejson-details/RejsonDetails/RejsonDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/rejson-details/RejsonDetails/RejsonDetails.tsx @@ -62,11 +62,6 @@ export interface Props { interface State { path: string | undefined; - data: JSONArrayValue | IJSONDocument | IJSONDocument[]; - openIndex: number; - prevPath: string; - currentPath: string; - dataContainer: object; addRootKVPair: boolean; newRootKey: string | undefined; newRootValue: JSONScalarValue | JSONArrayValue | IJSONObject; @@ -78,11 +73,6 @@ class RejsonDetails extends React.Component { super(props) this.state = { path: '', - data: this.props.data, - openIndex: -1, - prevPath: '', - currentPath: '', - dataContainer: {}, addRootKVPair: false, newRootKey: '', newRootValue: '', @@ -132,8 +122,9 @@ class RejsonDetails extends React.Component { } onClickSetRootKVPair = () => { + const { addRootKVPair } = this.state this.setState({ - addRootKVPair: !this.state.addRootKVPair, + addRootKVPair: !addRootKVPair, }) } @@ -213,7 +204,7 @@ class RejsonDetails extends React.Component { const error: string = this.validateRootKVPair() if (error === '') { - let updatedPath = '.' + let updatedPath body.operation = 'update' body.value = newRootValue as string @@ -603,7 +594,7 @@ class RejsonDetails extends React.Component { ) )} <> - {data && data !== null ? ( + {data ? ( this.renderResultArray(data as string) ) : ( { errs.newCaCert = fieldDisplayNames.newCaCert } - if ( - values.tls - && values.tlsClientAuthRequired - && values.selectedTlsClientCertId === '' - ) { - errs.selectedTlsClientCertId - === fieldDisplayNames.selectedTlsClientCertId - } if ( values.tls && values.tlsClientAuthRequired diff --git a/redisinsight/ui/src/pages/instance/InstancePage.tsx b/redisinsight/ui/src/pages/instance/InstancePage.tsx index e3bc9ac566..60397c8d9d 100644 --- a/redisinsight/ui/src/pages/instance/InstancePage.tsx +++ b/redisinsight/ui/src/pages/instance/InstancePage.tsx @@ -10,15 +10,14 @@ import { } from 'uiSrc/slices/instances' import { BrowserStorageItem } from 'uiSrc/constants' import { localStorageService } from 'uiSrc/services' -import CliWrapper from 'uiSrc/components/cli/CliWrapper' import { cliSettingsSelector, resetIsShowCli } from 'uiSrc/slices/cli/cli-settings' -import CliHeaderMinimized from 'uiSrc/components/cli/components/cli-header-minimized' import { appContextSelector, setAppContextConnectedInstanceId, setAppContextInitialState, } from 'uiSrc/slices/app/context' import { resetOutput } from 'uiSrc/slices/cli/cli-output' +import BottomGroupComponents from 'uiSrc/components/bottom-group-components/BottomGroupComponents' import InstancePageRouter from './InstancePageRouter' import styles from './styles.module.scss' @@ -46,9 +45,11 @@ const InstancePage = ({ routes = [] }: Props) => { const dispatch = useDispatch() const { instanceId: connectionInstanceId } = useParams<{ instanceId: string }>() - const { isShowCli } = useSelector(cliSettingsSelector) + const { isShowCli, isShowHelper } = useSelector(cliSettingsSelector) const { contextInstanceId } = useSelector(appContextSelector) + const isShowBottomGroup = isShowCli || isShowHelper + useEffect(() => { dispatch(fetchInstanceAction(connectionInstanceId)) dispatch(getDatabaseConfigInfoAction(connectionInstanceId)) @@ -80,7 +81,7 @@ const InstancePage = ({ routes = [] }: Props) => { direction="vertical" style={{ height: '100%' }} onPanelWidthChange={onPanelWidthChange} - className={cx({ 'show-cli': isShowCli })} + className={cx({ 'show-cli': isShowBottomGroup })} > {(EuiResizablePanel, EuiResizableButton) => ( <> @@ -89,8 +90,8 @@ const InstancePage = ({ routes = [] }: Props) => { scrollable={false} minSize="55px" paddingSize="none" - size={isShowCli ? sizes[firstPanelId] : 100} - wrapperProps={{ className: cx({ [styles.mainComponent]: !isShowCli }) }} + size={isShowBottomGroup ? sizes[firstPanelId] : 100} + wrapperProps={{ className: cx({ [styles.mainComponent]: !isShowBottomGroup }) }} data-testid={firstPanelId} > @@ -101,13 +102,13 @@ const InstancePage = ({ routes = [] }: Props) => { - {!isShowCli ? : } + )} diff --git a/redisinsight/ui/src/pages/instance/styles.module.scss b/redisinsight/ui/src/pages/instance/styles.module.scss index 19eb16c32e..53ea687f26 100644 --- a/redisinsight/ui/src/pages/instance/styles.module.scss +++ b/redisinsight/ui/src/pages/instance/styles.module.scss @@ -1,5 +1,5 @@ .mainComponent { - height: calc(100% - 35px) !important; + height: calc(100% - 29px) !important; } .resizableButton { diff --git a/redisinsight/ui/src/plugins/pluginImport.ts b/redisinsight/ui/src/plugins/pluginImport.ts index 1db8d1cdfd..60ae06a24f 100644 --- a/redisinsight/ui/src/plugins/pluginImport.ts +++ b/redisinsight/ui/src/plugins/pluginImport.ts @@ -55,13 +55,28 @@ export const importPluginScript = () => (config) => { const listenEvents = () => { globalThis.onmessage = (e) => { + // eslint-disable-next-line sonarjs/no-collapsible-if if (e.data.event === events.EXECUTE_COMMAND) { - globalThis.plugin[e.data.method] && globalThis.plugin[e.data.method](e.data.data) + const { plugin } = globalThis + // eslint-disable-next-line no-prototype-builtins + if (plugin.hasOwnProperty(e.data.method)) { + const action = plugin[e.data.method] + if (typeof action === 'function') { + action(e.data.data) + } + } } + // eslint-disable-next-line sonarjs/no-collapsible-if if (e.data.event === events.EXECUTE_REDIS_COMMAND) { - callbacks[e.data.requestId] && callbacks[e.data.requestId](e.data.data) - delete callbacks[e.data.requestId] + // eslint-disable-next-line no-prototype-builtins + if (callbacks.hasOwnProperty(e.data.requestId)) { + const action = callbacks[e.data.requestId] + if (typeof action === 'function') { + action(e.data.data) + } + delete callbacks[e.data.requestId] + } } } @@ -102,6 +117,7 @@ export const prepareIframeHtml = (config) => {