Compare commits

..

69 Commits

Author SHA1 Message Date
Nivedin
eecc3db4e9 chore: update placeholder text (#4023) 2024-04-30 16:49:32 +05:30
Andrew Bastin
426e7594f4 fix: tab systems erroring out due to out of sync tabOrdering and tabMap 2024-04-29 20:32:27 +05:30
Andrew Bastin
934dc473f0 chore: bump version to 2024.3.2 2024-04-29 19:21:48 +05:30
Andrew Bastin
be57255bf7 refactor: update to dioc v3 (#4009)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-04-29 19:06:18 +05:30
Balu Babu
f89561da54 fix: resolved mailer module email issue (#4000) 2024-04-29 12:05:07 +05:30
Joel Jacob Stephen
c2c4e620c2 fix(common): rest and graphql pages not being rendered when french is selected as app language (#4004)
Co-authored-by: nivedin <nivedinp@gmail.com>
2024-04-25 18:01:46 +05:30
Andrew Bastin
844eee0fa4 chore: update @hoppscotch/cli README 2024-04-22 21:12:27 +05:30
Andrew Bastin
d21bb65511 chore: bump cli version to 0.8 2024-04-22 20:29:03 +05:30
Andrew Bastin
4f614f7257 chore: bump version to 2024.3.1 2024-04-22 20:26:25 +05:30
Nivedin
0e2887b4e9 feat: first time user spotlight animation (#3977) 2024-04-22 12:21:30 +05:30
Andrew Bastin
18652ce400 fix: update prod.Dockerfile to add additional required deps during base build 2024-04-20 01:54:34 +05:30
Eduardo San Martin Morote
08c655235d fix: use pressed key rather than its code (#3978)
Co-authored-by: Joel Jacob Stephen <70131076+JoelJacobStephen@users.noreply.github.com>
2024-04-19 22:35:13 +05:30
Vincent M
51412549e8 chore(i18n): french lang translation (#3986) 2024-04-19 21:21:15 +05:30
James George
22c6eabd13 chore: migrate Node.js implementation for js-sandbox to isolated-vm (#3973)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
2024-04-19 21:08:46 +05:30
Mir Arif Hasan
a079e0f04b refactor: infra-config code refactor (#3982)
* chore: add getMissingInfraConfigEntries to avoid code duplication

* feat: isServiceConfigured modified

* docs: add missing function doc

* feat: argon2 version updated and removed types-argon2 depricated package

* Revert "feat: argon2 version updated and removed types-argon2 depricated package"

This reverts commit b99f3c5aae.

* Revert "feat: isServiceConfigured modified"

This reverts commit eaa6f105bb.
2024-04-19 12:43:43 +05:30
jamesgeorge007
375d53263a test: fix failing tests 2024-04-16 23:55:07 +05:30
Thomas Bonnet
57ef3e085f chore(i18n): Updating the packages/hoppscotch-common/locales/fr.json file. (#3555) 2024-04-16 18:15:38 +05:30
Timotej
9fb6e59e36 fix(desktop): set window caption color if Windows version >= 11 (#3939) 2024-04-16 17:51:26 +05:30
Sawako
1b0802b0e6 fix: spanish lang translation messages (#3950) 2024-04-16 17:47:10 +05:30
krisztianbarat
fb45fe4627 chore(i18n): update locale hu (#3875) 2024-04-16 17:45:55 +05:30
Akash K
0f592d1789 fix: use proper values for addTo field when auth type is api-key (#3966) 2024-04-16 17:44:28 +05:30
jamesgeorge007
787aab650f fix: redirect to the users list view on successful deletion from the profile view
SH Admin dashboard
2024-03-28 21:20:48 +05:30
Anwarul Islam
1f7a8edb14 fix: lint errors removed by using satisfies or as for type (#3934)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
Co-authored-by: amk-dev <akash.k.mohan98@gmail.com>
2024-03-28 20:28:48 +05:30
James George
81f1e05a6c chore(sh-admin): alert the user while deleting users who are team owners (#3937)
Co-authored-by: amk-dev <akash.k.mohan98@gmail.com>
Co-authored-by: nivedin <nivedinp@gmail.com>
2024-03-28 20:17:24 +05:30
James George
0a71783eaa fix(common): ensure requests are translated to the latest version during import and search actions (#3931) 2024-03-25 17:09:54 +05:30
Nivedin
c326f54f7e feat: added mutation and function to platform for updating user profile name (#3929)
Co-authored-by: amk-dev <akash.k.mohan98@gmail.com>
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-25 14:41:25 +05:30
Dmitry
1113c79e20 fix: can't import curl command with data-urlencode (#3905)
Co-authored-by: Anwarul Islam <anwaarulislaam@gmail.com>
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-25 13:12:48 +05:30
Akash K
6fd30f9aca chore: spotlight improvements for team requests search (#3930)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-25 12:41:18 +05:30
Anwarul Islam
2c5b0dcd1b feat: focus codemirror view when ImportCurl component launched (#3911)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-22 18:21:16 +05:30
Anwarul Islam
6f4455ba03 fix: request failing on change content type to multipart-formdata (#3922)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-22 18:11:16 +05:30
Nivedin
ba8c4480d9 fix: workspace list section bugs (#3925)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-22 18:02:28 +05:30
Joel Jacob Stephen
380397cc55 refactor(sh-admin): improvements to pending invites page in dashboard (#3926) 2024-03-22 17:54:40 +05:30
Akash K
d19807b212 chore: split axios request options into platform (#3927)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-22 13:42:41 +05:30
Balu Babu
c89c2a5f5c feat: added new mutation to update username in hopp app (#3924)
* feat: added new mutation to update username in hopp app

* feat: display name length validation added

---------

Co-authored-by: mirarifhasan <arif.ishan05@gmail.com>
2024-03-22 12:07:38 +05:30
Anwarul Islam
256553b9bb chore: update copy for header inspection (#3907)
Co-authored-by: James George <jamesgeorge998001@gmail.com>
2024-03-21 22:15:23 +05:30
Akash K
89d9951f3b fix: fix typo in team search url (#3923)
fix: fix typo in team search endpoint url

Co-authored-by: James George <jamesgeorge998001@gmail.com>
2024-03-21 16:44:58 +05:30
Balu Babu
dd65ad3103 chore: added input validation to search query (#3921) 2024-03-21 16:13:11 +05:30
Balu Babu
018ed3db26 refactor: AIO healthcheck bash script (#3920)
* chore: added logic to make script with with subpath

* chore: removed variable from failed echo message
2024-03-21 16:12:13 +05:30
Andrew Bastin
a9cd6c0c01 chore: update internal deployment docker compose config 2024-03-21 02:38:55 +05:30
James George
e53382666a fix(common): prevent exception with ShortcodeListAdapter initialization (#3917) 2024-03-20 20:29:04 +05:30
James George
7621ff2961 feat: add extended support for versioned entities in the CLI (#3912) 2024-03-20 20:13:22 +05:30
Akash K
fc20b76080 fix: direct import from url failing (#3918) 2024-03-20 20:06:51 +05:30
Nivedin
146c73d7b6 feat: github enterprise SSO provider addition (#3914) 2024-03-20 20:01:56 +05:30
Akash K
6b58915caa feat: oauth revamp + support for multiple grant types in oauth (#3885)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-20 00:18:03 +05:30
Akash K
457857a711 feat: team search in workspace search and spotlight (#3896)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-19 18:50:35 +05:30
Balu Babu
a3f3e3e62d refactor: collection search query (#3908) 2024-03-19 17:12:35 +05:30
Andrew Bastin
66f20d10e1 chore: enable subpath based access in test deploy docker compose 2024-03-19 16:26:04 +05:30
Andrew Bastin
32e9366609 chore: update test deploy docker compose aio port 2024-03-19 15:53:08 +05:30
Andrew Bastin
e41e956273 chore: add test deployment docker compose file 2024-03-19 14:39:57 +05:30
Nivedin
a14870f3f0 fix: collection auth headers active tab update bug and type fix (#3899) 2024-03-15 21:17:34 +05:30
Andrew Bastin
0e96665254 refactor: use trigram search index instead of full text search (#3900)
Co-authored-by: Balu Babu <balub997@gmail.com>
2024-03-15 20:10:12 +05:30
kaifulee
efdc1c2f5d chore: fix some typos (#3895)
Signed-off-by: kaifulee <cuishuang@outlook.com>
2024-03-15 20:06:34 +05:30
Andrew Bastin
c5334d4c06 chore(sh-admin): bump @hoppscotch/ui version to 0.1.3 2024-03-15 12:43:05 +05:30
Balu Babu
4f549974ed fix: reset infra-config bug (#3898) 2024-03-14 21:46:34 +05:30
Nivedin
41d617b507 fix: secret env bug in firebase due to undefined value (#3881)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-13 17:11:51 +05:30
Joel Jacob Stephen
be7387ed19 refactor(sh-admin): updated data sharing doc links + remove disabled property from all inputs in configurations (#3894) 2024-03-13 16:18:27 +05:30
Joel Jacob Stephen
acfb0189df feat(sh-admin): enhanced user management in admin dashboard (#3814)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-13 14:45:13 +05:30
Nivedin
8fdba760a2 refactor: personal workspace nomenclature update (#3893)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-13 14:21:23 +05:30
Nivedin
bf98009abb fix: request variable version syncing bug (#3889)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-12 11:42:05 +05:30
James George
dce396c164 chore: bump codemirror dependencies (#3888) 2024-03-11 14:21:39 +05:30
Nivedin
07e8af7947 refactor: update team nomenclature (#3880)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-08 23:54:32 +05:30
Joel Jacob Stephen
e69d5a6253 feat(sh-admin): introducing additional SSO related server configurations to dashboard (#3737)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-08 15:18:53 +05:30
Akash K
6d66d12a9e feat: common changes for site protection (#3878) 2024-03-07 23:43:20 +05:30
James George
439cd82c88 chore: pin dependencies across packages (#3876) 2024-03-07 23:37:48 +05:30
Akash K
6dbaf524ce feat: use tags as folders when importing from openapi (#3846) 2024-03-07 19:55:46 +05:30
Andrew Bastin
68e439d1a4 chore: bump version to 2024.3.0 2024-03-07 19:22:46 +05:30
Nivedin
8deba7a28e fix: context menu bug and incorrect position (#3874) 2024-03-07 17:59:06 +05:30
Nivedin
7ec8659381 feat: request variables (#3825)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-03-07 12:50:44 +05:30
Mir Arif Hasan
3611cac241 feat(backend): sso callback url and scope added in infra-config (#3718) 2024-03-07 12:07:51 +05:30
275 changed files with 26650 additions and 20768 deletions

View File

@@ -17,22 +17,21 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup environment - name: Setup environment
run: mv .env.example .env run: mv .env.example .env
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v2.2.4 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: true run_install: true
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Run tests - name: Run tests
run: pnpm test run: pnpm test

1
.npmrc
View File

@@ -1 +1,2 @@
shamefully-hoist=false shamefully-hoist=false
save-prefix=''

48
docker-compose.deploy.yml Normal file
View File

@@ -0,0 +1,48 @@
# THIS IS NOT TO BE USED FOR PERSONAL DEPLOYMENTS!
# Internal Docker Compose Image used for internal testing deployments
version: "3.7"
services:
hoppscotch-db:
image: postgres:15
user: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: testpass
POSTGRES_DB: hoppscotch
healthcheck:
test:
[
"CMD-SHELL",
"sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"
]
interval: 5s
timeout: 5s
retries: 10
hoppscotch-aio:
container_name: hoppscotch-aio
build:
dockerfile: prod.Dockerfile
context: .
target: aio
environment:
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
- ENABLE_SUBPATH_BASED_ACCESS=true
env_file:
- ./.env
depends_on:
hoppscotch-db:
condition: service_healthy
command: ["sh", "-c", "pnpm exec prisma migrate deploy && node /usr/src/app/aio_run.mjs"]
healthcheck:
test:
- CMD
- curl
- '-f'
- 'http://localhost:80'
interval: 2s
timeout: 10s
retries: 30

View File

@@ -112,7 +112,7 @@ services:
build: build:
dockerfile: packages/hoppscotch-backend/Dockerfile dockerfile: packages/hoppscotch-backend/Dockerfile
context: . context: .
target: dev target: prod
env_file: env_file:
- ./.env - ./.env
restart: always restart: always
@@ -122,7 +122,7 @@ services:
- PORT=3000 - PORT=3000
volumes: volumes:
# Uncomment the line below when modifying code. Only applicable when using the "dev" target. # Uncomment the line below when modifying code. Only applicable when using the "dev" target.
- ./packages/hoppscotch-backend/:/usr/src/app # - ./packages/hoppscotch-backend/:/usr/src/app
- /usr/src/app/node_modules/ - /usr/src/app/node_modules/
depends_on: depends_on:
hoppscotch-db: hoppscotch-db:

View File

@@ -9,6 +9,10 @@ curlCheck() {
fi fi
} }
curlCheck "http://localhost:3000" if [ "$ENABLE_SUBPATH_BASED_ACCESS" = "true" ]; then
curlCheck "http://localhost:3100" curlCheck "http://localhost:80/backend/ping"
curlCheck "http://localhost:3170/ping" else
curlCheck "http://localhost:3000"
curlCheck "http://localhost:3100"
curlCheck "http://localhost:3170/ping"
fi

View File

@@ -23,13 +23,13 @@
"./packages/*" "./packages/*"
], ],
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "16.3.0",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "16.2.4",
"@hoppscotch/ui": "^0.1.0", "@hoppscotch/ui": "0.1.0",
"@types/node": "17.0.27", "@types/node": "17.0.27",
"cross-env": "^7.0.3", "cross-env": "7.0.3",
"http-server": "^14.1.1", "http-server": "14.1.1",
"husky": "^7.0.4", "husky": "7.0.4",
"lint-staged": "12.4.0" "lint-staged": "12.4.0"
}, },
"pnpm": { "pnpm": {
@@ -37,7 +37,7 @@
"vue": "3.3.9" "vue": "3.3.9"
}, },
"packageExtensions": { "packageExtensions": {
"httpsnippet@^3.0.1": { "httpsnippet@3.0.1": {
"peerDependencies": { "peerDependencies": {
"ajv": "6.12.3" "ajv": "6.12.3"
} }

View File

@@ -17,16 +17,16 @@
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@codemirror/language": "6.9.3", "@codemirror/language": "6.10.1",
"@lezer/highlight": "1.2.0", "@lezer/highlight": "1.2.0",
"@lezer/lr": "^1.3.14" "@lezer/lr": "1.3.14"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.5.1", "@lezer/generator": "1.5.1",
"mocha": "^9.2.2", "mocha": "9.2.2",
"rollup": "^3.29.3", "rollup": "3.29.4",
"rollup-plugin-dts": "^6.0.2", "rollup-plugin-dts": "6.0.2",
"rollup-plugin-ts": "^3.4.5", "rollup-plugin-ts": "3.4.5",
"typescript": "^5.2.2" "typescript": "5.2.2"
} }
} }

View File

@@ -3,9 +3,7 @@
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"assets": [ "assets": [{ "include": "mailer/templates/**/*", "outDir": "dist" }],
"**/*.hbs"
],
"watchAssets": true "watchAssets": true
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "hoppscotch-backend", "name": "hoppscotch-backend",
"version": "2023.12.6", "version": "2024.3.2",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -24,83 +24,83 @@
"do-test": "pnpm run test" "do-test": "pnpm run test"
}, },
"dependencies": { "dependencies": {
"@apollo/server": "^4.9.4", "@apollo/server": "4.9.5",
"@nestjs-modules/mailer": "^1.9.1", "@nestjs-modules/mailer": "1.9.1",
"@nestjs/apollo": "^12.0.9", "@nestjs/apollo": "12.0.9",
"@nestjs/common": "^10.2.6", "@nestjs/common": "10.2.7",
"@nestjs/config": "^3.1.1", "@nestjs/config": "3.1.1",
"@nestjs/core": "^10.2.6", "@nestjs/core": "10.2.7",
"@nestjs/graphql": "^12.0.9", "@nestjs/graphql": "12.0.9",
"@nestjs/jwt": "^10.1.1", "@nestjs/jwt": "10.1.1",
"@nestjs/passport": "^10.0.2", "@nestjs/passport": "10.0.2",
"@nestjs/platform-express": "^10.2.6", "@nestjs/platform-express": "10.2.7",
"@nestjs/schedule": "^4.0.1", "@nestjs/schedule": "4.0.1",
"@nestjs/throttler": "^5.0.0", "@nestjs/throttler": "5.0.1",
"@prisma/client": "^5.8.0", "@prisma/client": "5.8.1",
"argon2": "^0.30.3", "argon2": "0.30.3",
"bcrypt": "^5.1.0", "bcrypt": "5.1.0",
"cookie": "^0.5.0", "cookie": "0.5.0",
"cookie-parser": "^1.4.6", "cookie-parser": "1.4.6",
"cron": "^3.1.6", "cron": "3.1.6",
"express": "^4.17.1", "express": "4.18.2",
"express-session": "^1.17.3", "express-session": "1.17.3",
"fp-ts": "^2.13.1", "fp-ts": "2.13.1",
"graphql": "^16.8.1", "graphql": "16.8.1",
"graphql-query-complexity": "^0.12.0", "graphql-query-complexity": "0.12.0",
"graphql-redis-subscriptions": "^2.6.0", "graphql-redis-subscriptions": "2.6.0",
"graphql-subscriptions": "^2.0.0", "graphql-subscriptions": "2.0.0",
"handlebars": "^4.7.7", "handlebars": "4.7.7",
"io-ts": "^2.2.16", "io-ts": "2.2.16",
"luxon": "^3.2.1", "luxon": "3.2.1",
"nodemailer": "^6.9.1", "nodemailer": "6.9.1",
"passport": "^0.6.0", "passport": "0.6.0",
"passport-github2": "^0.1.12", "passport-github2": "0.1.12",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "2.0.0",
"passport-jwt": "^4.0.1", "passport-jwt": "4.0.1",
"passport-local": "^1.0.0", "passport-local": "1.0.0",
"passport-microsoft": "^1.0.0", "passport-microsoft": "1.0.0",
"posthog-node": "^3.6.3", "posthog-node": "3.6.3",
"prisma": "^5.8.0", "prisma": "5.8.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "0.1.13",
"rimraf": "^3.0.2", "rimraf": "3.0.2",
"rxjs": "^7.6.0" "rxjs": "7.6.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.1.18", "@nestjs/cli": "10.2.1",
"@nestjs/schematics": "^10.0.2", "@nestjs/schematics": "10.0.3",
"@nestjs/testing": "^10.2.6", "@nestjs/testing": "10.2.7",
"@relmify/jest-fp-ts": "^2.0.2", "@relmify/jest-fp-ts": "2.0.2",
"@types/argon2": "^0.15.0", "@types/argon2": "0.15.0",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "5.0.0",
"@types/cookie": "^0.5.1", "@types/cookie": "0.5.1",
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "1.4.3",
"@types/express": "^4.17.14", "@types/express": "4.17.14",
"@types/jest": "^29.4.0", "@types/jest": "29.4.0",
"@types/luxon": "^3.2.0", "@types/luxon": "3.2.0",
"@types/node": "^18.11.10", "@types/node": "18.11.10",
"@types/nodemailer": "^6.4.7", "@types/nodemailer": "6.4.7",
"@types/passport-github2": "^1.2.5", "@types/passport-github2": "1.2.5",
"@types/passport-google-oauth20": "^2.0.11", "@types/passport-google-oauth20": "2.0.11",
"@types/passport-jwt": "^3.0.8", "@types/passport-jwt": "3.0.8",
"@types/passport-microsoft": "^0.0.0", "@types/passport-microsoft": "0.0.0",
"@types/supertest": "^2.0.12", "@types/supertest": "2.0.12",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "5.45.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "5.45.0",
"cross-env": "^7.0.3", "cross-env": "7.0.3",
"eslint": "^8.29.0", "eslint": "8.29.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "4.2.1",
"jest": "^29.4.1", "jest": "29.4.1",
"jest-mock-extended": "^3.0.1", "jest-mock-extended": "3.0.1",
"jwt": "link:@types/nestjs/jwt", "jwt": "link:@types/nestjs/jwt",
"prettier": "^2.8.4", "prettier": "2.8.4",
"source-map-support": "^0.5.21", "source-map-support": "0.5.21",
"supertest": "^6.3.2", "supertest": "6.3.2",
"ts-jest": "29.0.5", "ts-jest": "29.0.5",
"ts-loader": "^9.4.2", "ts-loader": "9.4.2",
"ts-node": "^10.9.1", "ts-node": "10.9.1",
"tsconfig-paths": "4.1.1", "tsconfig-paths": "4.1.1",
"typescript": "^4.9.3" "typescript": "4.9.3"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

View File

@@ -1,17 +1,22 @@
-- AlterTable -- This is a custom migration file which is not generated by Prisma.
ALTER TABLE -- The aim of this migration is to add text search indices to the TeamCollection and TeamRequest tables.
-- Create Extension
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Create GIN Trigram Index for Team Collection title
CREATE INDEX
"TeamCollection_title_trgm_idx"
ON
"TeamCollection" "TeamCollection"
ADD USING
titleSearch tsvector GENERATED ALWAYS AS (to_tsvector('english', title)) STORED; GIN (title gin_trgm_ops);
-- AlterTable -- Create GIN Trigram Index for Team Collection title
ALTER TABLE CREATE INDEX
"TeamRequest_title_trgm_idx"
ON
"TeamRequest" "TeamRequest"
ADD USING
titleSearch tsvector GENERATED ALWAYS AS (to_tsvector('english', title)) STORED; GIN (title gin_trgm_ops);
-- CreateIndex
CREATE INDEX "TeamCollection_textSearch_idx" ON "TeamCollection" USING GIN (titleSearch);
-- CreateIndex
CREATE INDEX "TeamRequest_textSearch_idx" ON "TeamRequest" USING GIN (titleSearch);

View File

@@ -84,6 +84,12 @@ export const USER_ALREADY_INVITED = 'admin/user_already_invited' as const;
*/ */
export const USER_UPDATE_FAILED = 'user/update_failed' as const; export const USER_UPDATE_FAILED = 'user/update_failed' as const;
/**
* User display name validation failure
* (UserService)
*/
export const USER_SHORT_DISPLAY_NAME = 'user/short_display_name' as const;
/** /**
* User deletion failure * User deletion failure
* (UserService) * (UserService)
@@ -750,3 +756,8 @@ export const DATABASE_TABLE_NOT_EXIST =
* (InfraConfigService) * (InfraConfigService)
*/ */
export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized'; export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized';
/**
* Inputs supplied are invalid
*/
export const INVALID_PARAMS = 'invalid_parameters' as const;

View File

@@ -156,6 +156,25 @@ export async function getDefaultInfraConfigs(): Promise<
return infraConfigDefaultObjs; return infraConfigDefaultObjs;
} }
/**
* Get the missing entries in the 'infra_config' table
* @returns Array of InfraConfig
*/
export async function getMissingInfraConfigEntries() {
const prisma = new PrismaService();
const [dbInfraConfigs, infraConfigDefaultObjs] = await Promise.all([
prisma.infraConfig.findMany(),
getDefaultInfraConfigs(),
]);
const missingEntries = infraConfigDefaultObjs.filter(
(config) =>
!dbInfraConfigs.some((dbConfig) => dbConfig.name === config.name),
);
return missingEntries;
}
/** /**
* Verify if 'infra_config' table is loaded with all entries * Verify if 'infra_config' table is loaded with all entries
* @returns boolean * @returns boolean
@@ -163,12 +182,7 @@ export async function getDefaultInfraConfigs(): Promise<
export async function isInfraConfigTablePopulated(): Promise<boolean> { export async function isInfraConfigTablePopulated(): Promise<boolean> {
const prisma = new PrismaService(); const prisma = new PrismaService();
try { try {
const dbInfraConfigs = await prisma.infraConfig.findMany(); const propsRemainingToInsert = await getMissingInfraConfigEntries();
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
const propsRemainingToInsert = infraConfigDefaultObjs.filter(
(p) => !dbInfraConfigs.find((e) => e.name === p.name),
);
if (propsRemainingToInsert.length > 0) { if (propsRemainingToInsert.length > 0) {
console.log( console.log(

View File

@@ -21,7 +21,12 @@ import {
validateUrl, validateUrl,
} from 'src/utils'; } from 'src/utils';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { ServiceStatus, getDefaultInfraConfigs, stopApp } from './helper'; import {
ServiceStatus,
getDefaultInfraConfigs,
getMissingInfraConfigEntries,
stopApp,
} from './helper';
import { EnableAndDisableSSOArgs, InfraConfigArgs } from './input-args'; import { EnableAndDisableSSOArgs, InfraConfigArgs } from './input-args';
import { AuthProvider } from 'src/auth/helper'; import { AuthProvider } from 'src/auth/helper';
@@ -56,14 +61,7 @@ export class InfraConfigService implements OnModuleInit {
*/ */
async initializeInfraConfigTable() { async initializeInfraConfigTable() {
try { try {
// Fetch the default values (value in .env) for configs to be saved in 'infra_config' table const propsToInsert = await getMissingInfraConfigEntries();
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
// Eliminate the rows (from 'infraConfigDefaultObjs') that are already present in the database table
const dbInfraConfigs = await this.prisma.infraConfig.findMany();
const propsToInsert = infraConfigDefaultObjs.filter(
(p) => !dbInfraConfigs.find((e) => e.name === p.name),
);
if (propsToInsert.length > 0) { if (propsToInsert.length > 0) {
await this.prisma.infraConfig.createMany({ data: propsToInsert }); await this.prisma.infraConfig.createMany({ data: propsToInsert });
@@ -285,6 +283,7 @@ export class InfraConfigService implements OnModuleInit {
/** /**
* Get InfraConfigs by names * Get InfraConfigs by names
* @param names Names of the InfraConfigs * @param names Names of the InfraConfigs
* @param checkDisallowedKeys If true, check if the names are allowed to fetch by client
* @returns InfraConfig model * @returns InfraConfig model
*/ */
async getMany(names: InfraConfigEnum[], checkDisallowedKeys: boolean = true) { async getMany(names: InfraConfigEnum[], checkDisallowedKeys: boolean = true) {
@@ -321,25 +320,28 @@ export class InfraConfigService implements OnModuleInit {
* Reset all the InfraConfigs to their default values (from .env) * Reset all the InfraConfigs to their default values (from .env)
*/ */
async reset() { async reset() {
// These are all the infra-configs that should not be reset
const RESET_EXCLUSION_LIST = [
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
InfraConfigEnum.ANALYTICS_USER_ID,
InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
];
try { try {
const infraConfigDefaultObjs = await getDefaultInfraConfigs(); const infraConfigDefaultObjs = await getDefaultInfraConfigs();
const updatedInfraConfigDefaultObjs = infraConfigDefaultObjs.filter(
(p) => RESET_EXCLUSION_LIST.includes(p.name) === false,
);
await this.prisma.infraConfig.deleteMany({ await this.prisma.infraConfig.deleteMany({
where: { name: { in: infraConfigDefaultObjs.map((p) => p.name) } }, where: {
name: {
in: updatedInfraConfigDefaultObjs.map((p) => p.name),
},
},
}); });
// Hardcode t
const updatedInfraConfigDefaultObjs = infraConfigDefaultObjs.filter(
(obj) => obj.name !== InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
);
await this.prisma.infraConfig.createMany({ await this.prisma.infraConfig.createMany({
data: [ data: updatedInfraConfigDefaultObjs,
...updatedInfraConfigDefaultObjs,
{
name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
value: 'true',
},
],
}); });
stopApp(); stopApp();

View File

@@ -1,4 +1,11 @@
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; import {
Controller,
Get,
HttpStatus,
Param,
Query,
UseGuards,
} from '@nestjs/common';
import { TeamCollectionService } from './team-collection.service'; import { TeamCollectionService } from './team-collection.service';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
@@ -7,13 +14,15 @@ import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorat
import { TeamMemberRole } from '@prisma/client'; import { TeamMemberRole } from '@prisma/client';
import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard'; import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard';
import { throwHTTPErr } from 'src/utils'; import { throwHTTPErr } from 'src/utils';
import { RESTError } from 'src/types/RESTError';
import { INVALID_PARAMS } from 'src/errors';
@UseGuards(ThrottlerBehindProxyGuard) @UseGuards(ThrottlerBehindProxyGuard)
@Controller({ path: 'team-collection', version: '1' }) @Controller({ path: 'team-collection', version: '1' })
export class TeamCollectionController { export class TeamCollectionController {
constructor(private readonly teamCollectionService: TeamCollectionService) {} constructor(private readonly teamCollectionService: TeamCollectionService) {}
@Get('search/:teamID/:searchQuery') @Get('search/:teamID')
@RequiresTeamRole( @RequiresTeamRole(
TeamMemberRole.VIEWER, TeamMemberRole.VIEWER,
TeamMemberRole.EDITOR, TeamMemberRole.EDITOR,
@@ -21,13 +30,20 @@ export class TeamCollectionController {
) )
@UseGuards(JwtAuthGuard, RESTTeamMemberGuard) @UseGuards(JwtAuthGuard, RESTTeamMemberGuard)
async searchByTitle( async searchByTitle(
@Param('searchQuery') searchQuery: string, @Query('searchQuery') searchQuery: string,
@Param('teamID') teamID: string, @Param('teamID') teamID: string,
@Query('take') take: string, @Query('take') take: string,
@Query('skip') skip: string, @Query('skip') skip: string,
) { ) {
if (!teamID || !searchQuery) {
return <RESTError>{
message: INVALID_PARAMS,
statusCode: HttpStatus.BAD_REQUEST,
};
}
const res = await this.teamCollectionService.searchByTitle( const res = await this.teamCollectionService.searchByTitle(
searchQuery, searchQuery.trim(),
teamID, teamID,
parseInt(take), parseInt(take),
parseInt(skip), parseInt(skip),

View File

@@ -20,7 +20,7 @@ import {
TEAM_COLL_PARENT_TREE_GEN_FAILED, TEAM_COLL_PARENT_TREE_GEN_FAILED,
} from '../errors'; } from '../errors';
import { PubSubService } from '../pubsub/pubsub.service'; import { PubSubService } from '../pubsub/pubsub.service';
import { isValidLength } from 'src/utils'; import { escapeSqlLikeString, isValidLength } from 'src/utils';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client'; import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client';
@@ -1125,7 +1125,7 @@ export class TeamCollectionService {
id: searchResults[i].id, id: searchResults[i].id,
path: !fetchedParentTree path: !fetchedParentTree
? [] ? []
: ([fetchedParentTree.right] as CollectionSearchNode[]), : (fetchedParentTree.right as CollectionSearchNode[]),
}); });
} }
@@ -1148,14 +1148,20 @@ export class TeamCollectionService {
skip: number, skip: number,
) { ) {
const query = Prisma.sql` const query = Prisma.sql`
select id,title,'collection' AS type SELECT
from "TeamCollection" id,title,'collection' AS type
where "TeamCollection"."teamID"=${teamID} FROM
and titlesearch @@ to_tsquery(${searchQuery}) "TeamCollection"
order by ts_rank(titlesearch,to_tsquery(${searchQuery})) WHERE
limit ${take} "TeamCollection"."teamID"=${teamID}
AND
title ILIKE ${`%${escapeSqlLikeString(searchQuery)}%`}
ORDER BY
similarity(title, ${searchQuery})
LIMIT ${take}
OFFSET ${skip === 0 ? 0 : (skip - 1) * take}; OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
`; `;
try { try {
const res = await this.prisma.$queryRaw<SearchQueryReturnType[]>(query); const res = await this.prisma.$queryRaw<SearchQueryReturnType[]>(query);
return E.right(res); return E.right(res);
@@ -1180,12 +1186,17 @@ export class TeamCollectionService {
skip: number, skip: number,
) { ) {
const query = Prisma.sql` const query = Prisma.sql`
select id,title,request->>'method' as method,'request' AS type SELECT
from "TeamRequest" id,title,request->>'method' as method,'request' AS type
where "TeamRequest"."teamID"=${teamID} FROM
and titlesearch @@ to_tsquery(${searchQuery}) "TeamRequest"
order by ts_rank(titlesearch,to_tsquery(${searchQuery})) WHERE
limit ${take} "TeamRequest"."teamID"=${teamID}
AND
title ILIKE ${`%${escapeSqlLikeString(searchQuery)}%`}
ORDER BY
similarity(title, ${searchQuery})
LIMIT ${take}
OFFSET ${skip === 0 ? 0 : (skip - 1) * take}; OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
`; `;
@@ -1250,45 +1261,53 @@ export class TeamCollectionService {
* @returns The parent tree of the parent collections * @returns The parent tree of the parent collections
*/ */
private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) { private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) {
function findChildren(id) { function findChildren(id: string): CollectionSearchNode[] {
const collection = parentCollections.filter((item) => item.id === id)[0]; const collection = parentCollections.filter((item) => item.id === id)[0];
if (collection.parentID == null) { if (collection.parentID == null) {
return { return <CollectionSearchNode[]>[
id: collection.id, {
title: collection.title, id: collection.id,
type: 'collection', title: collection.title,
path: [], type: 'collection' as const,
}; path: [],
},
];
} }
const res = { const res = <CollectionSearchNode[]>[
id: collection.id, {
title: collection.title, id: collection.id,
type: 'collection', title: collection.title,
path: findChildren(collection.parentID), type: 'collection' as const,
}; path: findChildren(collection.parentID),
},
];
return res; return res;
} }
if (parentCollections.length > 0) { if (parentCollections.length > 0) {
if (parentCollections[0].parentID == null) { if (parentCollections[0].parentID == null) {
return { return <CollectionSearchNode[]>[
{
id: parentCollections[0].id,
title: parentCollections[0].title,
type: 'collection',
path: [],
},
];
}
return <CollectionSearchNode[]>[
{
id: parentCollections[0].id, id: parentCollections[0].id,
title: parentCollections[0].title, title: parentCollections[0].title,
type: 'collection', type: 'collection',
path: [], path: findChildren(parentCollections[0].parentID),
}; },
} ];
return {
id: parentCollections[0].id,
title: parentCollections[0].title,
type: 'collection',
path: findChildren(parentCollections[0].parentID),
};
} }
return null; return <CollectionSearchNode[]>[];
} }
/** /**

View File

@@ -58,6 +58,29 @@ export class UserResolver {
if (E.isLeft(updatedUser)) throwErr(updatedUser.left); if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
return updatedUser.right; return updatedUser.right;
} }
@Mutation(() => User, {
description: 'Update a users display name',
})
@UseGuards(GqlAuthGuard)
async updateDisplayName(
@GqlUser() user: AuthUser,
@Args({
name: 'updatedDisplayName',
description: 'New name of user',
type: () => String,
})
updatedDisplayName: string,
) {
const updatedUser = await this.userService.updateUserDisplayName(
user.uid,
updatedDisplayName,
);
if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
return updatedUser.right;
}
@Mutation(() => Boolean, { @Mutation(() => Boolean, {
description: 'Delete an user account', description: 'Delete an user account',
}) })

View File

@@ -1,4 +1,9 @@
import { JSON_INVALID, USERS_NOT_FOUND, USER_NOT_FOUND } from 'src/errors'; import {
JSON_INVALID,
USERS_NOT_FOUND,
USER_NOT_FOUND,
USER_SHORT_DISPLAY_NAME,
} from 'src/errors';
import { mockDeep, mockReset } from 'jest-mock-extended'; import { mockDeep, mockReset } from 'jest-mock-extended';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
@@ -480,6 +485,14 @@ describe('UserService', () => {
); );
expect(result).toEqualLeft(USER_NOT_FOUND); expect(result).toEqualLeft(USER_NOT_FOUND);
}); });
test('should resolve left and error when short display name is passed', async () => {
const newDisplayName = '';
const result = await userService.updateUserDisplayName(
user.uid,
newDisplayName,
);
expect(result).toEqualLeft(USER_SHORT_DISPLAY_NAME);
});
}); });
describe('fetchAllUsers', () => { describe('fetchAllUsers', () => {

View File

@@ -8,7 +8,11 @@ import * as T from 'fp-ts/Task';
import * as A from 'fp-ts/Array'; import * as A from 'fp-ts/Array';
import { pipe, constVoid } from 'fp-ts/function'; import { pipe, constVoid } from 'fp-ts/function';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { USERS_NOT_FOUND, USER_NOT_FOUND } from 'src/errors'; import {
USERS_NOT_FOUND,
USER_NOT_FOUND,
USER_SHORT_DISPLAY_NAME,
} from 'src/errors';
import { SessionType, User } from './user.model'; import { SessionType, User } from './user.model';
import { USER_UPDATE_FAILED } from 'src/errors'; import { USER_UPDATE_FAILED } from 'src/errors';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
@@ -291,6 +295,10 @@ export class UserService {
* @returns a Either of User or error * @returns a Either of User or error
*/ */
async updateUserDisplayName(userUID: string, displayName: string) { async updateUserDisplayName(userUID: string, displayName: string) {
if (!displayName || displayName.length === 0) {
return E.left(USER_SHORT_DISPLAY_NAME);
}
try { try {
const dbUpdatedUser = await this.prisma.user.update({ const dbUpdatedUser = await this.prisma.user.update({
where: { uid: userUID }, where: { uid: userUID },

View File

@@ -250,3 +250,39 @@ export function checkEnvironmentAuthProvider(
} }
} }
} }
/**
* Adds escape backslashes to the input so that it can be used inside
* SQL LIKE/ILIKE queries. Inspired by PHP's `mysql_real_escape_string`
* function.
*
* Eg. "100%" -> "100\\%"
*
* Source: https://stackoverflow.com/a/32648526
*/
export function escapeSqlLikeString(str: string) {
if (typeof str != 'string') return str;
return str.replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function (char) {
switch (char) {
case '\0':
return '\\0';
case '\x08':
return '\\b';
case '\x09':
return '\\t';
case '\x1a':
return '\\z';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '"':
case "'":
case '\\':
case '%':
return '\\' + char; // prepends a backslash to backslash, percent,
// and double/single quotes
}
});
}

View File

@@ -52,11 +52,34 @@ hopp [options or commands] arguments
Taking the above example, `pw.env.get("ENV1")` will return `"value1"` Taking the above example, `pw.env.get("ENV1")` will return `"value1"`
## Install ## Install
- Before you install Hoppscotch CLI you need to make sure you have the dependencies it requires to run.
- **Windows & macOS**: You will need `node-gyp` installed. Find instructions here: https://github.com/nodejs/node-gyp
- **Debian/Ubuntu derivatives**:
```sh
sudo apt-get install python g++ build-essential
```
- **Alpine Linux**:
```sh
sudo apk add python3 make g++
```
- **Amazon Linux (AMI)**
```sh
sudo yum install gcc72 gcc72-c++
```
- **Arch Linux**
```sh
sudo pacman -S make gcc python
```
- **RHEL/Fedora derivatives**:
```sh
sudo dnf install python3 make gcc gcc-c++ zlib-devel brotli-devel openssl-devel libuv-devel
```
Install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running:
``` - Once the dependencies are installed, install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running:
npm i -g @hoppscotch/cli ```
``` npm i -g @hoppscotch/cli
```
## **Developing:** ## **Developing:**

View File

@@ -1,6 +1,31 @@
#!/usr/bin/env node #!/usr/bin/env node
// * The entry point of the CLI // * The entry point of the CLI
// @ts-check
import { cli } from "../dist/index.js"; import { cli } from "../dist/index.js";
cli(process.argv); import { spawnSync } from "child_process";
import { cloneDeep } from "lodash-es";
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
// As per isolated-vm documentation, we need to supply `--no-node-snapshot` for node >= 20
// src: https://github.com/laverdet/isolated-vm?tab=readme-ov-file#requirements
if (nodeVersion >= 20 && !process.execArgv.includes("--no-node-snapshot")) {
const argCopy = cloneDeep(process.argv);
// Replace first argument with --no-node-snapshot
// We can get argv[0] from process.argv0
argCopy[0] = "--no-node-snapshot";
const result = spawnSync(
process.argv0,
argCopy,
{ stdio: "inherit" }
);
// Exit with the same status code as the spawned process
process.exit(result.status ?? 0);
} else {
cli(process.argv);
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@hoppscotch/cli", "name": "@hoppscotch/cli",
"version": "0.6.0", "version": "0.8.0",
"description": "A CLI to run Hoppscotch test scripts in CI environments.", "description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io", "homepage": "https://hoppscotch.io",
"type": "module", "type": "module",
@@ -41,30 +41,29 @@
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"dependencies": { "dependencies": {
"axios": "^1.6.6", "axios": "1.6.7",
"chalk": "^5.3.0", "chalk": "5.3.0",
"commander": "^11.1.0", "commander": "11.1.0",
"lodash-es": "^4.17.21", "isolated-vm": "4.7.2",
"qs": "^6.11.2", "lodash-es": "4.17.21",
"zod": "^3.22.4" "qs": "6.11.2",
"verzod": "0.2.2",
"zod": "3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^",
"@relmify/jest-fp-ts": "^2.1.1", "@relmify/jest-fp-ts": "2.1.1",
"@swc/core": "^1.3.105", "@swc/core": "1.4.2",
"@types/jest": "^29.5.11", "@types/jest": "29.5.12",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "4.17.12",
"@types/qs": "^6.9.11", "@types/qs": "6.9.12",
"fp-ts": "^2.16.2", "fp-ts": "2.16.2",
"jest": "^29.7.0", "jest": "29.7.0",
"lodash": "^4.17.21", "prettier": "3.2.5",
"prettier": "^3.2.4", "qs": "6.11.2",
"qs": "^6.11.2", "ts-jest": "29.1.2",
"ts-jest": "^29.1.2", "tsup": "8.0.2",
"tsup": "^8.0.1", "typescript": "5.3.3"
"typescript": "^5.3.3",
"verzod": "^0.2.2",
"zod": "^3.22.4"
} }
} }

View File

@@ -20,7 +20,7 @@ describe("Test `hopp test <file>` command:", () => {
const out = getErrorCode(stderr); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });
}) });
describe("Supplied collection export file validations", () => { describe("Supplied collection export file validations", () => {
test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => { test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => {
@@ -66,6 +66,43 @@ describe("Test `hopp test <file>` command:", () => {
}); });
}); });
describe("Versioned entities", () => {
describe("Collections & Requests", () => {
const testFixtures = [
{ fileName: "coll-v1-req-v0.json", collVersion: 1, reqVersion: 0 },
{ fileName: "coll-v1-req-v1.json", collVersion: 1, reqVersion: 1 },
{ fileName: "coll-v2-req-v2.json", collVersion: 2, reqVersion: 2 },
{ fileName: "coll-v2-req-v3.json", collVersion: 2, reqVersion: 3 },
];
testFixtures.forEach(({ collVersion, fileName, reqVersion }) => {
test(`Successfully processes a supplied collection export file where the collection is based on the "v${collVersion}" schema and the request following the "v${reqVersion}" schema`, async () => {
const args = `test ${getTestJsonFilePath(fileName, "collection")}`;
const { error } = await runCLI(args);
expect(error).toBeNull();
});
});
});
describe("Environments", () => {
const testFixtures = [
{ fileName: "env-v0.json", version: 0 },
{ fileName: "env-v1.json", version: 1 },
];
testFixtures.forEach(({ fileName, version }) => {
test(`Successfully processes the supplied collection and environment export files where the environment is based on the "v${version}" schema`, async () => {
const ENV_PATH = getTestJsonFilePath(fileName, "environment");
const args = `test ${getTestJsonFilePath("sample-coll.json", "collection")} --env ${ENV_PATH}`;
const { error } = await runCLI(args);
expect(error).toBeNull();
});
});
});
});
test("Successfully processes a supplied collection export file of the expected format", async () => { test("Successfully processes a supplied collection export file of the expected format", async () => {
const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -75,7 +112,8 @@ describe("Test `hopp test <file>` command:", () => {
test("Successfully inherits headers and authorization set at the root collection", async () => { test("Successfully inherits headers and authorization set at the root collection", async () => {
const args = `test ${getTestJsonFilePath( const args = `test ${getTestJsonFilePath(
"collection-level-headers-auth-coll.json", "collection" "collection-level-headers-auth-coll.json",
"collection"
)}`; )}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -84,7 +122,8 @@ describe("Test `hopp test <file>` command:", () => {
test("Persists environment variables set in the pre-request script for consumption in the test script", async () => { test("Persists environment variables set in the pre-request script for consumption in the test script", async () => {
const args = `test ${getTestJsonFilePath( const args = `test ${getTestJsonFilePath(
"pre-req-script-env-var-persistence-coll.json", "collection" "pre-req-script-env-var-persistence-coll.json",
"collection"
)}`; )}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -106,7 +145,8 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => { test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => {
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath( const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath(
"notjson-coll.txt", "collection" "notjson-coll.txt",
"collection"
)}`; )}`;
const { stderr } = await runCLI(args); const { stderr } = await runCLI(args);
@@ -123,7 +163,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => { test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => {
const ENV_PATH = getTestJsonFilePath("malformed-envs.json", "environment"); const ENV_PATH = getTestJsonFilePath(
"malformed-envs.json",
"environment"
);
const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`; const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
const { stderr } = await runCLI(args); const { stderr } = await runCLI(args);
@@ -142,7 +185,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Successfully resolves values from the supplied environment export file", async () => { test("Successfully resolves values from the supplied environment export file", async () => {
const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection"); const TESTS_PATH = getTestJsonFilePath(
"env-flag-tests-coll.json",
"collection"
);
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`; const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
@@ -151,8 +197,14 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Successfully resolves environment variables referenced in the request body", async () => { test("Successfully resolves environment variables referenced in the request body", async () => {
const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json", "collection"); const COLL_PATH = getTestJsonFilePath(
const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json", "environment"); "req-body-env-vars-coll.json",
"collection"
);
const ENVS_PATH = getTestJsonFilePath(
"req-body-env-vars-envs.json",
"environment"
);
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -160,7 +212,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Works with shorth `-e` flag", async () => { test("Works with shorth `-e` flag", async () => {
const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection"); const TESTS_PATH = getTestJsonFilePath(
"env-flag-tests-coll.json",
"collection"
);
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
const args = `test ${TESTS_PATH} -e ${ENV_PATH}`; const args = `test ${TESTS_PATH} -e ${ENV_PATH}`;
@@ -169,7 +224,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
describe("Secret environment variables", () => { describe("Secret environment variables", () => {
jest.setTimeout(10000); jest.setTimeout(100000);
// Reads secret environment values from system environment // Reads secret environment values from system environment
test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => { test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => {
@@ -183,7 +238,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
secretHeaderValue: "secret-header-value", secretHeaderValue: "secret-header-value",
}; };
const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection"); const COLL_PATH = getTestJsonFilePath(
"secret-envs-coll.json",
"collection"
);
const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment"); const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment");
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
@@ -197,8 +255,14 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
// Prefers values specified in the environment export file over values set in the system environment // Prefers values specified in the environment export file over values set in the system environment
test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => { test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => {
const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection"); const COLL_PATH = getTestJsonFilePath(
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment"); "secret-envs-coll.json",
"collection"
);
const ENVS_PATH = getTestJsonFilePath(
"secret-supplied-values-envs.json",
"environment"
);
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
const { error, stdout } = await runCLI(args); const { error, stdout } = await runCLI(args);
@@ -212,9 +276,13 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
// Values set from the scripting context takes the highest precedence // Values set from the scripting context takes the highest precedence
test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => { test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => {
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath(
"secret-envs-persistence-coll.json", "collection" "secret-envs-persistence-coll.json",
"collection"
);
const ENVS_PATH = getTestJsonFilePath(
"secret-supplied-values-envs.json",
"environment"
); );
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
const { error, stdout } = await runCLI(args); const { error, stdout } = await runCLI(args);
@@ -227,10 +295,12 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => { test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => {
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath(
"secret-envs-persistence-scripting-coll.json", "collection" "secret-envs-persistence-scripting-coll.json",
"collection"
); );
const ENVS_PATH = getTestJsonFilePath( const ENVS_PATH = getTestJsonFilePath(
"secret-envs-persistence-scripting-envs.json", "environment" "secret-envs-persistence-scripting-envs.json",
"environment"
); );
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;

View File

@@ -1,84 +0,0 @@
import { isRESTCollection } from "../../../utils/checks";
describe("isRESTCollection", () => {
test("Undefined collection value.", () => {
expect(isRESTCollection(undefined)).toBeFalsy();
});
test("Invalid id value.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: 1,
})
).toBeFalsy();
});
test("Invalid requests value.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: null,
})
).toBeFalsy();
});
test("Invalid folders value.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [],
folders: undefined,
})
).toBeFalsy();
});
test("Invalid RESTCollection(s) in folders.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [],
folders: [
{
v: 1,
name: "test1",
id: "2",
requests: undefined,
folders: [],
},
],
})
).toBeFalsy();
});
test("Invalid HoppRESTRequest(s) in requests.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [{}],
folders: [],
})
).toBeFalsy();
});
test("Valid RESTCollection.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [],
folders: [],
})
).toBeTruthy();
});
});

View File

@@ -0,0 +1,55 @@
{
"v": 1,
"name": "coll-v1",
"folders": [
{
"v": 1,
"name": "coll-v1-child",
"folders": [],
"requests": [
{
"url": "https://echo.hoppscotch.io",
"path": "/get",
"headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true }
],
"params": [
{ "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false }
],
"name": "req-v0-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"contentType": "application/json",
"body": "",
"auth": "Bearer Token",
"bearerToken": "token123"
}
]
}
],
"requests": [
{
"url": "https://echo.hoppscotch.io",
"path": "/get",
"headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true }
],
"params": [
{ "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false }
],
"name": "req-v0",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"contentType": "application/json",
"body": "",
"auth": "Bearer Token",
"bearerToken": "token123"
}
]
}

View File

@@ -0,0 +1,97 @@
{
"v": 1,
"name": "coll-v1",
"folders": [
{
"v": 1,
"name": "coll-v1-child",
"folders": [],
"requests": [
{
"v": "1",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v1-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
}
}
]
}
],
"requests": [
{
"v": "1",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v1",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
}
}
]
}

View File

@@ -0,0 +1,109 @@
{
"v": 2,
"name": "coll-v2",
"folders": [
{
"v": 2,
"name": "coll-v2-child",
"folders": [],
"requests": [
{
"v": "2",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v2-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}
],
"requests": [
{
"v": "2",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v2",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}

View File

@@ -0,0 +1,109 @@
{
"v": 2,
"name": "coll-v2",
"folders": [
{
"v": 2,
"name": "coll-v2-child",
"folders": [],
"requests": [
{
"v": "3",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v3-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}
],
"requests": [
{
"v": "3",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v3",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}

View File

@@ -1,23 +1,23 @@
[ [
{ {
"v": 1, "v": 2,
"name": "CollectionA", "name": "CollectionA",
"folders": [ "folders": [
{ {
"v": 1, "v": 2,
"name": "FolderA", "name": "FolderA",
"folders": [ "folders": [
{ {
"v": 1, "v": 2,
"name": "FolderB", "name": "FolderB",
"folders": [ "folders": [
{ {
"v": 1, "v": 2,
"name": "FolderC", "name": "FolderC",
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"name": "RequestD", "name": "RequestD",
"params": [], "params": [],
@@ -40,7 +40,8 @@
"body": { "body": {
"contentType": null, "contentType": null,
"body": null "body": null
} },
"requestVariables": []
} }
], ],
"auth": { "auth": {
@@ -52,7 +53,7 @@
], ],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"name": "RequestC", "name": "RequestC",
"params": [], "params": [],
@@ -67,13 +68,14 @@
"body": { "body": {
"contentType": null, "contentType": null,
"body": null "body": null
} },
"requestVariables": []
} }
], ],
"auth": { "auth": {
"authType": "api-key", "authType": "api-key",
"authActive": true, "authActive": true,
"addTo": "Headers", "addTo": "HEADERS",
"key": "key", "key": "key",
"value": "test-key" "value": "test-key"
}, },
@@ -88,7 +90,7 @@
], ],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"name": "RequestB", "name": "RequestB",
"params": [], "params": [],
@@ -104,6 +106,7 @@
"contentType": null, "contentType": null,
"body": null "body": null
}, },
"requestVariables": [],
"id": "clpttpdq00003qp16kut6doqv" "id": "clpttpdq00003qp16kut6doqv"
} }
], ],
@@ -116,7 +119,7 @@
], ],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"name": "RequestA", "name": "RequestA",
"params": [], "params": [],
@@ -132,6 +135,7 @@
"contentType": null, "contentType": null,
"body": null "body": null
}, },
"requestVariables": [],
"id": "clpttpdq00003qp16kut6doqv" "id": "clpttpdq00003qp16kut6doqv"
} }
], ],
@@ -149,16 +153,16 @@
} }
}, },
{ {
"v": 1, "v": 2,
"name": "CollectionB", "name": "CollectionB",
"folders": [ "folders": [
{ {
"v": 1, "v": 2,
"name": "FolderA", "name": "FolderA",
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"name": "RequestB", "name": "RequestB",
"params": [], "params": [],
@@ -174,6 +178,7 @@
"contentType": null, "contentType": null,
"body": null "body": null
}, },
"requestVariables": [],
"id": "clpttpdq00003qp16kut6doqv" "id": "clpttpdq00003qp16kut6doqv"
} }
], ],
@@ -186,7 +191,7 @@
], ],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"name": "RequestA", "name": "RequestA",
"params": [], "params": [],
@@ -202,6 +207,7 @@
"contentType": null, "contentType": null,
"body": null "body": null
}, },
"requestVariables": [],
"id": "clpttpdq00003qp16kut6doqv" "id": "clpttpdq00003qp16kut6doqv"
} }
], ],
@@ -218,4 +224,4 @@
"token": "BearerToken" "token": "BearerToken"
} }
} }
] ]

View File

@@ -4,7 +4,7 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "<<URL>>", "endpoint": "<<URL>>",
"name": "test1", "name": "test1",
"params": [], "params": [],
@@ -16,7 +16,8 @@
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}" "body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}"
} },
"requestVariables": []
} }
] ]
} }

View File

@@ -5,7 +5,7 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>", "endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
"name": "", "name": "",
"params": [], "params": [],
@@ -13,20 +13,18 @@
"method": "GET", "method": "GET",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true, "authActive": true
"addTo": "Headers",
"key": "",
"value": ""
}, },
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");", "preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
"testScript": "// Check status code is 200\npwd.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});", "testScript": "// Check status code is 200\npwd.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
} },
"requestVariables": []
}, },
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.dio/<<HEADERS_TYPE2>>", "endpoint": "https://echo.hoppscotch.dio/<<HEADERS_TYPE2>>",
"name": "success", "name": "success",
"params": [], "params": [],
@@ -34,17 +32,15 @@
"method": "GET", "method": "GET",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true, "authActive": true
"addTo": "Headers",
"key": "",
"value": ""
}, },
"preRequestScript": "pw.env.setd(\"HEADERS_TYPE2\", \"devblin_local2\");", "preRequestScript": "pw.env.setd(\"HEADERS_TYPE2\", \"devblin_local2\");",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});", "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}"
} },
"requestVariables": []
} }
] ]
} }

View File

@@ -2,9 +2,9 @@
{ {
"v": 1, "v": 1,
"folders": [], "folders": [],
"requests": "requests":
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>", "endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
"name": "fail", "name": "fail",
"params": [], "params": [],
@@ -12,20 +12,18 @@
"method": "GET", "method": "GET",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true, "authActive": true
"addTo": "Headers",
"key": "",
"value": ""
}, },
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");", "preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});", "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
} },
"requestVariables": [],
}, },
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>", "endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
"name": "success", "name": "success",
"params": [], "params": [],
@@ -33,17 +31,15 @@
"method": "GET", "method": "GET",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true, "authActive": true
"addTo": "Headers",
"key": "",
"value": ""
}, },
"preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");", "preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});", "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}"
} },
"requestVariables": []
} }
] ]
} }

View File

@@ -2,9 +2,9 @@
{ {
"v": 1, "v": 1,
"folders": [], "folders": [],
"requests": "requests":
{ {
"v": "1", "v": "2",
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>", "endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
"name": "fail", "name": "fail",
"params": [], "params": [],
@@ -22,7 +22,8 @@
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
} },
"requestVariables": []
} }
] ]
} }

View File

@@ -5,7 +5,7 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>", "endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
"name": "", "name": "",
"params": [], "params": [],
@@ -13,20 +13,18 @@
"method": "GET", "method": "GET",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true, "authActive": true
"addTo": "Headers",
"key": "",
"value": ""
}, },
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");", "preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});", "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
} },
"requestVariables": []
}, },
{ {
"v": "1", "v": "3",
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>", "endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
"name": "success", "name": "success",
"params": [], "params": [],
@@ -34,17 +32,15 @@
"method": "GET", "method": "GET",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true, "authActive": true
"addTo": "Headers",
"key": "",
"value": ""
}, },
"preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");", "preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});", "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}" "body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}"
} },
"requestVariables": []
} }
] ]
} }

View File

@@ -4,7 +4,7 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": { "authType": "none", "authActive": true },
"body": { "body": null, "contentType": null }, "body": { "body": null, "contentType": null },
"name": "sample-req", "name": "sample-req",
@@ -13,7 +13,8 @@
"headers": [], "headers": [],
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"testScript": "pw.expect(pw.env.get(\"variable\")).toBe(\"value\")", "testScript": "pw.expect(pw.env.get(\"variable\")).toBe(\"value\")",
"preRequestScript": "pw.env.set(\"variable\", \"value\");" "preRequestScript": "pw.env.set(\"variable\", \"value\");",
"requestVariables": []
} }
], ],
"auth": { "authType": "inherit", "authActive": true }, "auth": { "authType": "inherit", "authActive": true },

View File

@@ -4,7 +4,7 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"name": "test-request", "name": "test-request",
"endpoint": "https://echo.hoppscotch.io", "endpoint": "https://echo.hoppscotch.io",
"method": "POST", "method": "POST",
@@ -19,7 +19,8 @@
"body": "{\n \"firstName\": \"<<firstName>>\",\n \"lastName\": \"<<lastName>>\",\n \"greetText\": \"<<salutation>>, <<fullName>>\",\n \"fullName\": \"<<fullName>>\",\n \"id\": \"<<id>>\"\n}" "body": "{\n \"firstName\": \"<<firstName>>\",\n \"lastName\": \"<<lastName>>\",\n \"greetText\": \"<<salutation>>, <<fullName>>\",\n \"fullName\": \"<<fullName>>\",\n \"id\": \"<<id>>\"\n}"
}, },
"preRequestScript": "", "preRequestScript": "",
"testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully resolves environments recursively\", ()=> {\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n});\n\npw.test(\"Successfully resolves environments referenced in the request body\", () => {\n const expectedId = \"7\"\n const expectedFirstName = \"John\"\n const expectedLastName = \"Doe\"\n const expectedFullName = `${expectedFirstName} ${expectedLastName}`\n const expectedGreetText = `Hello, ${expectedFullName}`\n\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n\n const { id, firstName, lastName, fullName, greetText } = JSON.parse(pw.response.body.data)\n\n pw.expect(id).toBe(expectedId)\n pw.expect(expectedFirstName).toBe(firstName)\n pw.expect(expectedLastName).toBe(lastName)\n pw.expect(fullName).toBe(expectedFullName)\n pw.expect(greetText).toBe(expectedGreetText)\n});" "testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully resolves environments recursively\", ()=> {\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n});\n\npw.test(\"Successfully resolves environments referenced in the request body\", () => {\n const expectedId = \"7\"\n const expectedFirstName = \"John\"\n const expectedLastName = \"Doe\"\n const expectedFullName = `${expectedFirstName} ${expectedLastName}`\n const expectedGreetText = `Hello, ${expectedFullName}`\n\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n\n const { id, firstName, lastName, fullName, greetText } = JSON.parse(pw.response.body.data)\n\n pw.expect(id).toBe(expectedId)\n pw.expect(expectedFirstName).toBe(firstName)\n pw.expect(expectedLastName).toBe(lastName)\n pw.expect(fullName).toBe(expectedFullName)\n pw.expect(greetText).toBe(expectedGreetText)\n});",
"requestVariables": []
} }
], ],
"auth": { "auth": {

View File

@@ -0,0 +1,26 @@
{
"v": 1,
"name": "tests",
"folders": [],
"requests": [
{
"v": "2",
"endpoint": "<<baseURL>>",
"name": "",
"params": [],
"headers": [],
"method": "GET",
"auth": {
"authType": "none",
"authActive": true
},
"preRequestScript": "",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
"body": {
"contentType": null,
"body": null
},
"requestVariables": []
}
]
}

View File

@@ -4,9 +4,15 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"body": { "body": null, "contentType": null }, "authType": "none",
"authActive": true
},
"body": {
"body": null,
"contentType": null
},
"name": "test-secret-headers", "name": "test-secret-headers",
"method": "GET", "method": "GET",
"params": [], "params": [],
@@ -17,13 +23,17 @@
"active": true "active": true
} }
], ],
"endpoint": "<<baseURL>>/headers", "requestVariables": [],
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})", "endpoint": "<<echoHoppBaseURL>>/headers",
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" "preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"authType": "none",
"authActive": true
},
"body": { "body": {
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}", "body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
"contentType": "application/json" "contentType": "application/json"
@@ -32,14 +42,21 @@
"method": "POST", "method": "POST",
"params": [], "params": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/post", "requestVariables": [],
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", "endpoint": "<<echoHoppBaseURL>>/post",
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)" "preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"body": { "body": null, "contentType": null }, "authType": "none",
"authActive": true
},
"body": {
"body": null,
"contentType": null
},
"name": "test-secret-query-params", "name": "test-secret-query-params",
"method": "GET", "method": "GET",
"params": [ "params": [
@@ -50,29 +67,34 @@
} }
], ],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/get", "requestVariables": [],
"endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})", "testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)" "preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"authType": "basic", "authType": "basic",
"password": "<<secretBasicAuthPassword>>", "password": "<<secretBasicAuthPassword>>",
"username": "<<secretBasicAuthUsername>>", "username": "<<secretBasicAuthUsername>>",
"authActive": true "authActive": true
}, },
"body": { "body": null, "contentType": null }, "body": {
"body": null,
"contentType": null
},
"name": "test-secret-basic-auth", "name": "test-secret-basic-auth",
"method": "GET", "method": "GET",
"params": [], "params": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>", "requestVariables": [],
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", "endpoint": "<<httpbinBaseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n // The endpoint at times results in a `502` bad gateway\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
"preRequestScript": "" "preRequestScript": ""
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"token": "<<secretBearerToken>>", "token": "<<secretBearerToken>>",
"authType": "bearer", "authType": "bearer",
@@ -80,28 +102,42 @@
"username": "testuser", "username": "testuser",
"authActive": true "authActive": true
}, },
"body": { "body": null, "contentType": null }, "body": {
"body": null,
"contentType": null
},
"name": "test-secret-bearer-auth", "name": "test-secret-bearer-auth",
"method": "GET", "method": "GET",
"params": [], "params": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/bearer", "requestVariables": [],
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", "endpoint": "<<httpbinBaseURL>>/bearer",
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n // Safeguard to prevent test failures due to the endpoint\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)" "preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"body": { "body": null, "contentType": null }, "authType": "none",
"authActive": true
},
"body": {
"body": null,
"contentType": null
},
"name": "test-secret-fallback", "name": "test-secret-fallback",
"method": "GET", "method": "GET",
"params": [], "params": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>", "requestVariables": [],
"endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})", "testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})",
"preRequestScript": "" "preRequestScript": ""
} }
], ],
"auth": { "authType": "inherit", "authActive": false }, "auth": {
"authType": "inherit",
"authActive": false
},
"headers": [] "headers": []
} }

View File

@@ -1,10 +1,10 @@
{ {
"v": 2, "v": 2,
"name": "secret-envs-setters-coll", "name": "secret-envs-persistence-coll",
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true "authActive": true
@@ -16,6 +16,7 @@
"name": "test-secret-headers", "name": "test-secret-headers",
"method": "GET", "method": "GET",
"params": [], "params": [],
"requestVariables": [],
"headers": [ "headers": [
{ {
"key": "Secret-Header-Key", "key": "Secret-Header-Key",
@@ -23,12 +24,12 @@
"active": true "active": true
} }
], ],
"endpoint": "<<baseURL>>/headers", "endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})", "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" "preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true "authActive": true
@@ -40,6 +41,7 @@
"name": "test-secret-headers-overrides", "name": "test-secret-headers-overrides",
"method": "GET", "method": "GET",
"params": [], "params": [],
"requestVariables": [],
"headers": [ "headers": [
{ {
"key": "Secret-Header-Key", "key": "Secret-Header-Key",
@@ -47,12 +49,12 @@
"active": true "active": true
} }
], ],
"endpoint": "<<baseURL>>/headers", "endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})", "testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})",
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" "preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true "authActive": true
@@ -64,13 +66,14 @@
"name": "test-secret-body", "name": "test-secret-body",
"method": "POST", "method": "POST",
"params": [], "params": [],
"requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/post", "endpoint": "<<echoHoppBaseURL>>/post",
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
"preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)" "preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"authType": "none", "authType": "none",
"authActive": true "authActive": true
@@ -88,13 +91,14 @@
"active": true "active": true
} }
], ],
"requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/get", "endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})", "testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
"preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)" "preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"authType": "basic", "authType": "basic",
"password": "<<secretBasicAuthPassword>>", "password": "<<secretBasicAuthPassword>>",
@@ -108,13 +112,14 @@
"name": "test-secret-basic-auth", "name": "test-secret-basic-auth",
"method": "GET", "method": "GET",
"params": [], "params": [],
"requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>", "endpoint": "<<httpbinBaseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n // The endpoint at times results in a `502` bad gateway\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
"preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}" "preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}"
}, },
{ {
"v": "1", "v": "3",
"auth": { "auth": {
"token": "<<secretBearerToken>>", "token": "<<secretBearerToken>>",
"authType": "bearer", "authType": "bearer",
@@ -129,9 +134,10 @@
"name": "test-secret-bearer-auth", "name": "test-secret-bearer-auth",
"method": "GET", "method": "GET",
"params": [], "params": [],
"requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/bearer", "endpoint": "<<httpbinBaseURL>>/bearer",
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n // Safeguard to prevent test failures due to the endpoint\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
"preRequestScript": "let secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)" "preRequestScript": "let secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
} }
], ],
@@ -140,4 +146,4 @@
"authActive": false "authActive": false
}, },
"headers": [] "headers": []
} }

View File

@@ -4,8 +4,8 @@
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
"v": "1", "v": "3",
"endpoint": "https://httpbin.org/post", "endpoint": "https://echo.hoppscotch.io/post",
"name": "req", "name": "req",
"params": [], "params": [],
"headers": [ "headers": [
@@ -18,11 +18,12 @@
"method": "POST", "method": "POST",
"auth": { "authType": "none", "authActive": true }, "auth": { "authType": "none", "authActive": true },
"preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")", "preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")",
"testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.json.key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})", "testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request script\", () => {\n pw.expect(pw.response.body.headers[\"custom-header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request script\", () => {\n pw.expect(JSON.parse(pw.response.body.data).key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n \"key\": \"<<customBodyValue>>\"\n}" "body": "{\n \"key\": \"<<customBodyValue>>\"\n}"
} },
"requestVariables": []
} }
], ],
"auth": { "authType": "inherit", "authActive": false }, "auth": { "authType": "inherit", "authActive": false },

View File

@@ -0,0 +1,9 @@
{
"name": "env-v0",
"variables": [
{
"key": "baseURL",
"value": "https://echo.hoppscotch.io"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"name": "env-v0",
"variables": [
{
"key": "baseURL",
"value": "https://echo.hoppscotch.io",
"secret": false
}
]
}

View File

@@ -32,7 +32,12 @@
"secret": true "secret": true
}, },
{ {
"key": "baseURL", "key": "echoHoppBaseURL",
"value": "https://echo.hoppscotch.io",
"secret": false
},
{
"key": "httpbinBaseURL",
"value": "https://httpbin.org", "value": "https://httpbin.org",
"secret": false "secret": false
} }

View File

@@ -38,7 +38,12 @@
"secret": true "secret": true
}, },
{ {
"key": "baseURL", "key": "echoHoppBaseURL",
"value": "https://echo.hoppscotch.io",
"secret": false
},
{
"key": "httpbinBaseURL",
"value": "https://httpbin.org", "value": "https://httpbin.org",
"secret": false "secret": false
} }

View File

@@ -3,15 +3,16 @@ import { resolve } from "path";
import { ExecResponse } from "./types"; import { ExecResponse } from "./types";
export const runCLI = (args: string, options = {}): Promise<ExecResponse> => export const runCLI = (args: string, options = {}): Promise<ExecResponse> => {
{ const CLI_PATH = resolve(__dirname, "../../bin/hopp.js");
const CLI_PATH = resolve(__dirname, "../../bin/hopp"); const command = `node ${CLI_PATH} ${args}`;
const command = `node ${CLI_PATH} ${args}`
return new Promise((resolve) => return new Promise((resolve) =>
exec(command, options, (error, stdout, stderr) => resolve({ error, stdout, stderr })) exec(command, options, (error, stdout, stderr) =>
); resolve({ error, stdout, stderr })
} )
);
};
export const trimAnsi = (target: string) => { export const trimAnsi = (target: string) => {
const ansiRegex = const ansiRegex =
@@ -25,12 +26,18 @@ export const getErrorCode = (out: string) => {
return ansiTrimmedStr.split(" ")[0]; return ansiTrimmedStr.split(" ")[0];
}; };
export const getTestJsonFilePath = (file: string, kind: "collection" | "environment") => { export const getTestJsonFilePath = (
file: string,
kind: "collection" | "environment"
) => {
const kindDir = { const kindDir = {
collection: "collections", collection: "collections",
environment: "environments", environment: "environments",
}[kind]; }[kind];
const filePath = resolve(__dirname, `../../src/__tests__/samples/${kindDir}/${file}`); const filePath = resolve(
__dirname,
`../../src/__tests__/samples/${kindDir}/${file}`
);
return filePath; return filePath;
}; };

View File

@@ -1,6 +1,7 @@
import chalk from "chalk"; import chalk from "chalk";
import { Command } from "commander"; import { Command } from "commander";
import * as E from "fp-ts/Either"; import * as E from "fp-ts/Either";
import { version } from "../package.json"; import { version } from "../package.json";
import { test } from "./commands/test"; import { test } from "./commands/test";
import { handleError } from "./handlers/error"; import { handleError } from "./handlers/error";
@@ -20,7 +21,7 @@ const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent(
"https://docs.hoppscotch.io/documentation/clients/cli" "https://docs.hoppscotch.io/documentation/clients/cli"
)}`; )}`;
const program = new Command() const program = new Command();
program program
.name("hopp") .name("hopp")

View File

@@ -6,7 +6,7 @@ import { error } from "../../types/errors";
import { import {
HoppEnvKeyPairObject, HoppEnvKeyPairObject,
HoppEnvPair, HoppEnvPair,
HoppEnvs HoppEnvs,
} from "../../types/request"; } from "../../types/request";
import { readJsonFile } from "../../utils/mutators"; import { readJsonFile } from "../../utils/mutators";
@@ -17,7 +17,7 @@ import { readJsonFile } from "../../utils/mutators";
*/ */
export async function parseEnvsData(path: string) { export async function parseEnvsData(path: string) {
const contents = await readJsonFile(path); const contents = await readJsonFile(path);
const envPairs: Array<Environment["variables"][number] | HoppEnvPair> = []; const envPairs: Array<HoppEnvPair | Record<string, string>> = [];
// The legacy key-value pair format that is still supported // The legacy key-value pair format that is still supported
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents); const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
@@ -26,7 +26,9 @@ export async function parseEnvsData(path: string) {
const HoppEnvExportObjectResult = Environment.safeParse(contents); const HoppEnvExportObjectResult = Environment.safeParse(contents);
// Shape of the bulk environment export object that is exported from the app // Shape of the bulk environment export object that is exported from the app
const HoppBulkEnvExportObjectResult = z.array(entityReference(Environment)).safeParse(contents) const HoppBulkEnvExportObjectResult = z
.array(entityReference(Environment))
.safeParse(contents);
// CLI doesnt support bulk environments export // CLI doesnt support bulk environments export
// Hence we check for this case and throw an error if it matches the format // Hence we check for this case and throw an error if it matches the format
@@ -36,13 +38,16 @@ export async function parseEnvsData(path: string) {
// Checks if the environment file is of the correct format // Checks if the environment file is of the correct format
// If it doesnt match either of them, we throw an error // If it doesnt match either of them, we throw an error
if (!HoppEnvKeyPairResult.success && HoppEnvExportObjectResult.type === "err") { if (
!HoppEnvKeyPairResult.success &&
HoppEnvExportObjectResult.type === "err"
) {
throw error({ code: "MALFORMED_ENV_FILE", path, data: error }); throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
} }
if (HoppEnvKeyPairResult.success) { if (HoppEnvKeyPairResult.success) {
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) { for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
envPairs.push({ key, value }); envPairs.push({ key, value, secret: false });
} }
} else if (HoppEnvExportObjectResult.type === "ok") { } else if (HoppEnvExportObjectResult.type === "ok") {
envPairs.push(...HoppEnvExportObjectResult.value.variables); envPairs.push(...HoppEnvExportObjectResult.value.variables);

View File

@@ -1,5 +1,3 @@
import { HoppCollection, isHoppRESTRequest } from "@hoppscotch/data";
import * as A from "fp-ts/Array";
import { CommanderError } from "commander"; import { CommanderError } from "commander";
import { HoppCLIError, HoppErrnoException } from "../types/errors"; import { HoppCLIError, HoppErrnoException } from "../types/errors";
@@ -14,48 +12,6 @@ export const hasProperty = <P extends PropertyKey>(
prop: P prop: P
): target is Record<P, unknown> => prop in target; ): target is Record<P, unknown> => prop in target;
/**
* Typeguard to check valid Hoppscotch REST Collection.
* @param param The object to be checked.
* @returns True, if unknown parameter is valid Hoppscotch REST Collection;
* False, otherwise.
*/
export const isRESTCollection = (param: unknown): param is HoppCollection => {
if (!!param && typeof param === "object") {
if (!hasProperty(param, "v") || typeof param.v !== "number") {
return false;
}
if (!hasProperty(param, "name") || typeof param.name !== "string") {
return false;
}
if (hasProperty(param, "id") && typeof param.id !== "string") {
return false;
}
if (!hasProperty(param, "requests") || !Array.isArray(param.requests)) {
return false;
} else {
// Checks each requests array to be valid HoppRESTRequest.
const checkRequests = A.every(isHoppRESTRequest)(param.requests);
if (!checkRequests) {
return false;
}
}
if (!hasProperty(param, "folders") || !Array.isArray(param.folders)) {
return false;
} else {
// Checks each folder to be valid REST collection.
const checkFolders = A.every(isRESTCollection)(param.folders);
if (!checkFolders) {
return false;
}
}
return true;
}
return false;
};
/** /**
* Checks if given error data is of type HoppCLIError, based on existence * Checks if given error data is of type HoppCLIError, based on existence
* of code property. * of code property.

View File

@@ -131,7 +131,7 @@ const getCollectionStack = (collections: HoppCollection[]): CollectionStack[] =>
* path of each request within collection-json file, failed-tests-report, errors, * path of each request within collection-json file, failed-tests-report, errors,
* total execution duration for requests, pre-request-scripts, test-scripts. * total execution duration for requests, pre-request-scripts, test-scripts.
* @returns True, if collection runner executed without any errors or failed test-cases. * @returns True, if collection runner executed without any errors or failed test-cases.
* False, if errors occured or test-cases failed. * False, if errors occurred or test-cases failed.
*/ */
export const collectionsRunnerResult = ( export const collectionsRunnerResult = (
requestsReport: RequestReport[] requestsReport: RequestReport[]

View File

@@ -112,7 +112,7 @@ export const printTestsMetrics = (testsMetrics: TestMetrics) => {
/** /**
* Prints details of each reported error for a request with error code. * Prints details of each reported error for a request with error code.
* @param path Request's path in collection for which errors occured. * @param path Request's path in collection for which errors occurred.
* @param errorsReport List of errors reported. * @param errorsReport List of errors reported.
*/ */
export const printErrorsReport = ( export const printErrorsReport = (

View File

@@ -1,8 +1,46 @@
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
import fs from "fs/promises"; import fs from "fs/promises";
import { FormDataEntry } from "../types/request"; import { entityReference } from "verzod";
import { z } from "zod";
import { error } from "../types/errors"; import { error } from "../types/errors";
import { isRESTCollection, isHoppErrnoException } from "./checks"; import { FormDataEntry } from "../types/request";
import { HoppCollection } from "@hoppscotch/data"; import { isHoppErrnoException } from "./checks";
const getValidRequests = (
collections: HoppCollection[],
collectionFilePath: string
) => {
return collections.map((collection) => {
// Validate requests using zod schema
const requestSchemaParsedResult = z
.array(entityReference(HoppRESTRequest))
.safeParse(collection.requests);
// Handle validation errors
if (!requestSchemaParsedResult.success) {
throw error({
code: "MALFORMED_COLLECTION",
path: collectionFilePath,
data: "Please check the collection data.",
});
}
// Recursively validate requests in nested folders
if (collection.folders.length > 0) {
collection.folders = getValidRequests(
collection.folders,
collectionFilePath
);
}
// Return validated collection
return {
...collection,
requests: requestSchemaParsedResult.data,
};
});
};
/** /**
* Parses array of FormDataEntry to FormData. * Parses array of FormDataEntry to FormData.
@@ -67,7 +105,11 @@ export async function parseCollectionData(
? contents ? contents
: [contents]; : [contents];
if (maybeArrayOfCollections.some((x) => !isRESTCollection(x))) { const collectionSchemaParsedResult = z
.array(entityReference(HoppCollection))
.safeParse(maybeArrayOfCollections);
if (!collectionSchemaParsedResult.success) {
throw error({ throw error({
code: "MALFORMED_COLLECTION", code: "MALFORMED_COLLECTION",
path, path,
@@ -75,5 +117,5 @@ export async function parseCollectionData(
}); });
} }
return maybeArrayOfCollections as HoppCollection[]; return getValidRequests(collectionSchemaParsedResult.data, path);
} }

View File

@@ -109,27 +109,40 @@ export function getEffectiveRESTRequest(
key: "Authorization", key: "Authorization",
value: `Basic ${btoa(`${username}:${password}`)}`, value: `Basic ${btoa(`${username}:${password}`)}`,
}); });
} else if ( } else if (request.auth.authType === "bearer") {
request.auth.authType === "bearer" ||
request.auth.authType === "oauth-2"
) {
effectiveFinalHeaders.push({ effectiveFinalHeaders.push({
active: true, active: true,
key: "Authorization", key: "Authorization",
value: `Bearer ${parseTemplateString( value: `Bearer ${parseTemplateString(request.auth.token, envVariables)}`,
request.auth.token,
envVariables
)}`,
}); });
} else if (request.auth.authType === "oauth-2") {
const { addTo } = request.auth;
if (addTo === "HEADERS") {
effectiveFinalHeaders.push({
active: true,
key: "Authorization",
value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, envVariables)}`,
});
} else if (addTo === "QUERY_PARAMS") {
effectiveFinalParams.push({
active: true,
key: "access_token",
value: parseTemplateString(
request.auth.grantTypeInfo.token,
envVariables
),
});
}
} else if (request.auth.authType === "api-key") { } else if (request.auth.authType === "api-key") {
const { key, value, addTo } = request.auth; const { key, value, addTo } = request.auth;
if (addTo === "Headers") { if (addTo === "HEADERS") {
effectiveFinalHeaders.push({ effectiveFinalHeaders.push({
active: true, active: true,
key: parseTemplateString(key, envVariables), key: parseTemplateString(key, envVariables),
value: parseTemplateString(value, envVariables), value: parseTemplateString(value, envVariables),
}); });
} else if (addTo === "Query params") { } else if (addTo === "QUERY_PARAMS") {
effectiveFinalParams.push({ effectiveFinalParams.push({
active: true, active: true,
key: parseTemplateString(key, envVariables), key: parseTemplateString(key, envVariables),

View File

@@ -41,10 +41,10 @@ const processVariables = (variable: Environment["variables"][number]) => {
...variable, ...variable,
value: value:
"value" in variable ? variable.value : process.env[variable.key] || "", "value" in variable ? variable.value : process.env[variable.key] || "",
} };
} }
return variable return variable;
} };
/** /**
* Processes given envs, which includes processing each variable in global * Processes given envs, which includes processing each variable in global
@@ -56,10 +56,10 @@ const processEnvs = (envs: HoppEnvs) => {
const processedEnvs = { const processedEnvs = {
global: envs.global.map(processVariables), global: envs.global.map(processVariables),
selected: envs.selected.map(processVariables), selected: envs.selected.map(processVariables),
} };
return processedEnvs return processedEnvs;
} };
/** /**
* Transforms given request data to request-config used by request-runner to * Transforms given request data to request-config used by request-runner to
@@ -70,7 +70,7 @@ const processEnvs = (envs: HoppEnvs) => {
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => { export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
const config: RequestConfig = { const config: RequestConfig = {
supported: true, supported: true,
displayUrl: req.effectiveFinalDisplayURL displayUrl: req.effectiveFinalDisplayURL,
}; };
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest; const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
const reqParams = finalParams(req); const reqParams = finalParams(req);
@@ -131,6 +131,7 @@ export const requestRunner =
let status: number; let status: number;
const baseResponse = await axios(requestConfig); const baseResponse = await axios(requestConfig);
const { config } = baseResponse; const { config } = baseResponse;
// PR-COMMENT: type error
const runnerResponse: RequestRunnerResponse = { const runnerResponse: RequestRunnerResponse = {
...baseResponse, ...baseResponse,
endpoint: getRequest.endpoint(config.url), endpoint: getRequest.endpoint(config.url),
@@ -257,10 +258,13 @@ export const processRequest =
let updatedEnvs = <HoppEnvs>{}; let updatedEnvs = <HoppEnvs>{};
// Fetch values for secret environment variables from system environment // Fetch values for secret environment variables from system environment
const processedEnvs = processEnvs(envs) const processedEnvs = processEnvs(envs);
// Executing pre-request-script // Executing pre-request-script
const preRequestRes = await preRequestScriptRunner(request, processedEnvs)(); const preRequestRes = await preRequestScriptRunner(
request,
processedEnvs
)();
if (E.isLeft(preRequestRes)) { if (E.isLeft(preRequestRes)) {
printPreRequestRunner.fail(); printPreRequestRunner.fail();
@@ -347,7 +351,7 @@ export const processRequest =
*/ */
export const preProcessRequest = ( export const preProcessRequest = (
request: HoppRESTRequest, request: HoppRESTRequest,
collection: HoppCollection, collection: HoppCollection
): HoppRESTRequest => { ): HoppRESTRequest => {
const tempRequest = Object.assign({}, request); const tempRequest = Object.assign({}, request);
const { headers: parentHeaders, auth: parentAuth } = collection; const { headers: parentHeaders, auth: parentAuth } = collection;
@@ -372,8 +376,10 @@ export const preProcessRequest = (
// Filter out header entries present in the parent (folder/collection) under the same name // Filter out header entries present in the parent (folder/collection) under the same name
// This ensures the child headers take precedence over the parent headers // This ensures the child headers take precedence over the parent headers
const filteredEntries = parentHeaders.filter((parentHeaderEntries) => { const filteredEntries = parentHeaders.filter((parentHeaderEntries) => {
return !tempRequest.headers.some((reqHeaderEntries) => reqHeaderEntries.key === parentHeaderEntries.key) return !tempRequest.headers.some(
}) (reqHeaderEntries) => reqHeaderEntries.key === parentHeaderEntries.key
);
});
tempRequest.headers.push(...filteredEntries); tempRequest.headers.push(...filteredEntries);
} else if (!tempRequest.headers) { } else if (!tempRequest.headers) {
tempRequest.headers = []; tempRequest.headers = [];

View File

@@ -10,6 +10,9 @@ module.exports = {
parserOptions: { parserOptions: {
sourceType: "module", sourceType: "module",
requireConfigFile: false, requireConfigFile: false,
ecmaFeatures: {
jsx: false,
},
}, },
extends: [ extends: [
"@vue/typescript/recommended", "@vue/typescript/recommended",

View File

@@ -563,12 +563,22 @@ details[open] summary .indicator {
.env-highlight { .env-highlight {
@apply text-accentContrast; @apply text-accentContrast;
&.env-found { &.request-variable-highlight {
@apply bg-accentDark; @apply bg-amber-500;
@apply hover:bg-accent; @apply hover:bg-amber-600;
} }
&.env-not-found { &.environment-variable-highlight {
@apply bg-green-500;
@apply hover:bg-green-600;
}
&.global-variable-highlight {
@apply bg-blue-500;
@apply hover:bg-blue-600;
}
&.environment-not-found-highlight {
@apply bg-red-500; @apply bg-red-500;
@apply hover:bg-red-600; @apply hover:bg-red-600;
} }

View File

@@ -27,6 +27,7 @@
"hide_secret": "Hide secret", "hide_secret": "Hide secret",
"label": "Label", "label": "Label",
"learn_more": "Learn more", "learn_more": "Learn more",
"download_here": "Download here",
"less": "Less", "less": "Less",
"more": "More", "more": "More",
"new": "New", "new": "New",
@@ -103,8 +104,10 @@
"auth": { "auth": {
"account_exists": "Account exists with different credential - Login to link both accounts", "account_exists": "Account exists with different credential - Login to link both accounts",
"all_sign_in_options": "All sign in options", "all_sign_in_options": "All sign in options",
"continue_with_auth_provider": "Continue with {provider}",
"continue_with_email": "Continue with Email", "continue_with_email": "Continue with Email",
"continue_with_github": "Continue with GitHub", "continue_with_github": "Continue with GitHub",
"continue_with_github_enterprise": "Continue with GitHub Enterprise",
"continue_with_google": "Continue with Google", "continue_with_google": "Continue with Google",
"continue_with_microsoft": "Continue with Microsoft", "continue_with_microsoft": "Continue with Microsoft",
"email": "Email", "email": "Email",
@@ -137,9 +140,30 @@
"redirect_no_token_endpoint": "No Token Endpoint Defined", "redirect_no_token_endpoint": "No Token Endpoint Defined",
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
"something_went_wrong_on_token_generation": "Something went wrong on token generation", "something_went_wrong_on_token_generation": "Something went wrong on token generation",
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed",
"grant_type": "Grant Type",
"grant_type_auth_code": "Authorization Code",
"token_fetched_successfully": "Token fetched successfully",
"token_fetch_failed": "Failed to fetch token",
"validation_failed": "Validation Failed, please check the form fields",
"label_authorization_endpoint": "Authorization Endpoint",
"label_client_id": "Client ID",
"label_client_secret": "Client Secret",
"label_code_challenge": "Code Challenge",
"label_code_challenge_method": "Code Challenge Method",
"label_code_verifier": "Code Verifier",
"label_scopes": "Scopes",
"label_token_endpoint": "Token Endpoint",
"label_use_pkce": "Use PKCE",
"label_implicit": "Implicit",
"label_password": "Password",
"label_username": "Username",
"label_auth_code": "Authorization Code",
"label_client_credentials": "Client Credentials"
}, },
"pass_key_by": "Pass by", "pass_key_by": "Pass by",
"pass_by_query_params_label": "Query Parameters",
"pass_by_headers_label": "Headers",
"password": "Password", "password": "Password",
"save_to_inherit": "Please save this request in any collection to inherit the authorization", "save_to_inherit": "Please save this request in any collection to inherit the authorization",
"token": "Token", "token": "Token",
@@ -151,10 +175,11 @@
"different_parent": "Cannot reorder collection with different parent", "different_parent": "Cannot reorder collection with different parent",
"edit": "Edit Collection", "edit": "Edit Collection",
"import_or_create": "Import or create a collection", "import_or_create": "Import or create a collection",
"import_collection":"Import Collection",
"invalid_name": "Please provide a name for the collection", "invalid_name": "Please provide a name for the collection",
"invalid_root_move": "Collection already in the root", "invalid_root_move": "Collection already in the root",
"moved": "Moved Successfully", "moved": "Moved Successfully",
"my_collections": "My Collections", "my_collections": "Personal Collections",
"name": "My New Collection", "name": "My New Collection",
"name_length_insufficient": "Collection name should be at least 3 characters long", "name_length_insufficient": "Collection name should be at least 3 characters long",
"new": "New Collection", "new": "New Collection",
@@ -166,14 +191,12 @@
"save_as": "Save as", "save_as": "Save as",
"save_to_collection": "Save to Collection", "save_to_collection": "Save to Collection",
"select": "Select a Collection", "select": "Select a Collection",
"select_location": "Select location", "select_location": "Select location"
"select_team": "Select a team",
"team_collections": "Team Collections"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?", "close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this workspace?",
"logout": "Are you sure you want to logout?", "logout": "Are you sure you want to logout?",
"remove_collection": "Are you sure you want to permanently delete this collection?", "remove_collection": "Are you sure you want to permanently delete this collection?",
"remove_environment": "Are you sure you want to permanently delete this environment?", "remove_environment": "Are you sure you want to permanently delete this environment?",
@@ -181,7 +204,7 @@
"remove_history": "Are you sure you want to permanently delete all history?", "remove_history": "Are you sure you want to permanently delete all history?",
"remove_request": "Are you sure you want to permanently delete this request?", "remove_request": "Are you sure you want to permanently delete this request?",
"remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_shared_request": "Are you sure you want to permanently delete this shared request?",
"remove_team": "Are you sure you want to delete this team?", "remove_team": "Are you sure you want to delete this workspace?",
"remove_telemetry": "Are you sure you want to opt-out of Telemetry?", "remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
@@ -234,18 +257,19 @@
"headers": "This request does not have any headers", "headers": "This request does not have any headers",
"history": "History is empty", "history": "History is empty",
"invites": "Invite list is empty", "invites": "Invite list is empty",
"members": "Team is empty", "members": "Workspace is empty",
"parameters": "This request does not have any parameters", "parameters": "This request does not have any parameters",
"pending_invites": "There are no pending invites for this team", "pending_invites": "There are no pending invites for this workspace",
"profile": "Login to view your profile", "profile": "Login to view your profile",
"protocols": "Protocols are empty", "protocols": "Protocols are empty",
"request_variables": "This request does not have any request variables",
"schema": "Connect to a GraphQL endpoint to view schema", "schema": "Connect to a GraphQL endpoint to view schema",
"secret_environments": "Secrets are not synced to Hoppscotch", "secret_environments": "Secrets are not synced to Hoppscotch",
"shared_requests": "Shared requests are empty", "shared_requests": "Shared requests are empty",
"shared_requests_logout": "Login to view your shared requests or create a new one", "shared_requests_logout": "Login to view your shared requests or create a new one",
"subscription": "Subscriptions are empty", "subscription": "Subscriptions are empty",
"team_name": "Team name empty", "team_name": "Workspace name empty",
"teams": "You don't belong to any teams", "teams": "You don't belong to any workspaces",
"tests": "There are no tests for this request" "tests": "There are no tests for this request"
}, },
"environment": { "environment": {
@@ -262,7 +286,7 @@
"import_or_create": "Import or create a environment", "import_or_create": "Import or create a environment",
"invalid_name": "Please provide a name for the environment", "invalid_name": "Please provide a name for the environment",
"list": "Environment variables", "list": "Environment variables",
"my_environments": "My Environments", "my_environments": "Personal Environments",
"name": "Name", "name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "New Environment", "new": "New Environment",
@@ -277,12 +301,12 @@
"select": "Select environment", "select": "Select environment",
"set": "Set environment", "set": "Set environment",
"set_as_environment": "Set as environment", "set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Workspace Environments",
"title": "Environments", "title": "Environments",
"updated": "Environment updated", "updated": "Environment updated",
"value": "Value", "value": "Value",
"variable": "Variable", "variable": "Variable",
"variables":"Variables", "variables": "Variables",
"variable_list": "Variable List" "variable_list": "Variable List"
}, },
"error": { "error": {
@@ -292,8 +316,9 @@
"check_how_to_add_origin": "Check how you can add an origin", "check_how_to_add_origin": "Check how you can add an origin",
"curl_invalid_format": "cURL is not formatted properly", "curl_invalid_format": "cURL is not formatted properly",
"danger_zone": "Danger zone", "danger_zone": "Danger zone",
"delete_account": "Your account is currently an owner in these teams:", "delete_account": "Your account is currently an owner in these workspaces:",
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.",
"empty_profile_name": "Profile name cannot be empty",
"empty_req_name": "Empty Request Name", "empty_req_name": "Empty Request Name",
"f12_details": "(F12 for details)", "f12_details": "(F12 for details)",
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again", "gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
@@ -313,6 +338,7 @@
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"please_install_extension": "Please install the extension and add origin to the extension.", "please_install_extension": "Please install the extension and add origin to the extension.",
"proxy_error": "Proxy error", "proxy_error": "Proxy error",
"same_profile_name": "Updated profile name is same as the current profile name",
"script_fail": "Could not execute pre-request script", "script_fail": "Could not execute pre-request script",
"something_went_wrong": "Something went wrong", "something_went_wrong": "Something went wrong",
"test_script_fail": "Could not execute post-request script", "test_script_fail": "Could not execute post-request script",
@@ -348,7 +374,8 @@
"mutations": "Mutations", "mutations": "Mutations",
"schema": "Schema", "schema": "Schema",
"subscriptions": "Subscriptions", "subscriptions": "Subscriptions",
"switch_connection": "Switch connection" "switch_connection": "Switch connection",
"url_placeholder": "Enter a GraphQL endpoint URL"
}, },
"graphql_collections": { "graphql_collections": {
"title": "GraphQL Collections" "title": "GraphQL Collections"
@@ -395,8 +422,8 @@
"from_insomnia_description": "Import from Insomnia collection", "from_insomnia_description": "Import from Insomnia collection",
"from_json": "Import from Hoppscotch", "from_json": "Import from Hoppscotch",
"from_json_description": "Import from Hoppscotch collection file", "from_json_description": "Import from Hoppscotch collection file",
"from_my_collections": "Import from My Collections", "from_my_collections": "Import from Personal Collections",
"from_my_collections_description": "Import from My Collections file", "from_my_collections_description": "Import from Personal Collections file",
"from_openapi": "Import from OpenAPI", "from_openapi": "Import from OpenAPI",
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)", "from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
"from_postman": "Import from Postman", "from_postman": "Import from Postman",
@@ -428,7 +455,7 @@
"not_found": "Environment variable “{environment}” not found." "not_found": "Environment variable “{environment}” not found."
}, },
"header": { "header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." "cookie": "The browser doesn't allow Hoppscotch to set Cookie Headers. Please use Authorization Headers instead. However, our Hoppscotch Desktop App is live now and supports Cookies."
}, },
"response": { "response": {
"401_error": "Please check your authentication credentials.", "401_error": "Please check your authentication credentials.",
@@ -513,7 +540,7 @@
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.", "email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
"no_permission": "You do not have permission to perform this action.", "no_permission": "You do not have permission to perform this action.",
"owner": "Owner", "owner": "Owner",
"owner_description": "Owners can add, edit, and delete requests, collections and team members.", "owner_description": "Owners can add, edit, and delete requests, collections and workspace members.",
"roles": "Roles", "roles": "Roles",
"roles_description": "Roles are used to control access to the shared collections.", "roles_description": "Roles are used to control access to the shared collections.",
"updated": "Profile updated", "updated": "Profile updated",
@@ -560,6 +587,7 @@
"raw_body": "Raw Request Body", "raw_body": "Raw Request Body",
"rename": "Rename Request", "rename": "Rename Request",
"renamed": "Request renamed", "renamed": "Request renamed",
"request_variables": "Request variables",
"run": "Run", "run": "Run",
"save": "Save", "save": "Save",
"save_as": "Save as", "save_as": "Save as",
@@ -571,6 +599,7 @@
"title": "Request", "title": "Request",
"type": "Request type", "type": "Request type",
"url": "URL", "url": "URL",
"url_placeholder": "Enter a URL or paste a cURL command",
"variables": "Variables", "variables": "Variables",
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
@@ -817,12 +846,19 @@
"title": "Tabs" "title": "Tabs"
}, },
"workspace": { "workspace": {
"delete": "Delete current team", "delete": "Delete current workspace",
"edit": "Edit current team", "edit": "Edit current workspace",
"invite": "Invite people to team", "invite": "Invite people to workspace",
"new": "Create new team", "new": "Create new workspace",
"switch_to_personal": "Switch to your personal workspace", "switch_to_personal": "Switch to your personal workspace",
"title": "Teams" "title": "Workspaces"
},
"phrases":{
"try": "Try",
"import_collections": "Import collections",
"create_environment": "Create environment",
"create_workspace": "Create workspace",
"share_request": "Share request"
} }
}, },
"sse": { "sse": {
@@ -879,7 +915,6 @@
"forum": "Ask questions and get answers", "forum": "Ask questions and get answers",
"github": "Follow us on Github", "github": "Follow us on Github",
"shortcuts": "Browse app faster", "shortcuts": "Browse app faster",
"team": "Get in touch with the team",
"title": "Support", "title": "Support",
"twitter": "Follow us on Twitter" "twitter": "Follow us on Twitter"
}, },
@@ -910,60 +945,60 @@
"websocket": "WebSocket" "websocket": "WebSocket"
}, },
"team": { "team": {
"already_member": "You are already a member of this team. Contact your team owner.", "already_member": "You are already a member of this workspace. Contact your workspace owner.",
"create_new": "Create new team", "create_new": "Create new workspace",
"deleted": "Team deleted", "deleted": "Workspace deleted",
"edit": "Edit Team", "edit": "Edit Workspace",
"email": "E-mail", "email": "E-mail",
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.", "email_do_not_match": "Email doesn't match with your account details. Contact your workspace owner.",
"exit": "Exit Team", "exit": "Exit Workspace",
"exit_disabled": "Only owner cannot exit the team", "exit_disabled": "Only owner cannot exit the workspace",
"failed_invites": "Failed invites", "failed_invites": "Failed invites",
"invalid_coll_id": "Invalid collection ID", "invalid_coll_id": "Invalid collection ID",
"invalid_email_format": "Email format is invalid", "invalid_email_format": "Email format is invalid",
"invalid_id": "Invalid team ID. Contact your team owner.", "invalid_id": "Invalid workspace ID. Contact your workspace owner.",
"invalid_invite_link": "Invalid invite link", "invalid_invite_link": "Invalid invite link",
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.", "invalid_invite_link_description": "The link you followed is invalid. Contact your workspace owner.",
"invalid_member_permission": "Please provide a valid permission to the team member", "invalid_member_permission": "Please provide a valid permission to the workspace member",
"invite": "Invite", "invite": "Invite",
"invite_more": "Invite more", "invite_more": "Invite more",
"invite_tooltip": "Invite people to this workspace", "invite_tooltip": "Invite people to this workspace",
"invited_to_team": "{owner} invited you to join {team}", "invited_to_team": "{owner} invited you to join {workspace}",
"join": "Invitation accepted", "join": "Invitation accepted",
"join_beta": "Join the beta program to access teams.", "join_team": "Join {workspace}",
"join_team": "Join {team}", "joined_team": "You have joined {workspace}",
"joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this workspace",
"joined_team_description": "You are now a member of this team", "left": "You left the workspace",
"left": "You left the team",
"login_to_continue": "Login to continue", "login_to_continue": "Login to continue",
"login_to_continue_description": "You need to be logged in to join a team.", "login_to_continue_description": "You need to be logged in to join a workspace.",
"logout_and_try_again": "Logout and sign in with another account", "logout_and_try_again": "Logout and sign in with another account",
"member_has_invite": "This email ID already has an invite. Contact your team owner.", "member_has_invite": "This email ID already has an invite. Contact your workspace owner.",
"member_not_found": "Member not found. Contact your team owner.", "member_not_found": "Member not found. Contact your workspace owner.",
"member_removed": "User removed", "member_removed": "User removed",
"member_role_updated": "User roles updated", "member_role_updated": "User roles updated",
"members": "Members", "members": "Members",
"more_members": "+{count} more", "more_members": "+{count} more",
"name_length_insufficient": "Team name should be at least 6 characters long", "name_length_insufficient": "Workspace name should be at least 6 characters long",
"name_updated": "Team name updated", "name_updated": "Workspace name updated",
"new": "New Team", "new": "New Workspace",
"new_created": "New team created", "new_created": "New workspace created",
"new_name": "My New Team", "new_name": "My New Workspace",
"no_access": "You do not have edit access to this team", "no_access": "You do not have edit access to this workspace",
"no_invite_found": "Invitation not found. Contact your team owner.", "no_invite_found": "Invitation not found. Contact your workspace owner.",
"no_request_found": "Request not found.", "no_request_found": "Request not found.",
"not_found": "Team not found. Contact your team owner.", "not_found": "Workspace not found. Contact your workspace owner.",
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.", "not_valid_viewer": "You are not a valid viewer. Contact your workspace owner.",
"parent_coll_move": "Cannot move collection to a child collection", "parent_coll_move": "Cannot move collection to a child collection",
"pending_invites": "Pending invites", "pending_invites": "Pending invites",
"permissions": "Permissions", "permissions": "Permissions",
"same_target_destination": "Same target and destination", "same_target_destination": "Same target and destination",
"saved": "Team saved", "saved": "Workspace saved",
"select_a_team": "Select a team", "select_a_team": "Select a workspace",
"success_invites": "Success invites", "success_invites": "Success invites",
"title": "Teams", "title": "Workspaces",
"we_sent_invite_link": "We sent an invite link to all invitees!", "we_sent_invite_link": "We sent an invite link to all invitees!",
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace.",
"search_title": "Team Requests"
}, },
"team_environment": { "team_environment": {
"deleted": "Environment Deleted", "deleted": "Environment Deleted",
@@ -989,8 +1024,14 @@
}, },
"workspace": { "workspace": {
"change": "Change workspace", "change": "Change workspace",
"personal": "My Workspace", "personal": "Personal Workspace",
"team": "Team Workspace", "other_workspaces": "My Workspaces",
"team": "Workspace",
"title": "Workspaces" "title": "Workspaces"
},
"site_protection": {
"login_to_continue": "Login to continue",
"login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.",
"error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status"
} }
} }

View File

@@ -32,8 +32,8 @@
"no": "No", "no": "No",
"open_workspace": "Abrir espacio de trabajo", "open_workspace": "Abrir espacio de trabajo",
"paste": "Pegar", "paste": "Pegar",
"prettify": "Embellecer", "prettify": "Formatear",
"properties": "Properties", "properties": "Propiedades",
"remove": "Eliminar", "remove": "Eliminar",
"rename": "Rename", "rename": "Rename",
"restore": "Restaurar", "restore": "Restaurar",
@@ -63,7 +63,7 @@
"contact_us": "Contáctanos", "contact_us": "Contáctanos",
"cookies": "Cookies", "cookies": "Cookies",
"copy": "Copiar", "copy": "Copiar",
"copy_interface_type": "Copy interface type", "copy_interface_type": "Copiar tipo de interfaz",
"copy_user_id": "Copiar token de autenticación de usuario", "copy_user_id": "Copiar token de autenticación de usuario",
"developer_option": "Opciones para desarrolladores", "developer_option": "Opciones para desarrolladores",
"developer_option_description": "Herramientas para desarrolladores que ayudan en el desarrollo y mantenimiento de Hoppscotch.", "developer_option_description": "Herramientas para desarrolladores que ayudan en el desarrollo y mantenimiento de Hoppscotch.",
@@ -80,14 +80,14 @@
"name": "Hoppscotch", "name": "Hoppscotch",
"new_version_found": "Se ha encontrado una nueva versión. Recarga la página para usarla.", "new_version_found": "Se ha encontrado una nueva versión. Recarga la página para usarla.",
"open_in_hoppscotch": "Open in Hoppscotch", "open_in_hoppscotch": "Open in Hoppscotch",
"options": "Options", "options": "Opciones",
"proxy_privacy_policy": "Política de privacidad de proxy", "proxy_privacy_policy": "Política de privacidad de proxy",
"reload": "Recargar", "reload": "Recargar",
"search": "Buscar", "search": "Buscar",
"share": "Compartir", "share": "Compartir",
"shortcuts": "Atajos", "shortcuts": "Atajos",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", "social_description": "Síguenos en redes sociales para estar al día de las últimas noticias, actualizaciones y lanzamientos.",
"social_links": "Social links", "social_links": "Redes sociales",
"spotlight": "Destacar", "spotlight": "Destacar",
"status": "Estado", "status": "Estado",
"status_description": "Comprobar el estado del sitio web", "status_description": "Comprobar el estado del sitio web",
@@ -119,27 +119,27 @@
}, },
"authorization": { "authorization": {
"generate_token": "Generar token", "generate_token": "Generar token",
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", "graphql_headers": "Las cabeceras de autorización se envían como parte de la carga útil de connection_init",
"include_in_url": "Incluir en la URL", "include_in_url": "Incluir en la URL",
"inherited_from": "Inherited from {auth} from Parent Collection {collection} ", "inherited_from": "Heredado {auth} de colección padre {collection} ",
"learn": "Aprender", "learn": "Aprender",
"oauth": { "oauth": {
"redirect_auth_server_returned_error": "Auth Server returned an error state", "redirect_auth_server_returned_error": "El servidor de autenticación ha devuelto un estado de error",
"redirect_auth_token_request_failed": "Request to get the auth token failed", "redirect_auth_token_request_failed": "Fallo en la solicitud de token de autentificación",
"redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", "redirect_auth_token_request_invalid_response": "Respuesta no válida del punto final de Token al solicitar un token de autentificación",
"redirect_invalid_state": "Invalid State value present in the redirect", "redirect_invalid_state": "Valor de estado no válido presente en la redirección",
"redirect_no_auth_code": "No Authorization Code present in the redirect", "redirect_no_auth_code": "No hay código de autorización en la redirección",
"redirect_no_client_id": "No Client ID defined", "redirect_no_client_id": "No se ha definido el ID de cliente",
"redirect_no_client_secret": "No Client Secret Defined", "redirect_no_client_secret": "No se ha definido ningún ID secreto de cliente",
"redirect_no_code_verifier": "No Code Verifier Defined", "redirect_no_code_verifier": "No se ha definido ningún verificador de códigos",
"redirect_no_token_endpoint": "No Token Endpoint Defined", "redirect_no_token_endpoint": "No se ha definido ningún punto final de token",
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_oauth_redirect": "Algo ha ido mal durante la redirección OAuth",
"something_went_wrong_on_token_generation": "Something went wrong on token generation", "something_went_wrong_on_token_generation": "Algo salió mal en la generación del token",
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" "token_generation_oidc_discovery_failed": "Fallo en la generación del token: OpenID Connect Discovery Failed"
}, },
"pass_key_by": "Pasar por", "pass_key_by": "Pasar por",
"password": "Contraseña", "password": "Contraseña",
"save_to_inherit": "Please save this request in any collection to inherit the authorization", "save_to_inherit": "Por favor, guarda esta solicitud en cualquier colección para heredar la autorización",
"token": "Token", "token": "Token",
"type": "Tipo de autorización", "type": "Tipo de autorización",
"username": "Nombre de usuario" "username": "Nombre de usuario"
@@ -148,7 +148,7 @@
"created": "Colección creada", "created": "Colección creada",
"different_parent": "No se puede reordenar la colección con un padre diferente", "different_parent": "No se puede reordenar la colección con un padre diferente",
"edit": "Editar colección", "edit": "Editar colección",
"import_or_create": "Import or create a collection", "import_or_create": "Importar o crear una colección",
"invalid_name": "Proporciona un nombre válido para la colección.", "invalid_name": "Proporciona un nombre válido para la colección.",
"invalid_root_move": "La colección ya está en la raíz", "invalid_root_move": "La colección ya está en la raíz",
"moved": "Movido con éxito", "moved": "Movido con éxito",
@@ -157,20 +157,20 @@
"name_length_insufficient": "El nombre de la colección debe tener al menos 3 caracteres", "name_length_insufficient": "El nombre de la colección debe tener al menos 3 caracteres",
"new": "Nueva colección", "new": "Nueva colección",
"order_changed": "Orden de colección actualizada", "order_changed": "Orden de colección actualizada",
"properties": "Collection Properties", "properties": "Propiedades de la colección",
"properties_updated": "Collection Properties Updated", "properties_updated": "Propiedades de la colección actualizadas",
"renamed": "Colección renombrada", "renamed": "Colección renombrada",
"request_in_use": "Solicitud en uso", "request_in_use": "Solicitud en uso",
"save_as": "Guardar como", "save_as": "Guardar como",
"save_to_collection": "Save to Collection", "save_to_collection": "Guardar en la colección",
"select": "Seleccionar colección", "select": "Seleccionar colección",
"select_location": "Seleccionar ubicación", "select_location": "Seleccionar ubicación",
"select_team": "Seleccionar equipo", "select_team": "Seleccionar equipo",
"team_collections": "Colecciones de equipos" "team_collections": "Colecciones de equipos"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?", "close_unsaved_tab": "¿Seguro que quieres cerrar esta pestaña?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "close_unsaved_tabs": "¿Estás seguro de que quieres cerrar todas las pestañas? {count} pestañas no guardadas se perderán.",
"exit_team": "¿Estás seguro de que quieres dejar este equipo?", "exit_team": "¿Estás seguro de que quieres dejar este equipo?",
"logout": "¿Estás seguro de que deseas cerrar la sesión?", "logout": "¿Estás seguro de que deseas cerrar la sesión?",
"remove_collection": "¿Estás seguro de que deseas eliminar esta colección de forma permanente?", "remove_collection": "¿Estás seguro de que deseas eliminar esta colección de forma permanente?",
@@ -178,7 +178,7 @@
"remove_folder": "¿Estás seguro de que deseas eliminar esta carpeta de forma permanente?", "remove_folder": "¿Estás seguro de que deseas eliminar esta carpeta de forma permanente?",
"remove_history": "¿Estás seguro de que deseas eliminar todo el historial de forma permanente?", "remove_history": "¿Estás seguro de que deseas eliminar todo el historial de forma permanente?",
"remove_request": "¿Estás seguro de que deseas eliminar esta solicitud de forma permanente?", "remove_request": "¿Estás seguro de que deseas eliminar esta solicitud de forma permanente?",
"remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_shared_request": "¿Estás seguro de que quieres eliminar definitivamente esta solicitud compartida?",
"remove_team": "¿Estás seguro de que deseas eliminar este equipo?", "remove_team": "¿Estás seguro de que deseas eliminar este equipo?",
"remove_telemetry": "¿Estás seguro de que deseas darse de baja de la telemetría?", "remove_telemetry": "¿Estás seguro de que deseas darse de baja de la telemetría?",
"request_change": "¿Estás seguro de que deseas descartar la solicitud actual, los cambios no guardados se perderán.", "request_change": "¿Estás seguro de que deseas descartar la solicitud actual, los cambios no guardados se perderán.",
@@ -186,35 +186,35 @@
"sync": "¿Estás seguro de que deseas sincronizar este espacio de trabajo?" "sync": "¿Estás seguro de que deseas sincronizar este espacio de trabajo?"
}, },
"context_menu": { "context_menu": {
"add_parameters": "Add to parameters", "add_parameters": "Añadir a parámetros",
"open_request_in_new_tab": "Open request in new tab", "open_request_in_new_tab": "Abrir solicitud en una nueva pestaña",
"set_environment_variable": "Set as variable" "set_environment_variable": "Establecer como variable"
}, },
"cookies": { "cookies": {
"modal": { "modal": {
"cookie_expires": "Expires", "cookie_expires": "Expira en",
"cookie_name": "Name", "cookie_name": "Nombre",
"cookie_path": "Path", "cookie_path": "Ruta",
"cookie_string": "Cookie string", "cookie_string": "Cookies",
"cookie_value": "Value", "cookie_value": "Valor",
"empty_domain": "Domain is empty", "empty_domain": "Dominio vacio",
"empty_domains": "Domain list is empty", "empty_domains": "No hay dominios",
"enter_cookie_string": "Enter cookie string", "enter_cookie_string": "Introducir cookies",
"interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", "interceptor_no_support": "El interceptor seleccionado actualmente no admite cookies. Seleccione otro interceptor e inténtelo de nuevo.",
"managed_tab": "Managed", "managed_tab": "Gestionado",
"new_domain_name": "New domain name", "new_domain_name": "Nuevo nombre de dominio",
"no_cookies_in_domain": "No cookies set for this domain", "no_cookies_in_domain": "No hay cookies para este dominio",
"raw_tab": "Raw", "raw_tab": "Sin procesar",
"set": "Set a cookie" "set": "Establecer una cookie"
} }
}, },
"count": { "count": {
"header": "Encabezado {count}", "header": "{count} encabezado(s)",
"message": "Mensaje {count}", "message": "{count} mensaje(s)",
"parameter": "Parámetro {count}", "parameter": "{count} parámetro(s)",
"protocol": "Protocolo {count}", "protocol": "{count} protocolo(s)",
"value": "Valor {cuenta}", "value": "{cuenta} valor(es)",
"variable": "Variable {count}" "variable": "{count} variable(es)"
}, },
"documentation": { "documentation": {
"generate": "Generar documentación", "generate": "Generar documentación",
@@ -223,26 +223,26 @@
"empty": { "empty": {
"authorization": "Esta solicitud no utiliza ninguna autorización.", "authorization": "Esta solicitud no utiliza ninguna autorización.",
"body": "Esta solicitud no tiene cuerpo", "body": "Esta solicitud no tiene cuerpo",
"collection": "La colección está vacía", "collection": "Colección vacía",
"collections": "Las colecciones están vacías", "collections": "No hay colecciones",
"documentation": "Conectarse a un punto final de GraphQL para ver la documentación", "documentation": "Conectarse a un punto final de GraphQL para ver la documentación",
"endpoint": "El punto final no puede estar vacío", "endpoint": "El punto final no puede estar vacío",
"environments": "Los entornos están vacíos", "environments": "No hay entornos",
"folder": "La carpeta está vacía", "folder": "Carpeta vacía",
"headers": "Esta solicitud no tiene encabezados", "headers": "Esta solicitud no tiene encabezados",
"history": "El historial está vacío", "history": "No hay historial",
"invites": "La lista de invitados está vacía", "invites": "Lista de invitados vacía",
"members": "El equipo está vacío", "members": "No hay miembros en el equipo",
"parameters": "Esta solicitud no tiene ningún parámetro", "parameters": "Esta solicitud no tiene ningún parámetro",
"pending_invites": "No hay invitaciones pendientes para este equipo", "pending_invites": "No hay invitaciones pendientes para este equipo",
"profile": "Iniciar sesión para ver tu perfil", "profile": "Iniciar sesión para ver tu perfil",
"protocols": "Los protocolos están vacíos", "protocols": "No hay protocolos",
"schema": "Conectarse a un punto final de GraphQL", "schema": "Conectarse a un punto final de GraphQL",
"shared_requests": "Shared requests are empty", "shared_requests": "No hay solicitudes compartidas",
"shared_requests_logout": "Login to view your shared requests or create a new one", "shared_requests_logout": "Iniciar sesión para ver sus solicitudes compartidas o crear una nueva",
"subscription": "Subscriptions are empty", "subscription": "No hay suscripciones",
"team_name": "Nombre del equipo vacío", "team_name": "Nombre del equipo vacío",
"teams": "Los equipos están vacíos", "teams": "No hay equipos",
"tests": "No hay pruebas para esta solicitud", "tests": "No hay pruebas para esta solicitud",
"shortcodes": "Aún no se han creado Shortcodes" "shortcodes": "Aún no se han creado Shortcodes"
}, },
@@ -250,41 +250,41 @@
"add_to_global": "Añadir a Global", "add_to_global": "Añadir a Global",
"added": "Adición al entorno", "added": "Adición al entorno",
"create_new": "Crear un nuevo entorno", "create_new": "Crear un nuevo entorno",
"created": "Environment created", "created": "Entorno creado",
"deleted": "Eliminar el entorno", "deleted": "Eliminar el entorno",
"duplicated": "Environment duplicated", "duplicated": "Entorno duplicado",
"edit": "Editar entorno", "edit": "Editar entorno",
"empty_variables": "No variables", "empty_variables": "No hay variables",
"global": "Global", "global": "Global",
"global_variables": "Global variables", "global_variables": "Variables globales",
"import_or_create": "Import or create a environment", "import_or_create": "Importar o crear un entorno",
"invalid_name": "Proporciona un nombre válido para el entorno.", "invalid_name": "Proporciona un nombre válido para el entorno.",
"list": "Environment variables", "list": "Variables de entorno",
"my_environments": "Mis entornos", "my_environments": "Mis entornos",
"name": "Name", "name": "Nombre",
"nested_overflow": "las variables de entorno anidadas están limitadas a 10 niveles", "nested_overflow": "las variables de entorno anidadas están limitadas a 10 niveles",
"new": "Nuevo entorno", "new": "Nuevo entorno",
"no_active_environment": "No active environment", "no_active_environment": "Ningún entorno activo",
"no_environment": "Sin entorno", "no_environment": "Sin entorno",
"no_environment_description": "No se ha seleccionado ningún entorno. Elije qué hacer con las siguientes variables.", "no_environment_description": "No se ha seleccionado ningún entorno. Elije qué hacer con las siguientes variables.",
"quick_peek": "Environment Quick Peek", "quick_peek": "Vistazo rápido al entorno",
"replace_with_variable": "Replace with variable", "replace_with_variable": "Sustituir por variable",
"scope": "Scope", "scope": "Ámbito",
"select": "Seleccionar entorno", "select": "Seleccionar entorno",
"set": "Set environment", "set": "Establecer entorno",
"set_as_environment": "Set as environment", "set_as_environment": "Establecer como entorno",
"team_environments": "Entornos de trabajo en equipo", "team_environments": "Entornos de trabajo en equipo",
"title": "Entornos", "title": "Entornos",
"updated": "Entorno actualizado", "updated": "Entorno actualizado",
"value": "Value", "value": "Valor",
"variable": "Variable", "variable": "Variable",
"variable_list": "Lista de variables" "variable_list": "Lista de variables"
}, },
"error": { "error": {
"authproviders_load_error": "Unable to load auth providers", "authproviders_load_error": "No se han podido cargar los proveedores de autenticación",
"browser_support_sse": "Este navegador no parece ser compatible con los eventos enviados por el servidor.", "browser_support_sse": "Este navegador no parece ser compatible con los eventos enviados por el servidor.",
"check_console_details": "Consulta el registro de la consola para obtener más detalles.", "check_console_details": "Consulta el registro de la consola para obtener más detalles.",
"check_how_to_add_origin": "Check how you can add an origin", "check_how_to_add_origin": "Comprueba cómo puede añadir un origen",
"curl_invalid_format": "cURL no está formateado correctamente", "curl_invalid_format": "cURL no está formateado correctamente",
"danger_zone": "Zona de peligro", "danger_zone": "Zona de peligro",
"delete_account": "Tu cuenta es actualmente propietaria en estos equipos:", "delete_account": "Tu cuenta es actualmente propietaria en estos equipos:",
@@ -300,13 +300,13 @@
"json_prettify_invalid_body": "No se puede aplicar embellecedor a un cuerpo inválido, resuelve errores de sintaxis json y vuelve a intentarlo", "json_prettify_invalid_body": "No se puede aplicar embellecedor a un cuerpo inválido, resuelve errores de sintaxis json y vuelve a intentarlo",
"network_error": "Parece que hay un error de red. Por favor, inténtalo de nuevo.", "network_error": "Parece que hay un error de red. Por favor, inténtalo de nuevo.",
"network_fail": "No se pudo enviar la solicitud", "network_fail": "No se pudo enviar la solicitud",
"no_collections_to_export": "No collections to export. Please create a collection to get started.", "no_collections_to_export": "No hay colecciones para exportar. Crea una colección para empezar.",
"no_duration": "Sin duración", "no_duration": "Sin duración",
"no_environments_to_export": "No environments to export. Please create an environment to get started.", "no_environments_to_export": "No hay entornos para exportar. Por favor, crea un entorno para empezar.",
"no_results_found": "No se han encontrado coincidencias", "no_results_found": "No se han encontrado coincidencias",
"page_not_found": "No se ha podido encontrar esta página", "page_not_found": "No se ha podido encontrar esta página",
"please_install_extension": "Please install the extension and add origin to the extension.", "please_install_extension": "Please install the extension and add origin to the extension.",
"proxy_error": "Proxy error", "proxy_error": "Error de proxy",
"script_fail": "No se pudo ejecutar el script de solicitud previa", "script_fail": "No se pudo ejecutar el script de solicitud previa",
"something_went_wrong": "Algo salió mal", "something_went_wrong": "Algo salió mal",
"test_script_fail": "No se ha podido ejecutar la secuencia de comandos posterior a la solicitud" "test_script_fail": "No se ha podido ejecutar la secuencia de comandos posterior a la solicitud"
@@ -314,15 +314,15 @@
"export": { "export": {
"as_json": "Exportar como JSON", "as_json": "Exportar como JSON",
"create_secret_gist": "Crear un Gist secreto", "create_secret_gist": "Crear un Gist secreto",
"failed": "Something went wrong while exporting", "failed": "Algo ha ido mal al exportar",
"gist_created": "Gist creado", "gist_created": "Gist creado",
"require_github": "Iniciar sesión con GitHub para crear un Gist secreto", "require_github": "Iniciar sesión con GitHub para crear un Gist secreto",
"title": "Exportar" "title": "Exportar"
}, },
"filter": { "filter": {
"all": "All", "all": "Todos",
"none": "None", "none": "Ninguno",
"starred": "Starred" "starred": "Destacado"
}, },
"folder": { "folder": {
"created": "Carpeta creada", "created": "Carpeta creada",
@@ -333,16 +333,16 @@
"renamed": "Carpeta renombrada" "renamed": "Carpeta renombrada"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", "connection_switch_confirm": "¿Deseas conectarte con el endpoint GraphQL más reciente?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", "connection_switch_new_url": "Al cambiar a una pestaña se desconectará de la conexión GraphQL activa. La nueva URL de conexión es",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", "connection_switch_url": "Estás conectado a un endpoint GraphQL cuya URL de conexión es",
"mutations": "Mutaciones", "mutations": "Mutaciones",
"schema": "Esquema", "schema": "Esquema",
"subscriptions": "Suscripciones", "subscriptions": "Suscripciones",
"switch_connection": "Switch connection" "switch_connection": "Cambiar conexión"
}, },
"graphql_collections": { "graphql_collections": {
"title": "GraphQL Collections" "title": "Colecciones de GraphQL"
}, },
"group": { "group": {
"time": "Tiempo", "time": "Tiempo",
@@ -355,8 +355,8 @@
}, },
"helpers": { "helpers": {
"authorization": "El encabezado de autorización se generará automáticamente cuando se envía la solicitud.", "authorization": "El encabezado de autorización se generará automáticamente cuando se envía la solicitud.",
"collection_properties_authorization": " This authorization will be set for every request in this collection.", "collection_properties_authorization": " Esta autorización se establecerá para cada solicitud de esta colección.",
"collection_properties_header": "This header will be set for every request in this collection.", "collection_properties_header": "Este encabezado se establecerá para cada solicitud de esta colección.",
"generate_documentation_first": "Generar la documentación primero", "generate_documentation_first": "Generar la documentación primero",
"network_fail": "No se puede acceder a la API. Comprueba tu conexión de red y vuelve a intentarlo.", "network_fail": "No se puede acceder a la API. Comprueba tu conexión de red y vuelve a intentarlo.",
"offline": "Parece estar desconectado. Es posible que los datos de este espacio de trabajo no estén actualizados.", "offline": "Parece estar desconectado. Es posible que los datos de este espacio de trabajo no estén actualizados.",
@@ -376,10 +376,10 @@
"import": { "import": {
"collections": "Importar colecciones", "collections": "Importar colecciones",
"curl": "Importar cURL", "curl": "Importar cURL",
"environments_from_gist": "Import From Gist", "environments_from_gist": "Importar desde Gist",
"environments_from_gist_description": "Import Hoppscotch Environments From Gist", "environments_from_gist_description": "Importar entornos Hoppscotch desde Gist",
"failed": "Importación fallida", "failed": "Importación fallida",
"from_file": "Import from File", "from_file": "Importar desde archivo",
"from_gist": "Importar desde Gist", "from_gist": "Importar desde Gist",
"from_gist_description": "Importar desde URL de Gist", "from_gist_description": "Importar desde URL de Gist",
"from_insomnia": "Importar desde Insomnia", "from_insomnia": "Importar desde Insomnia",
@@ -394,41 +394,41 @@
"from_postman_description": "Importar desde una colección de Postman", "from_postman_description": "Importar desde una colección de Postman",
"from_url": "Importar desde una URL", "from_url": "Importar desde una URL",
"gist_url": "Introduce la URL de Gist", "gist_url": "Introduce la URL de Gist",
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist", "gql_collections_from_gist_description": "Importar colecciones GraphQL desde Gist",
"hoppscotch_environment": "Hoppscotch Environment", "hoppscotch_environment": "Entorno de Hoppscotch",
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", "hoppscotch_environment_description": "Importar archivo JSON del entorno de Hoppscotch",
"import_from_url_invalid_fetch": "Couldn't get data from the url", "import_from_url_invalid_fetch": "No se han podido obtener datos de la url",
"import_from_url_invalid_file_format": "Error while importing collections", "import_from_url_invalid_file_format": "Error al importar colecciones",
"import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'", "import_from_url_invalid_type": "Tipo no admitido. Los valores aceptados son \"hoppscotch\", \"openapi\", \"postman\", \"insomnia\".",
"import_from_url_success": "Collections Imported", "import_from_url_success": "Colecciones Importadas",
"insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", "insomnia_environment_description": "Importar el entorno de Insomnia desde un archivo JSON/YAML",
"json_description": "Importar colecciones desde un archivo JSON de colecciones de Hoppscotch", "json_description": "Importar colecciones desde un archivo JSON de colecciones de Hoppscotch",
"postman_environment": "Postman Environment", "postman_environment": "Entorno de Postman",
"postman_environment_description": "Import Postman Environment from a JSON file", "postman_environment_description": "Importar entorno de Postman desde un archivo JSON",
"title": "Importar" "title": "Importar"
}, },
"inspections": { "inspections": {
"description": "Inspect possible errors", "description": "Inspeccionar posibles errores",
"environment": { "environment": {
"add_environment": "Add to Environment", "add_environment": "Añadir al Entorno",
"not_found": "Environment variable “{environment}” not found." "not_found": "No se ha encontrado la variable de entorno \"{environment}\"."
}, },
"header": { "header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." "cookie": "El navegador no permite que Hoppscotch establezca el encabezado Cookie. Mientras trabajamos en la aplicación de escritorio de Hoppscotch (próximamente), por favor utilice el encabezado de autorización en su lugar."
}, },
"response": { "response": {
"401_error": "Please check your authentication credentials.", "401_error": "Compruebe tus credenciales de autenticación.",
"404_error": "Please check your request URL and method type.", "404_error": "Compruebe la URL de su solicitud y el tipo de método.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.", "cors_error": "Por favor, comprueba tu configuración de Compartición de Recursos \"Cross-Origin\".",
"default_error": "Please check your request.", "default_error": "Por favor, comprueba tu solicitud.",
"network_error": "Please check your network connection." "network_error": "Comprueba tu conexión de red."
}, },
"title": "Inspector", "title": "Inspeccionador",
"url": { "url": {
"extension_not_installed": "Extension not installed.", "extension_not_installed": "Extensión no instalada.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", "extension_unknown_origin": "Asegúrate de haber agregado el origen del punto final de la API a la lista de Extensiones del Navegador Hoppscotch.",
"extention_enable_action": "Enable Browser Extension", "extention_enable_action": "Activar la extensión del navegador",
"extention_not_enabled": "Extension not enabled." "extention_not_enabled": "Extensión no habilitada."
} }
}, },
"layout": { "layout": {
@@ -442,10 +442,10 @@
"close_unsaved_tab": "Tienes cambios sin guardar", "close_unsaved_tab": "Tienes cambios sin guardar",
"collections": "Colecciones", "collections": "Colecciones",
"confirm": "Confirmar", "confirm": "Confirmar",
"customize_request": "Customize Request", "customize_request": "Personalizar solicitud",
"edit_request": "Editar solicitud", "edit_request": "Editar solicitud",
"import_export": "Importación y exportación", "import_export": "Importación y exportación",
"share_request": "Share Request" "share_request": "Compartir solicitud"
}, },
"mqtt": { "mqtt": {
"already_subscribed": "Ya estás suscrito a este tema.", "already_subscribed": "Ya estás suscrito a este tema.",
@@ -493,7 +493,7 @@
}, },
"profile": { "profile": {
"app_settings": "Ajustes de la aplicación", "app_settings": "Ajustes de la aplicación",
"default_hopp_displayname": "Unnamed User", "default_hopp_displayname": "Usuario anónimo",
"editor": "Editor", "editor": "Editor",
"editor_description": "Los editores pueden añadir, editar y eliminar solicitudes.", "editor_description": "Los editores pueden añadir, editar y eliminar solicitudes.",
"email_verification_mail": "Se ha enviado un correo electrónico de verificación a tu dirección de correo electrónico. Haz clic en el enlace para verificar tu dirección de correo electrónico.", "email_verification_mail": "Se ha enviado un correo electrónico de verificación a tu dirección de correo electrónico. Haz clic en el enlace para verificar tu dirección de correo electrónico.",
@@ -526,12 +526,12 @@
"enter_curl": "Ingrese cURL", "enter_curl": "Ingrese cURL",
"generate_code": "Generar código", "generate_code": "Generar código",
"generated_code": "Código generado", "generated_code": "Código generado",
"go_to_authorization_tab": "Go to Authorization tab", "go_to_authorization_tab": "Ir a la pestaña Autorización",
"go_to_body_tab": "Go to Body tab", "go_to_body_tab": "Ir a la pestaña de cuerpo",
"header_list": "Lista de encabezados", "header_list": "Lista de encabezados",
"invalid_name": "Proporciona un nombre para la solicitud.", "invalid_name": "Proporciona un nombre para la solicitud.",
"method": "Método", "method": "Método",
"moved": "Request moved", "moved": "Solicitud movida",
"name": "Nombre de solicitud", "name": "Nombre de solicitud",
"new": "Nueva solicitud", "new": "Nueva solicitud",
"order_changed": "Orden de solicitudes actualizadas", "order_changed": "Orden de solicitudes actualizadas",
@@ -543,8 +543,8 @@
"path": "Ruta", "path": "Ruta",
"payload": "Carga útil", "payload": "Carga útil",
"query": "Consulta", "query": "Consulta",
"raw_body": "Cuerpo de solicitud sin procesar", "raw_body": "cuerpo sin procesar",
"rename": "Rename Request", "rename": "Renombrar solicitud",
"renamed": "Solicitud renombrada", "renamed": "Solicitud renombrada",
"run": "Ejecutar", "run": "Ejecutar",
"save": "Guardar", "save": "Guardar",
@@ -552,8 +552,8 @@
"saved": "Solicitud guardada", "saved": "Solicitud guardada",
"share": "Compartir", "share": "Compartir",
"share_description": "Comparte Hoppscotch con tus amigos", "share_description": "Comparte Hoppscotch con tus amigos",
"share_request": "Share Request", "share_request": "Compartir solicitud",
"stop": "Stop", "stop": "Detener",
"title": "Solicitud", "title": "Solicitud",
"type": "Tipo de solicitud", "type": "Tipo de solicitud",
"url": "URL", "url": "URL",
@@ -571,7 +571,7 @@
"json": "JSON", "json": "JSON",
"pdf": "PDF", "pdf": "PDF",
"preview_html": "Vista previa de HTML", "preview_html": "Vista previa de HTML",
"raw": "Crudo", "raw": "Sin procesar",
"size": "Tamaño", "size": "Tamaño",
"status": "Estado", "status": "Estado",
"time": "Tiempo", "time": "Tiempo",
@@ -635,29 +635,29 @@
"verify_email": "Verificar correo electrónico" "verify_email": "Verificar correo electrónico"
}, },
"shared_requests": { "shared_requests": {
"button": "Button", "button": "Botón",
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", "button_info": "Crea un botón \"Ejecutar en Hoppscotch\" para tu página web, blog o un README.",
"copy_html": "Copy HTML", "copy_html": "Copiar HTML",
"copy_link": "Copy Link", "copy_link": "Copiar enlace",
"copy_markdown": "Copy Markdown", "copy_markdown": "Copiar Markdown",
"creating_widget": "Creating widget", "creating_widget": "Crear widget",
"customize": "Customize", "customize": "Personalizar",
"deleted": "Shared request deleted", "deleted": "Solicitud compartida eliminada",
"description": "Select a widget, you can change and customize this later", "description": "Selecciona un widget, puedes cambiarlo y personalizarlo más tarde",
"embed": "Embed", "embed": "Incrustar",
"embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", "embed_info": "Añada un mini \"Hoppscotch API Playground\" a tu sitio web, blog o documentación.",
"link": "Link", "link": "Enlace",
"link_info": "Create a shareable link to share with anyone on the internet with view access.", "link_info": "Crea un enlace compartible para compartirlo con cualquier persona en Internet con acceso de visualización.",
"modified": "Shared request modified", "modified": "Solicitud compartida modificada",
"not_found": "Shared request not found", "not_found": "Solicitud compartida no encontrada",
"open_new_tab": "Open in new tab", "open_new_tab": "Abrir en una nueva pestaña",
"preview": "Preview", "preview": "Vista previa",
"run_in_hoppscotch": "Run in Hoppscotch", "run_in_hoppscotch": "Correr en Hoppscotch",
"theme": { "theme": {
"dark": "Dark", "dark": "Oscuro",
"light": "Light", "light": "Claro",
"system": "System", "system": "Sistema",
"title": "Theme" "title": "Tema"
} }
}, },
"shortcut": { "shortcut": {
@@ -684,8 +684,8 @@
"title": "Navegación" "title": "Navegación"
}, },
"others": { "others": {
"prettify": "Prettify Editor's Content", "prettify": "Formatear el contenido del editor",
"title": "Others" "title": "Otros"
}, },
"request": { "request": {
"delete_method": "Seleccionar método DELETE", "delete_method": "Seleccionar método DELETE",
@@ -697,13 +697,13 @@
"post_method": "Seleccionar método POST", "post_method": "Seleccionar método POST",
"previous_method": "Seleccionar método anterior", "previous_method": "Seleccionar método anterior",
"put_method": "Seleccionar método PUT", "put_method": "Seleccionar método PUT",
"rename": "Rename Request", "rename": "Renombrar solicitud",
"reset_request": "Solicitud de reinicio", "reset_request": "Solicitud de reinicio",
"save_request": "Save Request", "save_request": "Guardar solicitud",
"save_to_collections": "Guardar en colecciones", "save_to_collections": "Guardar en colecciones",
"send_request": "Enviar solicitud", "send_request": "Enviar solicitud",
"share_request": "Share Request", "share_request": "Compartir solicitud",
"show_code": "Generate code snippet", "show_code": "Generar fragmento de código",
"title": "Solicitud", "title": "Solicitud",
"copy_request_link": "Copiar enlace de solicitud" "copy_request_link": "Copiar enlace de solicitud"
}, },
@@ -722,95 +722,95 @@
}, },
"show": { "show": {
"code": "Mostrar código", "code": "Mostrar código",
"collection": "Expand Collection Panel", "collection": "Ampliar el panel de colecciones",
"more": "Mostrar más", "more": "Mostrar más",
"sidebar": "Mostrar barra lateral" "sidebar": "Mostrar barra lateral"
}, },
"socketio": { "socketio": {
"communication": "Comunicación", "communication": "Comunicación",
"connection_not_authorized": "This SocketIO connection does not use any authentication.", "connection_not_authorized": "Esta conexión SocketIO no utiliza ningún tipo de autenticación.",
"event_name": "Nombre del evento", "event_name": "Nombre del evento",
"events": "Eventos", "events": "Eventos",
"log": "Registro", "log": "Registro",
"url": "URL" "url": "URL"
}, },
"spotlight": { "spotlight": {
"change_language": "Change Language", "change_language": "Cambiar idioma",
"environments": { "environments": {
"delete": "Delete current environment", "delete": "Borrar el entorno actual",
"duplicate": "Duplicate current environment", "duplicate": "Duplicar el entorno actual",
"duplicate_global": "Duplicate global environment", "duplicate_global": "Entorno global duplicado",
"edit": "Edit current environment", "edit": "Editar el entorno actual",
"edit_global": "Edit global environment", "edit_global": "Editar el entorno global",
"new": "Create new environment", "new": "Crear un nuevo entorno",
"new_variable": "Create a new environment variable", "new_variable": "Crear una nueva variable de entorno",
"title": "Environments" "title": "Entornos"
}, },
"general": { "general": {
"chat": "Chat with support", "chat": "Chatear con el servicio de asistencia",
"help_menu": "Help and support", "help_menu": "Ayuda y asistencia",
"open_docs": "Read Documentation", "open_docs": "Leer la documentación",
"open_github": "Open GitHub repository", "open_github": "Abrir repositorio de GitHub",
"open_keybindings": "Keyboard shortcuts", "open_keybindings": "Atajos de teclado",
"social": "Social", "social": "Social",
"title": "General" "title": "General"
}, },
"graphql": { "graphql": {
"connect": "Connect to server", "connect": "Conectarse al servidor",
"disconnect": "Disconnect from server" "disconnect": "Desconectarse del servidor"
}, },
"miscellaneous": { "miscellaneous": {
"invite": "Invite your friends to Hoppscotch", "invite": "Invita a tus amigos a Hoppscotch",
"title": "Miscellaneous" "title": "Varios"
}, },
"request": { "request": {
"save_as_new": "Save as new request", "save_as_new": "Guardar como nueva solicitud",
"select_method": "Select method", "select_method": "Seleccionar método",
"switch_to": "Switch to", "switch_to": "Cambiar a",
"tab_authorization": "Authorization tab", "tab_authorization": "Pestaña de autorización",
"tab_body": "Body tab", "tab_body": "Pestaña de cuerpo",
"tab_headers": "Headers tab", "tab_headers": "Pestaña de encabezados",
"tab_parameters": "Parameters tab", "tab_parameters": "Pestaña de parámetros",
"tab_pre_request_script": "Pre-request script tab", "tab_pre_request_script": "Pestaña del script de pre-solicitud",
"tab_query": "Query tab", "tab_query": "Pestaña de consulta",
"tab_tests": "Tests tab", "tab_tests": "Pestaña de pruebas",
"tab_variables": "Variables tab" "tab_variables": "Pestaña de variables"
}, },
"response": { "response": {
"copy": "Copy response", "copy": "Copiar respuesta",
"download": "Download response as file", "download": "Descargar la respuesta como archivo",
"title": "Response" "title": "Respuesta"
}, },
"section": { "section": {
"interceptor": "Interceptor", "interceptor": "Interceptor",
"interface": "Interface", "interface": "Interfaz",
"theme": "Theme", "theme": "Tema",
"user": "User" "user": "Usuario"
}, },
"settings": { "settings": {
"change_interceptor": "Change Interceptor", "change_interceptor": "Cambiar Interceptor",
"change_language": "Change Language", "change_language": "Cambiar idioma",
"theme": { "theme": {
"black": "Black", "black": "Negro",
"dark": "Dark", "dark": "Oscuro",
"light": "Light", "light": "Claro",
"system": "System preference" "system": "Preferencia del sistema"
} }
}, },
"tab": { "tab": {
"close_current": "Close current tab", "close_current": "Cerrar la pestaña actual",
"close_others": "Close all other tabs", "close_others": "Cerrar todas las demás pestañas",
"duplicate": "Duplicate current tab", "duplicate": "Duplicar pestaña actual",
"new_tab": "Open a new tab", "new_tab": "Abrir una nueva pestaña",
"title": "Tabs" "title": "Pestañas"
}, },
"workspace": { "workspace": {
"delete": "Delete current team", "delete": "Borrar el equipo actual",
"edit": "Edit current team", "edit": "Editar el equipo actual",
"invite": "Invite people to team", "invite": "Invitar al equipo",
"new": "Create new team", "new": "Crear un nuevo equipo",
"switch_to_personal": "Switch to your personal workspace", "switch_to_personal": "Cambia a tu espacio de trabajo personal",
"title": "Teams" "title": "Equipos"
} }
}, },
"sse": { "sse": {
@@ -825,10 +825,10 @@
"connected": "Conectado", "connected": "Conectado",
"connected_to": "Conectado a {name}", "connected_to": "Conectado a {name}",
"connecting_to": "Conectando con {name}...", "connecting_to": "Conectando con {name}...",
"connection_error": "Failed to connect", "connection_error": "Error de conexión",
"connection_failed": "Error de conexión", "connection_failed": "Conexión fallida",
"connection_lost": "Conexión perdida", "connection_lost": "Conexión perdida",
"copied_interface_to_clipboard": "Copied {language} interface type to clipboard", "copied_interface_to_clipboard": "Copiado tipo de interfaz {language} al portapapeles",
"copied_to_clipboard": "Copiado al portapapeles", "copied_to_clipboard": "Copiado al portapapeles",
"deleted": "Eliminado", "deleted": "Eliminado",
"deprecated": "OBSOLETO", "deprecated": "OBSOLETO",
@@ -836,21 +836,21 @@
"disconnected": "Desconectado", "disconnected": "Desconectado",
"disconnected_from": "Desconectado de {name}", "disconnected_from": "Desconectado de {name}",
"docs_generated": "Documentación generada", "docs_generated": "Documentación generada",
"download_failed": "Download failed", "download_failed": "Descarga fallida",
"download_started": "Descarga iniciada", "download_started": "Descarga iniciada",
"enabled": "Activado", "enabled": "Activado",
"file_imported": "Archivo importado", "file_imported": "Archivo importado",
"finished_in": "Terminado en {duration} ms", "finished_in": "Terminado en {duration}ms",
"hide": "Hide", "hide": "Ocultar",
"history_deleted": "Historial eliminado", "history_deleted": "Historial eliminado",
"linewrap": "Envolver líneas", "linewrap": "Envolver líneas",
"loading": "Cargando...", "loading": "Cargando...",
"message_received": "Mensaje: {mensaje} llegó sobre el tema: {topic}", "message_received": "Mensaje: llegó {message} al: {topic}",
"mqtt_subscription_failed": "Algo ha ido mal al suscribirse al tema: {topic}", "mqtt_subscription_failed": "Algo ha ido mal al suscribirse al tema: {topic}",
"none": "Ninguno", "none": "Ninguno",
"nothing_found": "Nada encontrado para", "nothing_found": "Nada encontrado para",
"published_error": "Algo ha ido mal al publicar el mensaje: {topic} al tema: {message}", "published_error": "Algo ha ido mal al publicar el mensaje: {message} al tema: {topic}",
"published_message": "Mensaje publicado: {mensaje} al tema: {topic}", "published_message": "Mensaje publicado: {message} al tema: {topic}",
"reconnection_error": "Fallo en la reconexión", "reconnection_error": "Fallo en la reconexión",
"show": "Show", "show": "Show",
"subscribed_failed": "Error al suscribirse al tema: {topic}", "subscribed_failed": "Error al suscribirse al tema: {topic}",
@@ -874,12 +874,12 @@
"tab": { "tab": {
"authorization": "Autorización", "authorization": "Autorización",
"body": "Cuerpo", "body": "Cuerpo",
"close": "Close Tab", "close": "Cerrar pestaña",
"close_others": "Close other Tabs", "close_others": "Cerrar otras pestañas",
"collections": "Colecciones", "collections": "Colecciones",
"documentation": "Documentación", "documentation": "Documentación",
"duplicate": "Duplicate Tab", "duplicate": "Duplicar pestaña",
"environments": "Environments", "environments": "Entornos",
"headers": "Encabezados", "headers": "Encabezados",
"history": "Historial", "history": "Historial",
"mqtt": "MQTT", "mqtt": "MQTT",
@@ -888,7 +888,7 @@
"queries": "Consultas", "queries": "Consultas",
"query": "Consulta", "query": "Consulta",
"schema": "Esquema", "schema": "Esquema",
"shared_requests": "Shared Requests", "shared_requests": "Solicitudes compartidas",
"socketio": "Socket.IO", "socketio": "Socket.IO",
"sse": "SSE", "sse": "SSE",
"tests": "Pruebas", "tests": "Pruebas",
@@ -905,7 +905,7 @@
"email_do_not_match": "El correo electrónico no coincide con los datos de tu cuenta. Ponte en contacto con el propietario de tu equipo.", "email_do_not_match": "El correo electrónico no coincide con los datos de tu cuenta. Ponte en contacto con el propietario de tu equipo.",
"exit": "Salir del equipo", "exit": "Salir del equipo",
"exit_disabled": "Solo el propietario puede salir del equipo", "exit_disabled": "Solo el propietario puede salir del equipo",
"failed_invites": "Failed invites", "failed_invites": "Invitaciones fallidas",
"invalid_coll_id": "Identificador de colección no válido", "invalid_coll_id": "Identificador de colección no válido",
"invalid_email_format": "El formato de correo electrónico no es válido", "invalid_email_format": "El formato de correo electrónico no es válido",
"invalid_id": "Identificador de equipo inválido. Ponte en contacto con el propietario de tu equipo.", "invalid_id": "Identificador de equipo inválido. Ponte en contacto con el propietario de tu equipo.",
@@ -915,7 +915,7 @@
"invite": "Invitar", "invite": "Invitar",
"invite_more": "Invitar a más", "invite_more": "Invitar a más",
"invite_tooltip": "Invite a personas a este espacio de trabajo", "invite_tooltip": "Invite a personas a este espacio de trabajo",
"invited_to_team": "{owner} te ha invitado a unirte al {tema}", "invited_to_team": "{owner} te ha invitado al equipo {team}",
"join": "Invitación aceptada", "join": "Invitación aceptada",
"join_beta": "Únete al programa beta para acceder a los equipos.", "join_beta": "Únete al programa beta para acceder a los equipos.",
"join_team": "Entrar a {team}", "join_team": "Entrar a {team}",
@@ -930,7 +930,7 @@
"member_removed": "Usuario eliminado", "member_removed": "Usuario eliminado",
"member_role_updated": "Funciones de usuario actualizadas", "member_role_updated": "Funciones de usuario actualizadas",
"members": "Miembros", "members": "Miembros",
"more_members": "+{count} more", "more_members": "+{count} más",
"name_length_insufficient": "El nombre del equipo debe tener al menos 6 caracteres", "name_length_insufficient": "El nombre del equipo debe tener al menos 6 caracteres",
"name_updated": "Nombre de equipo actualizado", "name_updated": "Nombre de equipo actualizado",
"new": "Nuevo equipo", "new": "Nuevo equipo",
@@ -944,13 +944,13 @@
"parent_coll_move": "No se puede mover la colección a una colección hija", "parent_coll_move": "No se puede mover la colección a una colección hija",
"pending_invites": "Invitaciones pendientes", "pending_invites": "Invitaciones pendientes",
"permissions": "Permisos", "permissions": "Permisos",
"same_target_destination": "Same target and destination", "same_target_destination": "Mismo objetivo y destino",
"saved": "Equipo guardado", "saved": "Equipo guardado",
"select_a_team": "Seleccionar un equipo", "select_a_team": "Seleccionar un equipo",
"success_invites": "Success invites", "success_invites": "Invitaciones con éxito",
"title": "Equipos", "title": "Equipos",
"we_sent_invite_link": "¡Hemos enviado un enlace de invitación a todos los invitados!", "we_sent_invite_link": "¡Hemos enviado un enlace de invitación a todos los invitados!",
"we_sent_invite_link_description": "Pide a todos los invitados que revisen tu bandeja de entrada. Haz clic en el enlace para unirse al equipo." "we_sent_invite_link_description": "Pide a todos los invitados que revisen su bandeja de entrada. Tienen que hacer clic en el enlace para unirse al equipo."
}, },
"team_environment": { "team_environment": {
"deleted": "Entorno eliminado", "deleted": "Entorno eliminado",

View File

@@ -1,15 +1,15 @@
{ {
"action": { "action": {
"add": "Add", "add": "Ajouter",
"autoscroll": "Autoscroll", "autoscroll": "Auto-scroll",
"cancel": "Annuler", "cancel": "Annuler",
"choose_file": "Choisir un fichier", "choose_file": "Choisir un fichier",
"clear": "Effacer", "clear": "Effacer",
"clear_all": "Tout effacer", "clear_all": "Tout effacer",
"clear_history": "Clear all History", "clear_history": "Effacer tout l'historique",
"close": "Close", "close": "Fermer",
"connect": "Connecter", "connect": "Connecter",
"connecting": "Connecting", "connecting": "Connexion",
"copy": "Copier", "copy": "Copier",
"create": "Create", "create": "Create",
"delete": "Supprimer", "delete": "Supprimer",
@@ -22,8 +22,8 @@
"edit": "Éditer", "edit": "Éditer",
"filter": "Filter", "filter": "Filter",
"go_back": "Retour", "go_back": "Retour",
"go_forward": "Go forward", "go_forward": "Avancer",
"group_by": "Group by", "group_by": "Grouper par",
"label": "Étiqueter", "label": "Étiqueter",
"learn_more": "En savoir plus", "learn_more": "En savoir plus",
"less": "Moins", "less": "Moins",
@@ -35,16 +35,16 @@
"prettify": "Formater", "prettify": "Formater",
"properties": "Properties", "properties": "Properties",
"remove": "Supprimer", "remove": "Supprimer",
"rename": "Rename", "rename": "Renommer",
"restore": "Restaurer", "restore": "Restaurer",
"save": "Sauvegarder", "save": "Sauvegarder",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Défiler vers le bas",
"scroll_to_top": "Scroll to top", "scroll_to_top": "Défiler vers le haut",
"search": "Chercher", "search": "Chercher",
"send": "Envoyer", "send": "Envoyer",
"share": "Share", "share": "Share",
"start": "Démarrer", "start": "Démarrer",
"starting": "Starting", "starting": "Démarrage",
"stop": "Arrêter", "stop": "Arrêter",
"to_close": "pour fermer", "to_close": "pour fermer",
"to_navigate": "pour naviguer", "to_navigate": "pour naviguer",
@@ -86,8 +86,8 @@
"search": "Chercher", "search": "Chercher",
"share": "Partager", "share": "Partager",
"shortcuts": "Raccourcis", "shortcuts": "Raccourcis",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", "social_description": "Suivez-nous sur les médias sociaux pour rester informé des dernières nouvelles, mises à jour et communiqués.",
"social_links": "Social links", "social_links": "Liens sociaux",
"spotlight": "Projecteur", "spotlight": "Projecteur",
"status": "Statut", "status": "Statut",
"status_description": "Vérifier l'état du site web", "status_description": "Vérifier l'état du site web",
@@ -95,7 +95,7 @@
"twitter": "Twitter", "twitter": "Twitter",
"type_a_command_search": "Tapez une commande ou recherchez…", "type_a_command_search": "Tapez une commande ou recherchez…",
"we_use_cookies": "Nous utilisons des cookies", "we_use_cookies": "Nous utilisons des cookies",
"whats_new": "Quoi de neuf?", "whats_new": "Quoi de neuf ?",
"wiki": "Wiki" "wiki": "Wiki"
}, },
"auth": { "auth": {
@@ -119,39 +119,38 @@
}, },
"authorization": { "authorization": {
"generate_token": "Générer un jeton", "generate_token": "Générer un jeton",
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", "graphql_headers": "Les en-têtes d'autorisation sont envoyés en tant que partie de la charge utile de connection_init.",
"include_in_url": "Inclure dans l'URL", "include_in_url": "Inclure dans l'URL",
"inherited_from": "Inherited from {auth} from Parent Collection {collection} ", "inherited_from": "Inherited from {auth} from Parent Collection {collection} ",
"learn": "Apprendre comment", "learn": "Apprendre comment",
"oauth": {
"redirect_auth_server_returned_error": "Auth Server returned an error state",
"redirect_auth_token_request_failed": "Request to get the auth token failed",
"redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token",
"redirect_invalid_state": "Invalid State value present in the redirect",
"redirect_no_auth_code": "No Authorization Code present in the redirect",
"redirect_no_client_id": "No Client ID defined",
"redirect_no_client_secret": "No Client Secret Defined",
"redirect_no_code_verifier": "No Code Verifier Defined",
"redirect_no_token_endpoint": "No Token Endpoint Defined",
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed"
},
"pass_key_by": "Pass by", "pass_key_by": "Pass by",
"password": "Mot de passe", "password": "Mot de passe",
"save_to_inherit": "Please save this request in any collection to inherit the authorization", "save_to_inherit": "Please save this request in any collection to inherit the authorization",
"token": "Jeton", "token": "Jeton",
"type": "Type d'autorisation", "type": "Type d'autorisation",
"username": "Nom d'utilisateur" "username": "Nom d'utilisateur",
"oauth": {
"something_went_wrong_on_token_generation": "Un problème s'est produit lors de la génération des jetons",
"redirect_auth_server_returned_error": "Le serveur d'authentification a renvoyé un état d'erreur",
"redirect_no_auth_code": "Pas de code d'autorisation dans la redirection",
"redirect_invalid_state": "Valeur d'état non valide présente dans la redirection",
"redirect_no_token_endpoint": "Aucun point de terminaison de jeton n'est défini",
"redirect_no_client_id": "Pas d'ID client défini",
"redirect_no_client_secret": "Pas de secret client défini",
"redirect_no_code_verifier": "Pas de vérificateur de code défini",
"redirect_auth_token_request_failed": "La demande d'obtention du jeton d'authentification a échoué",
"redirect_auth_token_request_invalid_response": "Réponse invalide du point de terminaison Token lors de la demande d'un jeton d'authentification",
"something_went_wrong_on_oauth_redirect": "Quelque chose s'est mal passé lors de la redirection OAuth"
}
}, },
"collection": { "collection": {
"created": "Collection créée", "created": "Collection créée",
"different_parent": "Cannot reorder collection with different parent", "different_parent": "Impossible de réorganiser une collection dont le parent est différent",
"edit": "Modifier la collection", "edit": "Modifier la collection",
"import_or_create": "Import or create a collection", "import_or_create": "Importer ou créer une collection",
"invalid_name": "Veuillez fournir un nom valide pour la collection", "invalid_name": "Veuillez fournir un nom valide pour la collection",
"invalid_root_move": "Collection already in the root", "invalid_root_move": "Collection déjà présente dans la racine",
"moved": "Moved Successfully", "moved": "Déplacement réussi",
"my_collections": "Mes collections", "my_collections": "Mes collections",
"name": "Ma nouvelle collection", "name": "Ma nouvelle collection",
"name_length_insufficient": "Le nom de la collection doit comporter au moins 3 caractères", "name_length_insufficient": "Le nom de la collection doit comporter au moins 3 caractères",
@@ -162,16 +161,16 @@
"renamed": "Collection renommée", "renamed": "Collection renommée",
"request_in_use": "Demande en cours d'utilisation", "request_in_use": "Demande en cours d'utilisation",
"save_as": "Enregistrer sous", "save_as": "Enregistrer sous",
"save_to_collection": "Save to Collection", "save_to_collection": "Enregistrer dans la collection",
"select": "Sélectionnez une collection", "select": "Sélectionnez une collection",
"select_location": "Sélectionnez l'emplacement", "select_location": "Sélectionnez l'emplacement",
"select_team": "Sélectionnez une équipe", "select_team": "Sélectionnez une équipe",
"team_collections": "Collections de l'équipe" "team_collections": "Collections de l'équipe"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?", "close_unsaved_tab": "Êtes-vous sûr de vouloir fermer cet onglet ?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "close_unsaved_tabs": "Êtes-vous sûr de vouloir fermer tous les onglets ? {count} onglets non enregistrés seront perdus",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Êtes-vous sûr de vouloir quitter cette équipe ?",
"logout": "Êtes-vous sûr de vouloir vous déconnecter?", "logout": "Êtes-vous sûr de vouloir vous déconnecter?",
"remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?", "remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?",
"remove_environment": "Voulez-vous vraiment supprimer définitivement cet environnement ?", "remove_environment": "Voulez-vous vraiment supprimer définitivement cet environnement ?",
@@ -181,31 +180,31 @@
"remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_shared_request": "Are you sure you want to permanently delete this shared request?",
"remove_team": "Voulez-vous vraiment supprimer cette équipe ?", "remove_team": "Voulez-vous vraiment supprimer cette équipe ?",
"remove_telemetry": "Êtes-vous sûr de vouloir désactiver la télémétrie ?", "remove_telemetry": "Êtes-vous sûr de vouloir désactiver la télémétrie ?",
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "request_change": "Êtes-vous sûr de vouloir rejeter la demande en cours ? Les modifications non enregistrées seront perdues.",
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Souhaitez-vous enregistrer les modifications apportées dans cet onglet ?",
"sync": "Voulez-vous vraiment synchroniser cet espace de travail ?" "sync": "Voulez-vous vraiment synchroniser cet espace de travail ?"
}, },
"context_menu": { "context_menu": {
"add_parameters": "Add to parameters", "add_parameters": "Ajouter aux paramètres",
"open_request_in_new_tab": "Open request in new tab", "open_request_in_new_tab": "Ouvrir la demande dans un nouvel onglet",
"set_environment_variable": "Set as variable" "set_environment_variable": "Définir comme variable"
}, },
"cookies": { "cookies": {
"modal": { "modal": {
"cookie_expires": "Expires", "new_domain_name": "Nouveau nom de domaine",
"cookie_name": "Name", "set": "Définir un cookie",
"cookie_path": "Path", "cookie_string": "Chaîne de caractères de cookie",
"cookie_string": "Cookie string", "enter_cookie_string": "Saisir la chaîne de caractères du cookie",
"cookie_value": "Value", "cookie_name": "Nom",
"empty_domain": "Domain is empty", "cookie_value": "Valeur",
"empty_domains": "Domain list is empty", "cookie_path": "Chemin d'accès",
"enter_cookie_string": "Enter cookie string", "cookie_expires": "Expiration",
"interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", "managed_tab": "Gestion",
"managed_tab": "Managed", "raw_tab": "Brut",
"new_domain_name": "New domain name", "interceptor_no_support": "L'intercepteur que vous avez sélectionné ne prend pas en charge les cookies. Sélectionnez un autre intercepteur et réessayez.",
"no_cookies_in_domain": "No cookies set for this domain", "empty_domains": "La liste des domaines est vide",
"raw_tab": "Raw", "empty_domain": "Le domaine est vide",
"set": "Set a cookie" "no_cookies_in_domain": "Aucun cookie n'est défini pour ce domaine"
} }
}, },
"count": { "count": {
@@ -238,7 +237,7 @@
"profile": "Connectez-vous pour voir votre profil", "profile": "Connectez-vous pour voir votre profil",
"protocols": "Les protocoles sont vides", "protocols": "Les protocoles sont vides",
"schema": "Se connecter à un point de terminaison GraphQL", "schema": "Se connecter à un point de terminaison GraphQL",
"shared_requests": "Shared requests are empty", "shared_requests": "Il n'y a pas de requêtes partagées",
"shared_requests_logout": "Login to view your shared requests or create a new one", "shared_requests_logout": "Login to view your shared requests or create a new one",
"subscription": "Subscriptions are empty", "subscription": "Subscriptions are empty",
"team_name": "Nom de l'équipe vide", "team_name": "Nom de l'équipe vide",
@@ -252,15 +251,15 @@
"create_new": "Créer un nouvel environnement", "create_new": "Créer un nouvel environnement",
"created": "Environnement créé", "created": "Environnement créé",
"deleted": "Environnement supprimé", "deleted": "Environnement supprimé",
"duplicated": "Environment duplicated", "duplicated": "Environnement dupliqué",
"edit": "Modifier l'environnement", "edit": "Modifier l'environnement",
"empty_variables": "No variables", "empty_variables": "No variables",
"global": "Global", "global": "Global",
"global_variables": "Global variables", "global_variables": "Variables globales",
"import_or_create": "Import or create a environment", "import_or_create": "Importer ou créer un environnement",
"invalid_name": "Veuillez fournir un nom valide pour l'environnement", "invalid_name": "Veuillez fournir un nom valide pour l'environnement",
"list": "Environment variables", "list": "Variables d'environnement",
"my_environments": "My Environments", "my_environments": "Mes environnements",
"name": "Name", "name": "Name",
"nested_overflow": "les variables d'environnement imbriquées sont limitées à 10 niveaux", "nested_overflow": "les variables d'environnement imbriquées sont limitées à 10 niveaux",
"new": "Nouvel environnement", "new": "Nouvel environnement",
@@ -284,11 +283,11 @@
"authproviders_load_error": "Unable to load auth providers", "authproviders_load_error": "Unable to load auth providers",
"browser_support_sse": "Ce navigateur ne semble pas prendre en charge les événements envoyés par le serveur.", "browser_support_sse": "Ce navigateur ne semble pas prendre en charge les événements envoyés par le serveur.",
"check_console_details": "Consultez le journal de la console pour plus de détails.", "check_console_details": "Consultez le journal de la console pour plus de détails.",
"check_how_to_add_origin": "Check how you can add an origin", "check_how_to_add_origin": "Vérifiez comment vous pouvez ajouter une origine",
"curl_invalid_format": "cURL n'est pas formaté correctement", "curl_invalid_format": "cURL n'est pas formaté correctement",
"danger_zone": "Danger zone", "danger_zone": "Zone de danger",
"delete_account": "Your account is currently an owner in these teams:", "delete_account": "Votre compte est actuellement propriétaire de ces équipes :",
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", "delete_account_description": "Vous devez vous retirer, transférer la propriété ou supprimer ces équipes avant de pouvoir supprimer votre compte.",
"empty_req_name": "Nom de la requête vide", "empty_req_name": "Nom de la requête vide",
"f12_details": "(F12 pour les détails)", "f12_details": "(F12 pour les détails)",
"gql_prettify_invalid_query": "Impossible de formater une requête non valide, résolvez les erreurs de syntaxe de la requête et réessayer", "gql_prettify_invalid_query": "Impossible de formater une requête non valide, résolvez les erreurs de syntaxe de la requête et réessayer",
@@ -300,13 +299,13 @@
"json_prettify_invalid_body": "Impossible de formater un corps non valide, résolvez les erreurs de syntaxe json et réessayez", "json_prettify_invalid_body": "Impossible de formater un corps non valide, résolvez les erreurs de syntaxe json et réessayez",
"network_error": "Il semble y avoir une erreur de réseau. Veuillez réessayer.", "network_error": "Il semble y avoir une erreur de réseau. Veuillez réessayer.",
"network_fail": "Impossible d'envoyer la requête", "network_fail": "Impossible d'envoyer la requête",
"no_collections_to_export": "No collections to export. Please create a collection to get started.", "no_collections_to_export": "Aucune collection à exporter. Veuillez créer une collection pour commencer.",
"no_duration": "Pas de durée", "no_duration": "Pas de durée",
"no_environments_to_export": "No environments to export. Please create an environment to get started.", "no_environments_to_export": "Aucun environnement à exporter. Veuillez créer un environnement pour commencer.",
"no_results_found": "Aucune correspondance trouvée", "no_results_found": "Aucune correspondance trouvée",
"page_not_found": "Cette page n'a pas pu être trouvée", "page_not_found": "Cette page n'a pas pu être trouvée",
"please_install_extension": "Please install the extension and add origin to the extension.", "please_install_extension": "Veuillez installer l'extension et ajouter l'origine à l'extension.",
"proxy_error": "Proxy error", "proxy_error": "Erreur de proxy",
"script_fail": "Impossible d'exécuter le script de pré-requête", "script_fail": "Impossible d'exécuter le script de pré-requête",
"something_went_wrong": "Quelque chose s'est mal passé", "something_went_wrong": "Quelque chose s'est mal passé",
"test_script_fail": "Impossible d'exécuter le script post-requête" "test_script_fail": "Impossible d'exécuter le script post-requête"
@@ -320,9 +319,9 @@
"title": "Exportation" "title": "Exportation"
}, },
"filter": { "filter": {
"all": "All", "all": "Tout",
"none": "None", "none": "Aucun",
"starred": "Starred" "starred": "Étoilé"
}, },
"folder": { "folder": {
"created": "Dossier créé", "created": "Dossier créé",
@@ -333,19 +332,19 @@
"renamed": "Dossier renommé" "renamed": "Dossier renommé"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", "connection_switch_confirm": "Voulez-vous vous connecter avec le dernier point de terminaison GraphQL ?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", "connection_switch_new_url": "Le passage à un autre onglet vous déconnectera de la connexion GraphQL active. La nouvelle URL de connexion est",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", "connection_switch_url": "Vous êtes connecté à un point de terminaison GraphQL dont l'URL de connexion est",
"mutations": "Mutations", "mutations": "Mutations",
"schema": "Schéma", "schema": "Schéma",
"subscriptions": "Abonnements", "subscriptions": "Abonnements",
"switch_connection": "Switch connection" "switch_connection": "Changer de connexion"
}, },
"graphql_collections": { "graphql_collections": {
"title": "GraphQL Collections" "title": "GraphQL Collections"
}, },
"group": { "group": {
"time": "Time", "time": "Temps",
"url": "URL" "url": "URL"
}, },
"header": { "header": {
@@ -376,7 +375,7 @@
"import": { "import": {
"collections": "Importer des collections", "collections": "Importer des collections",
"curl": "Importer en cURL", "curl": "Importer en cURL",
"environments_from_gist": "Import From Gist", "environments_from_gist": "Importer depuis Gist",
"environments_from_gist_description": "Import Hoppscotch Environments From Gist", "environments_from_gist_description": "Import Hoppscotch Environments From Gist",
"failed": "Échec de l'importation", "failed": "Échec de l'importation",
"from_file": "Import from File", "from_file": "Import from File",
@@ -408,27 +407,27 @@
"title": "Importer" "title": "Importer"
}, },
"inspections": { "inspections": {
"description": "Inspect possible errors", "description": "Inspecter les erreurs possibles",
"environment": { "environment": {
"add_environment": "Add to Environment", "add_environment": "Ajouter à l'environnement",
"not_found": "Environment variable “{environment} not found." "not_found": "La variable d'environnement “{environment} n'a pas été trouvée."
}, },
"header": { "header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." "cookie": "Le navigateur ne permet pas à Hoppscotch de définir l'en-tête Cookie. Pendant que nous travaillons sur l'application de bureau Hoppscotch (bientôt disponible), veuillez utiliser l'en-tête d'autorisation à la place."
}, },
"response": { "response": {
"401_error": "Please check your authentication credentials.", "401_error": "Veuillez vérifier vos informations d'authentification.",
"404_error": "Please check your request URL and method type.", "404_error": "Veuillez vérifier l'URL de votre demande et le type de méthode.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.", "cors_error": "Veuillez vérifier la configuration du partage des ressources entre les origines.",
"default_error": "Please check your request.", "default_error": "Veuillez vérifier votre demande.",
"network_error": "Please check your network connection." "network_error": "Veuillez vérifier votre connexion réseau."
}, },
"title": "Inspector", "title": "Inspecteur",
"url": { "url": {
"extension_not_installed": "Extension not installed.", "extension_not_installed": "L'extension n'est pas installée.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", "extension_unknown_origin": "Assurez-vous d'avoir ajouté l'origine du point de terminaison de l'API à la liste des extensions du navigateur Hoppscotch.",
"extention_enable_action": "Enable Browser Extension", "extention_enable_action": "Activer l'extension du navigateur",
"extention_not_enabled": "Extension not enabled." "extention_not_enabled": "L'extension n'est pas activée."
} }
}, },
"layout": { "layout": {
@@ -439,25 +438,25 @@
"row": "Disposition horizontale" "row": "Disposition horizontale"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "Vous avez des modifications non enregistrées",
"collections": "Collections", "collections": "Collections",
"confirm": "Confirmer", "confirm": "Confirmer",
"customize_request": "Customize Request", "customize_request": "Customize Request",
"edit_request": "Modifier la requête", "edit_request": "Modifier la requête",
"import_export": "Importer / Exporter", "import_export": "Importer / Exporter",
"share_request": "Share Request" "share_request": "Partager une requête"
}, },
"mqtt": { "mqtt": {
"already_subscribed": "You are already subscribed to this topic.", "already_subscribed": "Vous êtes déjà abonné à ce sujet.",
"clean_session": "Clean Session", "clean_session": "Effacer la Session",
"clear_input": "Clear input", "clear_input": "Effacer la saisie",
"clear_input_on_send": "Clear input on send", "clear_input_on_send": "Effacer la saisie lors de l'envoi",
"client_id": "Client ID", "client_id": "Client ID",
"color": "Pick a color", "color": "Choisir la couleur",
"communication": "Communication", "communication": "Communication",
"connection_config": "Connection Config", "connection_config": "Connection Config",
"connection_not_authorized": "This MQTT connection does not use any authentication.", "connection_not_authorized": "Cette connexion MQTT n'utilise pas d'authentification.",
"invalid_topic": "Please provide a topic for the subscription", "invalid_topic": "Veuillez fournir un sujet pour l'abonnement",
"keep_alive": "Keep Alive", "keep_alive": "Keep Alive",
"log": "Infos", "log": "Infos",
"lw_message": "Last-Will Message", "lw_message": "Last-Will Message",
@@ -466,7 +465,7 @@
"lw_topic": "Last-Will Topic", "lw_topic": "Last-Will Topic",
"message": "Message", "message": "Message",
"new": "New Subscription", "new": "New Subscription",
"not_connected": "Please start a MQTT connection first.", "not_connected": "Veuillez d'abord établir une connexion MQTT.",
"publish": "Publier", "publish": "Publier",
"qos": "QoS", "qos": "QoS",
"ssl": "SSL", "ssl": "SSL",
@@ -480,7 +479,7 @@
"navigation": { "navigation": {
"doc": "Documents", "doc": "Documents",
"graphql": "GraphQL", "graphql": "GraphQL",
"profile": "Profile", "profile": "Profil",
"realtime": "Temps réel", "realtime": "Temps réel",
"rest": "REST", "rest": "REST",
"settings": "Paramètres" "settings": "Paramètres"
@@ -493,7 +492,7 @@
}, },
"profile": { "profile": {
"app_settings": "Réglages de l'application", "app_settings": "Réglages de l'application",
"default_hopp_displayname": "Unnamed User", "default_hopp_displayname": "Utilisateur anonyme",
"editor": "Éditeur", "editor": "Éditeur",
"editor_description": "Les éditeurs peuvent ajouter, modifier et supprimer des demandes.", "editor_description": "Les éditeurs peuvent ajouter, modifier et supprimer des demandes.",
"email_verification_mail": "Un e-mail de vérification a été envoyé à votre adresse e-mail. Veuillez cliquer sur le lien pour vérifier votre adresse électronique.", "email_verification_mail": "Un e-mail de vérification a été envoyé à votre adresse e-mail. Veuillez cliquer sur le lien pour vérifier votre adresse électronique.",
@@ -526,17 +525,17 @@
"enter_curl": "Entrer cURL", "enter_curl": "Entrer cURL",
"generate_code": "Générer le code", "generate_code": "Générer le code",
"generated_code": "Code généré", "generated_code": "Code généré",
"go_to_authorization_tab": "Go to Authorization tab",
"go_to_body_tab": "Go to Body tab", "go_to_body_tab": "Go to Body tab",
"go_to_authorization_tab": "Aller à l'autorisation",
"header_list": "Liste des en-têtes", "header_list": "Liste des en-têtes",
"invalid_name": "Veuillez fournir un nom pour la requête", "invalid_name": "Veuillez fournir un nom pour la requête",
"method": "Méthode", "method": "Méthode",
"moved": "Request moved", "moved": "Request moved",
"name": "Nom de la requête", "name": "Nom de la requête",
"new": "Nouvelle requête", "new": "Nouvelle requête",
"order_changed": "Request Order Updated", "order_changed": "Demande de commande Mise à jour",
"override": "Remplacer", "override": "Remplacer",
"override_help": "Set <xmp>Content-Type</xmp> in Headers", "override_help": "Définir <xmp>Content-Type</xmp> dans les en-têtes",
"overriden": "Remplacé", "overriden": "Remplacé",
"parameter_list": "Paramètres de requête", "parameter_list": "Paramètres de requête",
"parameters": "Paramètres", "parameters": "Paramètres",
@@ -544,7 +543,7 @@
"payload": "Charge utile", "payload": "Charge utile",
"query": "Requête", "query": "Requête",
"raw_body": "Corps de requête brut", "raw_body": "Corps de requête brut",
"rename": "Rename Request", "rename": "Demande de renommage",
"renamed": "Requête renommée", "renamed": "Requête renommée",
"run": "Lancer", "run": "Lancer",
"save": "Sauvegarder", "save": "Sauvegarder",
@@ -564,7 +563,7 @@
"response": { "response": {
"audio": "Audio", "audio": "Audio",
"body": "Corps de réponse", "body": "Corps de réponse",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filtrer le corps de la réponse JSON (utilise la syntaxe JSONPath)",
"headers": "En-têtes", "headers": "En-têtes",
"html": "HTML", "html": "HTML",
"image": "Image", "image": "Image",
@@ -576,14 +575,14 @@
"status": "Statut", "status": "Statut",
"time": "Temps", "time": "Temps",
"title": "Réponse", "title": "Réponse",
"video": "Video", "video": "Vidéo",
"waiting_for_connection": "En attente de connexion", "waiting_for_connection": "En attente de connexion",
"xml": "XML" "xml": "XML"
}, },
"settings": { "settings": {
"accent_color": "Couleur d'accent", "accent_color": "Couleur d'accent",
"account": "Compte", "account": "Compte",
"account_deleted": "Your account has been deleted", "account_deleted": "Votre compte a été supprimé",
"account_description": "Personnalisez les paramètres de votre compte.", "account_description": "Personnalisez les paramètres de votre compte.",
"account_email_description": "Votre adresse e-mail principale.", "account_email_description": "Votre adresse e-mail principale.",
"account_name_description": "Ceci est votre nom d'affichage.", "account_name_description": "Ceci est votre nom d'affichage.",
@@ -592,8 +591,8 @@
"black_mode": "Noir", "black_mode": "Noir",
"choose_language": "Choisissez la langue", "choose_language": "Choisissez la langue",
"dark_mode": "Sombre", "dark_mode": "Sombre",
"delete_account": "Delete account", "delete_account": "Supprimer le compte",
"delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.", "delete_account_description": "Lorsque vous supprimez votre compte, toutes vos données sont définitivement effacées. Cette action ne peut être annulée.",
"expand_navigation": "Expand navigation", "expand_navigation": "Expand navigation",
"experiments": "Expériences", "experiments": "Expériences",
"experiments_notice": "Il s'agit d'une collection d'expériences sur lesquelles nous travaillons et qui pourraient s'avérer utiles, amusantes, les deux ou aucune. Ils ne sont pas définitifs et peuvent ne pas être stables, donc si quelque chose de trop étrange se produit, ne paniquez pas. Il suffit d'éteindre le truc. Blague à part,", "experiments_notice": "Il s'agit d'une collection d'expériences sur lesquelles nous travaillons et qui pourraient s'avérer utiles, amusantes, les deux ou aucune. Ils ne sont pas définitifs et peuvent ne pas être stables, donc si quelque chose de trop étrange se produit, ne paniquez pas. Il suffit d'éteindre le truc. Blague à part,",
@@ -601,7 +600,7 @@
"extension_version": "Version d'extension", "extension_version": "Version d'extension",
"extensions": "Extensions", "extensions": "Extensions",
"extensions_use_toggle": "Utilisez l'extension de navigateur pour envoyer des requêtes (le cas échéant)", "extensions_use_toggle": "Utilisez l'extension de navigateur pour envoyer des requêtes (le cas échéant)",
"follow": "Follow Us", "follow": "Suivez-nous",
"interceptor": "Intercepteur", "interceptor": "Intercepteur",
"interceptor_description": "Middleware entre l'application et les API.", "interceptor_description": "Middleware entre l'application et les API.",
"language": "Langue", "language": "Langue",
@@ -631,7 +630,7 @@
"theme_description": "Personnalisez le thème de votre application.", "theme_description": "Personnalisez le thème de votre application.",
"use_experimental_url_bar": "Utiliser la barre d'URL expérimentale avec mise en évidence de l'environnement", "use_experimental_url_bar": "Utiliser la barre d'URL expérimentale avec mise en évidence de l'environnement",
"user": "Utilisateur", "user": "Utilisateur",
"verified_email": "Verified email", "verified_email": "E-mail vérifié",
"verify_email": "Vérifier l'email" "verify_email": "Vérifier l'email"
}, },
"shared_requests": { "shared_requests": {
@@ -684,20 +683,20 @@
"title": "Navigation" "title": "Navigation"
}, },
"others": { "others": {
"prettify": "Prettify Editor's Content", "prettify": "Améliorer le contenu de l'éditeur",
"title": "Others" "title": "Autres"
}, },
"request": { "request": {
"delete_method": "Sélectionnez la méthode DELETE", "delete_method": "Sélectionnez la méthode DELETE",
"get_method": "Sélectionnez la méthode GET", "get_method": "Sélectionnez la méthode GET",
"head_method": "Sélectionnez la méthode HEAD", "head_method": "Sélectionnez la méthode HEAD",
"import_curl": "Import cURL", "import_curl": "Importer cURL",
"method": "Méthode", "method": "Méthode",
"next_method": "Sélectionnez la méthode suivante", "next_method": "Sélectionnez la méthode suivante",
"post_method": "Sélectionnez la méthode POST", "post_method": "Sélectionnez la méthode POST",
"previous_method": "Sélectionnez la méthode précédente", "previous_method": "Sélectionnez la méthode précédente",
"put_method": "Sélectionnez la méthode PUT", "put_method": "Sélectionnez la méthode PUT",
"rename": "Rename Request", "rename": "Demande de renommage",
"reset_request": "Réinitialiser la requête", "reset_request": "Réinitialiser la requête",
"save_request": "Save Request", "save_request": "Save Request",
"save_to_collections": "Enregistrer dans les collections", "save_to_collections": "Enregistrer dans les collections",
@@ -728,89 +727,89 @@
}, },
"socketio": { "socketio": {
"communication": "Communication", "communication": "Communication",
"connection_not_authorized": "This SocketIO connection does not use any authentication.", "connection_not_authorized": "Cette connexion SocketIO n'utilise pas d'authentification.",
"event_name": "Nom de l'événement", "event_name": "Nom de l'événement",
"events": "Événements", "events": "Événements",
"log": "Infos", "log": "Infos",
"url": "URL" "url": "URL"
}, },
"spotlight": { "spotlight": {
"change_language": "Change Language", "change_language": "Changer de langue",
"environments": { "environments": {
"delete": "Delete current environment", "delete": "Supprimer l'environnement actuel",
"duplicate": "Duplicate current environment", "duplicate": "Dupliquer l'environnement actuel",
"duplicate_global": "Duplicate global environment", "duplicate_global": "Duplication de l'environnement global",
"edit": "Edit current environment", "edit": "Modifier l'environnement actuel",
"edit_global": "Edit global environment", "edit_global": "Modifier l'environnement mondial",
"new": "Create new environment", "new": "Créer un nouvel environnement",
"new_variable": "Create a new environment variable", "new_variable": "Créer une nouvelle variable d'environnement",
"title": "Environments" "title": "Environments"
}, },
"general": { "general": {
"chat": "Chat with support", "chat": "Chat avec le support",
"help_menu": "Help and support", "help_menu": "Aide et assistance",
"open_docs": "Read Documentation", "open_docs": "Lire la documentation",
"open_github": "Open GitHub repository", "open_github": "Ouvrir le dépôt GitHub",
"open_keybindings": "Keyboard shortcuts", "open_keybindings": "Raccourcis clavier",
"social": "Social", "social": "Social",
"title": "General" "title": "Général"
}, },
"graphql": { "graphql": {
"connect": "Connect to server", "connect": "Connexion au serveur",
"disconnect": "Disconnect from server" "disconnect": "Déconnexion du serveur"
}, },
"miscellaneous": { "miscellaneous": {
"invite": "Invite your friends to Hoppscotch", "invite": "Invitez vos amis à Hoppscotch",
"title": "Miscellaneous" "title": "Divers"
}, },
"request": { "request": {
"save_as_new": "Save as new request", "save_as_new": "Sauvegarder comme nouvelle demande",
"select_method": "Select method", "select_method": "Sélectionner la méthode",
"switch_to": "Switch to", "switch_to": "Basculer vers",
"tab_authorization": "Authorization tab", "tab_authorization": "Onglet Autorisation",
"tab_body": "Body tab", "tab_body": "Onglet du corps",
"tab_headers": "Headers tab", "tab_headers": "Onglet En-têtes",
"tab_parameters": "Parameters tab", "tab_parameters": "Onglet Paramètres",
"tab_pre_request_script": "Pre-request script tab", "tab_pre_request_script": "Onglet script de pré-demande",
"tab_query": "Query tab", "tab_query": "Onglet Requête",
"tab_tests": "Tests tab", "tab_tests": "Onglet Tests",
"tab_variables": "Variables tab" "tab_variables": "Onglet Variables"
}, },
"response": { "response": {
"copy": "Copy response", "copy": "Copier la réponse",
"download": "Download response as file", "download": "Télécharger la réponse sous forme de fichier",
"title": "Response" "title": "Réponse"
}, },
"section": { "section": {
"interceptor": "Interceptor", "interceptor": "Intercepteur",
"interface": "Interface", "interface": "Interface",
"theme": "Theme", "theme": "Thème",
"user": "User" "user": "Utilisateur"
}, },
"settings": { "settings": {
"change_interceptor": "Change Interceptor", "change_interceptor": "Changer d'intercepteur",
"change_language": "Change Language", "change_language": "Changer de langue",
"theme": { "theme": {
"black": "Black", "black": "Noir",
"dark": "Dark", "dark": "Sombre",
"light": "Light", "light": "Clair",
"system": "System preference" "system": "Préférence du système"
} }
}, },
"tab": { "tab": {
"close_current": "Close current tab", "close_current": "Fermer l'onglet actuel",
"close_others": "Close all other tabs", "close_others": "Fermer tous les autres onglets",
"duplicate": "Duplicate current tab", "duplicate": "Dupliquer l'onglet actuel",
"new_tab": "Open a new tab", "new_tab": "Ouvrir un nouvel onglet",
"title": "Tabs" "title": "Onglets"
}, },
"workspace": { "workspace": {
"delete": "Delete current team", "delete": "Supprimer l'équipe actuelle",
"edit": "Edit current team", "edit": "Modifier l'équipe actuelle",
"invite": "Invite people to team", "invite": "Inviter les gens à rejoindre l'équipe",
"new": "Create new team", "new": "Créer une nouvelle équipe",
"switch_to_personal": "Switch to your personal workspace", "switch_to_personal": "Passez à votre espace de travail personnel",
"title": "Teams" "title": "Les équipes"
} }
}, },
"sse": { "sse": {
@@ -836,12 +835,12 @@
"disconnected": "Déconnecté", "disconnected": "Déconnecté",
"disconnected_from": "Déconnecté de {name}", "disconnected_from": "Déconnecté de {name}",
"docs_generated": "Documentation générée", "docs_generated": "Documentation générée",
"download_failed": "Download failed",
"download_started": "Téléchargement commencé", "download_started": "Téléchargement commencé",
"download_failed": "Téléchargement échoué",
"enabled": "Active", "enabled": "Active",
"file_imported": "Fichier importé", "file_imported": "Fichier importé",
"finished_in": "Terminé en {duration} ms", "finished_in": "Terminé en {duration} ms",
"hide": "Hide", "hide": "Cacher",
"history_deleted": "Historique supprimé", "history_deleted": "Historique supprimé",
"linewrap": "Retour à la ligne", "linewrap": "Retour à la ligne",
"loading": "Chargement...", "loading": "Chargement...",
@@ -852,7 +851,7 @@
"published_error": "Quelque chose s'est mal passé lors de la publication du message : {topic} dans le sujet : {message}", "published_error": "Quelque chose s'est mal passé lors de la publication du message : {topic} dans le sujet : {message}",
"published_message": "Message publié : {message} au sujet : {topic}", "published_message": "Message publié : {message} au sujet : {topic}",
"reconnection_error": "Échec de la reconnexion", "reconnection_error": "Échec de la reconnexion",
"show": "Show", "show": "Afficher",
"subscribed_failed": "Échec de l'inscription au sujet : {topic}", "subscribed_failed": "Échec de l'inscription au sujet : {topic}",
"subscribed_success": "Inscription réussie au sujet : {topic}", "subscribed_success": "Inscription réussie au sujet : {topic}",
"unsubscribed_failed": "Échec de la désinscription du sujet : {topic}", "unsubscribed_failed": "Échec de la désinscription du sujet : {topic}",
@@ -861,7 +860,7 @@
}, },
"support": { "support": {
"changelog": "En savoir plus sur les dernières versions", "changelog": "En savoir plus sur les dernières versions",
"chat": "Des questions? Discutez avec nous!", "chat": "Des questions ? Discutez avec nous!",
"community": "Posez des questions et aidez les autres", "community": "Posez des questions et aidez les autres",
"documentation": "En savoir plus sur Hoppscotch", "documentation": "En savoir plus sur Hoppscotch",
"forum": "Posez des questions et obtenez des réponses", "forum": "Posez des questions et obtenez des réponses",
@@ -874,21 +873,21 @@
"tab": { "tab": {
"authorization": "Autorisation", "authorization": "Autorisation",
"body": "Corps", "body": "Corps",
"close": "Close Tab", "close": "Fermer l'onglet",
"close_others": "Close other Tabs", "close_others": "Fermer les autres onglets",
"collections": "Collections", "collections": "Collections",
"documentation": "Documentation", "documentation": "Documentation",
"duplicate": "Duplicate Tab", "duplicate": "Dupliquer l'onglet",
"environments": "Environments", "environments": "Environments",
"headers": "En-têtes", "headers": "En-têtes",
"history": "Histoire", "history": "Historique",
"mqtt": "MQTT", "mqtt": "MQTT",
"parameters": "Paramètres", "parameters": "Paramètres",
"pre_request_script": "Script de pré-requête", "pre_request_script": "Script de pré-requête",
"queries": "Requêtes", "queries": "Requêtes",
"query": "Requête", "query": "Requête",
"schema": "Schema", "schema": "Schema",
"shared_requests": "Shared Requests", "shared_requests": "Requêtes partagées",
"socketio": "Socket.IO", "socketio": "Socket.IO",
"sse": "ESS", "sse": "ESS",
"tests": "Tests", "tests": "Tests",
@@ -905,7 +904,6 @@
"email_do_not_match": "L'email ne correspond pas aux détails de votre compte. Contactez le propriétaire de votre équipe.", "email_do_not_match": "L'email ne correspond pas aux détails de votre compte. Contactez le propriétaire de votre équipe.",
"exit": "Quitter l'équipe", "exit": "Quitter l'équipe",
"exit_disabled": "Seul le propriétaire ne peut pas quitter l'équipe", "exit_disabled": "Seul le propriétaire ne peut pas quitter l'équipe",
"failed_invites": "Failed invites",
"invalid_coll_id": "Invalid collection ID", "invalid_coll_id": "Invalid collection ID",
"invalid_email_format": "Le format de l'e-mail n'est pas valide", "invalid_email_format": "Le format de l'e-mail n'est pas valide",
"invalid_id": "L'email ne correspond pas aux détails de votre compte. Contactez le propriétaire de votre équipe.", "invalid_id": "L'email ne correspond pas aux détails de votre compte. Contactez le propriétaire de votre équipe.",
@@ -941,21 +939,22 @@
"no_request_found": "Request not found.", "no_request_found": "Request not found.",
"not_found": "Équipe non trouvée. Contactez le propriétaire de votre équipe.", "not_found": "Équipe non trouvée. Contactez le propriétaire de votre équipe.",
"not_valid_viewer": "Vous n'êtes pas un visionneur valide. Contactez le propriétaire de votre équipe.", "not_valid_viewer": "Vous n'êtes pas un visionneur valide. Contactez le propriétaire de votre équipe.",
"parent_coll_move": "Cannot move collection to a child collection", "parent_coll_move": "Impossible de déplacer une collection vers une collection enfant",
"success_invites": "Les invitations réussites",
"pending_invites": "Invitations en attente", "pending_invites": "Invitations en attente",
"failed_invites": "Échec des invitations",
"permissions": "Autorisations", "permissions": "Autorisations",
"same_target_destination": "Same target and destination", "same_target_destination": "me destinataire et même cible",
"saved": "Équipe enregistrée", "saved": "Équipe enregistrée",
"select_a_team": "Choisir une équipe", "select_a_team": "Choisir une équipe",
"success_invites": "Success invites",
"title": "Équipes", "title": "Équipes",
"we_sent_invite_link": "Nous avons envoyé un lien d'invitation à tous les invités !", "we_sent_invite_link": "Nous avons envoyé un lien d'invitation à tous les invités !",
"we_sent_invite_link_description": "Demandez à tous les invités de vérifier leur boîte de réception. Cliquez sur le lien pour rejoindre l'équipe." "we_sent_invite_link_description": "Demandez à tous les invités de vérifier leur boîte de réception. Cliquez sur le lien pour rejoindre l'équipe."
}, },
"team_environment": { "team_environment": {
"deleted": "Environment Deleted", "deleted": "Environment supprimé",
"duplicate": "Environment Duplicated", "duplicate": "Environment dupliqué",
"not_found": "Environment not found." "not_found": "Environment non trouvé"
}, },
"test": { "test": {
"failed": "Test échoué", "failed": "Test échoué",
@@ -975,10 +974,10 @@
"url": "URL" "url": "URL"
}, },
"workspace": { "workspace": {
"change": "Change workspace", "change": "Changer d'espace de travail",
"personal": "My Workspace", "personal": "Mon espace de travail",
"team": "Team Workspace", "team": "Espace de travail de l'équipe",
"title": "Workspaces" "title": "Espaces de travail"
}, },
"shortcodes": { "shortcodes": {
"actions": "Actions", "actions": "Actions",

View File

@@ -1,17 +1,17 @@
{ {
"action": { "action": {
"add": "Add", "add": "Hozzáadás",
"autoscroll": "Automatikus görgetés", "autoscroll": "Automatikus görgetés",
"cancel": "Mégse", "cancel": "Mégse",
"choose_file": "Válasszon egy fájlt", "choose_file": "Válasszon egy fájlt",
"clear": "Törlés", "clear": "Törlés",
"clear_all": "Összes törlése", "clear_all": "Összes törlése",
"clear_history": "Clear all History", "clear_history": "Összes előzmény törlése",
"close": "Bezárás", "close": "Bezárás",
"connect": "Kapcsolódás", "connect": "Kapcsolódás",
"connecting": "Kapcsolódás", "connecting": "Kapcsolódás",
"copy": "Másolás", "copy": "Másolás",
"create": "Create", "create": "Létrehozás",
"delete": "Törlés", "delete": "Törlés",
"disconnect": "Leválasztás", "disconnect": "Leválasztás",
"dismiss": "Eltüntetés", "dismiss": "Eltüntetés",
@@ -33,16 +33,16 @@
"open_workspace": "Munkaterület megnyitása", "open_workspace": "Munkaterület megnyitása",
"paste": "Beillesztés", "paste": "Beillesztés",
"prettify": "Csinosítás", "prettify": "Csinosítás",
"properties": "Properties", "properties": "Tulajdonságok",
"remove": "Eltávolítás", "remove": "Eltávolítás",
"rename": "Rename", "rename": "Átnevezés",
"restore": "Visszaállítás", "restore": "Visszaállítás",
"save": "Mentés", "save": "Mentés",
"scroll_to_bottom": "Görgetés az aljára", "scroll_to_bottom": "Görgetés az aljára",
"scroll_to_top": "Görgetés a tetejére", "scroll_to_top": "Görgetés a tetejére",
"search": "Keresés", "search": "Keresés",
"send": "Küldés", "send": "Küldés",
"share": "Share", "share": "Megosztás",
"start": "Indítás", "start": "Indítás",
"starting": "Indítás", "starting": "Indítás",
"stop": "Leállítás", "stop": "Leállítás",
@@ -61,9 +61,9 @@
"app": { "app": {
"chat_with_us": "Csevegjen velünk", "chat_with_us": "Csevegjen velünk",
"contact_us": "Lépjen kapcsolatba velünk", "contact_us": "Lépjen kapcsolatba velünk",
"cookies": "Cookies", "cookies": "Sütik",
"copy": "Másolás", "copy": "Másolás",
"copy_interface_type": "Copy interface type", "copy_interface_type": "Interface típusának másolása",
"copy_user_id": "Felhasználó-hitelesítési token másolása", "copy_user_id": "Felhasználó-hitelesítési token másolása",
"developer_option": "Fejlesztői beállítások", "developer_option": "Fejlesztői beállítások",
"developer_option_description": "Fejlesztői eszközök, amelyek segítenek a Hoppscotch fejlesztésében és karbantartásában.", "developer_option_description": "Fejlesztői eszközök, amelyek segítenek a Hoppscotch fejlesztésében és karbantartásában.",
@@ -79,15 +79,15 @@
"keyboard_shortcuts": "Gyorsbillentyűk", "keyboard_shortcuts": "Gyorsbillentyűk",
"name": "Hoppscotch", "name": "Hoppscotch",
"new_version_found": "Új verzió található. Töltse újra az oldalt a frissítéshez.", "new_version_found": "Új verzió található. Töltse újra az oldalt a frissítéshez.",
"open_in_hoppscotch": "Open in Hoppscotch", "open_in_hoppscotch": "Megnyitás Hoppscotch-ban.",
"options": "Beállítások", "options": "Beállítások",
"proxy_privacy_policy": "Proxy adatvédelmi irányelvei", "proxy_privacy_policy": "Proxy adatvédelmi irányelvei",
"reload": "Újratöltés", "reload": "Újratöltés",
"search": "Keresés", "search": "Keresés",
"share": "Megosztás", "share": "Megosztás",
"shortcuts": "Gyorsbillentyűk", "shortcuts": "Gyorsbillentyűk",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", "social_description": "Kövess minket a közösségi médiában, hogy ne maradj le a hírekről, frissítésekről és új kiadásokról.",
"social_links": "Social links", "social_links": "Közösségi média linkek",
"spotlight": "Reflektorfény", "spotlight": "Reflektorfény",
"status": "Állapot", "status": "Állapot",
"status_description": "A weboldal állapotának ellenőrzése", "status_description": "A weboldal állapotának ellenőrzése",
@@ -119,27 +119,27 @@
}, },
"authorization": { "authorization": {
"generate_token": "Token előállítása", "generate_token": "Token előállítása",
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", "graphql_headers": "Azonosító fejléc connection_init tartalmaként elküldve",
"include_in_url": "Felvétel az URL-be", "include_in_url": "Felvétel az URL-be",
"inherited_from": "Inherited from {auth} from Parent Collection {collection} ", "inherited_from": "Örökölt a(z) {auth}-tól, a(z) {collection} gyűjteményből ",
"learn": "Tudja meg, hogyan", "learn": "Tudja meg, hogyan",
"oauth": { "oauth": {
"redirect_auth_server_returned_error": "Auth Server returned an error state", "redirect_auth_server_returned_error": "Az Auth szerver hibás állapottal tért vissza",
"redirect_auth_token_request_failed": "Request to get the auth token failed", "redirect_auth_token_request_failed": "Kérés az auth token lekéréséhez sikertelen",
"redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", "redirect_auth_token_request_invalid_response": "Érvénytelen válasz a Token Endpoint-tól, az auth token lekérésekpr",
"redirect_invalid_state": "Invalid State value present in the redirect", "redirect_invalid_state": "Érvénytelen állapotérték az átirányításban",
"redirect_no_auth_code": "No Authorization Code present in the redirect", "redirect_no_auth_code": "Nincs azonosítás az átirányításban",
"redirect_no_client_id": "No Client ID defined", "redirect_no_client_id": "Nincs felhasználó azonosító",
"redirect_no_client_secret": "No Client Secret Defined", "redirect_no_client_secret": "Nincs felhasználó jelszó",
"redirect_no_code_verifier": "No Code Verifier Defined", "redirect_no_code_verifier": "Nincs kódellenőrző",
"redirect_no_token_endpoint": "No Token Endpoint Defined", "redirect_no_token_endpoint": "Nincs \"Token Endpoint\"",
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_oauth_redirect": "Valami rosszul sikerült az OAuth átirányításakor",
"something_went_wrong_on_token_generation": "Something went wrong on token generation", "something_went_wrong_on_token_generation": "Valami rosszul sikerült a token generálásakor",
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" "token_generation_oidc_discovery_failed": "Hiba a token generálásakor: OpenID Connect Discovery hiba"
}, },
"pass_key_by": "Átadta", "pass_key_by": "Átadta",
"password": "Jelszó", "password": "Jelszó",
"save_to_inherit": "Please save this request in any collection to inherit the authorization", "save_to_inherit": "Kérjük, mentse el ezt kérést bármelyik gyűjteménybe, hogy az azonosítás örökölhető lehessen",
"token": "Token", "token": "Token",
"type": "Felhatalmazás típusa", "type": "Felhatalmazás típusa",
"username": "Felhasználónév" "username": "Felhasználónév"
@@ -148,7 +148,7 @@
"created": "Gyűjtemény létrehozva", "created": "Gyűjtemény létrehozva",
"different_parent": "Nem lehet átrendezni a különböző szülővel rendelkező gyűjteményt", "different_parent": "Nem lehet átrendezni a különböző szülővel rendelkező gyűjteményt",
"edit": "Gyűjtemény szerkesztése", "edit": "Gyűjtemény szerkesztése",
"import_or_create": "Import or create a collection", "import_or_create": "Gyűjtemény importálása vagy létrehozása",
"invalid_name": "Adjon nevet a gyűjteménynek", "invalid_name": "Adjon nevet a gyűjteménynek",
"invalid_root_move": "A gyűjtemény már a gyökérben van", "invalid_root_move": "A gyűjtemény már a gyökérben van",
"moved": "Sikeresen áthelyezve", "moved": "Sikeresen áthelyezve",
@@ -157,20 +157,20 @@
"name_length_insufficient": "A gyűjtemény nevének legalább 3 karakter hosszúságúnak kell lennie", "name_length_insufficient": "A gyűjtemény nevének legalább 3 karakter hosszúságúnak kell lennie",
"new": "Új gyűjtemény", "new": "Új gyűjtemény",
"order_changed": "Gyűjtemény sorrendje frissítve", "order_changed": "Gyűjtemény sorrendje frissítve",
"properties": "Collection Properties", "properties": "Gyűjtemény tulajdonságok",
"properties_updated": "Collection Properties Updated", "properties_updated": "Gyűjtemény tulajdonságai frissítve",
"renamed": "Gyűjtemény átnevezve", "renamed": "Gyűjtemény átnevezve",
"request_in_use": "A kérés használatban", "request_in_use": "A kérés használatban",
"save_as": "Mentés másként", "save_as": "Mentés másként",
"save_to_collection": "Save to Collection", "save_to_collection": "Mentés egy gyűjteménybe",
"select": "Gyűjtemény kiválasztása", "select": "Gyűjtemény kiválasztása",
"select_location": "Hely kiválasztása", "select_location": "Hely kiválasztása",
"select_team": "Csapat kiválasztása", "select_team": "Csapat kiválasztása",
"team_collections": "Csapat gyűjteményei" "team_collections": "Csapat gyűjteményei"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?", "close_unsaved_tab": "Biztos, hogy bezárja ezt a lapot?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "close_unsaved_tabs": "Biztos, hogy bezárja az összes lapot? {count} elmentetlen lap el fog veszni.",
"exit_team": "Biztosan el szeretné hagyni ezt a csapatot?", "exit_team": "Biztosan el szeretné hagyni ezt a csapatot?",
"logout": "Biztosan ki szeretne jelentkezni?", "logout": "Biztosan ki szeretne jelentkezni?",
"remove_collection": "Biztosan véglegesen törölni szeretné ezt a gyűjteményt?", "remove_collection": "Biztosan véglegesen törölni szeretné ezt a gyűjteményt?",
@@ -178,7 +178,7 @@
"remove_folder": "Biztosan véglegesen törölni szeretné ezt a mappát?", "remove_folder": "Biztosan véglegesen törölni szeretné ezt a mappát?",
"remove_history": "Biztosan véglegesen törölni szeretné az összes előzményt?", "remove_history": "Biztosan véglegesen törölni szeretné az összes előzményt?",
"remove_request": "Biztosan véglegesen törölni szeretné ezt a kérést?", "remove_request": "Biztosan véglegesen törölni szeretné ezt a kérést?",
"remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_shared_request": "Biztos, hogy véglegesen törölni szeretné ezt a megosztott kérést?",
"remove_team": "Biztosan törölni szeretné ezt a csapatot?", "remove_team": "Biztosan törölni szeretné ezt a csapatot?",
"remove_telemetry": "Biztosan ki szeretné kapcsolni a telemetriát?", "remove_telemetry": "Biztosan ki szeretné kapcsolni a telemetriát?",
"request_change": "Biztosan el szeretné vetni a jelenlegi kérést? Minden mentetlen változtatás el fog veszni.", "request_change": "Biztosan el szeretné vetni a jelenlegi kérést? Minden mentetlen változtatás el fog veszni.",
@@ -186,26 +186,26 @@
"sync": "Szeretné visszaállítani a munkaterületét a felhőből? Ez el fogja vetni a helyi folyamatát." "sync": "Szeretné visszaállítani a munkaterületét a felhőből? Ez el fogja vetni a helyi folyamatát."
}, },
"context_menu": { "context_menu": {
"add_parameters": "Add to parameters", "add_parameters": "Paraméterek hozzáadása",
"open_request_in_new_tab": "Open request in new tab", "open_request_in_new_tab": "Kérés megnyitása új lapot",
"set_environment_variable": "Set as variable" "set_environment_variable": "Változóként való beállítás"
}, },
"cookies": { "cookies": {
"modal": { "modal": {
"cookie_expires": "Expires", "cookie_expires": "Lejárat",
"cookie_name": "Name", "cookie_name": "Név",
"cookie_path": "Path", "cookie_path": "Útvonal",
"cookie_string": "Cookie string", "cookie_string": "Süti szöveg",
"cookie_value": "Value", "cookie_value": "Érték",
"empty_domain": "Domain is empty", "empty_domain": "Üres domain",
"empty_domains": "Domain list is empty", "empty_domains": "Domain lista üres",
"enter_cookie_string": "Enter cookie string", "enter_cookie_string": "Süti szövegének megadása",
"interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", "interceptor_no_support": "A kiválasztott interceptor nem támogatja a sütiket. Válasszon ki egy másik interceptor-t és próbálja újra.",
"managed_tab": "Managed", "managed_tab": "Menedzselt",
"new_domain_name": "New domain name", "new_domain_name": "Új domain neve",
"no_cookies_in_domain": "No cookies set for this domain", "no_cookies_in_domain": "Nincs süti beállítva ehhez a domainhez.",
"raw_tab": "Raw", "raw_tab": "Nyers",
"set": "Set a cookie" "set": "Süti beállítása"
} }
}, },
"count": { "count": {
@@ -221,7 +221,7 @@
"generate_message": "Importáljon bármilyen Hoppscotch-gyűjteményt, hogy API-dokumentációt készítsen a folyamat során." "generate_message": "Importáljon bármilyen Hoppscotch-gyűjteményt, hogy API-dokumentációt készítsen a folyamat során."
}, },
"empty": { "empty": {
"authorization": "Ez a kérés nem használ felhatalmazást", "authorization": "Ez a kérés nem használ azonosítást",
"body": "Ennek a kérésnek nincs törzse", "body": "Ennek a kérésnek nincs törzse",
"collection": "A gyűjtemény üres", "collection": "A gyűjtemény üres",
"collections": "A gyűjtemények üresek", "collections": "A gyűjtemények üresek",
@@ -252,39 +252,39 @@
"create_new": "Új környezet létrehozása", "create_new": "Új környezet létrehozása",
"created": "Környezet létrehozva", "created": "Környezet létrehozva",
"deleted": "Környezet törlése", "deleted": "Környezet törlése",
"duplicated": "Environment duplicated", "duplicated": "Környezet duplikálása",
"edit": "Környezet szerkesztése", "edit": "Környezet szerkesztése",
"empty_variables": "No variables", "empty_variables": "Nincs változó",
"global": "Global", "global": "Globális",
"global_variables": "Global variables", "global_variables": "Globális változók",
"import_or_create": "Import or create a environment", "import_or_create": "Környezet importálása vagy létrehozása",
"invalid_name": "Adjon nevet a környezetnek", "invalid_name": "Adjon nevet a környezetnek",
"list": "Environment variables", "list": "Környezeti változók",
"my_environments": "Saját környezetek", "my_environments": "Saját környezetek",
"name": "Name", "name": "Név",
"nested_overflow": "az egymásba ágyazott környezeti változók 10 szintre vannak korlátozva", "nested_overflow": "az egymásba ágyazott környezeti változók 10 szintre vannak korlátozva",
"new": "Új környezet", "new": "Új környezet",
"no_active_environment": "No active environment", "no_active_environment": "Nincs aktív környezet",
"no_environment": "Nincs környezet", "no_environment": "Nincs környezet",
"no_environment_description": "Nem lettek környezetek kiválasztva. Válassza ki, hogy mit kell tenni a következő változókkal.", "no_environment_description": "Nem lettek környezetek kiválasztva. Válassza ki, hogy mit kell tenni a következő változókkal.",
"quick_peek": "Environment Quick Peek", "quick_peek": "Környezet gyors megnézése",
"replace_with_variable": "Replace with variable", "replace_with_variable": "Cserélje le egy változóra",
"scope": "Scope", "scope": "Hatókör",
"select": "Környezet kiválasztása", "select": "Környezet kiválasztása",
"set": "Set environment", "set": "Környezet beállítása",
"set_as_environment": "Set as environment", "set_as_environment": "Környezetként való beállítás",
"team_environments": "Csapatkörnyezetek", "team_environments": "Csapatkörnyezetek",
"title": "Környezetek", "title": "Környezetek",
"updated": "Környezet frissítve", "updated": "Környezet frissítve",
"value": "Value", "value": "Érték",
"variable": "Variable", "variable": "Változó",
"variable_list": "Változólista" "variable_list": "Változólista"
}, },
"error": { "error": {
"authproviders_load_error": "Unable to load auth providers", "authproviders_load_error": "Nem sikerült betölteni az azonosító szolgáltatókat",
"browser_support_sse": "Úgy tűnik, hogy ez a böngésző nem támogatja a kiszolgáló által küldött eseményeket.", "browser_support_sse": "Úgy tűnik, hogy ez a böngésző nem támogatja a kiszolgáló által küldött eseményeket.",
"check_console_details": "Nézze meg a konzolnaplót a részletekért.", "check_console_details": "Nézze meg a konzolnaplót a részletekért.",
"check_how_to_add_origin": "Check how you can add an origin", "check_how_to_add_origin": "Ellenőrizze, hogy hogyan adhat hozzá forrást",
"curl_invalid_format": "A cURL nincs megfelelően formázva", "curl_invalid_format": "A cURL nincs megfelelően formázva",
"danger_zone": "Veszélyes zóna", "danger_zone": "Veszélyes zóna",
"delete_account": "Az Ön fiókja jelenleg tulajdonos ezekben a csapatokban:", "delete_account": "Az Ön fiókja jelenleg tulajdonos ezekben a csapatokban:",
@@ -300,13 +300,13 @@
"json_prettify_invalid_body": "Nem sikerült csinosítani egy érvénytelen törzset, oldja meg a JSON szintaktikai hibáit, és próbálja újra", "json_prettify_invalid_body": "Nem sikerült csinosítani egy érvénytelen törzset, oldja meg a JSON szintaktikai hibáit, és próbálja újra",
"network_error": "Úgy tűnik, hogy hálózati hiba van. Próbálja újra.", "network_error": "Úgy tűnik, hogy hálózati hiba van. Próbálja újra.",
"network_fail": "Nem sikerült elküldeni a kérést", "network_fail": "Nem sikerült elküldeni a kérést",
"no_collections_to_export": "No collections to export. Please create a collection to get started.", "no_collections_to_export": "Nincs exportálható gyűjtemény. Kérjük, hozzon létre egyet, hogy elkezdhesse.",
"no_duration": "Nincs időtartam", "no_duration": "Nincs időtartam",
"no_environments_to_export": "No environments to export. Please create an environment to get started.", "no_environments_to_export": "Nincs exportálható környezet. Kérjük, hozzon létre egyet, hogy elkezdhesse.",
"no_results_found": "Nincs találat", "no_results_found": "Nincs találat",
"page_not_found": "Ez az oldal nem található", "page_not_found": "Ez az oldal nem található",
"please_install_extension": "Please install the extension and add origin to the extension.", "please_install_extension": "Kérjük telepítse a bővítményt és adja hozzá a forráshoz.",
"proxy_error": "Proxy error", "proxy_error": "Proxy hiba",
"script_fail": "Nem sikerült végrehajtani a kérés előtti parancsfájlt", "script_fail": "Nem sikerült végrehajtani a kérés előtti parancsfájlt",
"something_went_wrong": "Valami elromlott", "something_went_wrong": "Valami elromlott",
"test_script_fail": "Nem sikerült végrehajtani a kérés utáni parancsfájlt" "test_script_fail": "Nem sikerült végrehajtani a kérés utáni parancsfájlt"
@@ -314,7 +314,7 @@
"export": { "export": {
"as_json": "Exportálás JSON formátumban", "as_json": "Exportálás JSON formátumban",
"create_secret_gist": "Titkos Gist létrehozása", "create_secret_gist": "Titkos Gist létrehozása",
"failed": "Something went wrong while exporting", "failed": "Valami hiba történt az exportálás közben",
"gist_created": "Gist létrehozva", "gist_created": "Gist létrehozva",
"require_github": "Jelentkezzen be GitHub használatával a titkos Gist létrehozásához", "require_github": "Jelentkezzen be GitHub használatával a titkos Gist létrehozásához",
"title": "Exportálás" "title": "Exportálás"
@@ -333,16 +333,16 @@
"renamed": "Mappa átnevezve" "renamed": "Mappa átnevezve"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", "connection_switch_confirm": "Szeretne csatlakozni a legújabb GraphQL végponttal?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", "connection_switch_new_url": "A tab váltása lecsatlakoztatta az aktív GraphQL kapcsolatról. Az új kapcsolat",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", "connection_switch_url": "Kapcsolódott a GraphQL végponthoz. A kapcsolat",
"mutations": "Mutációk", "mutations": "Mutációk",
"schema": "Séma", "schema": "Séma",
"subscriptions": "Feliratkozások", "subscriptions": "Feliratkozások",
"switch_connection": "Switch connection" "switch_connection": "Kapcsolat váltása"
}, },
"graphql_collections": { "graphql_collections": {
"title": "GraphQL Collections" "title": "GraphQL gyűjtemény"
}, },
"group": { "group": {
"time": "Idő", "time": "Idő",
@@ -354,9 +354,9 @@
"save_workspace": "Saját munkaterület mentése" "save_workspace": "Saját munkaterület mentése"
}, },
"helpers": { "helpers": {
"authorization": "A felhatalmazási fejléc automatikusan elő lesz állítva a kérés elküldésekor.", "authorization": "Az Azonosítás fejléc automatikusan elő lesz állítva a kérés elküldésekor.",
"collection_properties_authorization": " This authorization will be set for every request in this collection.", "collection_properties_authorization": " Ez az azonosítás be lesz állítva minden kéréshez ebben a gyűjteményben.",
"collection_properties_header": "This header will be set for every request in this collection.", "collection_properties_header": "Ez a fejléc be lesz állítva mint minden kéréshez ebben a gyűjteményben.",
"generate_documentation_first": "Először állítsa elő a dokumentációt", "generate_documentation_first": "Először állítsa elő a dokumentációt",
"network_fail": "Nem lehet elérni az API-végpontot. Ellenőrizze a hálózati kapcsolatot vagy válasszon egy másik elfogót, és próbálja újra.", "network_fail": "Nem lehet elérni az API-végpontot. Ellenőrizze a hálózati kapcsolatot vagy válasszon egy másik elfogót, és próbálja újra.",
"offline": "Úgy tűnik, hogy kapcsolat nélküli módban van. Előfordulhat, hogy a munkaterületen lévő adatok nem naprakészek.", "offline": "Úgy tűnik, hogy kapcsolat nélküli módban van. Előfordulhat, hogy a munkaterületen lévő adatok nem naprakészek.",
@@ -376,11 +376,11 @@
"import": { "import": {
"collections": "Gyűjtemények importálása", "collections": "Gyűjtemények importálása",
"curl": "cURL importálása", "curl": "cURL importálása",
"environments_from_gist": "Import From Gist", "environments_from_gist": "Importálás Gist-ből",
"environments_from_gist_description": "Import Hoppscotch Environments From Gist", "environments_from_gist_description": "Hoppscotch környezetek importálása Gist-ből",
"failed": "Hiba az importálás során: a formátum nem azonosítható", "failed": "Hiba az importálás során: a formátum nem azonosítható",
"from_file": "Import from File", "from_file": "Importálás fájlból",
"from_gist": "Importálás Gistből", "from_gist": "Importálás Gist-ből",
"from_gist_description": "Importálás Gist URL-ből", "from_gist_description": "Importálás Gist URL-ből",
"from_insomnia": "Importálás Insomniából", "from_insomnia": "Importálás Insomniából",
"from_insomnia_description": "Importálás Insomnia-gyűjteményből", "from_insomnia_description": "Importálás Insomnia-gyűjteményből",
@@ -390,45 +390,45 @@
"from_my_collections_description": "Importálás saját gyűjtemények fájlból", "from_my_collections_description": "Importálás saját gyűjtemények fájlból",
"from_openapi": "Importálás OpenAPI-ból", "from_openapi": "Importálás OpenAPI-ból",
"from_openapi_description": "Importálás OpenAPI specifikációs fájlból (YML/JSON)", "from_openapi_description": "Importálás OpenAPI specifikációs fájlból (YML/JSON)",
"from_postman": "Importálás Postmanből", "from_postman": "Importálás Postman-ből",
"from_postman_description": "Importálás Postman-gyűjteményből", "from_postman_description": "Importálás Postman-gyűjteményből",
"from_url": "Importálás URL-ből", "from_url": "Importálás URL-ből",
"gist_url": "Gist URL megadása", "gist_url": "Gist URL megadása",
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist", "gql_collections_from_gist_description": "GraphQL gyűjtemények importálása Gist-ből",
"hoppscotch_environment": "Hoppscotch Environment", "hoppscotch_environment": "Hoppscotch környezet",
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", "hoppscotch_environment_description": "Hoppscotch környezet importálása JSON fájlból",
"import_from_url_invalid_fetch": "Nem sikerült lekérni az adatokat az URL-ről", "import_from_url_invalid_fetch": "Nem sikerült lekérni az adatokat az URL-ről",
"import_from_url_invalid_file_format": "Hiba a gyűjtemények importálása során", "import_from_url_invalid_file_format": "Hiba a gyűjtemények importálása során",
"import_from_url_invalid_type": "Nem támogatott típus. Az elfogadott értékek: „hoppscotch”, „openapi”, „postman” vagy „insomnia”.", "import_from_url_invalid_type": "Nem támogatott típus. Az elfogadott értékek: „hoppscotch”, „openapi”, „postman” vagy „insomnia”.",
"import_from_url_success": "Gyűjtemények importálva", "import_from_url_success": "Gyűjtemények importálva",
"insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", "insomnia_environment_description": "Insomnia környezet importálása JSON/YAML fájlból",
"json_description": "Gyűjtemények importálása Hoppscotch-gyűjtemények JSON-fájlból", "json_description": "Gyűjtemények importálása Hoppscotch-gyűjtemények JSON-fájlból",
"postman_environment": "Postman Environment", "postman_environment": "Postman környezet",
"postman_environment_description": "Import Postman Environment from a JSON file", "postman_environment_description": "Postman környezet importálása JSON fájlból",
"title": "Importálás" "title": "Importálás"
}, },
"inspections": { "inspections": {
"description": "Inspect possible errors", "description": "Lehetséges hibák ellenőrzése",
"environment": { "environment": {
"add_environment": "Add to Environment", "add_environment": "Hozzáadás a környezethez",
"not_found": "Environment variable “{environment}” not found." "not_found": "\"{environment}\" környezet nem található."
}, },
"header": { "header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." "cookie": "A böngésző nem engedélyezi a Hoppscotch-nak, hogy süti fejlécet állítson be. Amíg dolgozunk a Hoppscotch asztali alkalmazáson (hamarosan), kérjük használjon Authorization Header fejlécet."
}, },
"response": { "response": {
"401_error": "Please check your authentication credentials.", "401_error": "Kérjük ellenőrizze az autentikációs adatokat.",
"404_error": "Please check your request URL and method type.", "404_error": "Kérjük ellenőrizze a kérés URL-jét és típusát.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.", "cors_error": "Kérjük ellenőrizze a Cross-Origin Resource Sharing beállítást.",
"default_error": "Please check your request.", "default_error": "Kérjük ellenőrizze a kérését.",
"network_error": "Please check your network connection." "network_error": "Kérjük ellenőrizze a internet elérhetőségét."
}, },
"title": "Inspector", "title": "Inspector",
"url": { "url": {
"extension_not_installed": "Extension not installed.", "extension_not_installed": "Bővítmény nincs telepítve.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", "extension_unknown_origin": "Ellenőrizze, hogy hozzáadta az API végpont forrását Hoppscotch Browser bővítmény listájához.",
"extention_enable_action": "Enable Browser Extension", "extention_enable_action": "Bővítmény engedélyezése",
"extention_not_enabled": "Extension not enabled." "extention_not_enabled": "Bővítmény nincs engedélyezve."
} }
}, },
"layout": { "layout": {
@@ -442,10 +442,10 @@
"close_unsaved_tab": "Elmentetlen változtatásai vannak", "close_unsaved_tab": "Elmentetlen változtatásai vannak",
"collections": "Gyűjtemények", "collections": "Gyűjtemények",
"confirm": "Megerősítés", "confirm": "Megerősítés",
"customize_request": "Customize Request", "customize_request": "Kérés testreszabása",
"edit_request": "Kérés szerkesztése", "edit_request": "Kérés szerkesztése",
"import_export": "Importálás és exportálás", "import_export": "Importálás és exportálás",
"share_request": "Share Request" "share_request": "Kérés megosztása"
}, },
"mqtt": { "mqtt": {
"already_subscribed": "Ön már feliratkozott erre a témára.", "already_subscribed": "Ön már feliratkozott erre a témára.",
@@ -511,7 +511,7 @@
}, },
"request": { "request": {
"added": "Kérés hozzáadva", "added": "Kérés hozzáadva",
"authorization": "Felhatalmazás", "authorization": "Azonosítás",
"body": "Kérés törzse", "body": "Kérés törzse",
"choose_language": "Nyelv kiválasztása", "choose_language": "Nyelv kiválasztása",
"content_type": "Tartalom típusa", "content_type": "Tartalom típusa",
@@ -526,8 +526,8 @@
"enter_curl": "cURL-parancs megadása", "enter_curl": "cURL-parancs megadása",
"generate_code": "Kód előállítása", "generate_code": "Kód előállítása",
"generated_code": "Előállított kód", "generated_code": "Előállított kód",
"go_to_authorization_tab": "Go to Authorization tab", "go_to_authorization_tab": "Navigálás az Azonosítás lapra",
"go_to_body_tab": "Go to Body tab", "go_to_body_tab": "Navigálás a Törzs lapra.",
"header_list": "Fejléclista", "header_list": "Fejléclista",
"invalid_name": "Adjon nevet a kérésnek", "invalid_name": "Adjon nevet a kérésnek",
"method": "Módszer", "method": "Módszer",
@@ -552,8 +552,8 @@
"saved": "Kérés elmentve", "saved": "Kérés elmentve",
"share": "Megosztás", "share": "Megosztás",
"share_description": "A Hoppscotch megosztása az ismerőseivel", "share_description": "A Hoppscotch megosztása az ismerőseivel",
"share_request": "Share Request", "share_request": "Kérés megosztása",
"stop": "Stop", "stop": "Leállítás",
"title": "Kérés", "title": "Kérés",
"type": "Kérés típusa", "type": "Kérés típusa",
"url": "URL", "url": "URL",
@@ -587,7 +587,7 @@
"account_description": "A fiókbeállítások személyre szabása.", "account_description": "A fiókbeállítások személyre szabása.",
"account_email_description": "Az Ön elsődleges e-mail-címe.", "account_email_description": "Az Ön elsődleges e-mail-címe.",
"account_name_description": "Ez a megjelenített neve.", "account_name_description": "Ez a megjelenített neve.",
"additional": "Additional Settings", "additional": "További beállítások",
"background": "Háttér", "background": "Háttér",
"black_mode": "Fekete", "black_mode": "Fekete",
"choose_language": "Nyelv kiválasztása", "choose_language": "Nyelv kiválasztása",
@@ -635,29 +635,29 @@
"verify_email": "E-mail-cím ellenőrzése" "verify_email": "E-mail-cím ellenőrzése"
}, },
"shared_requests": { "shared_requests": {
"button": "Button", "button": "Gomb",
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", "button_info": "Hozza létre a 'Futtatás Hoppscotch-ban' gombot a weboldalán vagy blogján.",
"copy_html": "Copy HTML", "copy_html": "HTML másolása",
"copy_link": "Copy Link", "copy_link": "Link másolása",
"copy_markdown": "Copy Markdown", "copy_markdown": "Jelölő másolása",
"creating_widget": "Creating widget", "creating_widget": "Widget létrehozása",
"customize": "Customize", "customize": "Testreszabás",
"deleted": "Shared request deleted", "deleted": "Megosztott kérés törölve",
"description": "Select a widget, you can change and customize this later", "description": "Válasszon ki egy widgetet, ezt később módosíthatja és testreszabhatja",
"embed": "Embed", "embed": "Beágyazás",
"embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", "embed_info": "Adja hozzá a \"Hoppscotch API Playground\"-ot weboldalához vagy blogjához.",
"link": "Link", "link": "Link",
"link_info": "Create a shareable link to share with anyone on the internet with view access.", "link_info": "Link létrehozása olvasási joggal való megosztáshoz.",
"modified": "Shared request modified", "modified": "Megosztott kérés módosítva",
"not_found": "Shared request not found", "not_found": "Megosztott kérés nem található.",
"open_new_tab": "Open in new tab", "open_new_tab": "Megnyitás új lapon.",
"preview": "Preview", "preview": "Előnézet",
"run_in_hoppscotch": "Run in Hoppscotch", "run_in_hoppscotch": "Futtatás Hoppscotch-ban.",
"theme": { "theme": {
"dark": "Dark", "dark": "Sötét",
"light": "Light", "light": "Világos",
"system": "System", "system": "Rendszer",
"title": "Theme" "title": "Téma"
} }
}, },
"shortcut": { "shortcut": {
@@ -669,7 +669,7 @@
"title": "Általános" "title": "Általános"
}, },
"miscellaneous": { "miscellaneous": {
"invite": "Emberek meghívása a Hoppscotchba", "invite": "Emberek meghívása a Hoppscotch-ba",
"title": "Egyebek" "title": "Egyebek"
}, },
"navigation": { "navigation": {
@@ -691,19 +691,19 @@
"delete_method": "DELETE módszer kiválasztása", "delete_method": "DELETE módszer kiválasztása",
"get_method": "GET módszer kiválasztása", "get_method": "GET módszer kiválasztása",
"head_method": "HEAD módszer kiválasztása", "head_method": "HEAD módszer kiválasztása",
"import_curl": "Import cURL", "import_curl": "cURL importálása",
"method": "Módszer", "method": "Módszer",
"next_method": "Következő módszer kiválasztása", "next_method": "Következő módszer kiválasztása",
"post_method": "POST módszer kiválasztása", "post_method": "POST módszer kiválasztása",
"previous_method": "Előző módszer kiválasztása", "previous_method": "Előző módszer kiválasztása",
"put_method": "PUT módszer kiválasztása", "put_method": "PUT módszer kiválasztása",
"rename": "Rename Request", "rename": "Kérés átnevezése",
"reset_request": "Kérés visszaállítása", "reset_request": "Kérés visszaállítása",
"save_request": "Save Request", "save_request": "Kérés mentése",
"save_to_collections": "Mentés a gyűjteményekbe", "save_to_collections": "Mentés a gyűjteményekbe",
"send_request": "Kérés elküldése", "send_request": "Kérés elküldése",
"share_request": "Share Request", "share_request": "Kérés megosztása",
"show_code": "Generate code snippet", "show_code": "Kódrészlet generálása",
"title": "Kérés", "title": "Kérés",
"copy_request_link": "Kérés hivatkozásának másolása" "copy_request_link": "Kérés hivatkozásának másolása"
}, },
@@ -735,82 +735,82 @@
"url": "URL" "url": "URL"
}, },
"spotlight": { "spotlight": {
"change_language": "Change Language", "change_language": "Nyelv váltása",
"environments": { "environments": {
"delete": "Delete current environment", "delete": "Jelenlegi környezet törlése",
"duplicate": "Duplicate current environment", "duplicate": "Jelenlegi környezet duplikálása",
"duplicate_global": "Duplicate global environment", "duplicate_global": "Globális környezet duplikálása",
"edit": "Edit current environment", "edit": "Jelenlegi környezet szerkesztése",
"edit_global": "Edit global environment", "edit_global": "Globális környezet szerkesztése",
"new": "Create new environment", "new": "Új környezet létrehozása",
"new_variable": "Create a new environment variable", "new_variable": "Új környezeti változó létrehozása",
"title": "Environments" "title": "Környezetek"
}, },
"general": { "general": {
"chat": "Chat with support", "chat": "Üzenet a supportnak",
"help_menu": "Help and support", "help_menu": "Segítség és support",
"open_docs": "Read Documentation", "open_docs": "Dokumentáció olvasása",
"open_github": "Open GitHub repository", "open_github": "GitHub repository megnyitása",
"open_keybindings": "Keyboard shortcuts", "open_keybindings": "Billentyűkombinációk megnyitása",
"social": "Social", "social": "Közösség",
"title": "General" "title": "Általános"
}, },
"graphql": { "graphql": {
"connect": "Connect to server", "connect": "Csatlakozás a szerverhez",
"disconnect": "Disconnect from server" "disconnect": "Lecsatlakozás a szerverről"
}, },
"miscellaneous": { "miscellaneous": {
"invite": "Invite your friends to Hoppscotch", "invite": "Hívja meg barátait a Hoppscotch-ba",
"title": "Miscellaneous" "title": "Egyéb"
}, },
"request": { "request": {
"save_as_new": "Save as new request", "save_as_new": "Mentés új kérésként",
"select_method": "Select method", "select_method": "Módszer kiválasztása",
"switch_to": "Switch to", "switch_to": "Váltás",
"tab_authorization": "Authorization tab", "tab_authorization": "Azonosítás lap",
"tab_body": "Body tab", "tab_body": "Törzs lap",
"tab_headers": "Headers tab", "tab_headers": "Fejlécek lap",
"tab_parameters": "Parameters tab", "tab_parameters": "Paraméterek lap",
"tab_pre_request_script": "Pre-request script tab", "tab_pre_request_script": "Előzetes script lap",
"tab_query": "Query tab", "tab_query": "Kérés lap",
"tab_tests": "Tests tab", "tab_tests": "Tesztek lap",
"tab_variables": "Variables tab" "tab_variables": "Változók lap"
}, },
"response": { "response": {
"copy": "Copy response", "copy": "Válasz másolása",
"download": "Download response as file", "download": "Válasz letöltése fájlként",
"title": "Response" "title": "Válasz"
}, },
"section": { "section": {
"interceptor": "Interceptor", "interceptor": "Interceptor",
"interface": "Interface", "interface": "Interface",
"theme": "Theme", "theme": "Téma",
"user": "User" "user": "Felhasználó"
}, },
"settings": { "settings": {
"change_interceptor": "Change Interceptor", "change_interceptor": "Interceptor váltása",
"change_language": "Change Language", "change_language": "Nyelv váltása",
"theme": { "theme": {
"black": "Black", "black": "Fekete",
"dark": "Dark", "dark": "Sötét",
"light": "Light", "light": "Világos",
"system": "System preference" "system": "Rendszer"
} }
}, },
"tab": { "tab": {
"close_current": "Close current tab", "close_current": "Jelenlegi lap bezására",
"close_others": "Close all other tabs", "close_others": "Összes többi lap bezására",
"duplicate": "Duplicate current tab", "duplicate": "Jelenlegi lap diplikálása",
"new_tab": "Open a new tab", "new_tab": "Új lap megnyitása",
"title": "Tabs" "title": "Lapok"
}, },
"workspace": { "workspace": {
"delete": "Delete current team", "delete": "Jelenlegi csapat törlése",
"edit": "Edit current team", "edit": "Jelenlegi csapat szerkesztése",
"invite": "Invite people to team", "invite": "Emberek meghívása a jelenlegi csapatba",
"new": "Create new team", "new": "Új csapat létrehozása",
"switch_to_personal": "Switch to your personal workspace", "switch_to_personal": "Váltás a személyes munkaterületére",
"title": "Teams" "title": "Csapatok"
} }
}, },
"sse": { "sse": {
@@ -828,20 +828,20 @@
"connection_error": "Nem sikerült kapcsolódni", "connection_error": "Nem sikerült kapcsolódni",
"connection_failed": "A kapcsolódás sikertelen", "connection_failed": "A kapcsolódás sikertelen",
"connection_lost": "A kapcsolat elveszett", "connection_lost": "A kapcsolat elveszett",
"copied_interface_to_clipboard": "Copied {language} interface type to clipboard", "copied_interface_to_clipboard": "{language} interface típusa vágólapra másolva",
"copied_to_clipboard": "Vágólapra másolva", "copied_to_clipboard": "Vágólapra másolva",
"deleted": "Törölve", "deleted": "Törölve",
"deprecated": "ELAVULT", "deprecated": "ELAVULT",
"disabled": "Letiltva", "disabled": "Letiltva",
"disconnected": "Leválasztva", "disconnected": "Lecsatlakoztatva",
"disconnected_from": "Leválasztva innen: {name}", "disconnected_from": "Lecsatlakoztatva innen: {name}",
"docs_generated": "Dokumentáció előállítva", "docs_generated": "Dokumentáció előállítva",
"download_failed": "Download failed", "download_failed": "Letöltés sikertelen",
"download_started": "A letöltés elkezdődött", "download_started": "A letöltés elkezdődött",
"enabled": "Engedélyezve", "enabled": "Engedélyezve",
"file_imported": "Fájl importálva", "file_imported": "Fájl importálva",
"finished_in": "Befejeződött {duration} ms alatt", "finished_in": "Befejeződött {duration} ms alatt",
"hide": "Hide", "hide": "Elrejtés",
"history_deleted": "Előzmények törölve", "history_deleted": "Előzmények törölve",
"linewrap": "Sorok tördelése", "linewrap": "Sorok tördelése",
"loading": "Betöltés…", "loading": "Betöltés…",
@@ -872,13 +872,13 @@
"twitter": "Kövessen minket Twitteren" "twitter": "Kövessen minket Twitteren"
}, },
"tab": { "tab": {
"authorization": "Felhatalmazás", "authorization": "Azonosítás",
"body": "Törzs", "body": "Törzs",
"close": "Close Tab", "close": "Lap bezárása",
"close_others": "Close other Tabs", "close_others": "Többi lap bezárása",
"collections": "Gyűjtemények", "collections": "Gyűjtemények",
"documentation": "Dokumentáció", "documentation": "Dokumentáció",
"duplicate": "Duplicate Tab", "duplicate": "Lap duplikálása",
"environments": "Környezetek", "environments": "Környezetek",
"headers": "Fejlécek", "headers": "Fejlécek",
"history": "Előzmények", "history": "Előzmények",
@@ -905,7 +905,7 @@
"email_do_not_match": "Az e-mail-cím nem egyezik a fiókja részleteivel. Vegye fel a kapcsolatot a csapat tulajdonosával.", "email_do_not_match": "Az e-mail-cím nem egyezik a fiókja részleteivel. Vegye fel a kapcsolatot a csapat tulajdonosával.",
"exit": "Kilépés a csapatból", "exit": "Kilépés a csapatból",
"exit_disabled": "Csak a tulajdonos nem léphet ki a csapatból", "exit_disabled": "Csak a tulajdonos nem léphet ki a csapatból",
"failed_invites": "Failed invites", "failed_invites": "Hiba a meghívás közben",
"invalid_coll_id": "Érvénytelen gyűjteményazonosító", "invalid_coll_id": "Érvénytelen gyűjteményazonosító",
"invalid_email_format": "Az e-mail formátuma érvénytelen", "invalid_email_format": "Az e-mail formátuma érvénytelen",
"invalid_id": "Érvénytelen csapatazonosító. Vegye fel a kapcsolatot a csapat tulajdonosával.", "invalid_id": "Érvénytelen csapatazonosító. Vegye fel a kapcsolatot a csapat tulajdonosával.",

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/common", "name": "@hoppscotch/common",
"private": true, "private": true,
"version": "2023.12.6", "version": "2024.3.2",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",
"test": "vitest --run", "test": "vitest --run",
@@ -21,147 +21,147 @@
"do-lintfix": "pnpm run lintfix" "do-lintfix": "pnpm run lintfix"
}, },
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "10.1.0",
"@codemirror/autocomplete": "^6.11.1", "@codemirror/autocomplete": "6.13.0",
"@codemirror/commands": "^6.3.2", "@codemirror/commands": "6.3.3",
"@codemirror/lang-javascript": "^6.2.1", "@codemirror/lang-javascript": "6.2.2",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "6.0.1",
"@codemirror/lang-xml": "^6.0.2", "@codemirror/lang-xml": "6.1.0",
"@codemirror/language": "6.9.3", "@codemirror/language": "6.10.1",
"@codemirror/legacy-modes": "^6.3.3", "@codemirror/legacy-modes": "6.3.3",
"@codemirror/lint": "^6.4.2", "@codemirror/lint": "6.5.0",
"@codemirror/search": "^6.5.5", "@codemirror/search": "6.5.6",
"@codemirror/state": "^6.3.3", "@codemirror/state": "6.4.1",
"@codemirror/view": "^6.22.3", "@codemirror/view": "6.25.1",
"@hoppscotch/codemirror-lang-graphql": "workspace:^", "@hoppscotch/codemirror-lang-graphql": "workspace:^",
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^",
"@hoppscotch/ui": "^0.1.0", "@hoppscotch/ui": "0.1.0",
"@hoppscotch/vue-toasted": "^0.1.0", "@hoppscotch/vue-toasted": "0.1.0",
"@lezer/highlight": "1.2.0", "@lezer/highlight": "1.2.0",
"@unhead/vue": "^1.8.8", "@unhead/vue": "1.8.8",
"@urql/core": "^4.2.0", "@urql/core": "4.2.0",
"@urql/devtools": "^2.0.3", "@urql/devtools": "2.0.3",
"@urql/exchange-auth": "^2.1.6", "@urql/exchange-auth": "2.1.6",
"@urql/exchange-graphcache": "^6.3.3", "@urql/exchange-graphcache": "6.4.0",
"@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-legacy": "4.1.1",
"@vueuse/core": "^10.6.1", "@vueuse/core": "10.7.0",
"acorn-walk": "^8.3.0", "acorn-walk": "8.3.0",
"axios": "^1.6.2", "axios": "1.6.2",
"buffer": "^6.0.3", "buffer": "6.0.3",
"cookie-es": "^1.0.0", "cookie-es": "1.0.0",
"dioc": "^1.0.1", "dioc": "3.0.1",
"esprima": "^4.0.1", "esprima": "4.0.1",
"events": "^3.3.0", "events": "3.3.0",
"fp-ts": "^2.16.1", "fp-ts": "2.16.1",
"globalthis": "^1.0.3", "globalthis": "1.0.3",
"graphql": "^16.8.1", "graphql": "16.8.1",
"graphql-language-service-interface": "^2.10.2", "graphql-language-service-interface": "2.10.2",
"graphql-tag": "^2.12.6", "graphql-tag": "2.12.6",
"httpsnippet": "^3.0.1", "httpsnippet": "3.0.1",
"insomnia-importers": "^3.6.0", "insomnia-importers": "3.6.0",
"io-ts": "^2.2.20", "io-ts": "2.2.20",
"js-yaml": "^4.1.0", "js-yaml": "4.1.0",
"jsonpath-plus": "^7.2.0", "jsonpath-plus": "7.2.0",
"lodash-es": "^4.17.21", "lodash-es": "4.17.21",
"lossless-json": "^3.0.2", "lossless-json": "3.0.2",
"minisearch": "^6.3.0", "minisearch": "6.3.0",
"nprogress": "^0.2.0", "nprogress": "0.2.0",
"paho-mqtt": "^1.1.0", "paho-mqtt": "1.1.0",
"path": "^0.12.7", "path": "0.12.7",
"postman-collection": "^4.3.0", "postman-collection": "4.3.0",
"process": "^0.11.10", "process": "0.11.10",
"qs": "^6.11.2", "qs": "6.11.2",
"quicktype-core": "^23.0.79", "quicktype-core": "23.0.79",
"rxjs": "^7.8.1", "rxjs": "7.8.1",
"set-cookie-parser": "^2.6.0", "set-cookie-parser": "2.6.0",
"set-cookie-parser-es": "^1.0.5", "set-cookie-parser-es": "1.0.5",
"socket.io-client-v2": "npm:socket.io-client@^2.4.0", "socket.io-client-v2": "npm:socket.io-client@2.4.0",
"socket.io-client-v3": "npm:socket.io-client@^3.1.3", "socket.io-client-v3": "npm:socket.io-client@3.1.3",
"socket.io-client-v4": "npm:socket.io-client@^4.4.1", "socket.io-client-v4": "npm:socket.io-client@4.4.1",
"socketio-wildcard": "^2.0.0", "socketio-wildcard": "2.0.0",
"splitpanes": "^3.1.5", "splitpanes": "3.1.5",
"stream-browserify": "^3.0.0", "stream-browserify": "3.0.0",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "0.11.0",
"tern": "^0.24.3", "tern": "0.24.3",
"timers": "^0.1.1", "timers": "0.1.1",
"tippy.js": "^6.3.7", "tippy.js": "6.3.7",
"url": "^0.11.3", "url": "0.11.3",
"util": "^0.12.5", "util": "0.12.5",
"uuid": "^9.0.1", "uuid": "9.0.1",
"verzod": "^0.2.0", "verzod": "0.2.2",
"vue": "^3.3.8", "vue": "3.3.9",
"vue-i18n": "^9.7.1", "vue-i18n": "9.8.0",
"vue-pdf-embed": "^1.2.1", "vue-pdf-embed": "1.2.1",
"vue-router": "^4.2.5", "vue-router": "4.2.5",
"vue-tippy": "6.3.1", "vue-tippy": "6.3.1",
"vuedraggable-es": "^4.1.1", "vuedraggable-es": "4.1.1",
"wonka": "^6.3.4", "wonka": "6.3.4",
"workbox-window": "^7.0.0", "workbox-window": "7.0.0",
"xml-formatter": "^3.6.0", "xml-formatter": "3.6.0",
"yargs-parser": "^21.1.1", "yargs-parser": "21.1.1",
"zod": "^3.22.4" "zod": "3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@esbuild-plugins/node-modules-polyfill": "0.2.2",
"@graphql-codegen/add": "^5.0.0", "@graphql-codegen/add": "5.0.0",
"@graphql-codegen/cli": "^5.0.0", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/typed-document-node": "^5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "^4.0.1", "@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "^4.0.1", "@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql-graphcache": "^3.0.0", "@graphql-codegen/typescript-urql-graphcache": "3.0.0",
"@graphql-codegen/urql-introspection": "^3.0.0", "@graphql-codegen/urql-introspection": "3.0.0",
"@graphql-typed-document-node/core": "^3.2.0", "@graphql-typed-document-node/core": "3.2.0",
"@iconify-json/lucide": "^1.1.141", "@iconify-json/lucide": "1.1.144",
"@intlify/vite-plugin-vue-i18n": "^7.0.0", "@intlify/vite-plugin-vue-i18n": "7.0.0",
"@relmify/jest-fp-ts": "^2.1.1", "@relmify/jest-fp-ts": "2.1.1",
"@rushstack/eslint-patch": "^1.6.0", "@rushstack/eslint-patch": "1.6.0",
"@types/har-format": "^1.2.15", "@types/har-format": "1.2.15",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "4.0.9",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "4.17.12",
"@types/lossless-json": "^1.0.4", "@types/lossless-json": "1.0.4",
"@types/nprogress": "^0.2.3", "@types/nprogress": "0.2.3",
"@types/paho-mqtt": "^1.0.10", "@types/paho-mqtt": "1.0.10",
"@types/postman-collection": "^3.5.10", "@types/postman-collection": "3.5.10",
"@types/splitpanes": "^2.2.6", "@types/splitpanes": "2.2.6",
"@types/uuid": "^9.0.7", "@types/uuid": "9.0.7",
"@types/yargs-parser": "^21.0.3", "@types/yargs-parser": "21.0.3",
"@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/eslint-plugin": "7.3.1",
"@typescript-eslint/parser": "^6.12.0", "@typescript-eslint/parser": "7.3.1",
"@vitejs/plugin-vue": "^4.5.0", "@vitejs/plugin-vue": "4.5.1",
"@vue/compiler-sfc": "^3.3.8", "@vue/compiler-sfc": "3.3.10",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "12.0.0",
"@vue/runtime-core": "^3.3.8", "@vue/runtime-core": "3.3.10",
"autoprefixer": "^10.4.14", "autoprefixer": "10.4.16",
"cross-env": "^7.0.3", "cross-env": "7.0.3",
"dotenv": "^16.3.1", "dotenv": "16.3.1",
"eslint": "^8.54.0", "eslint": "8.57.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-vue": "^9.18.1", "eslint-plugin-vue": "9.24.0",
"glob": "^10.3.10", "glob": "10.3.10",
"npm-run-all": "^4.1.5", "npm-run-all": "4.1.5",
"openapi-types": "^12.1.3", "openapi-types": "12.1.3",
"postcss": "^8.4.23", "postcss": "8.4.31",
"prettier": "^3.1.0", "prettier": "3.1.0",
"prettier-plugin-tailwindcss": "^0.5.7", "prettier-plugin-tailwindcss": "0.5.7",
"rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-polyfill-node": "0.13.0",
"sass": "^1.69.5", "sass": "1.69.5",
"tailwindcss": "^3.3.2", "tailwindcss": "3.3.5",
"typescript": "^5.3.2", "typescript": "5.3.2",
"unplugin-fonts": "^1.1.1", "unplugin-fonts": "1.1.1",
"unplugin-icons": "^0.17.4", "unplugin-icons": "0.17.4",
"unplugin-vue-components": "^0.25.2", "unplugin-vue-components": "0.25.2",
"vite": "^4.5.0", "vite": "4.5.0",
"vite-plugin-checker": "^0.6.2", "vite-plugin-checker": "0.6.2",
"vite-plugin-fonts": "^0.7.0", "vite-plugin-fonts": "0.7.0",
"vite-plugin-html-config": "^1.0.11", "vite-plugin-html-config": "1.0.11",
"vite-plugin-inspect": "^0.7.42", "vite-plugin-inspect": "0.7.42",
"vite-plugin-pages": "^0.31.0", "vite-plugin-pages": "0.31.0",
"vite-plugin-pages-sitemap": "^1.6.1", "vite-plugin-pages-sitemap": "1.6.1",
"vite-plugin-pwa": "^0.17.0", "vite-plugin-pwa": "0.17.3",
"vite-plugin-vue-layouts": "^0.8.0", "vite-plugin-vue-layouts": "0.8.0",
"vitest": "^0.34.6", "vitest": "0.34.6",
"vue-tsc": "^1.8.22" "vue-tsc": "1.8.24"
} }
} }

View File

@@ -1,11 +1,11 @@
// generated by unplugin-vue-components /* eslint-disable */
// We suggest you to commit this file into source control /* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {} export {}
declare module '@vue/runtime-core' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
AppBanner: typeof import('./components/app/Banner.vue')['default'] AppBanner: typeof import('./components/app/Banner.vue')['default']
@@ -31,6 +31,8 @@ declare module '@vue/runtime-core' {
AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default'] AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default']
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default'] AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default'] AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default']
AppSpotlightSearch: typeof import('./components/app/SpotlightSearch.vue')['default']
AppSupport: typeof import('./components/app/Support.vue')['default'] AppSupport: typeof import('./components/app/Support.vue')['default']
Collections: typeof import('./components/collections/index.vue')['default'] Collections: typeof import('./components/collections/index.vue')['default']
CollectionsAdd: typeof import('./components/collections/Add.vue')['default'] CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
@@ -131,6 +133,7 @@ declare module '@vue/runtime-core' {
HttpRequest: typeof import('./components/http/Request.vue')['default'] HttpRequest: typeof import('./components/http/Request.vue')['default']
HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default'] HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default']
HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default'] HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default']
HttpRequestVariables: typeof import('./components/http/RequestVariables.vue')['default']
HttpResponse: typeof import('./components/http/Response.vue')['default'] HttpResponse: typeof import('./components/http/Response.vue')['default']
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default'] HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default'] HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
@@ -145,6 +148,7 @@ declare module '@vue/runtime-core' {
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"]
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
@@ -154,6 +158,7 @@ declare module '@vue/runtime-core' {
IconLucideLayers: typeof import('~icons/lucide/layers')['default'] IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: (typeof import("~icons/lucide/rss"))["default"]
IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default'] IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideX: typeof import('~icons/lucide/x')['default'] IconLucideX: typeof import('~icons/lucide/x')['default']
@@ -209,5 +214,4 @@ declare module '@vue/runtime-core' {
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default'] WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default'] WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
} }
} }

View File

@@ -1,8 +1,8 @@
<template> <template>
<div <div
ref="contextMenuRef" ref="contextMenuRef"
class="fixed translate-y-8 transform rounded border border-dividerDark bg-popover p-2 shadow-lg" class="fixed transform -translate-x-10 -translate-y-8 rounded border border-dividerDark bg-popover p-2 shadow-lg"
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`" :style="`top: ${position.top}px; left: ${position.left}px; z-index: 100;`"
> >
<div v-if="contextMenuOptions" class="flex flex-col"> <div v-if="contextMenuOptions" class="flex flex-col">
<div <div

View File

@@ -21,19 +21,7 @@
</div> </div>
</div> </div>
<div class="col-span-1 flex items-center justify-between space-x-2"> <div class="col-span-1 flex items-center justify-between space-x-2">
<button <AppSpotlightSearch />
class="flex h-full flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
@click="invokeAction('modals.search.toggle', undefined, 'mouseclick')"
>
<span class="inline-flex flex-1 items-center">
<icon-lucide-search class="svg-icons mr-2" />
{{ t("app.search") }}
</span>
<span class="flex space-x-1">
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
<kbd class="shortcut-key">K</kbd>
</span>
</button>
</div> </div>
<div class="col-span-2 flex items-center justify-between space-x-2"> <div class="col-span-2 flex items-center justify-between space-x-2">
<div class="flex"> <div class="flex">
@@ -251,7 +239,6 @@ import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
import { computed, reactive, ref, watch } from "vue" import { computed, reactive, ref, watch } from "vue"
import { useToast } from "~/composables/toast" import { useToast } from "~/composables/toast"
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql" import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { platform } from "~/platform" import { platform } from "~/platform"
import IconDownload from "~icons/lucide/download" import IconDownload from "~icons/lucide/download"
import IconLifeBuoy from "~icons/lucide/life-buoy" import IconLifeBuoy from "~icons/lucide/life-buoy"
@@ -330,11 +317,11 @@ const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
const workspace = workspaceService.currentWorkspace const workspace = workspaceService.currentWorkspace
const workspaceName = computed(() => const workspaceName = computed(() => {
workspace.value.type === "personal" return workspace.value.type === "personal"
? t("workspace.personal") ? t("workspace.personal")
: workspace.value.teamName : workspace.value.teamName
) })
const refetchTeams = () => { const refetchTeams = () => {
teamListAdapter.fetchList() teamListAdapter.fetchList()

View File

@@ -0,0 +1,135 @@
<template>
<div
class="border-animation relative p-[1px] rounded flex-1 self-stretch overflow-hidden flex items-center justify-center"
:class="{
'before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:aspect-square before:w-full before:absolute before:bg-':
!HAS_OPENED_SPOTLIGHT,
}"
aria-hidden="true"
>
<button
class="relative flex flex-1 cursor-text items-center justify-between self-stretch rounded bg-primaryDark px-2 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary overflow-hidden"
@click="
() => {
invokeAction('modals.search.toggle', undefined, 'mouseclick')
!HAS_OPENED_SPOTLIGHT && toggleSetting('HAS_OPENED_SPOTLIGHT')
}
"
>
<span class="inline-flex flex-1 items-center">
<icon-lucide-search class="svg-icons mr-2" />
<span v-if="!HAS_OPENED_SPOTLIGHT" class="flex flex-1">
{{ t("spotlight.phrases.try") }}
<TransitionGroup tag="div" name="list" class="ml-1 relative">
<span
v-for="(phrase, index) in phraseToShow"
:key="phrase.text"
:data-index="index"
class="truncate"
>
"{{ t(phrase.text) }}"
</span>
</TransitionGroup>
</span>
<template v-else>
{{ t("app.search") }}
</template>
</span>
<span class="flex space-x-1">
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
<kbd class="shortcut-key">K</kbd>
</span>
</button>
</div>
</template>
<script lang="ts" setup>
import { watch, computed, ref } from "vue"
import { useI18n } from "~/composables/i18n"
import { useSetting } from "~/composables/settings"
import { invokeAction } from "~/helpers/actions"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { toggleSetting } from "~/newstore/settings"
const t = useI18n()
const HAS_OPENED_SPOTLIGHT = useSetting("HAS_OPENED_SPOTLIGHT")
const phrases = ref([
{ text: "spotlight.phrases.import_collections", show: true },
{ text: "spotlight.phrases.create_environment", show: false },
{ text: "spotlight.phrases.create_workspace", show: false },
{ text: "spotlight.phrases.share_request", show: false },
])
let intervalId: ReturnType<typeof setTimeout> | null = null
//cycle through the phrases
const showNextPhrase = () => {
let i = 0
intervalId = setInterval(() => {
phrases.value[i].show = false
i++
if (i >= phrases.value.length) {
i = 0
}
phrases.value[i].show = true
}, 3000)
}
const stopPhraseInterval = () => {
if (intervalId) clearInterval(intervalId)
}
const phraseToShow = computed(() => {
return phrases.value.filter((phrase) => phrase.show)
})
watch(
HAS_OPENED_SPOTLIGHT,
() => {
!HAS_OPENED_SPOTLIGHT.value ? showNextPhrase() : stopPhraseInterval()
},
{
immediate: true,
}
)
</script>
<style>
/* Transition Classes */
.list-enter-active {
transition: all 1s ease;
}
.list-leave-active {
transition: all 0.4s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(-30px);
}
.list-leave-active {
position: absolute;
}
/* Conic gradient */
.border-animation::before {
background: conic-gradient(
transparent 270deg,
var(--accent-color),
transparent
);
animation: rotate 4s linear infinite;
}
@keyframes rotate {
from {
transform: translate(-50%, -50%) scale(1.4) rotate(0turn);
}
to {
transform: translate(-50%, -50%) scale(1.4) rotate(1turn);
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<span class="flex flex-1 items-center space-x-2">
<template v-for="(title, index) in collectionTitles" :key="index">
<span class="block" :class="{ truncate: index !== 0 }">
{{ title }}
</span>
<icon-lucide-chevron-right class="flex flex-shrink-0" />
</template>
<span
v-if="request"
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold"
:style="{ color: getMethodLabelColor(request.method) }"
>
{{ request.method.toUpperCase() }}
</span>
<span v-if="request" class="block">
{{ request.name }}
</span>
</span>
</template>
<script setup lang="ts">
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
defineProps<{
collectionTitles: string[]
request: {
name: string
method: string
}
}>()
</script>

View File

@@ -111,6 +111,7 @@ import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher" import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher" import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher" import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
import { TeamsSpotlightSearcherService } from "~/services/spotlight/searchers/teamRequest.searcher"
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher" import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
import { import {
SwitchWorkspaceSpotlightSearcherService, SwitchWorkspaceSpotlightSearcherService,
@@ -144,6 +145,7 @@ useService(SwitchEnvSpotlightSearcherService)
useService(WorkspaceSpotlightSearcherService) useService(WorkspaceSpotlightSearcherService)
useService(SwitchWorkspaceSpotlightSearcherService) useService(SwitchWorkspaceSpotlightSearcherService)
useService(InterceptorSpotlightSearcherService) useService(InterceptorSpotlightSearcherService)
useService(TeamsSpotlightSearcherService)
platform.spotlight?.additionalSearchers?.forEach((searcher) => platform.spotlight?.additionalSearchers?.forEach((searcher) =>
useService(searcher) useService(searcher)

View File

@@ -8,7 +8,7 @@
> >
<template #body> <template #body>
<HoppSmartTabs <HoppSmartTabs
v-model="selectedOptionTab" v-model="activeTab"
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4" styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
render-inactive-tabs render-inactive-tabs
> >
@@ -16,7 +16,6 @@
<HttpHeaders <HttpHeaders
v-model="editableCollection" v-model="editableCollection"
:is-collection-property="true" :is-collection-property="true"
@change-tab="changeOptionTab"
/> />
<div <div
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0" class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
@@ -34,6 +33,7 @@
:is-collection-property="true" :is-collection-property="true"
:is-root-collection="editingProperties?.isRootCollection" :is-root-collection="editingProperties?.isRootCollection"
:inherited-properties="editingProperties?.inheritedProperties" :inherited-properties="editingProperties?.inheritedProperties"
:source="source"
/> />
<div <div
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0" class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
@@ -64,28 +64,42 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { watch, ref } from "vue"
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { HoppCollection } from "@hoppscotch/data" import {
import { RESTOptionTabs } from "../http/RequestOptions.vue" GQLHeader,
import { TeamCollection } from "~/helpers/teams/TeamCollection" HoppCollection,
HoppGQLAuth,
HoppRESTAuth,
HoppRESTHeaders,
} from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import { useService } from "dioc/vue"
import { clone } from "lodash-es" import { clone } from "lodash-es"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { ref, watch } from "vue"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { PersistenceService } from "~/services/persistence"
const persistenceService = useService(PersistenceService)
const t = useI18n() const t = useI18n()
type EditingProperties = { export type EditingProperties = {
collection: HoppCollection | TeamCollection | null collection: Partial<HoppCollection> | null
isRootCollection: boolean isRootCollection: boolean
path: string path: string
inheritedProperties: HoppInheritedProperty | undefined inheritedProperties?: HoppInheritedProperty
} }
type HoppCollectionAuth = HoppRESTAuth | HoppGQLAuth
type HoppCollectionHeaders = HoppRESTHeaders | GQLHeader[]
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
show: boolean show: boolean
loadingState: boolean loadingState: boolean
editingProperties: EditingProperties | null editingProperties: EditingProperties | null
source: "REST" | "GraphQL"
modelValue: string
}>(), }>(),
{ {
show: false, show: false,
@@ -95,50 +109,68 @@ const props = withDefaults(
) )
const emit = defineEmits<{ const emit = defineEmits<{
(e: "set-collection-properties", newCollection: any): void (
e: "set-collection-properties",
newCollection: Omit<EditingProperties, "inheritedProperties">
): void
(e: "hide-modal"): void (e: "hide-modal"): void
(e: "update:modelValue"): void
}>() }>()
const editableCollection = ref({ const editableCollection = ref<{
body: { headers: HoppCollectionHeaders
contentType: null, auth: HoppCollectionAuth
body: null, }>({
},
headers: [], headers: [],
auth: { auth: {
authType: "inherit", authType: "inherit",
authActive: false, authActive: false,
}, },
}) as any })
const selectedOptionTab = ref("headers") watch(
editableCollection,
(updatedEditableCollection) => {
if (props.show && props.editingProperties) {
const unsavedCollectionProperties: EditingProperties = {
collection: updatedEditableCollection,
isRootCollection: props.editingProperties?.isRootCollection ?? false,
path: props.editingProperties?.path,
inheritedProperties: props.editingProperties?.inheritedProperties,
}
persistenceService.setLocalConfig(
"unsaved_collection_properties",
JSON.stringify(unsavedCollectionProperties)
)
}
},
{
deep: true,
}
)
const changeOptionTab = (tab: RESTOptionTabs) => { const activeTab = useVModel(props, "modelValue", emit)
selectedOptionTab.value = tab
}
watch( watch(
() => props.show, () => props.show,
(show) => { (show) => {
if (show && props.editingProperties?.collection) { if (show && props.editingProperties?.collection) {
editableCollection.value.auth = clone( editableCollection.value.auth = clone(
props.editingProperties.collection.auth props.editingProperties.collection.auth as HoppCollectionAuth
) )
editableCollection.value.headers = clone( editableCollection.value.headers = clone(
props.editingProperties.collection.headers props.editingProperties.collection.headers as HoppCollectionHeaders
) )
} else { } else {
editableCollection.value = { editableCollection.value = {
body: {
contentType: null,
body: null,
},
headers: [], headers: [],
auth: { auth: {
authType: "inherit", authType: "inherit",
authActive: false, authActive: false,
}, },
} }
persistenceService.removeLocalConfig("unsaved_collection_properties")
} }
} }
) )
@@ -146,7 +178,6 @@ watch(
const saveEditedCollection = () => { const saveEditedCollection = () => {
if (!props.editingProperties) return if (!props.editingProperties) return
const finalCollection = clone(editableCollection.value) const finalCollection = clone(editableCollection.value)
delete finalCollection.body
const collection = { const collection = {
path: props.editingProperties.path, path: props.editingProperties.path,
collection: { collection: {
@@ -155,10 +186,12 @@ const saveEditedCollection = () => {
}, },
isRootCollection: props.editingProperties.isRootCollection, isRootCollection: props.editingProperties.isRootCollection,
} }
emit("set-collection-properties", collection) emit("set-collection-properties", collection as EditingProperties)
persistenceService.removeLocalConfig("unsaved_collection_properties")
} }
const hideModal = () => { const hideModal = () => {
persistenceService.removeLocalConfig("unsaved_collection_properties")
emit("hide-modal") emit("hide-modal")
} }
</script> </script>

View File

@@ -9,7 +9,7 @@
" "
> >
<HoppButtonSecondary <HoppButtonSecondary
v-if="hasNoTeamAccess" v-if="hasNoTeamAccess || isShowingSearchResults"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
disabled disabled
class="!rounded-none" class="!rounded-none"
@@ -36,8 +36,9 @@
v-if="!saveRequest" v-if="!saveRequest"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:disabled=" :disabled="
collectionsType.type === 'team-collections' && (collectionsType.type === 'team-collections' &&
collectionsType.selectedTeam === undefined collectionsType.selectedTeam === undefined) ||
isShowingSearchResults
" "
:icon="IconImport" :icon="IconImport"
:title="t('modal.import_export')" :title="t('modal.import_export')"
@@ -58,7 +59,7 @@
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
:export-loading="exportLoading" :export-loading="exportLoading"
:has-no-team-access="hasNoTeamAccess" :has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
:collection-move-loading="collectionMoveLoading" :collection-move-loading="collectionMoveLoading"
:is-last-item="node.data.isLastItem" :is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
@@ -128,6 +129,14 @@
}) })
} }
" "
@click="
() => {
handleCollectionClick({
collectionID: node.id,
isOpen,
})
}
"
/> />
<CollectionsCollection <CollectionsCollection
v-if="node.data.type === 'folders'" v-if="node.data.type === 'folders'"
@@ -137,7 +146,7 @@
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
:export-loading="exportLoading" :export-loading="exportLoading"
:has-no-team-access="hasNoTeamAccess" :has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
:collection-move-loading="collectionMoveLoading" :collection-move-loading="collectionMoveLoading"
:is-last-item="node.data.isLastItem" :is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
@@ -209,6 +218,15 @@
}) })
} }
" "
@click="
() => {
handleCollectionClick({
// for the folders, we get a path, so we need to get the last part of the path which is the folder id
collectionID: node.id.split('/').pop() as string,
isOpen,
})
}
"
/> />
<CollectionsRequest <CollectionsRequest
v-if="node.data.type === 'requests'" v-if="node.data.type === 'requests'"
@@ -218,7 +236,7 @@
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:duplicate-loading="duplicateLoading" :duplicate-loading="duplicateLoading"
:is-active="isActiveRequest(node.data.data.data.id)" :is-active="isActiveRequest(node.data.data.data.id)"
:has-no-team-access="hasNoTeamAccess" :has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
:request-move-loading="requestMoveLoading" :request-move-loading="requestMoveLoading"
:is-last-item="node.data.isLastItem" :is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
@@ -283,7 +301,15 @@
</template> </template>
<template #emptyNode="{ node }"> <template #emptyNode="{ node }">
<HoppSmartPlaceholder <HoppSmartPlaceholder
v-if="node === null" v-if="filterText.length !== 0 && teamCollectionList.length === 0"
:text="`${t('state.nothing_found')}${filterText}`"
>
<template #icon>
<icon-lucide-search class="svg-icons opacity-75" />
</template>
</HoppSmartPlaceholder>
<HoppSmartPlaceholder
v-else-if="node === null"
:src="`/images/states/${colorMode.value}/pack.svg`" :src="`/images/states/${colorMode.value}/pack.svg`"
:alt="`${t('empty.collections')}`" :alt="`${t('empty.collections')}`"
:text="t('empty.collections')" :text="t('empty.collections')"
@@ -394,6 +420,11 @@ const props = defineProps({
default: () => ({ type: "my-collections", selectedTeam: undefined }), default: () => ({ type: "my-collections", selectedTeam: undefined }),
required: true, required: true,
}, },
filterText: {
type: String as PropType<string>,
default: "",
required: true,
},
teamCollectionList: { teamCollectionList: {
type: Array as PropType<TeamCollection[]>, type: Array as PropType<TeamCollection[]>,
default: () => [], default: () => [],
@@ -436,6 +467,8 @@ const props = defineProps({
}, },
}) })
const isShowingSearchResults = computed(() => props.filterText.length > 0)
const emit = defineEmits<{ const emit = defineEmits<{
( (
event: "add-request", event: "add-request",
@@ -543,6 +576,14 @@ const emit = defineEmits<{
} }
} }
): void ): void
(
event: "collection-click",
payload: {
// if the collection is open or not in the tree
isOpen: boolean
collectionID: string
}
): void
(event: "select", payload: Picked | null): void (event: "select", payload: Picked | null): void
(event: "expand-team-collection", payload: string): void (event: "expand-team-collection", payload: string): void
(event: "display-modal-add"): void (event: "display-modal-add"): void
@@ -555,6 +596,18 @@ const getPath = (path: string) => {
return pathArray.join("/") return pathArray.join("/")
} }
const handleCollectionClick = (payload: {
collectionID: string
isOpen: boolean
}) => {
const { collectionID, isOpen } = payload
emit("collection-click", {
collectionID,
isOpen,
})
}
const teamCollectionsList = toRef(props, "teamCollectionList") const teamCollectionsList = toRef(props, "teamCollectionList")
const hasNoTeamAccess = computed( const hasNoTeamAccess = computed(

View File

@@ -146,8 +146,10 @@
@hide-modal="displayModalImportExport(false)" @hide-modal="displayModalImportExport(false)"
/> />
<CollectionsProperties <CollectionsProperties
v-model="collectionPropertiesModalActiveTab"
:show="showModalEditProperties" :show="showModalEditProperties"
:editing-properties="editingProperties" :editing-properties="editingProperties"
source="GraphQL"
@hide-modal="displayModalEditProperties(false)" @hide-modal="displayModalEditProperties(false)"
@set-collection-properties="setCollectionProperties" @set-collection-properties="setCollectionProperties"
/> />
@@ -155,7 +157,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, ref } from "vue" import { nextTick, onMounted, ref } from "vue"
import { clone, cloneDeep } from "lodash-es" import { clone, cloneDeep } from "lodash-es"
import { import {
graphqlCollections$, graphqlCollections$,
@@ -186,6 +188,11 @@ import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection" import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection"
import { useToast } from "~/composables/toast" import { useToast } from "~/composables/toast"
import { getRequestsByPath } from "~/helpers/collection/request" import { getRequestsByPath } from "~/helpers/collection/request"
import { PersistenceService } from "~/services/persistence"
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
import { EditingProperties } from "../Properties.vue"
import { defineActionHandler } from "~/helpers/actions"
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
@@ -219,7 +226,7 @@ const editingRequest = ref<HoppGQLRequest | null>(null)
const editingRequestIndex = ref<number | null>(null) const editingRequestIndex = ref<number | null>(null)
const editingProperties = ref<{ const editingProperties = ref<{
collection: HoppCollection | null collection: Partial<HoppCollection> | null
isRootCollection: boolean isRootCollection: boolean
path: string path: string
inheritedProperties?: HoppInheritedProperty inheritedProperties?: HoppInheritedProperty
@@ -232,6 +239,53 @@ const editingProperties = ref<{
const filterText = ref("") const filterText = ref("")
const persistenceService = useService(PersistenceService)
const collectionPropertiesModalActiveTab = ref<GQLOptionTabs>("headers")
onMounted(() => {
const localOAuthTempConfig =
persistenceService.getLocalConfig("oauth_temp_config")
if (!localOAuthTempConfig) {
return
}
const { context, source, token }: PersistedOAuthConfig =
JSON.parse(localOAuthTempConfig)
if (source === "REST") {
return
}
if (context?.type === "collection-properties") {
// load the unsaved editing properties
const unsavedCollectionPropertiesString = persistenceService.getLocalConfig(
"unsaved_collection_properties"
)
if (unsavedCollectionPropertiesString) {
const unsavedCollectionProperties: EditingProperties = JSON.parse(
unsavedCollectionPropertiesString
)
const auth = unsavedCollectionProperties.collection?.auth
if (auth?.authType === "oauth-2") {
const grantTypeInfo = auth.grantTypeInfo
grantTypeInfo && (grantTypeInfo.token = token ?? "")
}
editingProperties.value = unsavedCollectionProperties
}
persistenceService.removeLocalConfig("oauth_temp_config")
collectionPropertiesModalActiveTab.value = "authorization"
showModalEditProperties.value = true
}
})
const filteredCollections = computed(() => { const filteredCollections = computed(() => {
const collectionsClone = clone(collections.value) const collectionsClone = clone(collections.value)
@@ -557,7 +611,7 @@ const editProperties = ({
if (collectionIndex === null || collection === null) return if (collectionIndex === null || collection === null) return
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
let inheritedProperties = {} let inheritedProperties = undefined
if (parentIndex) { if (parentIndex) {
const { auth, headers } = cascadeParentCollectionForHeaderAuth( const { auth, headers } = cascadeParentCollectionForHeaderAuth(
@@ -568,7 +622,7 @@ const editProperties = ({
inheritedProperties = { inheritedProperties = {
auth, auth,
headers, headers,
} as HoppInheritedProperty }
} }
editingProperties.value = { editingProperties.value = {
@@ -582,11 +636,15 @@ const editProperties = ({
} }
const setCollectionProperties = (newCollection: { const setCollectionProperties = (newCollection: {
collection: HoppCollection collection: Partial<HoppCollection> | null
path: string path: string
isRootCollection: boolean isRootCollection: boolean
}) => { }) => {
const { collection, path, isRootCollection } = newCollection const { collection, path, isRootCollection } = newCollection
if (!collection) {
return
}
if (isRootCollection) { if (isRootCollection) {
editGraphqlCollection(parseInt(path), collection) editGraphqlCollection(parseInt(path), collection)
} else { } else {
@@ -619,4 +677,11 @@ const resetSelectedData = () => {
editingRequest.value = null editingRequest.value = null
editingRequestIndex.value = null editingRequestIndex.value = null
} }
defineActionHandler("collection.new", () => {
displayModalAdd(true)
})
defineActionHandler("modals.collection.import", () => {
displayModalImportExport(true)
})
</script> </script>

View File

@@ -24,7 +24,6 @@
autocomplete="off" autocomplete="off"
class="flex w-full bg-transparent px-4 py-2 h-8" class="flex w-full bg-transparent px-4 py-2 h-8"
:placeholder="t('action.search')" :placeholder="t('action.search')"
:disabled="collectionsType.type === 'team-collections'"
/> />
</div> </div>
<CollectionsMyCollections <CollectionsMyCollections
@@ -58,8 +57,15 @@
<CollectionsTeamCollections <CollectionsTeamCollections
v-else v-else
:collections-type="collectionsType" :collections-type="collectionsType"
:team-collection-list="teamCollectionList" :team-collection-list="
:team-loading-collections="teamLoadingCollections" filterTexts.length > 0 ? teamsSearchResults : teamCollectionList
"
:team-loading-collections="
filterTexts.length > 0
? collectionsBeingLoadedFromSearch
: teamLoadingCollections
"
:filter-text="filterTexts"
:export-loading="exportLoading" :export-loading="exportLoading"
:duplicate-loading="duplicateLoading" :duplicate-loading="duplicateLoading"
:save-request="saveRequest" :save-request="saveRequest"
@@ -87,6 +93,7 @@
@expand-team-collection="expandTeamCollection" @expand-team-collection="expandTeamCollection"
@display-modal-add="displayModalAdd(true)" @display-modal-add="displayModalAdd(true)"
@display-modal-import-export="displayModalImportExport(true)" @display-modal-import-export="displayModalImportExport(true)"
@collection-click="handleCollectionClick"
/> />
<div <div
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight" class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight"
@@ -154,8 +161,10 @@
@hide-modal="displayTeamModalAdd(false)" @hide-modal="displayTeamModalAdd(false)"
/> />
<CollectionsProperties <CollectionsProperties
v-model="collectionPropertiesModalActiveTab"
:show="showModalEditProperties" :show="showModalEditProperties"
:editing-properties="editingProperties" :editing-properties="editingProperties"
source="REST"
@hide-modal="displayModalEditProperties(false)" @hide-modal="displayModalEditProperties(false)"
@set-collection-properties="setCollectionProperties" @set-collection-properties="setCollectionProperties"
/> />
@@ -163,7 +172,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, PropType, ref, watch } from "vue" import { computed, nextTick, onMounted, PropType, ref, watch } from "vue"
import { useToast } from "@composables/toast" import { useToast } from "@composables/toast"
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { Picked } from "~/helpers/types/HoppPicked" import { Picked } from "~/helpers/types/HoppPicked"
@@ -199,7 +208,7 @@ import {
HoppRESTRequest, HoppRESTRequest,
makeCollection, makeCollection,
} from "@hoppscotch/data" } from "@hoppscotch/data"
import { cloneDeep, isEqual } from "lodash-es" import { cloneDeep, debounce, isEqual } from "lodash-es"
import { GQLError } from "~/helpers/backend/GQLClient" import { GQLError } from "~/helpers/backend/GQLClient"
import { import {
createNewRootCollection, createNewRootCollection,
@@ -240,6 +249,11 @@ import { WorkspaceService } from "~/services/workspace.service"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { RESTTabService } from "~/services/tab/rest" import { RESTTabService } from "~/services/tab/rest"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
import { PersistenceService } from "~/services/persistence"
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
import { RESTOptionTabs } from "../http/RequestOptions.vue"
import { EditingProperties } from "./Properties.vue"
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
@@ -291,12 +305,7 @@ const editingRequestName = ref("")
const editingRequestIndex = ref<number | null>(null) const editingRequestIndex = ref<number | null>(null)
const editingRequestID = ref<string | null>(null) const editingRequestID = ref<string | null>(null)
const editingProperties = ref<{ const editingProperties = ref<EditingProperties>({
collection: Omit<HoppCollection, "v"> | TeamCollection | null
isRootCollection: boolean
path: string
inheritedProperties?: HoppInheritedProperty
}>({
collection: null, collection: null,
isRootCollection: false, isRootCollection: false,
path: "", path: "",
@@ -336,6 +345,96 @@ const teamLoadingCollections = useReadonlyStream(
[] []
) )
const {
cascadeParentCollectionForHeaderAuthForSearchResults,
searchTeams,
teamsSearchResults,
teamsSearchResultsLoading,
expandCollection,
expandingCollections,
} = useService(TeamSearchService)
watch(teamsSearchResults, (newSearchResults) => {
if (newSearchResults.length === 1 && filterTexts.value.length > 0) {
expandCollection(newSearchResults[0].id)
}
})
const debouncedSearch = debounce(searchTeams, 400)
const collectionsBeingLoadedFromSearch = computed(() => {
const collections = []
if (teamsSearchResultsLoading.value) {
collections.push("root")
}
collections.push(...expandingCollections.value)
return collections
})
watch(
filterTexts,
(newFilterText) => {
if (collectionsType.value.type === "team-collections") {
const selectedTeamID = collectionsType.value.selectedTeam?.id
selectedTeamID &&
debouncedSearch(newFilterText, selectedTeamID)?.catch(() => {})
}
},
{
immediate: true,
}
)
const persistenceService = useService(PersistenceService)
const collectionPropertiesModalActiveTab = ref<RESTOptionTabs>("headers")
onMounted(() => {
const localOAuthTempConfig =
persistenceService.getLocalConfig("oauth_temp_config")
if (!localOAuthTempConfig) {
return
}
const { context, source, token }: PersistedOAuthConfig =
JSON.parse(localOAuthTempConfig)
if (source === "GraphQL") {
return
}
if (context?.type === "collection-properties") {
// load the unsaved editing properties
const unsavedCollectionPropertiesString = persistenceService.getLocalConfig(
"unsaved_collection_properties"
)
if (unsavedCollectionPropertiesString) {
const unsavedCollectionProperties: EditingProperties = JSON.parse(
unsavedCollectionPropertiesString
)
const auth = unsavedCollectionProperties.collection?.auth
if (auth?.authType === "oauth-2") {
const grantTypeInfo = auth.grantTypeInfo
grantTypeInfo && (grantTypeInfo.token = token ?? "")
}
editingProperties.value = unsavedCollectionProperties
}
persistenceService.removeLocalConfig("oauth_temp_config")
collectionPropertiesModalActiveTab.value = "authorization"
showModalEditProperties.value = true
}
})
watch( watch(
() => myTeams.value, () => myTeams.value,
(newTeams) => { (newTeams) => {
@@ -364,7 +463,28 @@ const switchToMyCollections = () => {
teamCollectionAdapter.changeTeamID(null) teamCollectionAdapter.changeTeamID(null)
} }
/**
* right now, for search results, we rely on collection click + isOpen to expand the collection
*/
const handleCollectionClick = (payload: {
collectionID: string
isOpen: boolean
}) => {
if (
filterTexts.value.length > 0 &&
teamsSearchResults.value.length &&
payload.isOpen
) {
expandCollection(payload.collectionID)
return
}
}
const expandTeamCollection = (collectionID: string) => { const expandTeamCollection = (collectionID: string) => {
if (filterTexts.value.length > 0 && teamsSearchResults.value) {
return
}
teamCollectionAdapter.expandCollection(collectionID) teamCollectionAdapter.expandCollection(collectionID)
} }
@@ -739,7 +859,7 @@ const onAddRequest = (requestName: string) => {
saveContext: { saveContext: {
originLocation: "team-collection", originLocation: "team-collection",
requestID: createRequestInCollection.id, requestID: createRequestInCollection.id,
collectionID: createRequestInCollection.collection.id, collectionID: path,
teamID: createRequestInCollection.collection.team.id, teamID: createRequestInCollection.collection.team.id,
}, },
inheritedProperties: { inheritedProperties: {
@@ -1330,13 +1450,25 @@ const selectRequest = (selectedRequest: {
let possibleTab = null let possibleTab = null
if (collectionsType.value.type === "team-collections") { if (collectionsType.value.type === "team-collections") {
const { auth, headers } = let inheritedProperties: HoppInheritedProperty | undefined = undefined
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
possibleTab = tabs.getTabRefWithSaveContext({ if (filterTexts.value.length > 0) {
const collectionID = folderPath.split("/").at(-1)
if (!collectionID) return
inheritedProperties =
cascadeParentCollectionForHeaderAuthForSearchResults(collectionID)
} else {
inheritedProperties =
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
}
const possibleTab = tabs.getTabRefWithSaveContext({
originLocation: "team-collection", originLocation: "team-collection",
requestID: requestIndex, requestID: requestIndex,
}) })
if (possibleTab) { if (possibleTab) {
tabs.setActiveTab(possibleTab.value.id) tabs.setActiveTab(possibleTab.value.id)
} else { } else {
@@ -1348,10 +1480,7 @@ const selectRequest = (selectedRequest: {
requestID: requestIndex, requestID: requestIndex,
collectionID: folderPath, collectionID: folderPath,
}, },
inheritedProperties: { inheritedProperties: inheritedProperties,
auth,
headers,
},
}) })
} }
} else { } else {
@@ -2021,7 +2150,7 @@ const editProperties = (payload: {
{ {
parentID: "", parentID: "",
parentName: "", parentName: "",
inheritedHeaders: [], inheritedHeader: {},
}, },
], ],
} as HoppInheritedProperty } as HoppInheritedProperty
@@ -2039,7 +2168,7 @@ const editProperties = (payload: {
} }
editingProperties.value = { editingProperties.value = {
collection, collection: collection as Partial<HoppCollection>,
isRootCollection: isAlreadyInRoot(collectionIndex), isRootCollection: isAlreadyInRoot(collectionIndex),
path: collectionIndex, path: collectionIndex,
inheritedProperties, inheritedProperties,
@@ -2083,7 +2212,7 @@ const editProperties = (payload: {
} }
editingProperties.value = { editingProperties.value = {
collection: coll, collection: coll as unknown as Partial<HoppCollection>,
isRootCollection: isAlreadyInRoot(collectionIndex), isRootCollection: isAlreadyInRoot(collectionIndex),
path: collectionIndex, path: collectionIndex,
inheritedProperties, inheritedProperties,
@@ -2094,11 +2223,12 @@ const editProperties = (payload: {
} }
const setCollectionProperties = (newCollection: { const setCollectionProperties = (newCollection: {
collection: HoppCollection collection: Partial<HoppCollection> | null
path: string
isRootCollection: boolean isRootCollection: boolean
path: string
}) => { }) => {
const { collection, path, isRootCollection } = newCollection const { collection, path, isRootCollection } = newCollection
if (!collection) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
if (isRootCollection) { if (isRootCollection) {
@@ -2148,8 +2278,7 @@ const setCollectionProperties = (newCollection: {
auth, auth,
headers, headers,
}, },
"rest", "rest"
"team"
) )
}, 200) }, 200)
} }
@@ -2222,4 +2351,7 @@ const getErrorMessage = (err: GQLError<string>) => {
defineActionHandler("collection.new", () => { defineActionHandler("collection.new", () => {
displayModalAdd(true) displayModalAdd(true)
}) })
defineActionHandler("modals.collection.import", () => {
displayModalImportExport(true)
})
</script> </script>

View File

@@ -32,9 +32,13 @@
{{ tab.document.request.method }} {{ tab.document.request.method }}
</span> </span>
<div <div
class="flex items-center flex-1 flex-shrink-0 min-w-0 px-4 py-2 truncate rounded-r" class="flex items-center flex-1 flex-shrink-0 min-w-0 truncate rounded-r"
> >
{{ tab.document.request.endpoint }} <SmartEnvInput
v-model="tab.document.request.endpoint"
:readonly="true"
:envs="tabRequestVariables"
/>
</div> </div>
</div> </div>
<div class="flex mt-2 space-x-2 sm:mt-0"> <div class="flex mt-2 space-x-2 sm:mt-0">
@@ -69,6 +73,7 @@
v-model="tab.document.request" v-model="tab.document.request"
v-model:option-tab="selectedOptionTab" v-model:option-tab="selectedOptionTab"
:properties="properties" :properties="properties"
:envs="tabRequestVariables"
/> />
<HttpResponse <HttpResponse
v-if="tab.document.response" v-if="tab.document.response"
@@ -117,6 +122,15 @@ const sharedRequestURL = computed(() => {
return `${shortcodeBaseURL}/r/${props.sharedRequestID}` return `${shortcodeBaseURL}/r/${props.sharedRequestID}`
}) })
const tabRequestVariables = computed(() => {
return tab.value.document.request.requestVariables.map(({ key, value }) => ({
key,
value,
secret: false,
sourceEnv: "RequestVariable",
}))
})
const { subscribeToStream } = useStreamSubscriber() const { subscribeToStream } = useStreamSubscriber()
const newSendRequest = async () => { const newSendRequest = async () => {

View File

@@ -54,9 +54,7 @@
:key="tab.id" :key="tab.id"
:label="tab.label" :label="tab.label"
> >
<div <div class="divide-y divide-dividerLight">
class="divide-y divide-dividerLight rounded border border-divider"
>
<HoppSmartPlaceholder <HoppSmartPlaceholder
v-if="tab.variables.length === 0" v-if="tab.variables.length === 0"
:src="`/images/states/${colorMode.value}/blockchain.svg`" :src="`/images/states/${colorMode.value}/blockchain.svg`"
@@ -165,7 +163,7 @@ import { environmentsStore } from "~/newstore/environments"
import { platform } from "~/platform" import { platform } from "~/platform"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { SecretEnvironmentService } from "~/services/secret-environment.service" import { SecretEnvironmentService } from "~/services/secret-environment.service"
import { uniqueId } from "lodash-es" import { uniqueID } from "~/helpers/utils/uniqueID"
type EnvironmentVariable = { type EnvironmentVariable = {
id: number id: number
@@ -277,7 +275,7 @@ const workingEnv = computed(() => {
} as Environment } as Environment
} else if (props.action === "new") { } else if (props.action === "new") {
return { return {
id: uniqueId(), id: uniqueID(),
name: "", name: "",
variables: props.envVars(), variables: props.envVars(),
} }
@@ -331,7 +329,7 @@ watch(
: "variables" : "variables"
if (props.editingEnvironmentIndex !== "Global") { if (props.editingEnvironmentIndex !== "Global") {
editingID.value = workingEnv.value?.id ?? uniqueId() editingID.value = workingEnv.value?.id || uniqueID()
} }
vars.value = pipe( vars.value = pipe(
workingEnv.value?.variables ?? [], workingEnv.value?.variables ?? [],
@@ -416,14 +414,12 @@ const saveEnvironment = () => {
const variables = pipe( const variables = pipe(
filteredVariables, filteredVariables,
A.map((e) => A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e))
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
)
) )
const environmentUpdated: Environment = { const environmentUpdated: Environment = {
v: 1, v: 1,
id: uniqueId(), id: uniqueID(),
name: editingName.value, name: editingName.value,
variables, variables,
} }

View File

@@ -56,9 +56,7 @@
:key="tab.id" :key="tab.id"
:label="tab.label" :label="tab.label"
> >
<div <div class="divide-y divide-dividerLight">
class="divide-y divide-dividerLight rounded border border-divider"
>
<HoppSmartPlaceholder <HoppSmartPlaceholder
v-if="tab.variables.length === 0" v-if="tab.variables.length === 0"
:src="`/images/states/${colorMode.value}/blockchain.svg`" :src="`/images/states/${colorMode.value}/blockchain.svg`"
@@ -360,7 +358,7 @@ const saveEnvironment = async () => {
return return
} }
const filterdVariables = pipe( const filteredVariables = pipe(
vars.value, vars.value,
A.filterMap( A.filterMap(
flow( flow(
@@ -371,17 +369,15 @@ const saveEnvironment = async () => {
) )
const secretVariables = pipe( const secretVariables = pipe(
filterdVariables, filteredVariables,
A.filterMapWithIndex((i, e) => A.filterMapWithIndex((i, e) =>
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
) )
) )
const variables = pipe( const variables = pipe(
filterdVariables, filteredVariables,
A.map((e) => A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e))
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
)
) )
const environmentUpdated: Environment = { const environmentUpdated: Environment = {

View File

@@ -23,10 +23,10 @@
@click="provider.action" @click="provider.action"
/> />
<hr v-if="additonalLoginItems.length > 0" /> <hr v-if="additionalLoginItems.length > 0" />
<HoppSmartItem <HoppSmartItem
v-for="loginItem in additonalLoginItems" v-for="loginItem in additionalLoginItems"
:key="loginItem.id" :key="loginItem.id"
:icon="loginItem.icon" :icon="loginItem.icon"
:label="loginItem.text(t)" :label="loginItem.text(t)"
@@ -170,7 +170,7 @@ type AuthProviderItem = {
} }
let allowedAuthProviders: AuthProviderItem[] = [] let allowedAuthProviders: AuthProviderItem[] = []
let additonalLoginItems: LoginItemDef[] = [] const additionalLoginItems: LoginItemDef[] = []
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => { const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
await item.onClick() await item.onClick()
@@ -199,10 +199,33 @@ onMounted(async () => {
allowedAuthProviders = enabledAuthProviders allowedAuthProviders = enabledAuthProviders
// setup the additional login items // setup the additional login items
additonalLoginItems = platform.auth.additionalLoginItems?.forEach((item) => {
platform.auth.additionalLoginItems?.filter((item) => if (res.right.includes(item.id)) {
res.right.includes(item.id) additionalLoginItems.push(item)
) ?? [] }
// since the BE send the OIDC auth providers as OIDC:providerName,
// we need to split the string and use the providerName as the text
if (item.id === "OIDC") {
res.right
.filter((provider) => provider.startsWith("OIDC"))
.forEach((provider) => {
const OIDCName = provider.split(":")[1]
const loginItemText = OIDCName
? () =>
t("auth.continue_with_auth_provider", {
provider: OIDCName,
})
: item.text
const OIDCLoginItem = {
...item,
text: loginItemText,
}
additionalLoginItems.push(OIDCLoginItem)
})
}
})
isLoadingAllowedAuthProviders.value = false isLoadingAllowedAuthProviders.value = false
}) })
@@ -311,6 +334,14 @@ const authProvidersAvailable: AuthProviderItem[] = [
action: signInWithGithub, action: signInWithGithub,
isLoading: signingInWithGitHub, isLoading: signingInWithGitHub,
}, },
// the authprovider either send github or github:enterprise and both are handled by the same route
{
id: "GITHUB:ENTERPRISE",
icon: IconGithub,
label: t("auth.continue_with_github_enterprise"),
action: signInWithGithub,
isLoading: signingInWithGitHub,
},
{ {
id: "GOOGLE", id: "GOOGLE",
icon: IconGoogle, icon: IconGoogle,

View File

@@ -82,7 +82,7 @@
:active="authName === 'OAuth 2.0'" :active="authName === 'OAuth 2.0'"
@click=" @click="
() => { () => {
auth.authType = 'oauth-2' selectOAuth2AuthType()
hide() hide()
} }
" "
@@ -189,12 +189,12 @@
<div v-if="auth.authType === 'oauth-2'"> <div v-if="auth.authType === 'oauth-2'">
<div class="flex flex-1 border-b border-dividerLight"> <div class="flex flex-1 border-b border-dividerLight">
<SmartEnvInput <SmartEnvInput
v-model="auth.token" v-model="auth.grantTypeInfo.token"
:environment-highlights="false" :environment-highlights="false"
placeholder="Token" placeholder="Token"
/> />
</div> </div>
<HttpOAuth2Authorization v-model="auth" /> <HttpOAuth2Authorization v-model="auth" source="GraphQL" />
</div> </div>
<div v-if="auth.authType === 'api-key'"> <div v-if="auth.authType === 'api-key'">
<HttpAuthorizationApiKey v-model="auth" /> <HttpAuthorizationApiKey v-model="auth" />
@@ -220,19 +220,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "@composables/i18n"
import { pluckRef } from "@composables/ref"
import { useColorMode } from "@composables/theming"
import { HoppGQLAuth, HoppGQLAuthOAuth2 } from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import { computed, onMounted, ref } from "vue"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import IconCircle from "~icons/lucide/circle"
import IconCircleDot from "~icons/lucide/circle-dot"
import IconExternalLink from "~icons/lucide/external-link"
import IconHelpCircle from "~icons/lucide/help-circle" import IconHelpCircle from "~icons/lucide/help-circle"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import IconExternalLink from "~icons/lucide/external-link"
import IconCircleDot from "~icons/lucide/circle-dot" import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
import IconCircle from "~icons/lucide/circle"
import { computed, ref } from "vue"
import { HoppGQLAuth } from "@hoppscotch/data"
import { pluckRef } from "@composables/ref"
import { useI18n } from "@composables/i18n"
import { useColorMode } from "@composables/theming"
import { useVModel } from "@vueuse/core"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { onMounted } from "vue"
const t = useI18n() const t = useI18n()
@@ -280,6 +283,30 @@ const getAuthName = (type: HoppGQLAuth["authType"] | undefined) => {
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None" return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
} }
const selectOAuth2AuthType = () => {
const defaultGrantTypeInfo: HoppGQLAuthOAuth2["grantTypeInfo"] = {
...getDefaultAuthCodeOauthFlowParams(),
grantType: "AUTHORIZATION_CODE",
token: "",
}
// @ts-expect-error - the existing grantTypeInfo might be in the auth object, typescript doesnt know that
const existingGrantTypeInfo = auth.value.grantTypeInfo as
| HoppGQLAuthOAuth2["grantTypeInfo"]
| undefined
const grantTypeInfo = existingGrantTypeInfo
? existingGrantTypeInfo
: defaultGrantTypeInfo
auth.value = {
...auth.value,
authType: "oauth-2",
addTo: "HEADERS",
grantTypeInfo: grantTypeInfo,
}
}
const authActive = pluckRef(auth, "authActive") const authActive = pluckRef(auth, "authActive")
const clearContent = () => { const clearContent = () => {

View File

@@ -579,17 +579,23 @@ const getComputedAuthHeaders = (
}) })
} else if ( } else if (
request.auth.authType === "bearer" || request.auth.authType === "bearer" ||
request.auth.authType === "oauth-2" (request.auth.authType === "oauth-2" && request.auth.addTo === "HEADERS")
) { ) {
const requestAuth = request.auth
const isOAuth2 = requestAuth.authType === "oauth-2"
const token = isOAuth2 ? requestAuth.grantTypeInfo.token : requestAuth.token
headers.push({ headers.push({
active: true, active: true,
key: "Authorization", key: "Authorization",
value: `Bearer ${request.auth.token}`, value: `Bearer ${token}`,
}) })
} else if (request.auth.authType === "api-key") { } else if (request.auth.authType === "api-key") {
const { key, addTo } = request.auth const { key, addTo } = request.auth
if (addTo === "Headers" && key) { if (addTo === "HEADERS" && key) {
headers.push({ headers.push({
active: true, active: true,
key, key,

View File

@@ -10,7 +10,7 @@
autocomplete="off" autocomplete="off"
spellcheck="false" spellcheck="false"
class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark" class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark"
:placeholder="`${t('request.url')}`" :placeholder="`${t('graphql.url_placeholder')}`"
:disabled="connected" :disabled="connected"
@keyup.enter="onConnectClick" @keyup.enter="onConnectClick"
/> />

View File

@@ -82,7 +82,7 @@
:active="authName === 'OAuth 2.0'" :active="authName === 'OAuth 2.0'"
@click=" @click="
() => { () => {
auth.authType = 'oauth-2' selectOAuth2AuthType()
hide() hide()
} }
" "
@@ -150,7 +150,7 @@
<div v-else class="flex flex-1 border-b border-dividerLight"> <div v-else class="flex flex-1 border-b border-dividerLight">
<div class="w-2/3 border-r border-dividerLight"> <div class="w-2/3 border-r border-dividerLight">
<div v-if="auth.authType === 'basic'"> <div v-if="auth.authType === 'basic'">
<HttpAuthorizationBasic v-model="auth" /> <HttpAuthorizationBasic v-model="auth" :envs="envs" />
</div> </div>
<div v-if="auth.authType === 'inherit'" class="p-4"> <div v-if="auth.authType === 'inherit'" class="p-4">
<span v-if="inheritedProperties?.auth"> <span v-if="inheritedProperties?.auth">
@@ -169,17 +169,35 @@
</div> </div>
<div v-if="auth.authType === 'bearer'"> <div v-if="auth.authType === 'bearer'">
<div class="flex flex-1 border-b border-dividerLight"> <div class="flex flex-1 border-b border-dividerLight">
<SmartEnvInput v-model="auth.token" placeholder="Token" /> <SmartEnvInput
v-model="auth.token"
placeholder="Token"
:auto-complete-env="true"
:envs="envs"
/>
</div> </div>
</div> </div>
<div v-if="auth.authType === 'oauth-2'"> <div v-if="auth.authType === 'oauth-2'" class="w-full">
<div class="flex flex-1 border-b border-dividerLight"> <div class="flex flex-1 border-b border-dividerLight">
<SmartEnvInput v-model="auth.token" placeholder="Token" /> <!-- Ensure a new object is assigned here to avoid reactivity issues -->
<SmartEnvInput
:model-value="auth.grantTypeInfo.token"
placeholder="Token"
:envs="envs"
@update:model-value="
auth.grantTypeInfo = { ...auth.grantTypeInfo, token: $event }
"
/>
</div> </div>
<HttpOAuth2Authorization v-model="auth" /> <HttpOAuth2Authorization
v-model="auth"
:is-collection-property="isCollectionProperty"
:envs="envs"
:source="source"
/>
</div> </div>
<div v-if="auth.authType === 'api-key'"> <div v-if="auth.authType === 'api-key'">
<HttpAuthorizationApiKey v-model="auth" /> <HttpAuthorizationApiKey v-model="auth" :envs="envs" />
</div> </div>
</div> </div>
<div <div
@@ -208,24 +226,36 @@ import IconExternalLink from "~icons/lucide/external-link"
import IconCircleDot from "~icons/lucide/circle-dot" import IconCircleDot from "~icons/lucide/circle-dot"
import IconCircle from "~icons/lucide/circle" import IconCircle from "~icons/lucide/circle"
import { computed, ref } from "vue" import { computed, ref } from "vue"
import { HoppRESTAuth } from "@hoppscotch/data" import { HoppRESTAuth, HoppRESTAuthOAuth2 } from "@hoppscotch/data"
import { pluckRef } from "@composables/ref" import { pluckRef } from "@composables/ref"
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { useColorMode } from "@composables/theming" import { useColorMode } from "@composables/theming"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { onMounted } from "vue" import { onMounted } from "vue"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { AggregateEnvironment } from "~/newstore/environments"
import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
const t = useI18n() const t = useI18n()
const colorMode = useColorMode() const colorMode = useColorMode()
const props = defineProps<{ const props = withDefaults(
modelValue: HoppRESTAuth defineProps<{
isCollectionProperty?: boolean modelValue: HoppRESTAuth
isRootCollection?: boolean isCollectionProperty?: boolean
inheritedProperties?: HoppInheritedProperty isRootCollection?: boolean
}>() inheritedProperties?: HoppInheritedProperty
envs?: AggregateEnvironment[]
source?: "REST" | "GraphQL"
}>(),
{
source: "REST",
envs: undefined,
inheritedProperties: undefined,
}
)
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: HoppRESTAuth): void (e: "update:modelValue", value: HoppRESTAuth): void
@@ -261,6 +291,30 @@ const getAuthName = (type: HoppRESTAuth["authType"] | undefined) => {
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None" return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
} }
const selectOAuth2AuthType = () => {
const defaultGrantTypeInfo: HoppRESTAuthOAuth2["grantTypeInfo"] = {
...getDefaultAuthCodeOauthFlowParams(),
grantType: "AUTHORIZATION_CODE",
token: "",
}
// @ts-expect-error - the existing grantTypeInfo might be in the auth object, typescript doesnt know that
const existingGrantTypeInfo = auth.value.grantTypeInfo as
| HoppRESTAuthOAuth2["grantTypeInfo"]
| undefined
const grantTypeInfo = existingGrantTypeInfo
? existingGrantTypeInfo
: defaultGrantTypeInfo
auth.value = {
...auth.value,
authType: "oauth-2",
addTo: "HEADERS",
grantTypeInfo: grantTypeInfo,
}
}
const authActive = pluckRef(auth, "authActive") const authActive = pluckRef(auth, "authActive")
const clearContent = () => { const clearContent = () => {

View File

@@ -100,10 +100,12 @@
<HttpBodyParameters <HttpBodyParameters
v-if="body.contentType === 'multipart/form-data'" v-if="body.contentType === 'multipart/form-data'"
v-model="body" v-model="body"
:envs="envs"
/> />
<HttpURLEncodedParams <HttpURLEncodedParams
v-else-if="body.contentType === 'application/x-www-form-urlencoded'" v-else-if="body.contentType === 'application/x-www-form-urlencoded'"
v-model="body" v-model="body"
:envs="envs"
/> />
<HttpRawBody v-else-if="body.contentType !== null" v-model="body" /> <HttpRawBody v-else-if="body.contentType !== null" v-model="body" />
<HoppSmartPlaceholder <HoppSmartPlaceholder
@@ -141,6 +143,7 @@ import IconExternalLink from "~icons/lucide/external-link"
import IconInfo from "~icons/lucide/info" import IconInfo from "~icons/lucide/info"
import IconRefreshCW from "~icons/lucide/refresh-cw" import IconRefreshCW from "~icons/lucide/refresh-cw"
import { RESTOptionTabs } from "./RequestOptions.vue" import { RESTOptionTabs } from "./RequestOptions.vue"
import { AggregateEnvironment } from "~/newstore/environments"
const colorMode = useColorMode() const colorMode = useColorMode()
const t = useI18n() const t = useI18n()
@@ -148,6 +151,7 @@ const t = useI18n()
const props = defineProps<{ const props = defineProps<{
body: HoppRESTReqBody body: HoppRESTReqBody
headers: HoppRESTHeader[] headers: HoppRESTHeader[]
envs?: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -65,6 +65,8 @@
<SmartEnvInput <SmartEnvInput
v-model="entry.key" v-model="entry.key"
:placeholder="`${t('count.parameter', { count: index + 1 })}`" :placeholder="`${t('count.parameter', { count: index + 1 })}`"
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateBodyParam(index, { updateBodyParam(index, {
key: $event, key: $event,
@@ -87,6 +89,8 @@
<SmartEnvInput <SmartEnvInput
v-model="entry.value" v-model="entry.value"
:placeholder="`${t('count.value', { count: index + 1 })}`" :placeholder="`${t('count.value', { count: index + 1 })}`"
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateBodyParam(index, { updateBodyParam(index, {
key: entry.key, key: entry.key,
@@ -190,11 +194,13 @@ import { useI18n } from "@composables/i18n"
import { useToast } from "@composables/toast" import { useToast } from "@composables/toast"
import { useColorMode } from "@composables/theming" import { useColorMode } from "@composables/theming"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { AggregateEnvironment } from "~/newstore/environments"
type Body = HoppRESTReqBody & { contentType: "multipart/form-data" } type Body = HoppRESTReqBody & { contentType: "multipart/form-data" }
const props = defineProps<{ const props = defineProps<{
modelValue: Body modelValue: Body
envs: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -188,11 +188,25 @@ const copyCodeIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
const requestCode = computed(() => { const requestCode = computed(() => {
const aggregateEnvs = getAggregateEnvs() const aggregateEnvs = getAggregateEnvs()
const requestVariables = request.value.requestVariables.map(
(requestVariable) => {
if (requestVariable.active)
return {
key: requestVariable.key,
value: requestVariable.value,
secret: false,
}
return {}
}
)
const env: Environment = { const env: Environment = {
v: 1, v: 1,
id: "env", id: "env",
name: "Env", name: "Env",
variables: aggregateEnvs, variables: [
...(requestVariables as Environment["variables"]),
...aggregateEnvs,
],
} }
const effectiveRequest = getEffectiveRESTRequest(request.value, env) const effectiveRequest = getEffectiveRESTRequest(request.value, env)
@@ -212,6 +226,12 @@ const requestCode = computed(() => {
active: true, active: true,
})), })),
endpoint: effectiveRequest.effectiveFinalURL, endpoint: effectiveRequest.effectiveFinalURL,
requestVariables: effectiveRequest.effectiveFinalRequestVariables.map(
(requestVariable) => ({
...requestVariable,
active: true,
})
),
}) })
) )

View File

@@ -26,7 +26,7 @@
@click="clearContent()" @click="clearContent()"
/> />
<HoppButtonSecondary <HoppButtonSecondary
v-if="bulkMode" v-if="bulkHeaders"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="t('state.linewrap')" :title="t('state.linewrap')"
:class="{ '!text-accent': WRAP_LINES }" :class="{ '!text-accent': WRAP_LINES }"
@@ -92,6 +92,8 @@
:auto-complete-source="commonHeaders" :auto-complete-source="commonHeaders"
:env-index="index" :env-index="index"
:inspection-results="getInspectorResult(headerKeyResults, index)" :inspection-results="getInspectorResult(headerKeyResults, index)"
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateHeader(index, { updateHeader(index, {
id: header.id, id: header.id,
@@ -108,6 +110,8 @@
getInspectorResult(headerValueResults, index) getInspectorResult(headerValueResults, index)
" "
:env-index="index" :env-index="index"
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateHeader(index, { updateHeader(index, {
id: header.id, id: header.id,
@@ -303,6 +307,7 @@ import { useColorMode } from "@composables/theming"
import { computed, reactive, ref, watch } from "vue" import { computed, reactive, ref, watch } from "vue"
import { isEqual, cloneDeep } from "lodash-es" import { isEqual, cloneDeep } from "lodash-es"
import { import {
HoppRESTAuth,
HoppRESTHeader, HoppRESTHeader,
HoppRESTRequest, HoppRESTRequest,
parseRawKeyValueEntriesE, parseRawKeyValueEntriesE,
@@ -329,7 +334,11 @@ import {
getComputedHeaders, getComputedHeaders,
getComputedAuthHeaders, getComputedAuthHeaders,
} from "~/helpers/utils/EffectiveURL" } from "~/helpers/utils/EffectiveURL"
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments" import {
AggregateEnvironment,
aggregateEnvs$,
getAggregateEnvs,
} from "~/newstore/environments"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { InspectionService, InspectorResult } from "~/services/inspection" import { InspectionService, InspectorResult } from "~/services/inspection"
@@ -356,9 +365,15 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
// v-model integration with props and emit // v-model integration with props and emit
const props = defineProps<{ const props = defineProps<{
modelValue: HoppRESTRequest modelValue:
| HoppRESTRequest
| {
headers: HoppRESTHeader[]
auth: HoppRESTAuth
}
isCollectionProperty?: boolean isCollectionProperty?: boolean
inheritedProperties?: HoppInheritedProperty inheritedProperties?: HoppInheritedProperty
envs?: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -98,6 +98,7 @@ import { RESTTabService } from "~/services/tab/rest"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { useNestedSetting } from "~/composables/settings" import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings" import { toggleNestedSetting } from "~/newstore/settings"
import { EditorView } from "@codemirror/view"
const t = useI18n() const t = useI18n()
@@ -124,6 +125,7 @@ useCodemirror(
linter: null, linter: null,
completer: null, completer: null,
environmentHighlights: false, environmentHighlights: false,
onInit: (view: EditorView) => view.focus(),
}) })
) )

View File

@@ -87,6 +87,8 @@
:inspection-results=" :inspection-results="
getInspectorResult(parameterKeyResults, index) getInspectorResult(parameterKeyResults, index)
" "
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateParam(index, { updateParam(index, {
id: param.id, id: param.id,
@@ -102,6 +104,8 @@
:inspection-results=" :inspection-results="
getInspectorResult(parameterValueResults, index) getInspectorResult(parameterValueResults, index)
" "
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateParam(index, { updateParam(index, {
id: param.id, id: param.id,
@@ -209,6 +213,7 @@ import { InspectionService, InspectorResult } from "~/services/inspection"
import { RESTTabService } from "~/services/tab/rest" import { RESTTabService } from "~/services/tab/rest"
import { useNestedSetting } from "~/composables/settings" import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings" import { toggleNestedSetting } from "~/newstore/settings"
import { AggregateEnvironment } from "~/newstore/environments"
const colorMode = useColorMode() const colorMode = useColorMode()
@@ -242,6 +247,7 @@ useCodemirror(
const props = defineProps<{ const props = defineProps<{
modelValue: HoppRESTParam[] modelValue: HoppRESTParam[]
envs?: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -54,8 +54,9 @@
> >
<SmartEnvInput <SmartEnvInput
v-model="tab.document.request.endpoint" v-model="tab.document.request.endpoint"
:placeholder="`${t('request.url')}`" :placeholder="`${t('request.url_placeholder')}`"
:auto-complete-source="userHistories" :auto-complete-source="userHistories"
:auto-complete-env="true"
:inspection-results="tabResults" :inspection-results="tabResults"
@paste="onPasteUrl($event)" @paste="onPasteUrl($event)"
@enter="newSendRequest" @enter="newSendRequest"

View File

@@ -5,48 +5,51 @@
render-inactive-tabs render-inactive-tabs
> >
<HoppSmartTab <HoppSmartTab
v-if="properties ? properties.includes('params') : true" v-if="properties?.includes('params') ?? true"
:id="'params'" :id="'params'"
:label="`${t('tab.parameters')}`" :label="`${t('tab.parameters')}`"
:info="`${newActiveParamsCount$}`" :info="`${newActiveParamsCount}`"
> >
<HttpParameters v-model="request.params" /> <HttpParameters v-model="request.params" :envs="envs" />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab <HoppSmartTab
v-if="properties ? properties.includes('bodyParams') : true" v-if="properties?.includes('bodyParams') ?? true"
:id="'bodyParams'" :id="'bodyParams'"
:label="`${t('tab.body')}`" :label="`${t('tab.body')}`"
> >
<HttpBody <HttpBody
v-model:headers="request.headers" v-model:headers="request.headers"
v-model:body="request.body" v-model:body="request.body"
:envs="envs"
@change-tab="changeOptionTab" @change-tab="changeOptionTab"
/> />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab <HoppSmartTab
v-if="properties ? properties.includes('headers') : true" v-if="properties?.includes('headers') ?? true"
:id="'headers'" :id="'headers'"
:label="`${t('tab.headers')}`" :label="`${t('tab.headers')}`"
:info="`${newActiveHeadersCount$}`" :info="`${newActiveHeadersCount}`"
> >
<HttpHeaders <HttpHeaders
v-model="request" v-model="request"
:inherited-properties="inheritedProperties" :inherited-properties="inheritedProperties"
:envs="envs"
@change-tab="changeOptionTab" @change-tab="changeOptionTab"
/> />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab <HoppSmartTab
v-if="properties ? properties.includes('authorization') : true" v-if="properties?.includes('authorization') ?? true"
:id="'authorization'" :id="'authorization'"
:label="`${t('tab.authorization')}`" :label="`${t('tab.authorization')}`"
> >
<HttpAuthorization <HttpAuthorization
v-model="request.auth" v-model="request.auth"
:inherited-properties="inheritedProperties" :inherited-properties="inheritedProperties"
:envs="envs"
/> />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab <HoppSmartTab
v-if="properties ? properties.includes('preRequestScript') : true" v-if="properties?.includes('preRequestScript') ?? true"
:id="'preRequestScript'" :id="'preRequestScript'"
:label="`${t('tab.pre_request_script')}`" :label="`${t('tab.pre_request_script')}`"
:indicator=" :indicator="
@@ -58,7 +61,7 @@
<HttpPreRequestScript v-model="request.preRequestScript" /> <HttpPreRequestScript v-model="request.preRequestScript" />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab <HoppSmartTab
v-if="properties ? properties.includes('tests') : true" v-if="properties?.includes('tests') ?? true"
:id="'tests'" :id="'tests'"
:label="`${t('tab.tests')}`" :label="`${t('tab.tests')}`"
:indicator=" :indicator="
@@ -67,6 +70,15 @@
> >
<HttpTests v-model="request.testScript" /> <HttpTests v-model="request.testScript" />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab
v-if="properties?.includes('requestVariables') ?? true"
:id="'requestVariables'"
:label="`${t('tab.variables')}`"
:info="`${newActiveRequestVariablesCount}`"
:align-last="true"
>
<HttpRequestVariables v-model="request.requestVariables" />
</HoppSmartTab>
</HoppSmartTabs> </HoppSmartTabs>
</template> </template>
@@ -77,6 +89,7 @@ import { useVModel } from "@vueuse/core"
import { computed } from "vue" import { computed } from "vue"
import { defineActionHandler } from "~/helpers/actions" import { defineActionHandler } from "~/helpers/actions"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { AggregateEnvironment } from "~/newstore/environments"
const VALID_OPTION_TABS = [ const VALID_OPTION_TABS = [
"params", "params",
@@ -85,6 +98,7 @@ const VALID_OPTION_TABS = [
"authorization", "authorization",
"preRequestScript", "preRequestScript",
"tests", "tests",
"requestVariables",
] as const ] as const
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number] export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
@@ -98,6 +112,7 @@ const props = withDefaults(
optionTab: RESTOptionTabs optionTab: RESTOptionTabs
properties?: string[] properties?: string[]
inheritedProperties?: HoppInheritedProperty inheritedProperties?: HoppInheritedProperty
envs?: AggregateEnvironment[]
}>(), }>(),
{ {
optionTab: "params", optionTab: "params",
@@ -116,22 +131,27 @@ const changeOptionTab = (e: RESTOptionTabs) => {
selectedOptionTab.value = e selectedOptionTab.value = e
} }
const newActiveParamsCount$ = computed(() => { const newActiveParamsCount = computed(() => {
const e = request.value.params.filter( const count = request.value.params.filter(
(x) => x.active && (x.key !== "" || x.value !== "") (x) => x.active && (x.key || x.value)
).length ).length
if (e === 0) return null return count ? count : null
return `${e}`
}) })
const newActiveHeadersCount$ = computed(() => { const newActiveHeadersCount = computed(() => {
const e = request.value.headers.filter( const count = request.value.headers.filter(
(x) => x.active && (x.key !== "" || x.value !== "") (x) => x.active && (x.key || x.value)
).length ).length
if (e === 0) return null return count ? count : null
return `${e}` })
const newActiveRequestVariablesCount = computed(() => {
const count = request.value.requestVariables.filter(
(x) => x.active && (x.key || x.value)
).length
return count ? count : null
}) })
defineActionHandler("request.open-tab", ({ tab }) => { defineActionHandler("request.open-tab", ({ tab }) => {

View File

@@ -0,0 +1,412 @@
<template>
<div class="flex flex-1 flex-col">
<div
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
>
<label class="truncate font-semibold text-secondaryLight">
{{ t("request.request_variables") }}
</label>
<div class="flex">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://docs.hoppscotch.io/documentation/features/rest-api-testing"
blank
:title="t('app.wiki')"
:icon="IconHelpCircle"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.clear_all')"
:icon="IconTrash2"
@click="clearContent()"
/>
<HoppButtonSecondary
v-if="bulkVariables"
v-tippy="{ theme: 'tooltip' }"
:title="t('state.linewrap')"
:class="{ '!text-accent': WRAP_LINES }"
:icon="IconWrapText"
@click.prevent="
toggleNestedSetting('WRAP_LINES', 'httpRequestVariables')
"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('state.bulk_mode')"
:icon="IconEdit"
:class="{ '!text-accent': bulkMode }"
@click="bulkMode = !bulkMode"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('add.new')"
:icon="IconPlus"
:disabled="bulkMode"
@click="addVariable"
/>
</div>
</div>
<div v-if="bulkMode" class="h-full relative">
<div ref="bulkEditor" class="absolute inset-0"></div>
</div>
<div v-else>
<draggable
v-model="workingRequestVariables"
item-key="id"
animation="250"
handle=".draggable-handle"
draggable=".draggable-content"
ghost-class="cursor-move"
chosen-class="bg-primaryLight"
drag-class="cursor-grabbing"
>
<template #item="{ element: variable, index }">
<div
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
>
<HoppButtonSecondary
v-tippy="{
theme: 'tooltip',
delay: [500, 20],
content:
index !== workingRequestVariables?.length - 1
? t('action.drag_to_reorder')
: null,
}"
:icon="IconGripVertical"
class="opacity-0"
:class="{
'draggable-handle cursor-grab group-hover:opacity-100':
index !== workingRequestVariables?.length - 1,
}"
tabindex="-1"
/>
<SmartEnvInput
v-model="variable.key"
:placeholder="`${t('count.variable', { count: index + 1 })}`"
@change="
updateVariable(index, {
id: variable.id,
key: $event,
value: variable.value,
active: variable.active,
})
"
/>
<SmartEnvInput
v-model="variable.value"
:placeholder="`${t('count.value', { count: index + 1 })}`"
@change="
updateVariable(index, {
id: variable.id,
key: variable.key,
value: $event,
active: variable.active,
})
"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="
variable.hasOwnProperty('active')
? variable.active
? t('action.turn_off')
: t('action.turn_on')
: t('action.turn_off')
"
:icon="
variable.hasOwnProperty('active')
? variable.active
? IconCheckCircle
: IconCircle
: IconCheckCircle
"
color="green"
@click="
updateVariable(index, {
id: variable.id,
key: variable.key,
value: variable.value,
active: variable.hasOwnProperty('active')
? !variable.active
: false,
})
"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.remove')"
:icon="IconTrash"
color="red"
@click="deleteVariable(index)"
/>
</div>
</template>
</draggable>
<HoppSmartPlaceholder
v-if="workingRequestVariables.length === 0"
:src="`/images/states/${colorMode.value}/add_files.svg`"
:alt="`${t('empty.request_variables')}`"
:text="t('empty.request_variables')"
>
<template #body>
<HoppButtonSecondary
:label="`${t('add.new')}`"
:icon="IconPlus"
filled
@click="addVariable"
/>
</template>
</HoppSmartPlaceholder>
</div>
</div>
</template>
<script lang="ts" setup>
import { throwError } from "@functional/error"
import {
HoppRESTRequestVariable,
RawKeyValueEntry,
parseRawKeyValueEntriesE,
rawKeyValueEntriesToString,
} from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import * as A from "fp-ts/Array"
import * as E from "fp-ts/Either"
import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray"
import { flow, pipe } from "fp-ts/function"
import { cloneDeep, isEqual } from "lodash-es"
import { reactive, ref, watch } from "vue"
import draggable from "vuedraggable-es"
import { useCodemirror } from "~/composables/codemirror"
import { useI18n } from "~/composables/i18n"
import { useNestedSetting } from "~/composables/settings"
import { useColorMode } from "~/composables/theming"
import { useToast } from "~/composables/toast"
import linter from "~/helpers/editor/linting/rawKeyValue"
import { objRemoveKey } from "~/helpers/functional/object"
import { toggleNestedSetting } from "~/newstore/settings"
import IconCheckCircle from "~icons/lucide/check-circle"
import IconCircle from "~icons/lucide/circle"
import IconEdit from "~icons/lucide/edit"
import IconGripVertical from "~icons/lucide/grip-vertical"
import IconHelpCircle from "~icons/lucide/help-circle"
import IconPlus from "~icons/lucide/plus"
import IconTrash from "~icons/lucide/trash"
import IconTrash2 from "~icons/lucide/trash-2"
import IconWrapText from "~icons/lucide/wrap-text"
const colorMode = useColorMode()
const t = useI18n()
const toast = useToast()
const bulkMode = ref(false)
const bulkEditor = ref<any | null>(null)
const bulkVariables = ref("")
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpRequestVariables")
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
const props = defineProps<{
modelValue: HoppRESTRequestVariable[]
}>()
const emit = defineEmits<{
(e: "update:modelValue", value: Array<HoppRESTRequestVariable>): void
}>()
// The functional requestVariable list (the requestVariable actually applied to the session)
const requestVariables = useVModel(props, "modelValue", emit)
useCodemirror(
bulkEditor,
bulkVariables,
reactive({
extendedEditorConfig: {
mode: "text/x-yaml",
placeholder: `${t("state.bulk_mode_placeholder")}`,
lineWrapping: WRAP_LINES,
},
linter,
completer: null,
environmentHighlights: true,
})
)
const idTicker = ref(0)
const workingRequestVariables = ref<
Array<HoppRESTRequestVariable & { id: number }>
>([
{
id: idTicker.value++,
key: "",
value: "",
active: true,
},
])
// Sync logic between params and working/bulk params
watch(
requestVariables,
(newRequestVariableList) => {
// Sync should overwrite working params
const filteredWorkingRequestVariables: HoppRESTRequestVariable[] = pipe(
workingRequestVariables.value,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
const filteredBulkRequestVariables = pipe(
parseRawKeyValueEntriesE(bulkVariables.value),
E.map(
flow(
RA.filter((e) => e.key !== ""),
RA.toArray
)
),
E.getOrElse(() => [] as RawKeyValueEntry[])
)
if (!isEqual(newRequestVariableList, filteredWorkingRequestVariables)) {
workingRequestVariables.value = pipe(
newRequestVariableList,
A.map((x) => ({ id: idTicker.value++, ...x }))
)
}
if (!isEqual(newRequestVariableList, filteredBulkRequestVariables)) {
bulkVariables.value = rawKeyValueEntriesToString(newRequestVariableList)
}
},
{ immediate: true }
)
watch(workingRequestVariables, (newWorkingRequestVariables) => {
const fixedRequestVariables = pipe(
newWorkingRequestVariables,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
if (!isEqual(requestVariables.value, fixedRequestVariables)) {
requestVariables.value = cloneDeep(fixedRequestVariables)
}
})
watch(bulkVariables, (newBulkParams) => {
const filteredBulkRequestVariables = pipe(
parseRawKeyValueEntriesE(newBulkParams),
E.map(
flow(
RA.filter((e) => e.key !== ""),
RA.toArray
)
),
E.getOrElse(() => [] as RawKeyValueEntry[])
)
if (!isEqual(requestVariables.value, filteredBulkRequestVariables)) {
requestVariables.value = filteredBulkRequestVariables
}
})
// Rule: Working Request variable always have last element is always an empty param
watch(workingRequestVariables, (variableList) => {
if (
variableList.length > 0 &&
variableList[variableList.length - 1].key !== ""
) {
workingRequestVariables.value.push({
id: idTicker.value++,
key: "",
value: "",
active: true,
})
}
})
const addVariable = () => {
workingRequestVariables.value.push({
id: idTicker.value++,
key: "",
value: "",
active: true,
})
}
const updateVariable = (index: number, variable: any & { id: number }) => {
workingRequestVariables.value = workingRequestVariables.value.map((h, i) =>
i === index ? variable : h
)
}
const deleteVariable = (index: number) => {
const requestVariablesBeforeDeletion = cloneDeep(
workingRequestVariables.value
)
if (
!(
requestVariablesBeforeDeletion.length > 0 &&
index === requestVariablesBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
workingRequestVariables.value = requestVariablesBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingRequestVariables.value = pipe(
workingRequestVariables.value,
A.deleteAt(index),
O.getOrElseW(() =>
throwError("Working Request Variable Deletion Out of Bounds")
)
)
}
const clearContent = () => {
// set params list to the initial state
workingRequestVariables.value = [
{
id: idTicker.value++,
key: "",
value: "",
active: true,
},
]
bulkVariables.value = ""
}
</script>

View File

@@ -21,7 +21,7 @@
@click="clearContent()" @click="clearContent()"
/> />
<HoppButtonSecondary <HoppButtonSecondary
v-if="bulkMode" v-if="bulkUrlEncodedParams"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="t('state.linewrap')" :title="t('state.linewrap')"
:class="{ '!text-accent': WRAP_LINES }" :class="{ '!text-accent': WRAP_LINES }"
@@ -84,6 +84,8 @@
<SmartEnvInput <SmartEnvInput
v-model="param.key" v-model="param.key"
:placeholder="`${t('count.parameter', { count: index + 1 })}`" :placeholder="`${t('count.parameter', { count: index + 1 })}`"
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateUrlEncodedParam(index, { updateUrlEncodedParam(index, {
id: param.id, id: param.id,
@@ -96,6 +98,8 @@
<SmartEnvInput <SmartEnvInput
v-model="param.value" v-model="param.value"
:placeholder="`${t('count.value', { count: index + 1 })}`" :placeholder="`${t('count.value', { count: index + 1 })}`"
:auto-complete-env="true"
:envs="envs"
@change=" @change="
updateUrlEncodedParam(index, { updateUrlEncodedParam(index, {
id: param.id, id: param.id,
@@ -200,6 +204,7 @@ import { throwError } from "~/helpers/functional/error"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { useNestedSetting } from "~/composables/settings" import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings" import { toggleNestedSetting } from "~/newstore/settings"
import { AggregateEnvironment } from "~/newstore/environments"
type Body = HoppRESTReqBody & { type Body = HoppRESTReqBody & {
contentType: "application/x-www-form-urlencoded" contentType: "application/x-www-form-urlencoded"
@@ -207,6 +212,7 @@ type Body = HoppRESTReqBody & {
const props = defineProps<{ const props = defineProps<{
modelValue: Body modelValue: Body
envs: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -1,9 +1,19 @@
<template> <template>
<div class="flex flex-1 border-b border-dividerLight"> <div class="flex flex-1 border-b border-dividerLight">
<SmartEnvInput v-model="auth.key" placeholder="Key" /> <SmartEnvInput
v-model="auth.key"
:auto-complete-env="true"
placeholder="Key"
:envs="envs"
/>
</div> </div>
<div class="flex flex-1 border-b border-dividerLight"> <div class="flex flex-1 border-b border-dividerLight">
<SmartEnvInput v-model="auth.value" placeholder="Value" /> <SmartEnvInput
v-model="auth.value"
:auto-complete-env="true"
placeholder="Value"
:envs="envs"
/>
</div> </div>
<div class="flex items-center border-b border-dividerLight"> <div class="flex items-center border-b border-dividerLight">
<span class="flex items-center"> <span class="flex items-center">
@@ -18,7 +28,13 @@
> >
<HoppSmartSelectWrapper> <HoppSmartSelectWrapper>
<HoppButtonSecondary <HoppButtonSecondary
:label="auth.addTo || t('state.none')" :label="
auth.addTo
? auth.addTo === 'HEADERS'
? t('authorization.pass_by_headers_label')
: t('authorization.pass_by_query_params_label')
: t('state.none')
"
class="ml-2 rounded-none pr-8" class="ml-2 rounded-none pr-8"
/> />
</HoppSmartSelectWrapper> </HoppSmartSelectWrapper>
@@ -30,23 +46,23 @@
@keyup.escape="hide()" @keyup.escape="hide()"
> >
<HoppSmartItem <HoppSmartItem
:icon="auth.addTo === 'Headers' ? IconCircleDot : IconCircle" :icon="auth.addTo === 'HEADERS' ? IconCircleDot : IconCircle"
:active="auth.addTo === 'Headers'" :active="auth.addTo === 'HEADERS'"
:label="'Headers'" :label="t('authorization.pass_by_headers_label')"
@click=" @click="
() => { () => {
auth.addTo = 'Headers' auth.addTo = 'HEADERS'
hide() hide()
} }
" "
/> />
<HoppSmartItem <HoppSmartItem
:icon="auth.addTo === 'Query params' ? IconCircleDot : IconCircle" :icon="auth.addTo === 'QUERY_PARAMS' ? IconCircleDot : IconCircle"
:active="auth.addTo === 'Query params'" :active="auth.addTo === 'QUERY_PARAMS'"
:label="'Query params'" :label="t('authorization.pass_by_query_params_label')"
@click=" @click="
() => { () => {
auth.addTo = 'Query params' auth.addTo = 'QUERY_PARAMS'
hide() hide()
} }
" "
@@ -65,11 +81,13 @@ import { useI18n } from "@composables/i18n"
import { HoppRESTAuthAPIKey } from "@hoppscotch/data" import { HoppRESTAuthAPIKey } from "@hoppscotch/data"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { ref } from "vue" import { ref } from "vue"
import { AggregateEnvironment } from "~/newstore/environments"
const t = useI18n() const t = useI18n()
const props = defineProps<{ const props = defineProps<{
modelValue: HoppRESTAuthAPIKey modelValue: HoppRESTAuthAPIKey
envs?: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -3,12 +3,16 @@
<SmartEnvInput <SmartEnvInput
v-model="auth.username" v-model="auth.username"
:placeholder="t('authorization.username')" :placeholder="t('authorization.username')"
:auto-complete-env="true"
:envs="envs"
/> />
</div> </div>
<div class="flex flex-1 border-b border-dividerLight"> <div class="flex flex-1 border-b border-dividerLight">
<SmartEnvInput <SmartEnvInput
v-model="auth.password" v-model="auth.password"
:placeholder="t('authorization.password')" :placeholder="t('authorization.password')"
:auto-complete-env="true"
:envs="envs"
/> />
</div> </div>
</template> </template>
@@ -17,11 +21,13 @@
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { HoppRESTAuthBasic } from "@hoppscotch/data" import { HoppRESTAuthBasic } from "@hoppscotch/data"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { AggregateEnvironment } from "~/newstore/environments"
const t = useI18n() const t = useI18n()
const props = defineProps<{ const props = defineProps<{
modelValue: HoppRESTAuthBasic modelValue: HoppRESTAuthBasic
envs?: AggregateEnvironment[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -21,28 +21,24 @@
</span> </span>
</div> </div>
<div class="flex flex-col space-y-2 py-4"> <div class="flex flex-col space-y-2 py-4">
<span> <HoppSmartItem
<HoppSmartItem to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld" blank
blank :icon="IconChrome"
:icon="IconChrome" label="Chrome"
label="Chrome" :info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null" :active-info-icon="hasChromeExtInstalled"
:active-info-icon="hasChromeExtInstalled" outline
outline />
/> <HoppSmartItem
</span> to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
<span> blank
<HoppSmartItem :icon="IconFirefox"
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch" label="Firefox"
blank :info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
:icon="IconFirefox" :active-info-icon="hasFirefoxExtInstalled"
label="Firefox" outline
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null" />
:active-info-icon="hasFirefoxExtInstalled"
outline
/>
</span>
</div> </div>
<div class="space-y-4 py-4"> <div class="space-y-4 py-4">
<div class="flex items-center"> <div class="flex items-center">

View File

@@ -290,7 +290,12 @@ const widgets: Widget[] = [
}, },
] ]
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization" type EmbedTabs =
| "params"
| "bodyParams"
| "headers"
| "authorization"
| "requestVariables"
type EmbedOption = { type EmbedOption = {
selectedTab: EmbedTabs selectedTab: EmbedTabs

View File

@@ -56,7 +56,12 @@ import { useI18n } from "~/composables/i18n"
const t = useI18n() const t = useI18n()
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization" type EmbedTabs =
| "params"
| "bodyParams"
| "headers"
| "authorization"
| "requestVariables"
type EmbedOption = { type EmbedOption = {
selectedTab: EmbedTabs selectedTab: EmbedTabs

View File

@@ -173,6 +173,11 @@ const embedOptions = ref<EmbedOption>({
label: t("tab.authorization"), label: t("tab.authorization"),
enabled: false, enabled: false,
}, },
{
value: "requestVariables",
label: t("tab.variables"),
enabled: false,
},
], ],
theme: "system", theme: "system",
}) })
@@ -223,7 +228,12 @@ const currentUser = useReadonlyStream(
const step = ref(1) const step = ref(1)
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization" type EmbedTabs =
| "params"
| "bodyParams"
| "headers"
| "authorization"
| "requestVariables"
type EmbedOption = { type EmbedOption = {
selectedTab: EmbedTabs selectedTab: EmbedTabs
@@ -263,6 +273,10 @@ const loading = computed(
) )
onLoggedIn(() => { onLoggedIn(() => {
if (adapter.isInitialized()) {
return
}
try { try {
// wait for a bit to let the auth token to be set // wait for a bit to let the auth token to be set
// because in some race conditions, the token is not set this fixes that // because in some race conditions, the token is not set this fixes that
@@ -369,6 +383,11 @@ const displayCustomizeRequestModal = (
label: t("tab.authorization"), label: t("tab.authorization"),
enabled: false, enabled: false,
}, },
{
value: "requestVariables",
label: t("tab.variables"),
enabled: false,
},
], ],
theme: "system", theme: "system",
} }

View File

@@ -28,7 +28,7 @@
</button> </button>
</div> </div>
<div <div
class="flex" class="flex overflow-x-scroll"
:class="[{ 'border-b pt-2 ': !noActiveTab }, embedColours]" :class="[{ 'border-b pt-2 ': !noActiveTab }, embedColours]"
> >
<span <span
@@ -57,7 +57,12 @@ import { computed } from "vue"
import { useI18n } from "~/composables/i18n" import { useI18n } from "~/composables/i18n"
type Tabs = "params" | "bodyParams" | "headers" | "authorization" type Tabs =
| "params"
| "bodyParams"
| "headers"
| "authorization"
| "requestVariables"
type EmbedOption = { type EmbedOption = {
selectedTab: Tabs selectedTab: Tabs

View File

@@ -79,29 +79,27 @@ import { history, historyKeymap } from "@codemirror/commands"
import { inputTheme } from "~/helpers/editor/themes/baseTheme" import { inputTheme } from "~/helpers/editor/themes/baseTheme"
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment" import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
import { useReadonlyStream } from "@composables/stream" import { useReadonlyStream } from "@composables/stream"
import { import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
AggregateEnvironment,
aggregateEnvsWithSecrets$,
} from "~/newstore/environments"
import { platform } from "~/platform" import { platform } from "~/platform"
import { onClickOutside, useDebounceFn } from "@vueuse/core" import { onClickOutside, useDebounceFn } from "@vueuse/core"
import { InspectorResult } from "~/services/inspection" import { InspectorResult } from "~/services/inspection"
import { invokeAction } from "~/helpers/actions" import { invokeAction } from "~/helpers/actions"
import { Environment } from "@hoppscotch/data"
import { useI18n } from "~/composables/i18n" import { useI18n } from "~/composables/i18n"
import IconEye from "~icons/lucide/eye" import IconEye from "~icons/lucide/eye"
import IconEyeoff from "~icons/lucide/eye-off" import IconEyeoff from "~icons/lucide/eye-off"
import { CompletionContext, autocompletion } from "@codemirror/autocomplete"
import { useService } from "dioc/vue"
import { RESTTabService } from "~/services/tab/rest"
import { syntaxTree } from "@codemirror/language"
const t = useI18n() const t = useI18n()
type Env = Environment["variables"][number] & { source: string }
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
modelValue?: string modelValue?: string
placeholder?: string placeholder?: string
styles?: string styles?: string
envs?: Env[] | null envs?: AggregateEnvironment[] | null
focus?: boolean focus?: boolean
selectTextOnMount?: boolean selectTextOnMount?: boolean
environmentHighlights?: boolean environmentHighlights?: boolean
@@ -110,6 +108,7 @@ const props = withDefaults(
inspectionResults?: InspectorResult[] | undefined inspectionResults?: InspectorResult[] | undefined
contextMenuEnabled?: boolean contextMenuEnabled?: boolean
secret?: boolean secret?: boolean
autoCompleteEnv?: boolean
}>(), }>(),
{ {
modelValue: "", modelValue: "",
@@ -124,6 +123,7 @@ const props = withDefaults(
inspectionResults: undefined, inspectionResults: undefined,
contextMenuEnabled: true, contextMenuEnabled: true,
secret: false, secret: false,
autoCompleteEnvSource: false,
} }
) )
@@ -353,29 +353,62 @@ watch(
let clipboardEv: ClipboardEvent | null = null let clipboardEv: ClipboardEvent | null = null
let pastedValue: string | null = null let pastedValue: string | null = null
const aggregateEnvs = useReadonlyStream(aggregateEnvsWithSecrets$, []) as Ref< const aggregateEnvs = useReadonlyStream(aggregateEnvs$, []) as Ref<
AggregateEnvironment[] AggregateEnvironment[]
> >
const tabs = useService(RESTTabService)
const envVars = computed(() => { const envVars = computed(() => {
return props.envs if (props.envs) {
? props.envs.map((x) => { return props.envs.map((x) => {
if (x.secret) { const { key, secret } = x
return { const value = secret ? "********" : x.value
key: x.key, const sourceEnv = "sourceEnv" in x ? x.sourceEnv : null
sourceEnv: "source" in x ? x.source : null, return {
value: "********", key,
} value,
} sourceEnv,
return { secret,
key: x.key, }
value: x.value, })
sourceEnv: "source" in x ? x.source : null, }
} return [
}) ...tabs.currentActiveTab.value.document.request.requestVariables.map(
: aggregateEnvs.value ({ active, key, value }) =>
active
? {
key,
value,
sourceEnv: "RequestVariable",
secret: false,
}
: ({} as AggregateEnvironment)
),
...aggregateEnvs.value,
]
}) })
function envAutoCompletion(context: CompletionContext) {
const options = (envVars.value ?? [])
.map((env) => ({
label: env?.key ? `<<${env.key}>>` : "",
info: env?.value ?? "",
apply: env?.key ? `<<${env.key}>>` : "",
}))
.filter((x) => x)
const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1)
const textBefore = context.state.sliceDoc(nodeBefore.from, context.pos)
const tagBefore = /<<\w*$/.exec(textBefore)
if (!tagBefore && !context.explicit) return null
return {
from: tagBefore ? nodeBefore.from + tagBefore.index : context.pos,
options: options,
validFor: /^(<<\w*)?$/,
}
}
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view) const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
function handleTextSelection() { function handleTextSelection() {
@@ -384,7 +417,9 @@ function handleTextSelection() {
const { from, to } = selection const { from, to } = selection
if (from === to) return if (from === to) return
const text = view.value?.state.doc.sliceString(from, to) const text = view.value?.state.doc.sliceString(from, to)
const { top, left } = view.value?.coordsAtPos(from) const coords = view.value?.coordsAtPos(from)
const top = coords?.top ?? 0
const left = coords?.left ?? 0
if (text) { if (text) {
invokeAction("contextmenu.open", { invokeAction("contextmenu.open", {
position: { position: {
@@ -406,16 +441,17 @@ function handleTextSelection() {
} }
} }
const initView = (el: any) => { // Debounce to prevent double click from selecting the word
// Debounce to prevent double click from selecting the word const debouncedTextSelection = (time: number) =>
const debounceFn = useDebounceFn(() => { useDebounceFn(() => {
handleTextSelection() handleTextSelection()
}, 140) }, time)
const initView = (el: any) => {
// Only add event listeners if context menu is enabled in the component // Only add event listeners if context menu is enabled in the component
if (props.contextMenuEnabled) { if (props.contextMenuEnabled) {
el.addEventListener("mouseup", debounceFn) el.addEventListener("mouseup", debouncedTextSelection(140))
el.addEventListener("keyup", debounceFn) el.addEventListener("keyup", debouncedTextSelection(140))
} }
const extensions: Extension = getExtensions(props.readonly || isSecret.value) const extensions: Extension = getExtensions(props.readonly || isSecret.value)
@@ -465,10 +501,17 @@ const getExtensions = (readonly: boolean): Extension => {
}, },
scroll(event) { scroll(event) {
if (event.target && props.contextMenuEnabled) { if (event.target && props.contextMenuEnabled) {
handleTextSelection() // Debounce to make the performance better
debouncedTextSelection(30)()
} }
}, },
}), }),
props.autoCompleteEnv
? autocompletion({
activateOnTyping: true,
override: [envAutoCompletion],
})
: [],
ViewPlugin.fromClass( ViewPlugin.fromClass(
class { class {
update(update: ViewUpdate) { update(update: ViewUpdate) {

View File

@@ -20,7 +20,7 @@
: '' : ''
" "
> >
<div class="p-4"> <div class="p-4 truncate">
<label <label
class="font-semibold text-secondaryDark" class="font-semibold text-secondaryDark"
:class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }" :class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }"
@@ -131,6 +131,7 @@
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmRemove" :show="confirmRemove"
:title="t('confirm.remove_team')" :title="t('confirm.remove_team')"
:loading-state="loading"
@hide-modal="confirmRemove = false" @hide-modal="confirmRemove = false"
@resolve="deleteTeam()" @resolve="deleteTeam()"
/> />
@@ -161,6 +162,8 @@ import IconMoreVertical from "~icons/lucide/more-vertical"
import IconUserX from "~icons/lucide/user-x" import IconUserX from "~icons/lucide/user-x"
import IconUserPlus from "~icons/lucide/user-plus" import IconUserPlus from "~icons/lucide/user-plus"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import { useService } from "dioc/vue"
import { WorkspaceService } from "~/services/workspace.service"
const t = useI18n() const t = useI18n()
@@ -173,6 +176,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(e: "edit-team"): void (e: "edit-team"): void
(e: "invite-team"): void (e: "invite-team"): void
(e: "refetch-teams"): void
}>() }>()
const toast = useToast() const toast = useToast()
@@ -180,7 +184,12 @@ const toast = useToast()
const confirmRemove = ref(false) const confirmRemove = ref(false)
const confirmExit = ref(false) const confirmExit = ref(false)
const loading = ref(false)
const workspaceService = useService(WorkspaceService)
const deleteTeam = () => { const deleteTeam = () => {
loading.value = true
pipe( pipe(
backendDeleteTeam(props.teamID), backendDeleteTeam(props.teamID),
TE.match( TE.match(
@@ -188,9 +197,25 @@ const deleteTeam = () => {
// TODO: Better errors ? We know the possible errors now // TODO: Better errors ? We know the possible errors now
toast.error(`${t("error.something_went_wrong")}`) toast.error(`${t("error.something_went_wrong")}`)
console.error(err) console.error(err)
loading.value = false
confirmRemove.value = false
}, },
() => { () => {
toast.success(`${t("team.deleted")}`) toast.success(`${t("team.deleted")}`)
loading.value = false
emit("refetch-teams")
const currentWorkspace = workspaceService.currentWorkspace.value
// If the current workspace is the deleted workspace, change the workspace to personal
if (
currentWorkspace.type === "team" &&
currentWorkspace.teamID === props.teamID
) {
workspaceService.changeWorkspace({ type: "personal" })
}
confirmRemove.value = false
} }
) )
)() // Tasks (and TEs) are lazy, so call the function returned )() // Tasks (and TEs) are lazy, so call the function returned

View File

@@ -4,6 +4,7 @@
<HoppButtonSecondary <HoppButtonSecondary
:label="`${t('team.create_new')}`" :label="`${t('team.create_new')}`"
outline outline
:icon="IconPlus"
@click="displayModalAdd(true)" @click="displayModalAdd(true)"
/> />
<div v-if="loading" class="flex flex-col items-center justify-center"> <div v-if="loading" class="flex flex-col items-center justify-center">
@@ -16,13 +17,6 @@
:alt="`${t('empty.teams')}`" :alt="`${t('empty.teams')}`"
:text="`${t('empty.teams')}`" :text="`${t('empty.teams')}`"
> >
<template #body>
<HoppButtonSecondary
:label="`${t('team.create_new')}`"
filled
@click="displayModalAdd(true)"
/>
</template>
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
<div <div
v-else-if="!loading" v-else-if="!loading"
@@ -39,6 +33,7 @@
:compact="modal" :compact="modal"
@edit-team="editTeam(team, team.id)" @edit-team="editTeam(team, team.id)"
@invite-team="inviteTeam(team, team.id)" @invite-team="inviteTeam(team, team.id)"
@refetch-teams="refetchTeams"
/> />
</div> </div>
<div v-if="!loading && adapterError" class="flex flex-col items-center"> <div v-if="!loading && adapterError" class="flex flex-col items-center">
@@ -76,6 +71,7 @@ import { useReadonlyStream } from "@composables/stream"
import { useColorMode } from "@composables/theming" import { useColorMode } from "@composables/theming"
import { WorkspaceService } from "~/services/workspace.service" import { WorkspaceService } from "~/services/workspace.service"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import IconPlus from "~icons/lucide/plus"
const t = useI18n() const t = useI18n()

View File

@@ -27,13 +27,10 @@ const workspaceService = useService(WorkspaceService)
const workspace = workspaceService.currentWorkspace const workspace = workspaceService.currentWorkspace
const currentWorkspace = computed(() => { const currentWorkspace = computed(() => {
if (props.isOnlyPersonal) { if (props.isOnlyPersonal || workspace.value.type === "personal") {
return `${t("workspace.personal")}` return t("workspace.personal")
} }
if (workspace.value.type === "team") { return teamWorkspaceName.value
return teamWorkspaceName.value
}
return `${t("workspace.personal")}`
}) })
const teamWorkspaceName = computed(() => { const teamWorkspaceName = computed(() => {

Some files were not shown because too many files have changed in this diff Show More