diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/collection-with-variables.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/collection-with-variables.json new file mode 100644 index 00000000..d2073f3e --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/collection-with-variables.json @@ -0,0 +1,102 @@ +{ + "id": "cmeicx49r00xylb1jxektmknk", + "_ref_id": "coll_meicx3z7_a1cb5e72-cd1b-414b-adc2-7d601ca0936d", + "v": 10, + "name": "coll-with-variables", + "folders": [ + { + "id": "cmeicy6fu00xzlb1jqmmqbjdm", + "_ref_id": "coll_meie14lh_818ea8a2-9839-4a1c-8cce-cc7565b5f594", + "v": 10, + "name": "folder-1", + "folders": [], + "requests": [ + { + "v": "15", + "id": "cmeicyhnn00y1lb1j8d80g7ys", + "name": "request-1", + "method": "GET", + "endpoint": "https://echo.hoppscotch.io", + "params": [ + { + "key": "collection-var-1", + "value": "<>", + "active": true, + "description": "" + }, + { + "key": "collection-var-2", + "value": "<>", + "active": true, + "description": "" + }, + { + "key": "folder-var-1", + "value": "<>", + "active": true, + "description": "" + }, + { + "key": "folder-var-2", + "value": "<>", + "active": true, + "description": "" + } + ], + "headers": [], + "preRequestScript": "", + "testScript": "export {};\npw.test('Correctly inherits collection variables from the parent collection and folder', () =>\n{\n\n pw.expect([\n \"collection-var-1-initial-value\",\n \"collection-var-1-current-value\"\n ]).toInclude(pw.response.body.args[\"collection-var-1\"])\n\n pw.expect([\n \"collection-var-2-initial-value\",\n \"collection-var-2-current-value\"\n ]).toInclude(pw.response.body.args[\"collection-var-2\"])\n\n pw.expect([\n \"folder-variable-1-initial-value\",\n \"folder-variable-1-current-value\"\n ]).toInclude(pw.response.body.args[\"folder-var-1\"])\n\n pw.expect([\n \"folder-variable-2-initial-value\",\n \"folder-variable-2-current-value\"\n ]).toInclude(pw.response.body.args[\"folder-var-2\"])\n\n});", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "contentType": null, + "body": null + }, + "requestVariables": [], + "responses": {} + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [], + "variables": [ + { + "key": "folder-variable-1", + "secret": false, + "currentValue": "", + "initialValue": "folder-variable-1-initial-value" + }, + { + "key": "folder-variable-2", + "secret": false, + "currentValue": "", + "initialValue": "folder-variable-2-initial-value" + } + ] + } + ], + "requests": [], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [], + "variables": [ + { + "key": "collection-variable-1", + "secret": false, + "currentValue": "", + "initialValue": "collection-var-1-initial-value" + }, + { + "key": "collection-variable-2", + "secret": false, + "currentValue": "", + "initialValue": "collection-var-2-initial-value" + } + ] +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json index 01412b76..f56863a4 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json @@ -1,7 +1,7 @@ { "v": 6, "id": "cm9wmuzj46s3imbs891pdamv4", - "name": "Multiple child collections with authorization & headers set at each level", + "name": "Multiple child collections with authorization, headers and variables set at each level", "folders": [ { "v": 6, @@ -688,5 +688,13 @@ "description": "" } ], + "variables": [ + { + "key": "collection-variable", + "currentValue": "collection-variable-value", + "initialValue": "collection-variable-value", + "secret": false + } + ], "_ref_id": "coll_m9wn4jl9_aa8a3bc2-a96f-4cac-86f3-2df4bb355cc8" } diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/valid-mixed-versions-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/valid-mixed-versions-coll.json index cfe97d5c..db7df38f 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/valid-mixed-versions-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/valid-mixed-versions-coll.json @@ -86,5 +86,6 @@ "authActive": true }, "headers": [], + "variables": [], "_ref_id": "coll_mbhuxoci_a8fc710e-04c1-489c-a183-7f16946a7225" } diff --git a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts index 3fc497a7..3e399e67 100644 --- a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts +++ b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts @@ -11,29 +11,29 @@ import { WorkspaceEnvironment, } from "../../../utils/workspace-access"; -export const WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: WorkspaceCollection[] = +export const WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK: WorkspaceCollection[] = [ { id: "clx1ldkzs005t10f8rp5u60q7", - data: '{"auth":{"token":"BearerToken","authType":"bearer","authActive":true},"headers":[{"key":"X-Test-Header","value":"Set at root collection","active":true,"description":""}]}', + data: '{"auth":{"token":"BearerToken","authType":"bearer","authActive":true},"headers":[{"key":"X-Test-Header","value":"Set at root collection","active":true,"description":""}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "CollectionA", parentID: null, folders: [ { id: "clx1ldkzs005v10f86b9wx4yc", - data: '{"auth":{"authType":"inherit","authActive":true},"headers":[]}', + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[],"variables":[]}', title: "FolderA", parentID: "clx1ldkzs005t10f8rp5u60q7", folders: [ { id: "clx1ldkzt005x10f8i0u5lzgj", - data: '{"auth":{"key":"key","addTo":"HEADERS","value":"test-key","authType":"api-key","authActive":true},"headers":[{"key":"X-Test-Header","value":"Overriden at FolderB","active":true}]}', + data: '{"auth":{"key":"key","addTo":"HEADERS","value":"test-key","authType":"api-key","authActive":true},"headers":[{"key":"X-Test-Header","value":"Overriden at FolderB","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "FolderB", parentID: "clx1ldkzs005v10f86b9wx4yc", folders: [ { id: "clx1ldkzu005z10f880zx17bg", - data: '{"auth":{"authType":"inherit","authActive":true},"headers":[]}', + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[],"variables":[]}', title: "FolderC", parentID: "clx1ldkzt005x10f8i0u5lzgj", folders: [], @@ -85,7 +85,7 @@ export const WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Workspa }, ]; -export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppCollection[] = +export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK: HoppCollection[] = [ { v: CollectionSchemaVersion, @@ -142,6 +142,7 @@ export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppC authActive: true, }, headers: [], + variables: [], }, ], requests: [ @@ -181,6 +182,14 @@ export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppC description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ], requests: [ @@ -211,6 +220,7 @@ export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppC authActive: true, }, headers: [], + variables: [], }, ], requests: [ @@ -250,27 +260,35 @@ export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppC description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ]; -export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: WorkspaceCollection[] = +export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK: WorkspaceCollection[] = [ { id: "clx1f86hv000010f8szcfya0t", - data: '{"auth":{"authType":"basic","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value set at the root collection","active":true},{"key":"Inherited-Header","value":"Inherited header at all levels","active":true}]}', + data: '{"auth":{"authType":"basic","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value set at the root collection","active":true},{"key":"Inherited-Header","value":"Inherited header at all levels","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: - "Multiple child collections with authorization & headers set at each level", + "Multiple child collections with authorization, headers and variables set at each level", parentID: null, folders: [ { id: "clx1fjgah000110f8a5bs68gd", - data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-1","active":true}]}', + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-1","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-1", parentID: "clx1f86hv000010f8szcfya0t", folders: [ { id: "clx1fjwmm000410f8l1gkkr1a", - data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-11","active":true}]}', + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-11","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-11", parentID: "clx1fjgah000110f8a5bs68gd", folders: [], @@ -287,7 +305,7 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1fjyxm000510f8pv90dt43", - data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-12","active":true},{"key":"key","value":"Set at folder-12","active":true}]}', + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-12","active":true},{"key":"key","value":"Set at folder-12","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-12", parentID: "clx1fjgah000110f8a5bs68gd", folders: [], @@ -304,7 +322,7 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1fk1cv000610f88kc3aupy", - data: '{"auth":{"token":"test-token","authType":"bearer","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-13","active":true},{"key":"key","value":"Set at folder-13","active":true}]}', + data: '{"auth":{"token":"test-token","authType":"bearer","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-13","active":true},{"key":"key","value":"Set at folder-13","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-13", parentID: "clx1fjgah000110f8a5bs68gd", folders: [], @@ -333,13 +351,13 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1fjk9o000210f8j0573pls", - data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-2","active":true}]}', + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-2","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-2", parentID: "clx1f86hv000010f8szcfya0t", folders: [ { id: "clx1fk516000710f87sfpw6bo", - data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-21","active":true}]}', + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-21","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-21", parentID: "clx1fjk9o000210f8j0573pls", folders: [], @@ -356,7 +374,7 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1fk72t000810f8gfwkpi5y", - data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-22","active":true},{"key":"key","value":"Set at folder-22","active":true}]}', + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-22","active":true},{"key":"key","value":"Set at folder-22","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-22", parentID: "clx1fjk9o000210f8j0573pls", folders: [], @@ -373,7 +391,7 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1fk95g000910f8bunhaoo8", - data: '{"auth":{"token":"test-token","authType":"bearer","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-23","active":true},{"key":"key","value":"Set at folder-23","active":true}]}', + data: '{"auth":{"token":"test-token","authType":"bearer","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-23","active":true},{"key":"key","value":"Set at folder-23","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-23", parentID: "clx1fjk9o000210f8j0573pls", folders: [], @@ -402,13 +420,13 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1fjmlq000310f86o4d3w2o", - data: '{"auth":{"key":"testuser","addTo":"HEADERS","value":"testpass","authType":"basic","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-3","active":true}]}', + data: '{"auth":{"key":"testuser","addTo":"HEADERS","value":"testpass","authType":"basic","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-3","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-3", parentID: "clx1f86hv000010f8szcfya0t", folders: [ { id: "clx1iwq0p003e10f8u8zg0p85", - data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-31","active":true}]}', + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-31","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-31", parentID: "clx1fjmlq000310f86o4d3w2o", folders: [], @@ -425,7 +443,7 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1izut7003m10f894ip59zg", - data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-32","active":true},{"key":"key","value":"Set at folder-32","active":true}]}', + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-32","active":true},{"key":"key","value":"Set at folder-32","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-32", parentID: "clx1fjmlq000310f86o4d3w2o", folders: [], @@ -442,7 +460,7 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp }, { id: "clx1j2ka9003q10f8cdbzpgpg", - data: '{"auth":{"token":"test-token","authType":"bearer","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-33","active":true},{"key":"key","value":"Set at folder-33","active":true}]}', + data: '{"auth":{"token":"test-token","authType":"bearer","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-33","active":true},{"key":"key","value":"Set at folder-33","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-33", parentID: "clx1fjmlq000310f86o4d3w2o", folders: [], @@ -485,17 +503,17 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppCollection[] = [ { - v: 9, + v: 10, id: "clx1f86hv000010f8szcfya0t", - name: "Multiple child collections with authorization & headers set at each level", + name: "Multiple child collections with authorization, headers and variables set at each level", folders: [ { - v: 9, + v: 10, id: "clx1fjgah000110f8a5bs68gd", name: "folder-1", folders: [ { - v: 9, + v: 10, id: "clx1fjwmm000410f8l1gkkr1a", name: "folder-11", folders: [], @@ -535,9 +553,17 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1fjyxm000510f8pv90dt43", name: "folder-12", folders: [], @@ -593,9 +619,17 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1fk1cv000610f88kc3aupy", name: "folder-13", folders: [], @@ -669,6 +703,14 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ], requests: [ @@ -705,14 +747,22 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1fjk9o000210f8j0573pls", name: "folder-2", folders: [ { - v: 9, + v: 10, id: "clx1fk516000710f87sfpw6bo", name: "folder-21", folders: [], @@ -750,9 +800,17 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1fk72t000810f8gfwkpi5y", name: "folder-22", folders: [], @@ -808,9 +866,17 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1fk95g000910f8bunhaoo8", name: "folder-23", folders: [], @@ -871,6 +937,14 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ], requests: [ @@ -913,14 +987,23 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, + { - v: 9, + v: 10, id: "clx1fjmlq000310f86o4d3w2o", name: "folder-3", folders: [ { - v: 9, + v: 10, id: "clx1iwq0p003e10f8u8zg0p85", name: "folder-31", folders: [], @@ -958,9 +1041,17 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1izut7003m10f894ip59zg", name: "folder-32", folders: [], @@ -1016,9 +1107,17 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { - v: 9, + v: 10, id: "clx1j2ka9003q10f8cdbzpgpg", name: "folder-33", folders: [], @@ -1079,6 +1178,14 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ], requests: [ @@ -1134,6 +1241,14 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ], requests: [ @@ -1179,16 +1294,24 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ]; // Collections with `data` field set to `null` at certain levels -export const WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: WorkspaceCollection[] = +export const WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK: WorkspaceCollection[] = [ { id: "clx1kxvao005m10f8luqivrf1", data: null, - title: "Collection with no authorization/headers set", + title: "Collection with no authorization/headers/variables set", parentID: null, folders: [ { @@ -1210,7 +1333,7 @@ export const WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: }, { id: "clx1kym98005o10f8qg17t9o2", - data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Set at folder-2","active":true}]}', + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Set at folder-2","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-2", parentID: "clx1kxvao005m10f8luqivrf1", folders: [], @@ -1235,7 +1358,7 @@ export const WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: }, { id: "clx1l2eaz005s10f8loetbbeb", - data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Set at folder-4","active":true}]}', + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Set at folder-4","active":true}],"variables":[{"key":"collection-variable","currentValue":"collection-variable-value","initialValue":"collection-variable-value","secret":false}]}', title: "folder-4", parentID: "clx1kxvao005m10f8luqivrf1", folders: [], @@ -1246,12 +1369,12 @@ export const WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: }, ]; -export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: HoppCollection[] = +export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK: HoppCollection[] = [ { v: CollectionSchemaVersion, id: "clx1kxvao005m10f8luqivrf1", - name: "Collection with no authorization/headers set", + name: "Collection with no authorization/headers/variables set", folders: [ { v: CollectionSchemaVersion, @@ -1284,6 +1407,7 @@ export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK authActive: true, }, headers: [], + variables: [], }, { v: CollectionSchemaVersion, @@ -1323,6 +1447,14 @@ export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, { v: CollectionSchemaVersion, @@ -1335,6 +1467,7 @@ export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK authActive: true, }, headers: [], + variables: [], }, { v: CollectionSchemaVersion, @@ -1354,6 +1487,14 @@ export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK description: "", }, ], + variables: [ + { + key: "collection-variable", + currentValue: "collection-variable-value", + initialValue: "collection-variable-value", + secret: false, + }, + ], }, ], requests: [], @@ -1362,6 +1503,7 @@ export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK authActive: true, }, headers: [], + variables: [], }, ]; diff --git a/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts b/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts index eb7ef180..299eb3ba 100644 --- a/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts @@ -5,18 +5,18 @@ import { transformWorkspaceEnvironment, } from "../../utils/workspace-access"; import { - TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK, - TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, + TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK, + TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK, TRANSFORMED_ENVIRONMENT_V0_FORMAT_MOCK, TRANSFORMED_ENVIRONMENT_V1_FORMAT_MOCK, TRANSFORMED_ENVIRONMENT_V2_FORMAT_MOCK, TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, - WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK, - WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, + WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK, + WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK, WORKSPACE_ENVIRONMENT_V0_FORMAT_MOCK, WORKSPACE_ENVIRONMENT_V1_FORMAT_MOCK, WORKSPACE_ENVIRONMENT_V2_FORMAT_MOCK, - WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, + WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK, } from "./fixtures/workspace-access.mock"; describe("workspace-access", () => { @@ -24,15 +24,17 @@ describe("workspace-access", () => { test("Successfully transforms collection data with deeply nested collections and authorization/headers set at each level to the `HoppCollection` format", () => { expect( transformWorkspaceCollections( - WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK + WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK ) - ).toEqual(TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK); + ).toEqual( + TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK + ); }); test("Successfully transforms collection data with multiple child collections and authorization/headers set at each level to the `HoppCollection` format", () => { expect( transformWorkspaceCollections( - WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK + WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK ) ).toEqual(TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK); }); @@ -40,10 +42,10 @@ describe("workspace-access", () => { test("Adds the default value for `auth` & `header` fields while transforming collections without authorization/headers set at certain levels", () => { expect( transformWorkspaceCollections( - WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK + WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK ) ).toEqual( - TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK + TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK ); }); }); diff --git a/packages/hoppscotch-cli/src/types/request.ts b/packages/hoppscotch-cli/src/types/request.ts index 519ae292..023f86e9 100644 --- a/packages/hoppscotch-cli/src/types/request.ts +++ b/packages/hoppscotch-cli/src/types/request.ts @@ -1,4 +1,9 @@ -import { Environment, HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; +import { + Environment, + HoppCollection, + HoppCollectionVariable, + HoppRESTRequest, +} from "@hoppscotch/data"; import { z } from "zod"; import { TestReport } from "../interfaces/response"; @@ -37,5 +42,6 @@ export type ProcessRequestParams = { envs: HoppEnvs; path: string; delay: number; - legacySandbox: boolean; + legacySandbox?: boolean; + collectionVariables?: HoppCollectionVariable[]; }; diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts index 27f9d350..fa635783 100644 --- a/packages/hoppscotch-cli/src/utils/collections.ts +++ b/packages/hoppscotch-cli/src/utils/collections.ts @@ -115,12 +115,18 @@ const processCollection = async ( for (const request of collection.requests) { const _request = preProcessRequest(request as HoppRESTRequest, collection); const requestPath = `${path}/${_request.name}`; + + const collectionVariables = collection.variables.filter( + (variable) => variable.key && variable.key.trim() !== "" + ); + const processRequestParams: ProcessRequestParams = { path: requestPath, request: _request, envs, delay, legacySandbox, + collectionVariables, }; // Request processing initiated message. @@ -161,6 +167,20 @@ const processCollection = async ( updatedFolder.headers.push(...filteredHeaders); } + if (updatedFolder.variables?.length) { + // Filter out variable entries present in the parent collection under the same name + // This ensures the folder variables take precedence over the collection variables + const filteredVariables = collection.variables.filter( + (collectionVariableEntries) => { + return !updatedFolder.variables.some( + (folderVariableEntries) => + folderVariableEntries.key === collectionVariableEntries.key + ); + } + ); + updatedFolder.variables.push(...filteredVariables); + } + await processCollection( updatedFolder, `${path}/${updatedFolder.name}`, diff --git a/packages/hoppscotch-cli/src/utils/getters.ts b/packages/hoppscotch-cli/src/utils/getters.ts index c76ff0ea..f87431dc 100644 --- a/packages/hoppscotch-cli/src/utils/getters.ts +++ b/packages/hoppscotch-cli/src/utils/getters.ts @@ -1,5 +1,6 @@ import { EnvironmentVariable, + HoppCollectionVariable, HoppRESTHeader, HoppRESTParam, HoppRESTRequestVariables, @@ -269,11 +270,13 @@ export const getResourceContents = async ( * * @param {HoppRESTRequestVariables} requestVariables - Incoming request variables. * @param {EnvironmentVariable[]} environmentVariables - Incoming environment variables. + * @param {HoppCollectionVariable[]} collectionVariables - Optional collection variables to be included. * @returns {EnvironmentVariable[]} The resolved list of variables that conforms to the shape of environment variables. */ export const getResolvedVariables = ( requestVariables: HoppRESTRequestVariables, - environmentVariables: EnvironmentVariable[] + environmentVariables: EnvironmentVariable[], + collectionVariables: HoppCollectionVariable[] = [] ): EnvironmentVariable[] => { // Transforming request variables to the shape of environment variables const activeRequestVariables = requestVariables @@ -287,11 +290,21 @@ export const getResolvedVariables = ( const requestVariableKeys = activeRequestVariables.map(({ key }) => key); - // Request variables have higher priority, hence filtering out environment variables with the same keys - const filteredEnvironmentVariables = environmentVariables.filter( + // Request variables have higher priority, hence filtering out collection variables with the same keys + const filteredCollectionVariables = collectionVariables.filter( ({ key }) => !requestVariableKeys.includes(key) ); + const collectionVariableKeys = filteredCollectionVariables.map( + ({ key }) => key + ); + + // Filtering out environment variables that have keys present in request or collection variables + const filteredEnvironmentVariables = environmentVariables.filter( + ({ key }) => + ![...requestVariableKeys, ...collectionVariableKeys].includes(key) + ); + // Setting currentValue to initialValue for environment variables // because the exported file might not have the currentValue field const processedEnvironmentVariables = filteredEnvironmentVariables.map( @@ -304,5 +317,19 @@ export const getResolvedVariables = ( }) ); - return [...activeRequestVariables, ...processedEnvironmentVariables]; + const processedCollectionVariables = filteredCollectionVariables.map( + ({ key, initialValue, currentValue, secret }) => ({ + key, + initialValue, + currentValue: + currentValue && currentValue !== "" ? currentValue : initialValue, + secret, + }) + ); + + return [ + ...activeRequestVariables, + ...processedCollectionVariables, + ...processedEnvironmentVariables, + ]; }; diff --git a/packages/hoppscotch-cli/src/utils/pre-request.ts b/packages/hoppscotch-cli/src/utils/pre-request.ts index f6a3d350..4837ff6f 100644 --- a/packages/hoppscotch-cli/src/utils/pre-request.ts +++ b/packages/hoppscotch-cli/src/utils/pre-request.ts @@ -7,6 +7,7 @@ import { parseTemplateString, parseTemplateStringE, generateJWTToken, + HoppCollectionVariable, } from "@hoppscotch/data"; import { runPreRequestScript } from "@hoppscotch/js-sandbox/node"; import * as A from "fp-ts/Array"; @@ -46,7 +47,8 @@ import { calculateHawkHeader } from "@hoppscotch/data"; export const preRequestScriptRunner = ( request: HoppRESTRequest, envs: HoppEnvs, - legacySandbox: boolean + legacySandbox: boolean, + collectionVariables?: HoppCollectionVariable[] ): TE.TaskEither< HoppCLIError, { effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs } @@ -67,7 +69,7 @@ export const preRequestScriptRunner = ( ), TE.chainW((env) => TE.tryCatch( - () => getEffectiveRESTRequest(request, env), + () => getEffectiveRESTRequest(request, env, collectionVariables), (reason) => error({ code: "PRE_REQUEST_SCRIPT_ERROR", data: reason }) ) ), @@ -93,7 +95,8 @@ export const preRequestScriptRunner = ( */ export async function getEffectiveRESTRequest( request: HoppRESTRequest, - environment: Environment + environment: Environment, + collectionVariables?: HoppCollectionVariable[] ): Promise< E.Either< HoppCLIError, @@ -104,7 +107,8 @@ export async function getEffectiveRESTRequest( const resolvedVariables = getResolvedVariables( request.requestVariables, - envVariables + envVariables, + collectionVariables ); // Parsing final headers with applied ENVs. diff --git a/packages/hoppscotch-cli/src/utils/request.ts b/packages/hoppscotch-cli/src/utils/request.ts index d0104383..4f1611f2 100644 --- a/packages/hoppscotch-cli/src/utils/request.ts +++ b/packages/hoppscotch-cli/src/utils/request.ts @@ -202,7 +202,8 @@ export const processRequest = params: ProcessRequestParams ): T.Task<{ envs: HoppEnvs; report: RequestReport }> => async () => { - const { envs, path, request, delay, legacySandbox } = params; + const { envs, path, request, delay, legacySandbox, collectionVariables } = + params; // Initialising updatedEnvs with given parameter envs, will eventually get updated. const result = { @@ -236,7 +237,8 @@ export const processRequest = const preRequestRes = await preRequestScriptRunner( request, processedEnvs, - legacySandbox + legacySandbox, + collectionVariables )(); if (E.isLeft(preRequestRes)) { printPreRequestRunner.fail(); diff --git a/packages/hoppscotch-cli/src/utils/workspace-access.ts b/packages/hoppscotch-cli/src/utils/workspace-access.ts index 2c408803..d78bd483 100644 --- a/packages/hoppscotch-cli/src/utils/workspace-access.ts +++ b/packages/hoppscotch-cli/src/utils/workspace-access.ts @@ -3,6 +3,7 @@ import { Environment, EnvironmentSchemaVersion, HoppCollection, + HoppCollectionVariable, HoppRESTAuth, HoppRESTHeaders, HoppRESTRequest, @@ -173,12 +174,17 @@ export const transformWorkspaceCollections = ( return collections.map((collection) => { const { id, title, data, requests, folders } = collection; - const parsedData: { auth?: HoppRESTAuth; headers?: HoppRESTHeaders } = data - ? JSON.parse(data) - : {}; + const parsedData: { + auth?: HoppRESTAuth; + headers?: HoppRESTHeaders; + variables: HoppCollectionVariable[]; + } = data ? JSON.parse(data) : {}; - const { auth = { authType: "inherit", authActive: true }, headers = [] } = - parsedData; + const { + auth = { authType: "inherit", authActive: true }, + headers = [], + variables = [], + } = parsedData; const transformedAuth = transformAuth(auth); @@ -186,6 +192,10 @@ export const transformWorkspaceCollections = ( header.description ? header : { ...header, description: "" } ); + const filteredCollectionVariables = variables.filter( + (variable) => variable.key.trim() !== "" + ); + // The response doesn't include a way to infer the schema version, so it's set to the latest version // Any relevant migrations have to be accounted here // `ref_id` field isn't necessary being applicable only to personal workspace and asociates with syncing @@ -197,6 +207,7 @@ export const transformWorkspaceCollections = ( requests: transformWorkspaceRequests(requests), auth: transformedAuth, headers: transformedHeaders, + variables: filteredCollectionVariables, }; }); }; diff --git a/packages/hoppscotch-common/assets/scss/styles.scss b/packages/hoppscotch-common/assets/scss/styles.scss index 79a83dcf..25052adb 100644 --- a/packages/hoppscotch-common/assets/scss/styles.scss +++ b/packages/hoppscotch-common/assets/scss/styles.scss @@ -503,6 +503,11 @@ details[open] summary .indicator { @apply hover:bg-amber-600; } + &.collection-variable-highlight { + @apply bg-purple-500; + @apply hover:bg-purple-600; + } + &.environment-variable-highlight { @apply bg-green-500; @apply hover:bg-green-600; diff --git a/packages/hoppscotch-common/assets/themes/tippy-themes.scss b/packages/hoppscotch-common/assets/themes/tippy-themes.scss index 041658de..7fe6ae22 100644 --- a/packages/hoppscotch-common/assets/themes/tippy-themes.scss +++ b/packages/hoppscotch-common/assets/themes/tippy-themes.scss @@ -1,6 +1,7 @@ // Base shared styles for all tippy themes @mixin base-tippy-styles { .cm-tooltip { + @apply z-[1000] #{!important}; .tippy-box { @apply shadow-none #{!important}; @apply fixed; diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index ae0e76bb..e5527b1c 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -416,6 +416,7 @@ "empty_schema": "No schema found", "endpoint": "Endpoint cannot be empty", "environments": "Environments are empty", + "collection_variables": "Collection variables are empty", "folder": "Folder is empty", "headers": "This request does not have any headers", "history": "History is empty", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index daceeccd..7aa85941 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -72,6 +72,7 @@ declare module 'vue' { CollectionsRequest: typeof import('./components/collections/Request.vue')['default'] CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default'] CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default'] + CollectionsVariables: typeof import('./components/collections/Variables.vue')['default'] ConsoleItem: typeof import('./components/console/Item.vue')['default'] ConsolePanel: typeof import('./components/console/Panel.vue')['default'] ConsoleValue: typeof import('./components/console/Value.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/collections/Collection.vue b/packages/hoppscotch-common/src/components/collections/Collection.vue index 1782dbc3..11ef126d 100644 --- a/packages/hoppscotch-common/src/components/collections/Collection.vue +++ b/packages/hoppscotch-common/src/components/collections/Collection.vue @@ -58,8 +58,9 @@ -
+
- +
diff --git a/packages/hoppscotch-common/src/components/collections/ImportExport.vue b/packages/hoppscotch-common/src/components/collections/ImportExport.vue index 37ed3881..5fda8674 100644 --- a/packages/hoppscotch-common/src/components/collections/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/collections/ImportExport.vue @@ -127,6 +127,7 @@ function translateToTeamCollectionFormat(x: HoppCollection) { const data = { auth: x.auth, headers: x.headers, + variables: x.variables, } const obj = { @@ -419,7 +420,7 @@ const HoppInsomniaImporter: ImporterOrExporter = { importSummary: currentImportSummary, component: FileSource({ caption: "import.from_file", - acceptedFileTypes: ".json", + acceptedFileTypes: ".json, .yaml, .yml, .har", description: "import.from_insomnia_import_summary", onImportFromFile: async (content) => { isInsomniaImporterInProgress.value = true diff --git a/packages/hoppscotch-common/src/components/collections/Properties.vue b/packages/hoppscotch-common/src/components/collections/Properties.vue index 219ea567..9a0fecba 100644 --- a/packages/hoppscotch-common/src/components/collections/Properties.vue +++ b/packages/hoppscotch-common/src/components/collections/Properties.vue @@ -4,7 +4,7 @@ dialog :title="t('collection.properties')" :full-width-body="true" - styles="sm:max-w-2xl" + styles="sm:max-w-3xl" @close="hideModal" > diff --git a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue index 54d1a93b..79a5af0a 100644 --- a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue @@ -141,7 +141,7 @@ import { } from "~/helpers/backend/mutations/TeamRequest" import { Picked } from "~/helpers/types/HoppPicked" import { - cascadeParentCollectionForHeaderAuth, + cascadeParentCollectionForProperties, editGraphqlRequest, editRESTRequest, saveGraphqlRequestAs, @@ -357,15 +357,11 @@ const saveRequestAs = async () => { }, } - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - `${picked.value.collectionIndex}`, - "rest" - ) - - RESTTabs.currentActiveTab.value.document.inheritedProperties = { - auth, - headers, - } + RESTTabs.currentActiveTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties( + `${picked.value.collectionIndex}`, + "rest" + ) platform.analytics?.logEvent({ type: "HOPP_SAVE_REQUEST", @@ -395,15 +391,8 @@ const saveRequestAs = async () => { }, } - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - picked.value.folderPath, - "rest" - ) - - RESTTabs.currentActiveTab.value.document.inheritedProperties = { - auth, - headers, - } + RESTTabs.currentActiveTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties(picked.value.folderPath, "rest") platform.analytics?.logEvent({ type: "HOPP_SAVE_REQUEST", @@ -434,15 +423,8 @@ const saveRequestAs = async () => { }, } - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - picked.value.folderPath, - "rest" - ) - - RESTTabs.currentActiveTab.value.document.inheritedProperties = { - auth, - headers, - } + RESTTabs.currentActiveTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties(picked.value.folderPath, "rest") platform.analytics?.logEvent({ type: "HOPP_SAVE_REQUEST", @@ -538,15 +520,8 @@ const saveRequestAs = async () => { workspaceType: "team", }) - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - picked.value.folderPath, - "graphql" - ) - - GQLTabs.currentActiveTab.value.document.inheritedProperties = { - auth, - headers, - } + GQLTabs.currentActiveTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties(picked.value.folderPath, "graphql") requestSaved("GQL") } else if (picked.value.pickedType === "gql-my-folder") { @@ -573,15 +548,8 @@ const saveRequestAs = async () => { workspaceType: "team", }) - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - picked.value.folderPath, - "graphql" - ) - - GQLTabs.currentActiveTab.value.document.inheritedProperties = { - auth, - headers, - } + GQLTabs.currentActiveTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties(picked.value.folderPath, "graphql") requestSaved("GQL") } else if (picked.value.pickedType === "gql-my-collection") { @@ -608,15 +576,11 @@ const saveRequestAs = async () => { workspaceType: "team", }) - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - `${picked.value.collectionIndex}`, - "graphql" - ) - - GQLTabs.currentActiveTab.value.document.inheritedProperties = { - auth, - headers, - } + GQLTabs.currentActiveTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties( + `${picked.value.collectionIndex}`, + "graphql" + ) requestSaved("GQL") } diff --git a/packages/hoppscotch-common/src/components/collections/Variables.vue b/packages/hoppscotch-common/src/components/collections/Variables.vue new file mode 100644 index 00000000..416990c7 --- /dev/null +++ b/packages/hoppscotch-common/src/components/collections/Variables.vue @@ -0,0 +1,316 @@ + + + diff --git a/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue b/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue index 9321ea3a..b92624cc 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue @@ -339,7 +339,7 @@ const isSelected = computed( const collectionIcon = computed(() => { if (isSelected.value) return IconCheckCircle else if (!showChildren.value && !props.isFiltered) return IconFolder - else if (!showChildren.value || props.isFiltered) return IconFolderOpen + else if (showChildren.value || props.isFiltered) return IconFolderOpen return IconFolder }) diff --git a/packages/hoppscotch-common/src/components/collections/graphql/index.vue b/packages/hoppscotch-common/src/components/collections/graphql/index.vue index 9317b6a6..7894e814 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/index.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/index.vue @@ -165,7 +165,7 @@ import { graphqlCollections$, addGraphqlFolder, saveGraphqlRequestAs, - cascadeParentCollectionForHeaderAuth, + cascadeParentCollectionForProperties, editGraphqlCollection, editGraphqlFolder, moveGraphqlRequest, @@ -402,11 +402,6 @@ const onAddRequest = ({ name, path }: { name: string; path: string }) => { const insertionIndex = saveGraphqlRequestAs(path, newRequest) - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - path, - "graphql" - ) - tabs.createNewTab({ saveContext: { originLocation: "user-collection", @@ -415,10 +410,7 @@ const onAddRequest = ({ name, path }: { name: string; path: string }) => { }, request: newRequest, isDirty: false, - inheritedProperties: { - auth, - headers, - }, + inheritedProperties: cascadeParentCollectionForProperties(path, "graphql"), }) platform.analytics?.logEvent({ @@ -524,10 +516,6 @@ const selectRequest = ({ folderPath: folderPath, requestIndex: requestIndex, }) - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - folderPath, - "graphql" - ) // Switch to that request if that request is open if (possibleTab) { tabs.setActiveTab(possibleTab.value.id) @@ -541,10 +529,10 @@ const selectRequest = ({ }, request: cloneDeep(request), isDirty: false, - inheritedProperties: { - auth, - headers, - }, + inheritedProperties: cascadeParentCollectionForProperties( + folderPath, + "graphql" + ), }) } @@ -557,11 +545,6 @@ const dropRequest = ({ requestIndex: number collectionIndex: number }) => { - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - `${collectionIndex}`, - "graphql" - ) - const possibleTab = tabs.getTabRefWithSaveContext({ originLocation: "user-collection", folderPath, @@ -576,10 +559,8 @@ const dropRequest = ({ .length, } - possibleTab.value.document.inheritedProperties = { - auth, - headers, - } + possibleTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties(`${collectionIndex}`, "graphql") } moveGraphqlRequest(folderPath, requestIndex, `${collectionIndex}`) @@ -610,15 +591,10 @@ const editProperties = ({ let inheritedProperties = undefined if (parentIndex) { - const { auth, headers } = cascadeParentCollectionForHeaderAuth( + inheritedProperties = cascadeParentCollectionForProperties( parentIndex, "graphql" ) - - inheritedProperties = { - auth, - headers, - } } editingProperties.value = { @@ -647,18 +623,10 @@ const setCollectionProperties = (newCollection: { editGraphqlFolder(path, collection) } - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - path, - "graphql" - ) - nextTick(() => { updateInheritedPropertiesForAffectedRequests( path, - { - auth, - headers, - }, + cascadeParentCollectionForProperties(path, "graphql"), "graphql" ) }) diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue index 43fc4636..a0188afb 100644 --- a/packages/hoppscotch-common/src/components/collections/index.vue +++ b/packages/hoppscotch-common/src/components/collections/index.vue @@ -198,7 +198,12 @@ v-model="collectionPropertiesModalActiveTab" :show="showModalEditProperties" :editing-properties="editingProperties" - :show-details="collectionsType.type === 'team-collections'" + :show-details=" + collectionsType.type === 'team-collections' && hasTeamWriteAccess + " + :has-team-write-access=" + collectionsType.type === 'team-collections' ? hasTeamWriteAccess : true + " source="REST" @hide-modal="displayModalEditProperties(false)" @set-collection-properties="setCollectionProperties" @@ -225,8 +230,13 @@ import { makeCollection, } from "@hoppscotch/data" import { useService } from "dioc/vue" + import * as TE from "fp-ts/TaskEither" import { pipe } from "fp-ts/function" +import * as A from "fp-ts/Array" +import * as O from "fp-ts/Option" +import { flow } from "fp-ts/function" + import { cloneDeep, debounce, isEqual } from "lodash-es" import { PropType, computed, nextTick, onMounted, ref, watch } from "vue" import { useReadonlyStream } from "~/composables/stream" @@ -272,7 +282,7 @@ import { Picked } from "~/helpers/types/HoppPicked" import { addRESTCollection, addRESTFolder, - cascadeParentCollectionForHeaderAuth, + cascadeParentCollectionForProperties, duplicateRESTCollection, editRESTCollection, editRESTFolder, @@ -301,6 +311,9 @@ import { RESTOptionTabs } from "../http/RequestOptions.vue" import { Collection as NodeCollection } from "./MyCollections.vue" import { EditingProperties } from "./Properties.vue" import { CollectionRunnerData } from "../http/test/RunnerModal.vue" +import { HoppCollectionVariable } from "@hoppscotch/data" +import { SecretEnvironmentService } from "~/services/secret-environment.service" +import { CurrentValueService } from "~/services/current-environment-value.service" const t = useI18n() const toast = useToast() @@ -375,6 +388,10 @@ const draggingToRoot = ref(false) const collectionMoveLoading = ref([]) const requestMoveLoading = ref([]) +//collection variables current value and secret value +const secretEnvironmentService = useService(SecretEnvironmentService) +const currentEnvironmentValueService = useService(CurrentValueService) + // TeamList-Adapter const workspaceService = useService(WorkspaceService) const teamListAdapter = workspaceService.acquireTeamListAdapter(null) @@ -393,7 +410,7 @@ const teamLoadingCollections = useReadonlyStream( const teamEnvironmentAdapter = new TeamEnvironmentAdapter(undefined) const { - cascadeParentCollectionForHeaderAuthForSearchResults, + cascadeParentCollectionForPropertiesForSearchResults, searchTeams, teamsSearchResults, teamsSearchResultsLoading, @@ -774,6 +791,7 @@ const addNewRootCollection = (name: string) => { authType: "none", authActive: true, }, + variables: [], }) ) @@ -834,8 +852,6 @@ const onAddRequest = (requestName: string) => { if (collectionsType.value.type === "my-collections") { const insertionIndex = saveRESTRequestAs(path, newRequest) - const { auth, headers } = cascadeParentCollectionForHeaderAuth(path, "rest") - tabs.createNewTab({ type: "request", request: newRequest, @@ -845,10 +861,7 @@ const onAddRequest = (requestName: string) => { folderPath: path, requestIndex: insertionIndex, }, - inheritedProperties: { - auth, - headers, - }, + inheritedProperties: cascadeParentCollectionForProperties(path, "rest"), }) platform.analytics?.logEvent({ @@ -889,8 +902,7 @@ const onAddRequest = (requestName: string) => { }, (result) => { const { createRequestInCollection } = result - const { auth, headers } = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(path) + tabs.createNewTab({ type: "request", request: newRequest, @@ -901,10 +913,8 @@ const onAddRequest = (requestName: string) => { collectionID: path, teamID: createRequestInCollection.collection.team.id, }, - inheritedProperties: { - auth, - headers, - }, + inheritedProperties: + teamCollectionAdapter.cascadeParentCollectionForProperties(path), }) modalLoadingState.value = false @@ -1569,6 +1579,17 @@ const onRemoveCollection = () => { toast.success(t("state.deleted")) displayConfirmModal(false) + + // delete the secret collection variables + // and current collection variables value if the collection is removed + if (collectionToRemove) { + secretEnvironmentService.deleteSecretEnvironment( + collectionToRemove._ref_id ?? `${collectionIndex}` + ) + currentEnvironmentValueService.deleteEnvironment( + collectionToRemove._ref_id ?? `${collectionIndex}` + ) + } } else if (hasTeamWriteAccess.value) { const collectionID = editingCollectionID.value @@ -1584,6 +1605,13 @@ const onRemoveCollection = () => { removeTeamCollectionOrFolder(collectionID).then(() => { resetTeamRequestsContext() + + // delete the secret collection variables + // and current collection variables value if the collection is removed + if (collectionID) { + secretEnvironmentService.deleteSecretEnvironment(collectionID) + currentEnvironmentValueService.deleteEnvironment(collectionID) + } }) } } @@ -1611,6 +1639,8 @@ const onRemoveFolder = () => { emit("select", null) } + const folderIndex = pathToLastIndex(folderPath) + const folderToRemove = folderPath ? navigateToFolderWithIndexPath( restCollectionStore.value.state, @@ -1630,6 +1660,17 @@ const onRemoveFolder = () => { toast.success(t("state.deleted")) displayConfirmModal(false) + + // delete the secret collection variables + // and current collection variables value if the collection is removed + if (folderToRemove) { + secretEnvironmentService.deleteSecretEnvironment( + folderToRemove.id ?? `${folderIndex}` + ) + currentEnvironmentValueService.deleteEnvironment( + folderToRemove.id ?? `${folderIndex}` + ) + } } else if (hasTeamWriteAccess.value) { const collectionID = editingCollectionID.value @@ -1645,6 +1686,13 @@ const onRemoveFolder = () => { removeTeamCollectionOrFolder(collectionID).then(() => { resetTeamRequestsContext() + + // delete the secret collection variables + // and current collection variables value if the collection is removed + if (collectionID) { + secretEnvironmentService.deleteSecretEnvironment(collectionID) + currentEnvironmentValueService.deleteEnvironment(collectionID) + } }) } } @@ -1950,10 +1998,10 @@ const selectRequest = (selectedRequest: { if (!collectionID) return inheritedProperties = - cascadeParentCollectionForHeaderAuthForSearchResults(collectionID) + cascadeParentCollectionForPropertiesForSearchResults(collectionID) } else { inheritedProperties = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath) + teamCollectionAdapter.cascadeParentCollectionForProperties(folderPath) } const possibleTab = tabs.getTabRefWithSaveContext({ @@ -1978,10 +2026,6 @@ const selectRequest = (selectedRequest: { }) } } else { - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - folderPath, - "rest" - ) possibleTab = tabs.getTabRefWithSaveContext({ originLocation: "user-collection", requestIndex: parseInt(requestIndex), @@ -2000,10 +2044,10 @@ const selectRequest = (selectedRequest: { folderPath: folderPath!, requestIndex: parseInt(requestIndex), }, - inheritedProperties: { - auth, - headers, - }, + inheritedProperties: cascadeParentCollectionForProperties( + folderPath, + "rest" + ), }) } } @@ -2045,6 +2089,10 @@ const selectResponse = (payload: { requestIndex: parseInt(requestIndex), exampleID: responseID, }, + inheritedProperties: cascadeParentCollectionForProperties( + folderPath, + "rest" + ), }) } } else { @@ -2070,6 +2118,10 @@ const selectResponse = (payload: { collectionID: folderPath, exampleID: responseID, }, + inheritedProperties: + teamCollectionAdapter.cascadeParentCollectionForProperties( + folderPath + ), }) } } @@ -2101,11 +2153,6 @@ const dropRequest = (payload: { let possibleTab = null if (collectionsType.value.type === "my-collections") { - const { auth, headers } = cascadeParentCollectionForHeaderAuth( - destinationCollectionIndex, - "rest" - ) - possibleTab = tabs.getTabRefWithSaveContext({ originLocation: "user-collection", folderPath, @@ -2123,10 +2170,8 @@ const dropRequest = (payload: { ).length, } - possibleTab.value.document.inheritedProperties = { - auth, - headers, - } + possibleTab.value.document.inheritedProperties = + cascadeParentCollectionForProperties(destinationCollectionIndex, "rest") } // When it's drop it's basically getting deleted from last folder. reordering last folder accordingly @@ -2164,10 +2209,6 @@ const dropRequest = (payload: { requestMoveLoading.value.indexOf(requestIndex), 1 ) - const { auth, headers } = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth( - destinationCollectionIndex - ) possibleTab = tabs.getTabRefWithSaveContext({ originLocation: "team-collection", @@ -2179,10 +2220,10 @@ const dropRequest = (payload: { originLocation: "team-collection", requestID: requestIndex, } - possibleTab.value.document.inheritedProperties = { - auth, - headers, - } + possibleTab.value.document.inheritedProperties = + teamCollectionAdapter.cascadeParentCollectionForProperties( + destinationCollectionIndex + ) } toast.success(`${t("request.moved")}`) } @@ -2303,16 +2344,11 @@ const dropCollection = (payload: { `${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}` ) - const { auth, headers } = cascadeParentCollectionForHeaderAuth( + const inheritedProperty = cascadeParentCollectionForProperties( `${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`, "rest" ) - const inheritedProperty = { - auth, - headers, - } - updateInheritedPropertiesForAffectedRequests( `${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`, inheritedProperty, @@ -2345,16 +2381,11 @@ const dropCollection = (payload: { 1 ) - const { auth, headers } = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth( + const inheritedProperty = + teamCollectionAdapter.cascadeParentCollectionForProperties( destinationCollectionIndex ) - const inheritedProperty = { - auth, - headers, - } - updateInheritedPropertiesForAffectedRequests( `${destinationCollectionIndex}`, inheritedProperty, @@ -2696,16 +2727,43 @@ const shareRequest = ({ request }: { request: HoppRESTRequest }) => { } } +/** + * Used to get the current value of a variable + * It checks if the variable is a secret or not and returns the value accordingly. + * @param isSecret If the variable is a secret + * @param varIndex The index of the variable in the collection + * @param collectionID The ID of the collection + * @returns The current value of the variable, either from the secret environment or the current environment service + */ +const getCurrentValue = ( + isSecret: boolean, + varIndex: number, + collectionID: string +) => { + if (isSecret) { + return secretEnvironmentService.getSecretEnvironmentVariable( + collectionID, + varIndex + )?.value + } + return currentEnvironmentValueService.getEnvironmentVariable( + collectionID, + varIndex + )?.currentValue +} + const editProperties = (payload: { collectionIndex: string collection: HoppCollection | TeamCollection }) => { const { collection, collectionIndex } = payload + const collectionId = collection.id ?? collectionIndex.split("/").pop() + if (collectionsType.value.type === "my-collections") { const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder - let inheritedProperties = { + let inheritedProperties: HoppInheritedProperty = { auth: { parentID: "", parentName: "", @@ -2714,34 +2772,42 @@ const editProperties = (payload: { authActive: true, }, }, - headers: [ - { - parentID: "", - parentName: "", - inheritedHeader: {}, - }, - ], - } as HoppInheritedProperty + headers: [], + variables: [], + } if (parentIndex) { - const { auth, headers } = cascadeParentCollectionForHeaderAuth( + inheritedProperties = cascadeParentCollectionForProperties( parentIndex, "rest" ) - - inheritedProperties = { - auth, - headers, - } } + const collectionVariables = pipe( + (collection as HoppCollection).variables ?? [], + A.mapWithIndex((index, e) => { + return { + ...e, + currentValue: + getCurrentValue( + e.secret, + index, + (collection as HoppCollection)._ref_id ?? collectionId! + ) ?? e.currentValue, + } + }) + ) + editingProperties.value = { - collection: collection as Partial, + collection: { + ...collection, + variables: collectionVariables, + } as Partial, isRootCollection: isAlreadyInRoot(collectionIndex), path: collectionIndex, inheritedProperties, } - } else if (hasTeamWriteAccess.value) { + } else { const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder const data = (collection as TeamCollection).data @@ -2757,25 +2823,39 @@ const editProperties = (payload: { authActive: true, } as HoppRESTAuth, headers: [] as HoppRESTHeaders, + variables: [] as HoppCollectionVariable[], folders: null, requests: null, } if (parentIndex) { - const { auth, headers } = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(parentIndex) + const { auth, headers, variables } = + teamCollectionAdapter.cascadeParentCollectionForProperties(parentIndex) inheritedProperties = { auth, headers, + variables, } } if (data) { + const collectionVariables = pipe( + (data.variables ?? []) as HoppCollectionVariable[], + A.mapWithIndex((index, e) => { + return { + ...e, + currentValue: + getCurrentValue(e.secret, index, collectionId!) ?? e.currentValue, + } + }) + ) + coll = { ...coll, auth: data.auth, headers: data.headers as HoppRESTHeaders, + variables: collectionVariables as HoppCollectionVariable[], } } @@ -2803,6 +2883,65 @@ const setCollectionProperties = (newCollection: { // Since path is being preserved, we extract the collectionId from path instead const collectionId = collection.id ?? path.split("/").pop() + //setting current value and secret values to of collection variables + if (collection.variables) { + const filteredVariables = pipe( + collection.variables, + A.filterMap( + flow( + O.fromPredicate((e) => e.key !== ""), + O.map((e) => e) + ) + ) + ) + + const secretVariables = pipe( + filteredVariables, + A.filterMapWithIndex((i, e) => + e.secret + ? O.some({ + key: e.key, + value: e.currentValue, + varIndex: i, + }) + : O.none + ) + ) + + const nonSecretVariables = pipe( + filteredVariables, + A.filterMapWithIndex((i, e) => + !e.secret + ? O.some({ + key: e.key, + currentValue: e.currentValue, + varIndex: i, + isSecret: e.secret ?? false, + }) + : O.none + ) + ) + + secretEnvironmentService.addSecretEnvironment( + collection._ref_id ?? collectionId!, + secretVariables + ) + + currentEnvironmentValueService.addEnvironment( + collection._ref_id ?? collectionId!, + nonSecretVariables + ) + + //set current value and secret values to empty string + collection.variables = pipe( + filteredVariables, + A.map((e) => ({ + ...e, + currentValue: "", + })) + ) + } + if (collectionsType.value.type === "my-collections") { if (isRootCollection) { editRESTCollection(parseInt(path), collection) @@ -2810,16 +2949,14 @@ const setCollectionProperties = (newCollection: { editRESTFolder(path, collection) } - const { auth, headers } = cascadeParentCollectionForHeaderAuth(path, "rest") + const inheritedProperty = cascadeParentCollectionForProperties(path, "rest") nextTick(() => { updateInheritedPropertiesForAffectedRequests( path, - { - auth, - headers, - }, - "rest" + inheritedProperty, + "rest", + collection._ref_id ?? collectionId! ) }) toast.success(t("collection.properties_updated")) @@ -2827,6 +2964,7 @@ const setCollectionProperties = (newCollection: { const data = { auth: collection.auth, headers: collection.headers, + variables: collection.variables, } pipe( updateTeamCollection(collectionId, JSON.stringify(data), undefined), @@ -2843,15 +2981,13 @@ const setCollectionProperties = (newCollection: { //This is a hack to update the inherited properties of the requests if there an tab opened // since it takes a little bit of time to update the collection tree setTimeout(() => { - const { auth, headers } = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(path) + const inheritedProperty = + teamCollectionAdapter.cascadeParentCollectionForProperties(path) updateInheritedPropertiesForAffectedRequests( path, - { - auth, - headers, - }, - "rest" + inheritedProperty, + "rest", + collectionId ) }, 200) } @@ -2866,7 +3002,7 @@ const runCollectionHandler = ( ) => { if (payload.path && collectionsType.value.type === "team-collections") { const inheritedProperties = - teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(payload.path) + teamCollectionAdapter.cascadeParentCollectionForProperties(payload.path) if (inheritedProperties) { collectionRunnerData.value = { diff --git a/packages/hoppscotch-common/src/components/environments/ImportExport.vue b/packages/hoppscotch-common/src/components/environments/ImportExport.vue index 1060c337..7db641fa 100644 --- a/packages/hoppscotch-common/src/components/environments/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/environments/ImportExport.vue @@ -29,7 +29,7 @@ import { import { GQLError } from "~/helpers/backend/GQLClient" import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql" import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment" -import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv" +import { insomniaEnvImporter } from "~/helpers/import-export/import/insomnia/insomniaEnv" import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv" import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment" diff --git a/packages/hoppscotch-common/src/components/environments/my/Details.vue b/packages/hoppscotch-common/src/components/environments/my/Details.vue index 8042e6eb..ba773fec 100644 --- a/packages/hoppscotch-common/src/components/environments/my/Details.vue +++ b/packages/hoppscotch-common/src/components/environments/my/Details.vue @@ -134,7 +134,7 @@ h.key.toLowerCase() === "authorization")) return [] - if (!request) return [] - - if (!request.auth || !request.auth.authActive) return [] + if (!request || !request.auth || !request.auth.authActive) return [] const headers: GQLHeader[] = [] @@ -627,75 +625,61 @@ const computedHeaders = computedAsync(async () => })) ) -const inheritedProperties = computedAsync(async () => { - if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers) - return [] - - //filter out headers that are already in the request headers - - const inheritedHeaders = props.inheritedProperties.headers.filter( - (header) => - !request.value.headers.some( - (requestHeader) => requestHeader.key === header.inheritedHeader?.key - ) - ) - - const headers = inheritedHeaders - .filter( - (header) => - header.inheritedHeader !== null && - header.inheritedHeader !== undefined && - header.inheritedHeader.active - ) - .map((header, index) => { - const { key, value, active, description } = header.inheritedHeader - - return { - inheritedFrom: props.inheritedProperties?.headers[index].parentName, - source: "headers", - id: `header-${index}`, - header: { - key, - value, - active, - description, - }, - } - }) - - let auth = [] as { +const inheritedProperty = ref< + { inheritedFrom: string - source: "auth" + source: "auth" | "headers" id: string - header: { - key: string - value: string - active: boolean - } + header: GQLHeader }[] +>([]) - const [computedAuthHeader] = await getComputedAuthHeaders( - request.value, - props.inheritedProperties.auth.inheritedAuth as HoppGQLAuth - ) +watch( + () => [props.inheritedProperties, request.value], + async () => { + if (!props.inheritedProperties) return - if ( - computedAuthHeader && - request.value.auth.authType === "inherit" && - request.value.auth.authActive - ) { - auth = [ - { - inheritedFrom: props.inheritedProperties?.auth.parentName, - source: "auth", - id: `header-auth`, - header: computedAuthHeader, - }, - ] - } + //filter out headers that are already in the request headers + const inheritedHeaders = props.inheritedProperties.headers.filter( + (header) => + !request.value.headers.some( + (requestHeader) => + requestHeader.key === header.inheritedHeader?.key && + requestHeader.active + ) + ) + inheritedProperty.value = inheritedHeaders.map((header, index) => ({ + inheritedFrom: props.inheritedProperties!.headers[index].parentName!, + source: "headers", + id: `header-${index}`, + header: header.inheritedHeader, + })) - return [...headers, ...auth] -}) + if ( + props.inheritedProperties.auth && + request.value.auth.authType === "inherit" && + request.value.auth.authActive && + !request.value.headers.some( + (requestHeader) => + requestHeader.key === "Authorization" && requestHeader.active + ) + ) { + const [computedAuthHeader] = await getComputedAuthHeaders( + request.value, + props.inheritedProperties.auth.inheritedAuth as HoppGQLAuth + ) + if (computedAuthHeader) { + inheritedProperty.value.push({ + inheritedFrom: props.inheritedProperties.auth.parentName, + source: "auth", + id: `header-auth`, + header: computedAuthHeader, + }) + } + } + }, + { immediate: true, deep: true } +) const masking = ref(true) diff --git a/packages/hoppscotch-common/src/components/http/Headers.vue b/packages/hoppscotch-common/src/components/http/Headers.vue index 11c35048..7d419c3b 100644 --- a/packages/hoppscotch-common/src/components/http/Headers.vue +++ b/packages/hoppscotch-common/src/components/http/Headers.vue @@ -156,7 +156,7 @@ = ref([]) +const inheritedProperty = ref< + { + inheritedFrom: string + source: "auth" | "headers" + id: string + header: HoppRESTHeader + }[] +>([]) + const currentSelectedEnvironment = getCurrentEnvironment() watch([props.modelValue, aggregateEnvs], async () => { @@ -583,77 +592,54 @@ watch([props.modelValue, aggregateEnvs], async () => { })) }) -const inheritedProperties = computedAsync(async () => { - if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers) - return [] +watch( + () => [props.inheritedProperties, request.value], + async () => { + if (!props.inheritedProperties) return - //filter out headers that are already in the request headers - - const inheritedHeaders = props.inheritedProperties.headers.filter( - (header) => - !request.value.headers.some( - (requestHeader) => requestHeader.key === header.inheritedHeader?.key - ) - ) - - const headers = inheritedHeaders - .filter( + //filter out headers that are already in the request headers + const inheritedHeaders = props.inheritedProperties.headers.filter( (header) => - header.inheritedHeader !== null && - header.inheritedHeader !== undefined && - header.inheritedHeader.active + !request.value.headers.some( + (requestHeader) => + requestHeader.key === header.inheritedHeader?.key && + requestHeader.active + ) ) - .map((header, index) => { - const { key, value, active, description } = header.inheritedHeader + inheritedProperty.value = inheritedHeaders.map((header, index) => ({ + inheritedFrom: props.inheritedProperties!.headers[index].parentName!, + source: "headers", + id: `header-${index}`, + header: header.inheritedHeader, + })) - return { - inheritedFrom: props.inheritedProperties?.headers[index].parentName, - source: "headers", - id: `header-${index}`, - header: { - key, - value, - active, - description, - }, + if ( + props.inheritedProperties.auth && + request.value.auth.authType === "inherit" && + request.value.auth.authActive && + !request.value.headers.some( + (requestHeader) => + requestHeader.key === "Authorization" && requestHeader.active + ) + ) { + const [computedAuthHeader] = await getComputedAuthHeaders( + aggregateEnvs.value, + request.value, + props.inheritedProperties.auth.inheritedAuth, + false + ) + if (computedAuthHeader) { + inheritedProperty.value.push({ + inheritedFrom: props.inheritedProperties.auth.parentName, + source: "auth", + id: `header-auth`, + header: computedAuthHeader, + }) } - }) - - let auth = [] as { - inheritedFrom: string - source: "auth" - id: string - header: { - key: string - value: string - active: boolean } - }[] - - const [computedAuthHeader] = await getComputedAuthHeaders( - aggregateEnvs.value, - request.value, - props.inheritedProperties.auth.inheritedAuth, - false - ) - - if ( - computedAuthHeader && - request.value.auth.authType === "inherit" && - request.value.auth.authActive - ) { - auth = [ - { - inheritedFrom: props.inheritedProperties?.auth.parentName, - source: "auth", - id: `header-auth`, - header: computedAuthHeader, - }, - ] - } - - return [...headers, ...auth] -}) + }, + { immediate: true, deep: true } +) const masking = ref(true) diff --git a/packages/hoppscotch-common/src/components/http/KeyValue.vue b/packages/hoppscotch-common/src/components/http/KeyValue.vue index e8e5622c..01285a22 100644 --- a/packages/hoppscotch-common/src/components/http/KeyValue.vue +++ b/packages/hoppscotch-common/src/components/http/KeyValue.vue @@ -62,7 +62,7 @@ v-if="showDescription" :value="description" :placeholder="t('count.description')" - class="flex flex-1 px-4 bg-transparent" + class="flex flex-1 px-4 bg-transparent text-secondaryDark" type="text" :class="{ 'opacity-50': !entityActive }" @update:value="emit('update:description', $event.target.value)" diff --git a/packages/hoppscotch-common/src/components/http/example/ResponseRequest.vue b/packages/hoppscotch-common/src/components/http/example/ResponseRequest.vue index de0767e9..4dba1dd3 100644 --- a/packages/hoppscotch-common/src/components/http/example/ResponseRequest.vue +++ b/packages/hoppscotch-common/src/components/http/example/ResponseRequest.vue @@ -163,6 +163,7 @@ const tryExampleResponse = () => { params, requestVariables, }, + inheritedProperties: tab.value.document.inheritedProperties, }) } diff --git a/packages/hoppscotch-common/src/components/http/test/Runner.vue b/packages/hoppscotch-common/src/components/http/test/Runner.vue index c6cec9a3..0ab4eb35 100644 --- a/packages/hoppscotch-common/src/components/http/test/Runner.vue +++ b/packages/hoppscotch-common/src/components/http/test/Runner.vue @@ -143,6 +143,7 @@ import { } from "~/helpers/runner/adapter" import { getErrorMessage } from "~/helpers/runner/collection-tree" import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter" +import { transformInheritedCollectionVariablesToAggregateEnv } from "~/helpers/utils/inheritedCollectionVarTransformer" import { getRESTCollectionByRefId, getRESTCollectionInheritedProps, @@ -269,21 +270,28 @@ const runTests = async () => { } ) + const parentVariables = transformInheritedCollectionVariablesToAggregateEnv( + tab.value.document.inheritedProperties?.variables ?? [] + ) + resolvedCollection = { ...collection.value, auth: requestAuth, headers: requestHeaders as HoppRESTHeader[], + variables: parentVariables, } } else { - const { auth, headers } = collectionInheritedProps ?? { + const { auth, headers, variables } = collectionInheritedProps ?? { auth: { authActive: true, authType: "none" }, headers: [], + variables: [], } resolvedCollection = { ...collection.value, auth, headers, + variables, } } diff --git a/packages/hoppscotch-common/src/components/smart/EnvInput.vue b/packages/hoppscotch-common/src/components/smart/EnvInput.vue index 8c9643fb..b33a1774 100644 --- a/packages/hoppscotch-common/src/components/smart/EnvInput.vue +++ b/packages/hoppscotch-common/src/components/smart/EnvInput.vue @@ -101,6 +101,7 @@ import { useService } from "dioc/vue" import { RESTTabService } from "~/services/tab/rest" import { syntaxTree } from "@codemirror/language" import { uniqueID } from "~/helpers/utils/uniqueID" +import { transformInheritedCollectionVariablesToAggregateEnv } from "~/helpers/utils/inheritedCollectionVarTransformer" const t = useI18n() @@ -120,6 +121,7 @@ const props = withDefaults( contextMenuEnabled?: boolean secret?: boolean autoCompleteEnv?: boolean + autoCompleteEnvSource?: AggregateEnvironment[] | null }>(), { modelValue: "", @@ -135,7 +137,8 @@ const props = withDefaults( inspectionResults: undefined, contextMenuEnabled: true, secret: false, - autoCompleteEnvSource: false, + autoCompleteEnv: false, + autoCompleteEnvSource: null, } ) @@ -405,33 +408,21 @@ const envVars = computed(() => { }) } - const requestVariables = + const collectionVariables = + tabs.currentActiveTab.value.document.type === "request" || tabs.currentActiveTab.value.document.type === "example-response" - ? tabs.currentActiveTab.value.document.response.originalRequest - .requestVariables - : tabs.currentActiveTab.value.document.type === "request" - ? tabs.currentActiveTab.value.document.request.requestVariables - : [] + ? transformInheritedCollectionVariablesToAggregateEnv( + tabs.currentActiveTab.value.document.inheritedProperties?.variables ?? + [], + false + ) + : [] - // Transform request variables to match the env format - return [ - ...requestVariables.map(({ active, key, value }) => - active - ? { - key, - currentValue: value, - initialValue: value, - sourceEnv: "RequestVariable", - secret: false, - } - : ({} as AggregateEnvironment) - ), - ...aggregateEnvs.value, - ] + return [...collectionVariables, ...aggregateEnvs.value] }) function envAutoCompletion(context: CompletionContext) { - const options = (envVars.value ?? []) + const options = (props.autoCompleteEnvSource ?? envVars.value ?? []) .map((env) => ({ label: env?.key ? `<<${env.key}>>` : "", info: env?.currentValue ?? "", @@ -559,6 +550,7 @@ const getExtensions = (readonly: boolean): Extension => { ? autocompletion({ activateOnTyping: true, override: [envAutoCompletion], + tooltipClass: () => "tooltip-autocomplete", }) : [], @@ -694,6 +686,12 @@ watch( ) + +