feat: collection variables (#5325)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
ad974dbd5b
commit
2d2e65369f
77 changed files with 2696 additions and 773 deletions
|
|
@ -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": "<<collection-variable-1>>",
|
||||
"active": true,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"key": "collection-var-2",
|
||||
"value": "<<collection-variable-2>>",
|
||||
"active": true,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"key": "folder-var-1",
|
||||
"value": "<<folder-variable-1>>",
|
||||
"active": true,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"key": "folder-var-2",
|
||||
"value": "<<folder-variable-2>>",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,5 +86,6 @@
|
|||
"authActive": true
|
||||
},
|
||||
"headers": [],
|
||||
"variables": [],
|
||||
"_ref_id": "coll_mbhuxoci_a8fc710e-04c1-489c-a183-7f16946a7225"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@
|
|||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!hasNoTeamAccess" class="flex">
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="!hasNoTeamAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFilePlus"
|
||||
:title="t('request.add')"
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
@click="emit('add-request')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="!hasNoTeamAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFolderPlus"
|
||||
:title="t('folder.new')"
|
||||
|
|
@ -109,6 +111,7 @@
|
|||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess"
|
||||
ref="requestAction"
|
||||
:icon="IconFilePlus"
|
||||
:label="t('request.new')"
|
||||
|
|
@ -121,6 +124,7 @@
|
|||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess"
|
||||
ref="folderAction"
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('folder.new')"
|
||||
|
|
@ -145,6 +149,7 @@
|
|||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess"
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
|
|
@ -157,6 +162,7 @@
|
|||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess"
|
||||
ref="duplicateAction"
|
||||
:icon="IconCopy"
|
||||
:label="t('action.duplicate')"
|
||||
|
|
@ -170,6 +176,7 @@
|
|||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess"
|
||||
ref="exportAction"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.title')"
|
||||
|
|
@ -182,18 +189,6 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
emit('remove-collection')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="propertiesAction"
|
||||
:icon="IconSettings2"
|
||||
|
|
@ -206,6 +201,19 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!hasNoTeamAccess"
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
emit('remove-collection')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
<template #body>
|
||||
|
|
@ -13,7 +13,11 @@
|
|||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab id="headers" :label="`${t('tab.headers')}`">
|
||||
<HoppSmartTab
|
||||
v-if="hasTeamWriteAccess"
|
||||
id="headers"
|
||||
:label="`${t('tab.headers')}`"
|
||||
>
|
||||
<HttpHeaders
|
||||
v-model="editableCollection"
|
||||
:is-collection-property="true"
|
||||
|
|
@ -27,7 +31,11 @@
|
|||
</div>
|
||||
</HoppSmartTab>
|
||||
|
||||
<HoppSmartTab id="authorization" :label="`${t('tab.authorization')}`">
|
||||
<HoppSmartTab
|
||||
v-if="hasTeamWriteAccess"
|
||||
id="authorization"
|
||||
:label="`${t('tab.authorization')}`"
|
||||
>
|
||||
<HttpAuthorization
|
||||
v-model="editableCollection.auth"
|
||||
:is-collection-property="true"
|
||||
|
|
@ -43,6 +51,19 @@
|
|||
</div>
|
||||
</HoppSmartTab>
|
||||
|
||||
<!-- Collection variables is only available for REST collections for now -->
|
||||
<HoppSmartTab
|
||||
v-if="source === 'REST'"
|
||||
id="variables"
|
||||
:label="`${t('tab.variables')}`"
|
||||
>
|
||||
<CollectionsVariables
|
||||
v-model="editableCollection.variables"
|
||||
:inherited-properties="editingProperties.inheritedProperties"
|
||||
:has-team-write-access="hasTeamWriteAccess"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
|
||||
<HoppSmartTab
|
||||
v-if="showDetails"
|
||||
:id="'details'"
|
||||
|
|
@ -117,23 +138,25 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import {
|
||||
GQLHeader,
|
||||
HoppCollection,
|
||||
HoppGQLAuth,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeaders,
|
||||
} from "@hoppscotch/data"
|
||||
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { clone } from "lodash-es"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||
import { clone } from "lodash-es"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppCollectionVariable,
|
||||
HoppRESTAuth,
|
||||
HoppGQLAuth,
|
||||
HoppRESTHeaders,
|
||||
GQLHeader,
|
||||
} from "@hoppscotch/data"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { PersistenceService } from "~/services/persistence"
|
||||
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
|
|
@ -141,6 +164,7 @@ import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
|||
|
||||
const persistenceService = useService(PersistenceService)
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
export type EditingProperties = {
|
||||
collection: Partial<HoppCollection> | null
|
||||
|
|
@ -148,12 +172,9 @@ export type EditingProperties = {
|
|||
path: string
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}
|
||||
|
||||
type HoppCollectionAuth = HoppRESTAuth | HoppGQLAuth
|
||||
type HoppCollectionHeaders = HoppRESTHeaders | GQLHeader[]
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
show: boolean
|
||||
|
|
@ -161,12 +182,14 @@ const props = withDefaults(
|
|||
editingProperties: EditingProperties
|
||||
source: "REST" | "GraphQL"
|
||||
modelValue: string
|
||||
showDetails: boolean
|
||||
showDetails?: boolean
|
||||
hasTeamWriteAccess?: boolean
|
||||
}>(),
|
||||
{
|
||||
show: false,
|
||||
loadingState: false,
|
||||
showDetails: false,
|
||||
hasTeamWriteAccess: true,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -182,104 +205,107 @@ const emit = defineEmits<{
|
|||
const editableCollection = ref<{
|
||||
headers: HoppCollectionHeaders
|
||||
auth: HoppCollectionAuth
|
||||
variables: HoppCollectionVariable[]
|
||||
}>({
|
||||
headers: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
auth: { authType: "inherit", authActive: false },
|
||||
variables: [],
|
||||
})
|
||||
|
||||
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||
IconCopy,
|
||||
1000
|
||||
)
|
||||
const activeTab = useVModel(props, "modelValue", emit)
|
||||
|
||||
const activeTabIsDetails = computed(() => activeTab.value === "details")
|
||||
|
||||
watch(
|
||||
editableCollection,
|
||||
async (updatedEditableCollection) => {
|
||||
if (props.show && props.editingProperties) {
|
||||
const unsavedCollectionProperties: EditingProperties = {
|
||||
collection: updatedEditableCollection,
|
||||
isRootCollection: props.editingProperties.isRootCollection ?? false,
|
||||
path: props.editingProperties.path,
|
||||
inheritedProperties: props.editingProperties.inheritedProperties,
|
||||
}
|
||||
await persistenceService.setLocalConfig(
|
||||
"unsaved_collection_properties",
|
||||
JSON.stringify(unsavedCollectionProperties)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
const persistUnsavedChanges = async (
|
||||
updated: typeof editableCollection.value
|
||||
) => {
|
||||
if (!props.show) return
|
||||
await persistenceService.setLocalConfig(
|
||||
"unsaved_collection_properties",
|
||||
JSON.stringify({
|
||||
collection: updated,
|
||||
isRootCollection: props.editingProperties.isRootCollection ?? false,
|
||||
path: props.editingProperties.path,
|
||||
inheritedProperties: props.editingProperties.inheritedProperties,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const handleModalVisibility = async (show: boolean) => {
|
||||
enforceTabAccessRules()
|
||||
|
||||
if (show && props.editingProperties.collection) {
|
||||
loadEditableCollection()
|
||||
} else {
|
||||
resetEditableCollection()
|
||||
await persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const activeTab = useVModel(props, "modelValue", emit)
|
||||
const enforceTabAccessRules = () => {
|
||||
// `Details` tab doesn't exist for personal workspace, hence switching to the `Headers` tab
|
||||
// The modal can appear empty while switching from a team workspace with `Details` as the active tab
|
||||
if (activeTab.value === "details" && !props.showDetails)
|
||||
activeTab.value = "headers"
|
||||
// If the user doesn't have write access to the team, switch to `Variables` tab
|
||||
// when the `Headers` or `Authorization` tab is active
|
||||
if (
|
||||
!props.hasTeamWriteAccess &&
|
||||
["headers", "authorization"].includes(activeTab.value)
|
||||
)
|
||||
activeTab.value = "variables"
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
async (show) => {
|
||||
// `Details` tab doesn't exist for personal workspace, hence switching to the `Headers` tab
|
||||
// The modal can appear empty while switching from a team workspace with `Details` as the active tab
|
||||
if (activeTab.value === "details" && !props.showDetails) {
|
||||
activeTab.value = "headers"
|
||||
}
|
||||
|
||||
if (show && props.editingProperties.collection) {
|
||||
editableCollection.value.auth = clone(
|
||||
props.editingProperties.collection.auth as HoppCollectionAuth
|
||||
)
|
||||
editableCollection.value.headers = clone(
|
||||
props.editingProperties.collection.headers as HoppCollectionHeaders
|
||||
)
|
||||
} else {
|
||||
editableCollection.value = {
|
||||
headers: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
}
|
||||
|
||||
await persistenceService.removeLocalConfig(
|
||||
"unsaved_collection_properties"
|
||||
)
|
||||
}
|
||||
const loadEditableCollection = () => {
|
||||
editableCollection.value = {
|
||||
auth: clone(props.editingProperties.collection!.auth as HoppCollectionAuth),
|
||||
headers: clone(
|
||||
props.editingProperties.collection!.headers as HoppCollectionHeaders
|
||||
),
|
||||
variables: clone(props.editingProperties.collection!.variables || []),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const resetEditableCollection = () => {
|
||||
editableCollection.value = {
|
||||
headers: [],
|
||||
auth: { authType: "inherit", authActive: false },
|
||||
variables: [],
|
||||
}
|
||||
}
|
||||
|
||||
const saveEditedCollection = async () => {
|
||||
if (!props.editingProperties) return
|
||||
const finalCollection = clone(editableCollection.value)
|
||||
const collection = {
|
||||
emit("set-collection-properties", {
|
||||
path: props.editingProperties.path,
|
||||
collection: {
|
||||
...props.editingProperties.collection,
|
||||
...finalCollection,
|
||||
...clone(editableCollection.value),
|
||||
},
|
||||
isRootCollection: props.editingProperties.isRootCollection,
|
||||
}
|
||||
emit("set-collection-properties", collection as EditingProperties)
|
||||
} as EditingProperties)
|
||||
await persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||
}
|
||||
|
||||
watch(editableCollection, persistUnsavedChanges, { deep: true })
|
||||
watch(() => props.show, handleModalVisibility)
|
||||
|
||||
const hideModal = async () => {
|
||||
await persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const changeOptionTab = (e: RESTOptionTabs) => {
|
||||
activeTab.value = e
|
||||
const changeOptionTab = (tab: RESTOptionTabs) => {
|
||||
activeTab.value = tab
|
||||
}
|
||||
|
||||
const copyCollectionID = () => {
|
||||
copyToClipboard(props.editingProperties.path)
|
||||
copyIcon.value = IconCheck
|
||||
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
toast.success(t("state.copied_to_clipboard"))
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -128,18 +128,6 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
emit('remove-request')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="shareAction"
|
||||
:icon="IconShare2"
|
||||
|
|
@ -152,6 +140,18 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
emit('remove-request')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,316 @@
|
|||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-1 justify-between items-center pl-4">
|
||||
<span class="truncate font-semibold text-secondaryLight">{{
|
||||
t("environment.variable_list")
|
||||
}}</span>
|
||||
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/documentation/features/environments"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col border border-divider rounded">
|
||||
<HoppSmartTabs v-model="selectedEnvOption" render-inactive-tabs>
|
||||
<template #actions>
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<HoppButtonSecondary
|
||||
v-if="hasTeamWriteAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear_all')"
|
||||
:icon="clearIcon"
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="hasTeamWriteAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconPlus"
|
||||
:title="t('add.new')"
|
||||
@click="addEnvironmentVariable"
|
||||
/>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => tippyActions!.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-if="hasTeamWriteAccess"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconCopyLeft"
|
||||
:label="t('environment.replace_all_initial_with_current')"
|
||||
@click="
|
||||
() => {
|
||||
vars.forEach((v) => {
|
||||
v.initialValue = v.currentValue
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconCopyRight"
|
||||
:label="t('environment.replace_all_current_with_initial')"
|
||||
@click="
|
||||
() => {
|
||||
vars.forEach((v) => {
|
||||
v.currentValue = v.initialValue
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<HoppSmartTab
|
||||
v-for="tab in tabsData"
|
||||
:id="tab.id"
|
||||
:key="tab.id"
|
||||
:label="tab.label"
|
||||
>
|
||||
<div class="divide-y divide-dividerLight">
|
||||
<HoppSmartPlaceholder
|
||||
v-if="tab.variables.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="tab.emptyStateLabel"
|
||||
:text="tab.emptyStateLabel"
|
||||
>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
v-if="hasTeamWriteAccess"
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
@click="addEnvironmentVariable"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(env, index) in tab.variables"
|
||||
:key="`${tab.id}-${index}`"
|
||||
class="flex divide-x divide-dividerLight"
|
||||
>
|
||||
<input
|
||||
v-model="env.key"
|
||||
v-focus
|
||||
class="flex flex-1 bg-transparent px-4 py-2 text-secondaryDark"
|
||||
:placeholder="`${t('count.variable', {
|
||||
count: index + 1,
|
||||
})}`"
|
||||
:name="'variable' + index"
|
||||
:class="{
|
||||
'opacity-25': !hasTeamWriteAccess,
|
||||
}"
|
||||
:disabled="!hasTeamWriteAccess"
|
||||
/>
|
||||
<div class="flex items-center flex-1">
|
||||
<SmartEnvInput
|
||||
v-model="env.initialValue"
|
||||
:placeholder="`${t('count.initialValue', { count: index + 1 })}`"
|
||||
:envs="liveEnvs"
|
||||
:auto-complete-env="true"
|
||||
:name="'initialValue' + index"
|
||||
:secret="tab.isSecret"
|
||||
:readonly="!hasTeamWriteAccess"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('environment.replace_initial_with_current')"
|
||||
:icon="IconCopyLeft"
|
||||
:disabled="!hasTeamWriteAccess"
|
||||
@click="
|
||||
() => {
|
||||
env.initialValue = env.currentValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center flex-1">
|
||||
<SmartEnvInput
|
||||
v-model="env.currentValue"
|
||||
:placeholder="`${t('count.currentValue', { count: index + 1 })}`"
|
||||
:envs="liveEnvs"
|
||||
:auto-complete-env="true"
|
||||
:name="'currentValue' + index"
|
||||
:secret="tab.isSecret"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('environment.replace_current_with_initial')"
|
||||
:icon="IconCopyRight"
|
||||
@click="
|
||||
() => {
|
||||
env.currentValue = env.initialValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="hasTeamWriteAccess" class="flex">
|
||||
<HoppButtonSecondary
|
||||
id="variable"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.remove')"
|
||||
:icon="IconTrash"
|
||||
color="red"
|
||||
@click="removeEnvironmentVariable(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import IconDone from "~icons/lucide/check"
|
||||
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 IconCopyRight from "~icons/lucide/clipboard-paste"
|
||||
import IconCopyLeft from "~icons/lucide/clipboard-copy"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import { computed, ComputedRef, Ref, ref } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { HoppCollectionVariable } from "@hoppscotch/data"
|
||||
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvsWithCurrentValue$,
|
||||
} from "~/newstore/environments"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { transformInheritedCollectionVariablesToAggregateEnv } from "~/helpers/utils/inheritedCollectionVarTransformer"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppCollectionVariable[]
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
hasTeamWriteAccess: boolean
|
||||
}>()
|
||||
|
||||
type SelectedEnv = "variables" | "secret"
|
||||
|
||||
const selectedEnvOption = ref<SelectedEnv>("variables")
|
||||
|
||||
const tippyActions = ref<HTMLDivElement | null>(null)
|
||||
|
||||
const vars = useVModel(props, "modelValue")
|
||||
|
||||
const secretVars = computed(() =>
|
||||
pipe(
|
||||
vars.value,
|
||||
A.filter((e) => e.secret)
|
||||
)
|
||||
)
|
||||
|
||||
const nonSecretVars = computed(() =>
|
||||
pipe(
|
||||
vars.value,
|
||||
A.filter((e) => !e.secret)
|
||||
)
|
||||
)
|
||||
|
||||
const tabsData: ComputedRef<
|
||||
{
|
||||
id: string
|
||||
label: string
|
||||
emptyStateLabel: string
|
||||
isSecret: boolean
|
||||
variables: HoppCollectionVariable[]
|
||||
}[]
|
||||
> = computed(() => {
|
||||
return [
|
||||
{
|
||||
id: "variables",
|
||||
label: t("environment.variables"),
|
||||
emptyStateLabel: t("empty.collection_variables"),
|
||||
isSecret: false,
|
||||
variables: nonSecretVars.value,
|
||||
},
|
||||
{
|
||||
id: "secret",
|
||||
label: t("environment.secrets"),
|
||||
emptyStateLabel: t("empty.secret_environments"),
|
||||
isSecret: true,
|
||||
variables: secretVars.value,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||
IconTrash2,
|
||||
1000
|
||||
)
|
||||
|
||||
const aggregateEnvs = useReadonlyStream(
|
||||
aggregateEnvsWithCurrentValue$,
|
||||
[]
|
||||
) as Ref<AggregateEnvironment[]>
|
||||
|
||||
const liveEnvs = computed(() => {
|
||||
const parentInheritedVariables =
|
||||
transformInheritedCollectionVariablesToAggregateEnv(
|
||||
props.inheritedProperties?.variables ?? []
|
||||
)
|
||||
return [...parentInheritedVariables, ...aggregateEnvs.value]
|
||||
})
|
||||
|
||||
const addEnvironmentVariable = () => {
|
||||
vars.value.push({
|
||||
key: "",
|
||||
currentValue: "",
|
||||
initialValue: "",
|
||||
secret: selectedEnvOption.value === "secret",
|
||||
})
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
vars.value = vars.value.filter((e) =>
|
||||
selectedEnvOption.value === "secret" ? !e.secret : e.secret
|
||||
)
|
||||
|
||||
clearIcon.value = IconDone
|
||||
toast.success(`${t("state.cleared")}`)
|
||||
}
|
||||
|
||||
const removeEnvironmentVariable = (index: number) => {
|
||||
vars.value.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<string[]>([])
|
||||
const requestMoveLoading = ref<string[]>([])
|
||||
|
||||
//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<HoppCollection>,
|
||||
collection: {
|
||||
...collection,
|
||||
variables: collectionVariables,
|
||||
} as Partial<HoppCollection>,
|
||||
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 = {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
<input
|
||||
v-model="env.key"
|
||||
v-focus
|
||||
class="flex flex-1 bg-transparent px-4 py-2"
|
||||
class="flex flex-1 bg-transparent px-4 py-2 text-secondaryDark"
|
||||
:placeholder="`${t('count.variable', {
|
||||
count: index + 1,
|
||||
})}`"
|
||||
|
|
@ -150,6 +150,7 @@
|
|||
:select-text-on-mount="
|
||||
env.key ? env.key === editingVariableName : false
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
|
|
@ -173,6 +174,7 @@
|
|||
:select-text-on-mount="
|
||||
env.key ? env.key === editingVariableName : false
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@
|
|||
<input
|
||||
v-model="env.key"
|
||||
v-focus
|
||||
class="flex flex-1 bg-transparent px-4 py-2"
|
||||
class="flex flex-1 bg-transparent px-4 py-2 text-secondaryDark"
|
||||
:placeholder="`${t('count.variable', {
|
||||
count: index + 1,
|
||||
})}`"
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@
|
|||
</draggable>
|
||||
|
||||
<draggable
|
||||
v-model="inheritedProperties"
|
||||
v-model="inheritedProperty"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
|
|
@ -533,9 +533,7 @@ const getComputedAuthHeaders = async (
|
|||
if (req && req.headers.find((h) => 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@
|
|||
</draggable>
|
||||
|
||||
<draggable
|
||||
v-model="inheritedProperties"
|
||||
v-model="inheritedProperty"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
|
|
@ -262,7 +262,7 @@ import { cloneDeep, isEqual } from "lodash-es"
|
|||
import { reactive, Ref, ref, toRef, watch } from "vue"
|
||||
import draggable from "vuedraggable-es"
|
||||
|
||||
import { computedAsync, useVModel } from "@vueuse/core"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import linter from "~/helpers/editor/linting/rawKeyValue"
|
||||
|
|
@ -558,6 +558,15 @@ const computedHeaders: Ref<
|
|||
}[]
|
||||
> = 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ const tryExampleResponse = () => {
|
|||
params,
|
||||
requestVariables,
|
||||
},
|
||||
inheritedProperties: tab.value.document.inheritedProperties,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tooltip-autocomplete {
|
||||
@apply z-[1001] #{!important};
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.autocomplete-wrapper {
|
||||
@apply relative;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Environment,
|
||||
HoppCollectionVariable,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequest,
|
||||
HoppRESTRequestVariable,
|
||||
|
|
@ -58,6 +59,7 @@ import {
|
|||
OutgoingSandboxPostRequestWorkerMessage,
|
||||
OutgoingSandboxPreRequestWorkerMessage,
|
||||
} from "./workers/sandbox.worker"
|
||||
import { transformInheritedCollectionVariablesToAggregateEnv } from "./utils/inheritedCollectionVarTransformer"
|
||||
|
||||
const sandboxWorker = new Worker(
|
||||
new URL("./workers/sandbox.worker.ts", import.meta.url),
|
||||
|
|
@ -117,8 +119,10 @@ export const combineEnvVariables = (variables: {
|
|||
temp?: Environment["variables"]
|
||||
}
|
||||
requestVariables: Environment["variables"]
|
||||
collectionVariables: Environment["variables"]
|
||||
}) => [
|
||||
...variables.requestVariables,
|
||||
...variables.collectionVariables,
|
||||
...(variables.environments.temp ?? []),
|
||||
...variables.environments.selected,
|
||||
...variables.environments.global,
|
||||
|
|
@ -423,6 +427,16 @@ export function runRESTRequest$(
|
|||
}
|
||||
)
|
||||
|
||||
const collectionVariables =
|
||||
transformInheritedCollectionVariablesToAggregateEnv(
|
||||
tab.value.document.inheritedProperties?.variables || []
|
||||
).map(({ key, initialValue, currentValue, secret }) => ({
|
||||
key,
|
||||
initialValue,
|
||||
currentValue,
|
||||
secret,
|
||||
}))
|
||||
|
||||
const finalRequest = {
|
||||
...tab.value.document.request,
|
||||
auth: requestAuth ?? { authType: "none", authActive: false },
|
||||
|
|
@ -430,8 +444,9 @@ export function runRESTRequest$(
|
|||
}
|
||||
|
||||
const finalEnvs = {
|
||||
requestVariables: finalRequestVariables as Environment["variables"],
|
||||
environments: preRequestScriptResult.right.envs,
|
||||
requestVariables: finalRequestVariables as Environment["variables"],
|
||||
collectionVariables,
|
||||
}
|
||||
|
||||
const finalEnvsWithNonEmptyValues = filterNonEmptyEnvironmentVariables(
|
||||
|
|
@ -569,7 +584,8 @@ function updateEnvsAfterTestScript(runResult: E.Right<SandboxTestResult>) {
|
|||
|
||||
export function runTestRunnerRequest(
|
||||
request: HoppRESTRequest,
|
||||
persistEnv = true
|
||||
persistEnv = true,
|
||||
inheritedVariables: HoppCollectionVariable[] = []
|
||||
): Promise<
|
||||
| E.Left<"script_fail">
|
||||
| E.Right<{
|
||||
|
|
@ -609,6 +625,7 @@ export function runTestRunnerRequest(
|
|||
temp: !persistEnv ? getTemporaryVariables() : [],
|
||||
},
|
||||
requestVariables: finalRequestVariables,
|
||||
collectionVariables: inheritedVariables,
|
||||
})
|
||||
),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import {
|
||||
CollectionVariable,
|
||||
HoppCollection,
|
||||
HoppCollectionVariable,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequest,
|
||||
|
|
@ -30,7 +32,11 @@ type TeamCollectionJSON = {
|
|||
data: string
|
||||
}
|
||||
|
||||
type CollectionDataProps = { auth: HoppRESTAuth; headers: HoppRESTHeaders }
|
||||
type CollectionDataProps = {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeaders
|
||||
variables: HoppCollectionVariable[]
|
||||
}
|
||||
|
||||
export const BACKEND_PAGE_SIZE = 10
|
||||
|
||||
|
|
@ -109,6 +115,7 @@ const parseCollectionData = (
|
|||
const defaultDataProps: CollectionDataProps = {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
|
|
@ -137,9 +144,15 @@ const parseCollectionData = (
|
|||
defaultDataProps.headers
|
||||
)
|
||||
|
||||
const variables = parseWithDefaultValue<CollectionDataProps["variables"]>(
|
||||
z.array(CollectionVariable).safeParse(parsedData?.variables),
|
||||
defaultDataProps.variables
|
||||
)
|
||||
|
||||
return {
|
||||
auth,
|
||||
headers,
|
||||
variables,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +160,7 @@ const parseCollectionData = (
|
|||
const teamCollectionJSONToHoppRESTColl = (
|
||||
coll: TeamCollectionJSON
|
||||
): HoppCollection => {
|
||||
const { auth, headers } = parseCollectionData(coll.data)
|
||||
const { auth, headers, variables } = parseCollectionData(coll.data)
|
||||
|
||||
return makeCollection({
|
||||
name: coll.name,
|
||||
|
|
@ -155,6 +168,7 @@ const teamCollectionJSONToHoppRESTColl = (
|
|||
requests: coll.requests,
|
||||
auth,
|
||||
headers,
|
||||
variables,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -214,9 +228,10 @@ export const teamCollToHoppRESTColl = (
|
|||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
const { auth, headers } = parseCollectionData(data)
|
||||
const { auth, headers, variables } = parseCollectionData(data)
|
||||
|
||||
return makeCollection({
|
||||
id: coll.id,
|
||||
|
|
@ -225,6 +240,7 @@ export const teamCollToHoppRESTColl = (
|
|||
requests: coll.requests?.map((x) => x.request) ?? [],
|
||||
auth: auth ?? { authType: "inherit", authActive: true },
|
||||
headers: headers ?? [],
|
||||
variables: variables ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,8 @@ function removeDuplicatesAndKeepLast(arr: HoppInheritedProperty["headers"]) {
|
|||
export function updateInheritedPropertiesForAffectedRequests(
|
||||
path: string,
|
||||
inheritedProperties: HoppInheritedProperty,
|
||||
type: "rest" | "graphql"
|
||||
type: "rest" | "graphql",
|
||||
collectionId?: string
|
||||
) {
|
||||
const tabService =
|
||||
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
||||
|
|
@ -226,6 +227,22 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||
|
||||
tab.value.document.inheritedProperties.headers = mergedHeaders
|
||||
}
|
||||
|
||||
if (tab.value.document.inheritedProperties?.variables && collectionId) {
|
||||
const tabInheritedVariables =
|
||||
tab.value.document.inheritedProperties.variables.filter(
|
||||
(variable) => variable.parentID !== collectionId
|
||||
)
|
||||
|
||||
// filter out the variables with the parentID as the path in the inheritedProperties
|
||||
const inheritedVariables = inheritedProperties.variables.filter(
|
||||
(variable) => variable.parentID === collectionId
|
||||
)
|
||||
|
||||
const finalVariables = [...inheritedVariables, ...tabInheritedVariables]
|
||||
|
||||
tab.value.document.inheritedProperties.variables = finalVariables
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import IconUser from "~icons/lucide/user?raw"
|
|||
import IconUsers from "~icons/lucide/users?raw"
|
||||
import IconGlobe from "~icons/lucide/globe?raw"
|
||||
import IconVariable from "~icons/lucide/variable?raw"
|
||||
import IconLibrary from "~icons/lucide/library?raw"
|
||||
import { isComment } from "./helpers"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ const HOPP_ENV_HIGHLIGHT =
|
|||
"cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
|
||||
|
||||
const HOPP_REQUEST_VARIABLE_HIGHLIGHT = "request-variable-highlight"
|
||||
const HOPP_COLLECTION_ENVIRONMENT_HIGHLIGHT = "collection-variable-highlight"
|
||||
const HOPP_ENVIRONMENT_HIGHLIGHT = "environment-variable-highlight"
|
||||
const HOPP_GLOBAL_ENVIRONMENT_HIGHLIGHT = "global-variable-highlight"
|
||||
const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "environment-not-found-highlight"
|
||||
|
|
@ -138,10 +140,20 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
|
||||
const isSecret = tooltipEnv?.secret === true
|
||||
const hasSource = Boolean(tooltipEnv?.sourceEnv)
|
||||
|
||||
let tooltipSourceEnvID = "Global"
|
||||
|
||||
if (tooltipEnv?.sourceEnv === "Global") {
|
||||
tooltipSourceEnvID = "Global"
|
||||
} else {
|
||||
tooltipSourceEnvID =
|
||||
tooltipEnv?.sourceEnv === "CollectionVariable"
|
||||
? tooltipEnv.sourceEnvID!
|
||||
: currentSelectedEnvironment.id
|
||||
}
|
||||
|
||||
const hasSecretStored = secretEnvironmentService.hasSecretValue(
|
||||
tooltipEnv?.sourceEnv !== "Global"
|
||||
? currentSelectedEnvironment.id
|
||||
: "Global",
|
||||
tooltipSourceEnvID,
|
||||
tooltipEnv?.key ?? ""
|
||||
)
|
||||
|
||||
|
|
@ -198,7 +210,9 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
? IconVariable
|
||||
: selectedEnvType === "TEAM_ENV"
|
||||
? IconUsers
|
||||
: IconUser
|
||||
: tooltipEnv?.sourceEnv === "CollectionVariable"
|
||||
? IconLibrary
|
||||
: IconUser
|
||||
}</span>`
|
||||
|
||||
const appendEditAction = (tooltip: HTMLElement) => {
|
||||
|
|
@ -236,7 +250,9 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
}
|
||||
})
|
||||
editIcon.innerHTML = `<span class="inline-flex items-center justify-center my-1">${IconEdit}</span>`
|
||||
tooltip.appendChild(editIcon)
|
||||
if (tooltipEnv?.sourceEnv !== "CollectionVariable") {
|
||||
tooltip.appendChild(editIcon)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -275,7 +291,7 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
const envContainer = document.createElement("div")
|
||||
tooltipContainer.appendChild(envContainer)
|
||||
envContainer.className =
|
||||
"flex flex-col items-start space-y-1 flex-1 w-full mt-2"
|
||||
"flex flex-col items-start space-y-1 flex-1 w-full mt-2 !z-[1002]"
|
||||
|
||||
const initialValueBlock = document.createElement("div")
|
||||
initialValueBlock.className = "flex items-center space-x-2"
|
||||
|
|
@ -323,6 +339,8 @@ function checkEnv(env: string, aggregateEnvs: AggregateEnvironment[]) {
|
|||
|
||||
if (envSource === "RequestVariable")
|
||||
className = HOPP_REQUEST_VARIABLE_HIGHLIGHT
|
||||
else if (envSource === "CollectionVariable")
|
||||
className = HOPP_COLLECTION_ENVIRONMENT_HIGHLIGHT
|
||||
else if (envSource === "Global") className = HOPP_GLOBAL_ENVIRONMENT_HIGHLIGHT
|
||||
else if (envSource !== undefined) className = HOPP_ENVIRONMENT_HIGHLIGHT
|
||||
|
||||
|
|
@ -374,56 +392,18 @@ export class HoppEnvironmentPlugin {
|
|||
private editorView: Ref<EditorView | undefined>
|
||||
) {
|
||||
const aggregateEnvs = getAggregateEnvsWithCurrentValue()
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: currentTab.document.request
|
||||
this.envs = [...aggregateEnvs]
|
||||
|
||||
if (!currentTabRequest) return
|
||||
|
||||
watch(
|
||||
currentTabRequest,
|
||||
(request) => {
|
||||
const requestVariables = request?.requestVariables
|
||||
? request.requestVariables
|
||||
: []
|
||||
|
||||
this.envs = [
|
||||
...requestVariables.map(({ key, value }) => ({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})),
|
||||
...aggregateEnvs,
|
||||
]
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
environmentHighlightStyle(this.envs),
|
||||
]),
|
||||
})
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
const requestVariables = currentTabRequest?.requestVariables ?? []
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
environmentHighlightStyle(this.envs),
|
||||
]),
|
||||
})
|
||||
|
||||
subscribeToStream(aggregateEnvsWithCurrentValue$, (envs) => {
|
||||
this.envs = [
|
||||
...requestVariables.map(({ key, value }) => ({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})),
|
||||
...envs,
|
||||
]
|
||||
this.envs = [...envs]
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
|
|
|
|||
|
|
@ -404,10 +404,10 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||
v: 9,
|
||||
name: options.name || "Untitled Request",
|
||||
url: finalUrl,
|
||||
headers: request.headers,
|
||||
headers: runHeaders,
|
||||
query,
|
||||
variables,
|
||||
auth: request.auth as HoppGQLAuth,
|
||||
auth,
|
||||
}
|
||||
|
||||
if (operationType === "subscription") {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export { hoppRESTImporter } from "./hopp"
|
||||
export { hoppOpenAPIImporter } from "./openapi"
|
||||
export { hoppPostmanImporter } from "./postman"
|
||||
export { hoppInsomniaImporter } from "./insomnia"
|
||||
export { hoppInsomniaImporter } from "./insomnia/insomniaColl"
|
||||
export { toTeamsImporter } from "./myCollections"
|
||||
export { harImporter } from "./har"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
HoppCollection,
|
||||
HoppCollectionVariable,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
HoppRESTParam,
|
||||
|
|
@ -14,39 +15,33 @@ import {
|
|||
import * as A from "fp-ts/Array"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as TO from "fp-ts/TaskOption"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { ImportRequest, convert } from "insomnia-importers"
|
||||
import { Header, Parameter } from "insomnia-importers/dist/src/entities"
|
||||
import { convert } from "insomnia-importers"
|
||||
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from ".."
|
||||
import { replaceInsomniaTemplating } from "./insomniaEnv"
|
||||
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
||||
import {
|
||||
InsomniaDoc,
|
||||
InsomniaDocV5,
|
||||
InsomniaFolderResource,
|
||||
InsomniaFolderV5,
|
||||
InsomniaRequestResource,
|
||||
InsomniaResource,
|
||||
InsoReqAuth,
|
||||
} from "./types"
|
||||
|
||||
// TODO: Insomnia allows custom prefixes for Bearer token auth, Hoppscotch doesn't. We just ignore the prefix for now
|
||||
|
||||
type UnwrapPromise<T extends Promise<any>> =
|
||||
T extends Promise<infer Y> ? Y : never
|
||||
|
||||
type InsomniaDoc = UnwrapPromise<ReturnType<typeof convert>>
|
||||
type InsomniaResource = ImportRequest
|
||||
|
||||
// insomnia-importers v3.6.0 doesn't provide a type for path parameters and they have deprecated the library
|
||||
type InsomniaPathParameter = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type InsomniaFolderResource = ImportRequest & { _type: "request_group" }
|
||||
type InsomniaRequestResource = Omit<ImportRequest, "headers" | "parameters"> & {
|
||||
_type: "request"
|
||||
} & {
|
||||
pathParameters?: InsomniaPathParameter[]
|
||||
} & {
|
||||
headers: (Header & { description: string })[]
|
||||
parameters: (Parameter & { description: string })[]
|
||||
}
|
||||
|
||||
const parseInsomniaDoc = (content: string) =>
|
||||
TO.tryCatch(() => convert(content))
|
||||
/**
|
||||
* Used to check if the document is an Insomnia v5 document
|
||||
* Insomnia v5 documents have a type field that starts with "collection.insomnia.rest/
|
||||
* @param data InsomniaDoc
|
||||
* @returns true if the document is an Insomnia v5 document
|
||||
*/
|
||||
const isV5InsomniaDoc = (data: InsomniaDoc) =>
|
||||
data.type &&
|
||||
typeof data.type === "string" &&
|
||||
(data.type as string).startsWith("collection.insomnia.rest/5")
|
||||
|
||||
const replacePathVarTemplating = (expression: string) =>
|
||||
expression.replaceAll(/:([^/]+)/g, "<<$1>>")
|
||||
|
|
@ -84,24 +79,22 @@ const getRequestsIn = (
|
|||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* The provided type by insomnia-importers, this type corrects it
|
||||
*/
|
||||
type InsoReqAuth =
|
||||
| { type: "basic"; disabled?: boolean; username?: string; password?: string }
|
||||
| {
|
||||
type: "oauth2"
|
||||
disabled?: boolean
|
||||
accessTokenUrl?: string
|
||||
authorizationUrl?: string
|
||||
clientId?: string
|
||||
scope?: string
|
||||
}
|
||||
| {
|
||||
type: "bearer"
|
||||
disabled?: boolean
|
||||
token?: string
|
||||
}
|
||||
const getCollectionVariables = (
|
||||
environment: Record<string, string> | undefined,
|
||||
folderRes?: InsomniaFolderResource
|
||||
): HoppCollectionVariable[] => {
|
||||
const env =
|
||||
folderRes && folderRes.environment ? folderRes.environment : environment
|
||||
|
||||
if (!env) return []
|
||||
|
||||
return Object.entries(env).map(([key, value]) => ({
|
||||
key: replaceVarTemplating(key),
|
||||
currentValue: "", // set it as empty value since it is handled by currentValue service and we don't want it to sync with BE
|
||||
initialValue: replaceVarTemplating(value),
|
||||
secret: false,
|
||||
}))
|
||||
}
|
||||
|
||||
const getHoppReqAuth = (req: InsomniaRequestResource): HoppRESTAuth => {
|
||||
if (!req.authentication) return { authType: "none", authActive: true }
|
||||
|
|
@ -128,6 +121,9 @@ const getHoppReqAuth = (req: InsomniaRequestResource): HoppRESTAuth => {
|
|||
token: "",
|
||||
isPKCE: false,
|
||||
tokenEndpoint: replaceVarTemplating(auth.accessTokenUrl ?? ""),
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -246,6 +242,7 @@ const getHoppFolder = (
|
|||
requests: getRequestsIn(folderRes, resources).map(getHoppRequest),
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: getCollectionVariables(undefined, folderRes), // undefined is used to indicate no environment variables for v4 and below
|
||||
})
|
||||
|
||||
const getHoppCollections = (docs: InsomniaDoc[]) => {
|
||||
|
|
@ -256,10 +253,112 @@ const getHoppCollections = (docs: InsomniaDoc[]) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const hoppInsomniaImporter = (fileContents: string[]) =>
|
||||
const getFolders = (collections: InsomniaDocV5["collection"]) => {
|
||||
if (!collections) return []
|
||||
return collections.filter(
|
||||
(x): x is InsomniaFolderV5 => "children" in x && !("url" in x)
|
||||
)
|
||||
}
|
||||
|
||||
const getRequests = (
|
||||
collections: InsomniaDocV5["collection"]
|
||||
): InsomniaRequestResource[] => {
|
||||
if (!collections) return []
|
||||
return collections.filter((x): x is InsomniaRequestResource => "url" in x)
|
||||
}
|
||||
|
||||
const getParsedHoppFolder = (
|
||||
name: string,
|
||||
collection: InsomniaFolderV5
|
||||
): HoppCollection => {
|
||||
return makeCollection({
|
||||
name: name ?? collection.name ?? "Untitled Collection",
|
||||
folders: getFolders(collection.children ?? []).map((f) =>
|
||||
getParsedHoppFolder(f.name, f)
|
||||
),
|
||||
requests: getRequests(collection.children ?? []).map(getParsedHoppRequest),
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: getCollectionVariables(collection.environment),
|
||||
})
|
||||
}
|
||||
|
||||
const getParsedHoppRequest = (req: InsomniaRequestResource) => {
|
||||
return makeRESTRequest({
|
||||
name: req.name ?? "Untitled Request",
|
||||
method: req.method?.toUpperCase() ?? "GET",
|
||||
endpoint: replaceVarTemplating(req.url ?? "", true),
|
||||
auth: getHoppReqAuth(req),
|
||||
body: getHoppReqBody(req),
|
||||
headers: getHoppReqHeaders(req),
|
||||
params: getHoppReqParams(req),
|
||||
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
|
||||
requestVariables: getHoppReqVariables(req),
|
||||
|
||||
//insomnia doesn't have saved response
|
||||
responses: {},
|
||||
})
|
||||
}
|
||||
|
||||
const getParsedHoppCollections = (docs: InsomniaDocV5[]): HoppCollection[] =>
|
||||
docs.flatMap((doc) => {
|
||||
if (doc && Array.isArray(doc.collection)) {
|
||||
return makeCollection({
|
||||
name: doc.name ?? "Untitled Collection",
|
||||
folders: getFolders(doc.collection).map((f) =>
|
||||
getParsedHoppFolder(f.name, f)
|
||||
),
|
||||
requests: getRequests(doc.collection).map((x) =>
|
||||
getParsedHoppRequest(x)
|
||||
),
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: getCollectionVariables(doc.environments?.data),
|
||||
})
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
const parseInsomniaDoc = (content: string) =>
|
||||
pipe(
|
||||
fileContents,
|
||||
A.traverse(TO.ApplicativeSeq)(parseInsomniaDoc),
|
||||
TO.map(getHoppCollections),
|
||||
TO.tryCatch(() => convert(content)),
|
||||
TO.map((doc) => getHoppCollections([doc])),
|
||||
TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
)
|
||||
|
||||
const parseV5InsomniaDoc = (content: string) =>
|
||||
pipe(
|
||||
safeParseJSONOrYAML(content),
|
||||
TO.fromOption,
|
||||
TO.map((parsed) => parsed as InsomniaDocV5),
|
||||
TO.map((doc) => getParsedHoppCollections([doc])),
|
||||
TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
)
|
||||
|
||||
/**
|
||||
* insomina-importers v3.6.0 does supoort insomina v5
|
||||
* (NOTE: Currently, insomnia-importers only supports v4 and below)
|
||||
* https://github.com/Kong/insomnia/issues/8504
|
||||
*/
|
||||
export const hoppInsomniaImporter = (fileContents: string[]) => {
|
||||
return pipe(
|
||||
fileContents,
|
||||
A.traverse(TE.ApplicativeSeq)((content) =>
|
||||
pipe(
|
||||
safeParseJSONOrYAML(content),
|
||||
O.fold(
|
||||
() => TE.left(IMPORTER_INVALID_FILE_FORMAT),
|
||||
(parsed) =>
|
||||
isV5InsomniaDoc(parsed as InsomniaDoc)
|
||||
? parseV5InsomniaDoc(content)
|
||||
: parseInsomniaDoc(content)
|
||||
)
|
||||
)
|
||||
),
|
||||
TE.map(A.flatten)
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as O from "fp-ts/Option"
|
||||
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from ".."
|
||||
|
||||
import { z } from "zod"
|
||||
import {
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { ImportRequest, convert } from "insomnia-importers"
|
||||
import { Header, Parameter } from "insomnia-importers/dist/src/entities"
|
||||
|
||||
type UnwrapPromise<T extends Promise<any>> =
|
||||
T extends Promise<infer Y> ? Y : never
|
||||
|
||||
export type InsomniaDoc = UnwrapPromise<ReturnType<typeof convert>>
|
||||
export type InsomniaResource = ImportRequest
|
||||
|
||||
// insomnia-importers v3.6.0 doesn't provide a type for path parameters and they have deprecated the library
|
||||
export type InsomniaPathParameter = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type InsomniaFolderResource = ImportRequest & { _type: "request_group" }
|
||||
export type InsomniaRequestResource = Omit<
|
||||
ImportRequest,
|
||||
"headers" | "parameters"
|
||||
> & {
|
||||
_type: "request"
|
||||
} & {
|
||||
pathParameters?: InsomniaPathParameter[]
|
||||
} & {
|
||||
headers: (Header & { description: string })[]
|
||||
parameters: (Parameter & { description: string })[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The provided type by insomnia-importers, this type corrects it
|
||||
*/
|
||||
export type InsoReqAuth =
|
||||
| { type: "basic"; disabled?: boolean; username?: string; password?: string }
|
||||
| {
|
||||
type: "oauth2"
|
||||
disabled?: boolean
|
||||
accessTokenUrl?: string
|
||||
authorizationUrl?: string
|
||||
clientId?: string
|
||||
scope?: string
|
||||
}
|
||||
| {
|
||||
type: "bearer"
|
||||
disabled?: boolean
|
||||
token?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Insomnia v5 document types
|
||||
* These types are used to represent the structure of Insomnia v5 documents.
|
||||
*/
|
||||
export type InsomniaDocV5 = {
|
||||
type: `collection.insomnia.rest/${string}`
|
||||
name: string
|
||||
meta: {
|
||||
id: string
|
||||
created: number
|
||||
modified: number
|
||||
description?: string
|
||||
}
|
||||
collection: (
|
||||
| InsomniaFolderV5
|
||||
| InsomniaRequestResource
|
||||
| InsomniaScriptOnlyV5
|
||||
)[]
|
||||
cookieJar?: InsomniaCookieJarV5
|
||||
environments?: InsomniaEnvironmentV5
|
||||
}
|
||||
|
||||
export type InsomniaMetaV5 = {
|
||||
id: string
|
||||
created: number
|
||||
modified: number
|
||||
description?: string
|
||||
sortKey?: number
|
||||
}
|
||||
|
||||
export type InsomniaScriptOnlyV5 = {
|
||||
name: string
|
||||
meta: InsomniaMetaV5
|
||||
scripts: {
|
||||
afterResponse?: string
|
||||
preRequest?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type InsomniaFolderV5 = {
|
||||
name: string
|
||||
meta: InsomniaMetaV5
|
||||
children?: (InsomniaFolderV5 | InsomniaRequestResource)[]
|
||||
environment?: Record<string, string>
|
||||
scripts?: {
|
||||
afterResponse?: string
|
||||
preRequest?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type InsomniaKeyValueV5 = {
|
||||
id?: string
|
||||
name: string
|
||||
value: string
|
||||
description?: string
|
||||
disabled?: boolean
|
||||
type?: string
|
||||
multiline?: boolean
|
||||
}
|
||||
|
||||
export type InsomniaCookieJarV5 = {
|
||||
name: string
|
||||
meta: {
|
||||
id: string
|
||||
created: number
|
||||
modified: number
|
||||
}
|
||||
cookies: {
|
||||
key: string
|
||||
value: string
|
||||
domain: string
|
||||
path: string
|
||||
secure?: boolean
|
||||
httpOnly?: boolean
|
||||
hostOnly?: boolean
|
||||
creation: string
|
||||
lastAccessed: string
|
||||
sameSite?: "lax" | "strict" | "none"
|
||||
id: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export type InsomniaEnvironmentV5 = {
|
||||
name: string
|
||||
meta: {
|
||||
id: string
|
||||
created: number
|
||||
modified: number
|
||||
isPrivate?: boolean
|
||||
}
|
||||
data: Record<string, string>
|
||||
subEnvironments?: {
|
||||
name: string
|
||||
meta: {
|
||||
id: string
|
||||
created: number
|
||||
modified: number
|
||||
isPrivate?: boolean
|
||||
sortKey?: number
|
||||
}
|
||||
data: Record<string, string>
|
||||
}[]
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
ValidContentTypes,
|
||||
HoppRESTRequestResponses,
|
||||
makeHoppRESTResponseOriginalRequest,
|
||||
HoppCollectionVariable,
|
||||
} from "@hoppscotch/data"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
|
|
@ -28,6 +29,7 @@ import {
|
|||
RequestAuthDefinition,
|
||||
Variable,
|
||||
VariableDefinition,
|
||||
VariableList,
|
||||
} from "postman-collection"
|
||||
import { stringArrayJoin } from "~/helpers/functional/array"
|
||||
import { PMRawLanguage } from "~/types/pm-coll-exts"
|
||||
|
|
@ -79,6 +81,32 @@ const parseDescription = (descField?: string | DescriptionDefinition) => {
|
|||
return descField.content
|
||||
}
|
||||
|
||||
const getHoppCollVariables = (
|
||||
ig: ItemGroup<Item>
|
||||
): HoppCollectionVariable[] => {
|
||||
if (!("variables" in ig && ig.variables)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return pipe(
|
||||
(ig.variables as VariableList).all(),
|
||||
A.filter(
|
||||
(variable) =>
|
||||
variable.key !== undefined &&
|
||||
variable.key !== null &&
|
||||
variable.key.length > 0
|
||||
),
|
||||
A.map((variable) => {
|
||||
return <HoppCollectionVariable>{
|
||||
key: replacePMVarTemplating(variable.key ?? ""),
|
||||
initialValue: replacePMVarTemplating(variable.value ?? ""),
|
||||
currentValue: "",
|
||||
secret: variable.type === "secret",
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getHoppReqHeaders = (
|
||||
headers: Item["request"]["headers"] | null
|
||||
): HoppRESTHeader[] => {
|
||||
|
|
@ -288,6 +316,9 @@ const getHoppReqAuth = (
|
|||
tokenEndpoint: accessTokenURL,
|
||||
clientSecret: "",
|
||||
isPKCE: false,
|
||||
authRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -457,6 +488,7 @@ const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection =>
|
|||
requests: pipe(ig.items.all(), A.filter(isPMItem), A.map(getHoppRequest)),
|
||||
auth: getHoppReqAuth(ig.auth),
|
||||
headers: [],
|
||||
variables: getHoppCollVariables(ig),
|
||||
})
|
||||
|
||||
export const getHoppCollections = (collections: PMCollection[]) => {
|
||||
|
|
|
|||
|
|
@ -259,6 +259,12 @@ export type HoppSavedExampleDocument = {
|
|||
* (atleast as far as we can say)
|
||||
*/
|
||||
isDirty: boolean
|
||||
|
||||
/**
|
||||
* The inherited properties from the parent collection
|
||||
* (if any)
|
||||
*/
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import * as E from "fp-ts/Either"
|
||||
import { BehaviorSubject, Subscription } from "rxjs"
|
||||
import {
|
||||
HoppCollectionVariable,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
translateToNewRequest,
|
||||
|
|
@ -26,6 +27,9 @@ import {
|
|||
TeamCollectionOrderUpdatedDocument,
|
||||
} from "~/helpers/backend/graphql"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
import { getService } from "~/modules/dioc"
|
||||
|
||||
export const TEAMS_BACKEND_PAGE_SIZE = 10
|
||||
|
||||
|
|
@ -1034,12 +1038,48 @@ export default class NewTeamCollectionAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
private getCurrentValue = (
|
||||
env: HoppCollectionVariable,
|
||||
varIndex: number,
|
||||
collectionID: string
|
||||
) => {
|
||||
//collection variables current value and secret value
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
if (env && env.secret) {
|
||||
return secretEnvironmentService.getSecretEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.value
|
||||
}
|
||||
return currentEnvironmentValueService.getEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.currentValue
|
||||
}
|
||||
|
||||
/**
|
||||
* This function populates the values of the variables with the current values or secrets.
|
||||
* @param variables Variables to populate
|
||||
* @returns Populated variables with current values or secrets
|
||||
*/
|
||||
private populateValues(
|
||||
variables: HoppCollectionVariable[],
|
||||
parentID: string
|
||||
) {
|
||||
return variables.map((v, index) => ({
|
||||
...v,
|
||||
currentValue: this.getCurrentValue(v, index, parentID) ?? v.currentValue,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to obtain the inherited auth and headers for a given folder path, used for both REST and GraphQL team collections
|
||||
* @param folderPath the path of the folder to cascade the auth from
|
||||
* @returns the inherited auth and headers for the given folder path
|
||||
*/
|
||||
public cascadeParentCollectionForHeaderAuth(folderPath: string) {
|
||||
public cascadeParentCollectionForProperties(folderPath: string) {
|
||||
let auth: HoppInheritedProperty["auth"] = {
|
||||
parentID: folderPath ?? "",
|
||||
parentName: "",
|
||||
|
|
@ -1050,14 +1090,16 @@ export default class NewTeamCollectionAdapter {
|
|||
}
|
||||
const headers: HoppInheritedProperty["headers"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers }
|
||||
const variables: HoppInheritedProperty["variables"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers, variables }
|
||||
|
||||
const path = folderPath.split("/")
|
||||
|
||||
// Check if the path is empty or invalid
|
||||
if (!path || path.length === 0) {
|
||||
console.error("Invalid path:", folderPath)
|
||||
return { auth, headers }
|
||||
return { auth, headers, variables }
|
||||
}
|
||||
|
||||
// Loop through the path and get the last parent folder with authType other than 'inherit'
|
||||
|
|
@ -1067,17 +1109,19 @@ export default class NewTeamCollectionAdapter {
|
|||
// Check if parentFolder is undefined or null
|
||||
if (!parentFolder) {
|
||||
console.error("Parent folder not found for path:", path)
|
||||
return { auth, headers }
|
||||
return { auth, headers, variables }
|
||||
}
|
||||
|
||||
const data: {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeader[]
|
||||
variables: HoppCollectionVariable[]
|
||||
} = parentFolder.data
|
||||
? JSON.parse(parentFolder.data)
|
||||
: {
|
||||
auth: null,
|
||||
headers: null,
|
||||
variables: null,
|
||||
}
|
||||
|
||||
if (!data.auth) {
|
||||
|
|
@ -1091,8 +1135,11 @@ export default class NewTeamCollectionAdapter {
|
|||
|
||||
if (!data.headers) data.headers = []
|
||||
|
||||
if (!data.variables) data.variables = []
|
||||
|
||||
const parentFolderAuth = data.auth
|
||||
const parentFolderHeaders = data.headers
|
||||
const parentFolderVariables = data.variables
|
||||
|
||||
if (
|
||||
parentFolderAuth?.authType === "inherit" &&
|
||||
|
|
@ -1137,8 +1184,22 @@ export default class NewTeamCollectionAdapter {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Update variables, overwriting duplicates by key
|
||||
if (parentFolderVariables) {
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
|
||||
variables.push({
|
||||
parentID: parentFolder.id ?? currentPath,
|
||||
parentName: parentFolder.title,
|
||||
inheritedVariables: this.populateValues(
|
||||
parentFolderVariables,
|
||||
parentFolder.id ?? currentPath
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { auth, headers }
|
||||
return { auth, headers, variables }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
HoppCollectionVariable,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
HoppRESTRequest,
|
||||
|
|
@ -337,7 +338,7 @@ export class TeamSearchService extends Service {
|
|||
this.teamsSearchResultsLoading.value = false
|
||||
}
|
||||
|
||||
cascadeParentCollectionForHeaderAuthForSearchResults = (
|
||||
cascadeParentCollectionForPropertiesForSearchResults = (
|
||||
collectionID: string
|
||||
): HoppInheritedProperty => {
|
||||
const defaultInheritedAuth: HoppInheritedProperty["auth"] = {
|
||||
|
|
@ -351,15 +352,23 @@ export class TeamSearchService extends Service {
|
|||
|
||||
const defaultInheritedHeaders: HoppInheritedProperty["headers"] = []
|
||||
|
||||
const defaultInheritedVariables: HoppInheritedProperty["variables"] = []
|
||||
|
||||
const collection = Object.values(this.searchResultsCollections).find(
|
||||
(col) => col.id === collectionID
|
||||
)
|
||||
|
||||
if (!collection)
|
||||
return { auth: defaultInheritedAuth, headers: defaultInheritedHeaders }
|
||||
return {
|
||||
auth: defaultInheritedAuth,
|
||||
headers: defaultInheritedHeaders,
|
||||
variables: defaultInheritedVariables,
|
||||
}
|
||||
|
||||
const inheritedAuthData = this.findInheritableParentAuth(collectionID)
|
||||
const inheritedHeadersData = this.findInheritableParentHeaders(collectionID)
|
||||
const inheritedVariablesData =
|
||||
this.findInheritableParentVariables(collectionID)
|
||||
|
||||
return {
|
||||
auth: E.isRight(inheritedAuthData)
|
||||
|
|
@ -368,6 +377,9 @@ export class TeamSearchService extends Service {
|
|||
headers: E.isRight(inheritedHeadersData)
|
||||
? Object.values(inheritedHeadersData.right)
|
||||
: defaultInheritedHeaders,
|
||||
variables: E.isRight(inheritedVariablesData)
|
||||
? Object.values(inheritedVariablesData.right)
|
||||
: defaultInheritedVariables,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -394,6 +406,7 @@ export class TeamSearchService extends Service {
|
|||
const parentInheritedData = JSON.parse(collection.data) as {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeader[]
|
||||
variables: HoppCollectionVariable[]
|
||||
}
|
||||
|
||||
const inheritedAuth = parentInheritedData.auth
|
||||
|
|
@ -437,6 +450,7 @@ export class TeamSearchService extends Service {
|
|||
const parentInheritedData = JSON.parse(collection.data) as {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeader[]
|
||||
variables: HoppCollectionVariable[]
|
||||
}
|
||||
|
||||
const inheritedHeaders = parentInheritedData.headers
|
||||
|
|
@ -464,6 +478,45 @@ export class TeamSearchService extends Service {
|
|||
return E.right(existingHeaders)
|
||||
}
|
||||
|
||||
findInheritableParentVariables = (
|
||||
collectionID: string,
|
||||
existingVariables: HoppInheritedProperty["variables"] = []
|
||||
): E.Either<string, HoppInheritedProperty["variables"]> => {
|
||||
const collection = Object.values(this.searchResultsCollections).find(
|
||||
(col) => col.id === collectionID
|
||||
)
|
||||
|
||||
const vars = [...Object.values(existingVariables)]
|
||||
|
||||
if (!collection) {
|
||||
return E.left("PARENT_NOT_FOUND" as const)
|
||||
}
|
||||
|
||||
if (collection.data) {
|
||||
const parentData = JSON.parse(collection.data) as {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeader[]
|
||||
variables: HoppCollectionVariable[]
|
||||
}
|
||||
|
||||
const variables = parentData.variables
|
||||
|
||||
if (variables) {
|
||||
vars.push({
|
||||
parentID: collection.id,
|
||||
parentName: collection.title,
|
||||
inheritedVariables: variables,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (collection.parentID) {
|
||||
return this.findInheritableParentVariables(collection.parentID, vars)
|
||||
}
|
||||
|
||||
return E.right(vars)
|
||||
}
|
||||
|
||||
expandCollection = async (collectionID: string) => {
|
||||
if (this.expandingCollections.value.includes(collectionID)) return
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
HoppGQLAuth,
|
||||
HoppRESTHeader,
|
||||
HoppRESTAuth,
|
||||
HoppCollectionVariable,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
export type HoppInheritedProperty = {
|
||||
|
|
@ -16,4 +17,9 @@ export type HoppInheritedProperty = {
|
|||
parentName: string
|
||||
inheritedHeader: HoppRESTHeader | GQLHeader
|
||||
}[]
|
||||
variables: {
|
||||
parentID: string
|
||||
parentName: string
|
||||
inheritedVariables: HoppCollectionVariable[]
|
||||
}[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import { HoppCollectionVariable } from "@hoppscotch/data"
|
||||
|
||||
const getCurrentValue = (
|
||||
isSecret: boolean,
|
||||
varIndex: number,
|
||||
collectionID: string,
|
||||
showSecret: boolean = false
|
||||
) => {
|
||||
//collection variables current value and secret value
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
if (isSecret && showSecret) {
|
||||
return secretEnvironmentService.getSecretEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.value
|
||||
}
|
||||
return currentEnvironmentValueService.getEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.currentValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to transform inherited collection variables into an array of `AggregateEnvironment` objects.
|
||||
* @param variables - The inherited collection variables to transform.
|
||||
* @param showSecret - Whether to show secret values in the transformed variables.
|
||||
* @returns An array of `AggregateEnvironment` objects representing the transformed collection variables.
|
||||
*/
|
||||
export const transformInheritedCollectionVariablesToAggregateEnv = (
|
||||
variables: HoppInheritedProperty["variables"],
|
||||
showSecret: boolean = true
|
||||
): AggregateEnvironment[] => {
|
||||
return variables.flatMap(({ parentID, inheritedVariables }) =>
|
||||
inheritedVariables.map(
|
||||
({ currentValue, initialValue, key, secret }, index) => ({
|
||||
key,
|
||||
currentValue:
|
||||
getCurrentValue(secret, index, parentID, showSecret) ?? currentValue,
|
||||
initialValue,
|
||||
sourceEnv: "CollectionVariable",
|
||||
secret,
|
||||
sourceEnvID: parentID,
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to populate current values in inherited collection variables.
|
||||
* @param variables - The inherited collection variables to populate.
|
||||
* @param parentID - The ID of the parent collection from which to inherit values.
|
||||
* @returns - An array of `HoppCollectionVariable` objects with populated current values.
|
||||
*/
|
||||
export const populateValuesInInheritedCollectionVars = (
|
||||
variables: HoppCollectionVariable[],
|
||||
parentID?: string
|
||||
): HoppCollectionVariable[] =>
|
||||
parentID
|
||||
? variables.map((variable, index) => ({
|
||||
...variable,
|
||||
currentValue:
|
||||
getCurrentValue(variable.secret, index, parentID) ??
|
||||
variable.currentValue,
|
||||
}))
|
||||
: []
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
generateUniqueRefId,
|
||||
HoppCollection,
|
||||
HoppCollectionVariable,
|
||||
HoppGQLAuth,
|
||||
HoppGQLRequest,
|
||||
HoppRESTAuth,
|
||||
|
|
@ -17,6 +18,8 @@ import { getService } from "~/modules/dioc"
|
|||
import { getI18n } from "~/modules/i18n"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
|
||||
const defaultRESTCollectionState = {
|
||||
state: [
|
||||
|
|
@ -29,6 +32,7 @@ const defaultRESTCollectionState = {
|
|||
authActive: false,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
|
@ -44,6 +48,7 @@ const defaultGraphqlCollectionState = {
|
|||
authActive: false,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
|
@ -69,15 +74,56 @@ export function navigateToFolderWithIndexPath(
|
|||
return target !== undefined ? target : null
|
||||
}
|
||||
|
||||
const getCurrentValue = (
|
||||
env: HoppCollectionVariable,
|
||||
varIndex: number,
|
||||
collectionID: string,
|
||||
showSecret: boolean
|
||||
) => {
|
||||
//collection variables current value and secret value
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
if (env && env.secret && showSecret) {
|
||||
return secretEnvironmentService.getSecretEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.value
|
||||
}
|
||||
return currentEnvironmentValueService.getEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.currentValue
|
||||
}
|
||||
|
||||
/**
|
||||
* This function populates the values of the variables with the current values or secrets.
|
||||
* @param variables Variables to populate
|
||||
* @returns Populated variables with current values or secrets
|
||||
*/
|
||||
function populateValues(
|
||||
variables: HoppCollectionVariable[],
|
||||
parentID: string,
|
||||
showSecret: boolean
|
||||
) {
|
||||
return variables.map((v, index) => ({
|
||||
...v,
|
||||
currentValue:
|
||||
getCurrentValue(v, index, parentID, showSecret) ?? v.currentValue,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to obtain the inherited auth and headers for a given folder path, used for both REST and GraphQL personal collections
|
||||
* @param folderPath the path of the folder to cascade the auth from
|
||||
* @param type the type of collection
|
||||
* @param showSecret whether to show secret values in the collection variables
|
||||
* @returns the inherited auth and headers for the given folder path
|
||||
*/
|
||||
export function cascadeParentCollectionForHeaderAuth(
|
||||
export function cascadeParentCollectionForProperties(
|
||||
folderPath: string | undefined,
|
||||
type: "rest" | "graphql"
|
||||
type: "rest" | "graphql",
|
||||
showSecret: boolean = false
|
||||
) {
|
||||
const collectionStore =
|
||||
type === "rest" ? restCollectionStore : graphqlCollectionStore
|
||||
|
|
@ -92,14 +138,16 @@ export function cascadeParentCollectionForHeaderAuth(
|
|||
}
|
||||
const headers: HoppInheritedProperty["headers"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers }
|
||||
const variables: HoppInheritedProperty["variables"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers, variables }
|
||||
|
||||
const path = folderPath.split("/").map((i) => parseInt(i))
|
||||
|
||||
// Check if the path is empty or invalid
|
||||
if (!path || path.length === 0) {
|
||||
console.error("Invalid path:", folderPath)
|
||||
return { auth, headers }
|
||||
return { auth, headers, variables }
|
||||
}
|
||||
|
||||
// Loop through the path and get the last parent folder with authType other than 'inherit'
|
||||
|
|
@ -112,7 +160,7 @@ export function cascadeParentCollectionForHeaderAuth(
|
|||
// Check if parentFolder is undefined or null
|
||||
if (!parentFolder) {
|
||||
console.error("Parent folder not found for path:", path)
|
||||
return { auth, headers }
|
||||
return { auth, headers, variables }
|
||||
}
|
||||
|
||||
const parentFolderAuth = parentFolder.auth as HoppRESTAuth | HoppGQLAuth
|
||||
|
|
@ -120,6 +168,9 @@ export function cascadeParentCollectionForHeaderAuth(
|
|||
| HoppRESTHeaders
|
||||
| GQLHeader[]
|
||||
|
||||
const parentFolderVariables =
|
||||
parentFolder.variables as HoppCollectionVariable[]
|
||||
|
||||
// check if the parent folder has authType 'inherit' and if it is the root folder
|
||||
if (
|
||||
parentFolderAuth?.authType === "inherit" &&
|
||||
|
|
@ -164,9 +215,23 @@ export function cascadeParentCollectionForHeaderAuth(
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (parentFolderVariables) {
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
|
||||
variables.push({
|
||||
parentID: parentFolder._ref_id ?? parentFolder.id ?? currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedVariables: populateValues(
|
||||
parentFolderVariables,
|
||||
parentFolder.id ?? currentPath,
|
||||
showSecret
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { auth, headers }
|
||||
return { auth, headers, variables }
|
||||
}
|
||||
|
||||
function reorderItems(array: unknown[], from: number, to: number) {
|
||||
|
|
@ -254,6 +319,7 @@ const restCollectionDispatchers = defineDispatchers({
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
})
|
||||
|
||||
const newState = state
|
||||
|
|
@ -879,6 +945,7 @@ const gqlCollectionDispatchers = defineDispatchers({
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
})
|
||||
const newState = state
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
|
@ -1222,8 +1289,13 @@ function computeCollectionInheritedProps(
|
|||
ref_id: string,
|
||||
type: "my-collections" | "team-collections" = "my-collections",
|
||||
parentAuth: HoppRESTAuth | null = null,
|
||||
parentHeaders: HoppRESTHeaders | null = null
|
||||
): { auth: HoppRESTAuth; headers: HoppRESTHeaders } | null {
|
||||
parentHeaders: HoppRESTHeaders | null = null,
|
||||
parentVariables: HoppCollectionVariable[] | null = null
|
||||
): {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeaders
|
||||
variables: HoppCollectionVariable[]
|
||||
} | null {
|
||||
// Determine the inherited authentication and headers
|
||||
const inheritedAuth =
|
||||
collection.auth?.authType === "inherit" && collection.auth.authActive
|
||||
|
|
@ -1235,6 +1307,11 @@ function computeCollectionInheritedProps(
|
|||
...collection.headers,
|
||||
]
|
||||
|
||||
const inheritedVariables = [
|
||||
...(parentVariables ?? []),
|
||||
...collection.variables,
|
||||
]
|
||||
|
||||
// Check if the current collection matches the target reference ID
|
||||
const isTargetCollection =
|
||||
type === "my-collections"
|
||||
|
|
@ -1245,6 +1322,7 @@ function computeCollectionInheritedProps(
|
|||
return {
|
||||
auth: inheritedAuth,
|
||||
headers: inheritedHeaders,
|
||||
variables: inheritedVariables,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1255,7 +1333,8 @@ function computeCollectionInheritedProps(
|
|||
ref_id,
|
||||
type,
|
||||
inheritedAuth,
|
||||
inheritedHeaders
|
||||
inheritedHeaders,
|
||||
inheritedVariables
|
||||
)
|
||||
if (result) return result // Return as soon as a match is found
|
||||
}
|
||||
|
|
@ -1267,7 +1346,11 @@ export function getRESTCollectionInheritedProps(
|
|||
collectionID: string,
|
||||
collections: HoppCollection[] = restCollectionStore.value.state,
|
||||
type: "my-collections" | "team-collections" = "my-collections"
|
||||
): { auth: HoppRESTAuth; headers: HoppRESTHeaders } | null {
|
||||
): {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeaders
|
||||
variables: HoppCollectionVariable[]
|
||||
} | null {
|
||||
for (const collection of collections) {
|
||||
const result = computeCollectionInheritedProps(
|
||||
collection,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import DispatchingStore, {
|
|||
} from "~/newstore/DispatchingStore"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
export type SelectedEnvironmentIndex =
|
||||
| { type: "NO_ENV_SELECTED" }
|
||||
|
|
@ -49,9 +50,6 @@ const defaultEnvironmentsState = {
|
|||
} as SelectedEnvironmentIndex,
|
||||
}
|
||||
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
type EnvironmentStore = typeof defaultEnvironmentsState
|
||||
|
||||
const dispatchers = defineDispatchers({
|
||||
|
|
@ -423,6 +421,7 @@ export type AggregateEnvironment = {
|
|||
currentValue: string
|
||||
secret: boolean
|
||||
sourceEnv: string
|
||||
sourceEnvID?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -431,13 +430,27 @@ export type AggregateEnvironment = {
|
|||
* NOTE: The source environment attribute will be "Global" for Global Env as source.
|
||||
* The priority of the variables is as follows:
|
||||
* 1. Pre-defined variables
|
||||
* 2. Selected Environment Variables
|
||||
* 3. Global Environment Variables
|
||||
* 2. Request Variables (from the current request)
|
||||
* 3. Selected Environment Variables
|
||||
* 4. Global Environment Variables
|
||||
*/
|
||||
export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
|
||||
[currentEnvironment$, globalEnv$]
|
||||
).pipe(
|
||||
map(([selectedEnv, globalEnv]) => {
|
||||
const restTabs = getService(RESTTabService)
|
||||
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: currentTab.document.request
|
||||
|
||||
const requestVariables = currentTabRequest?.requestVariables
|
||||
? currentTabRequest.requestVariables
|
||||
: []
|
||||
|
||||
const effectiveAggregateEnvs: AggregateEnvironment[] = []
|
||||
|
||||
// Ensure pre-defined variables are prioritised over other environment variables with the same name
|
||||
|
|
@ -453,6 +466,18 @@ export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
|
|||
|
||||
const aggregateEnvKeys = effectiveAggregateEnvs.map(({ key }) => key)
|
||||
|
||||
requestVariables.forEach(({ key, value, active }) => {
|
||||
if (!aggregateEnvKeys.includes(key) && active) {
|
||||
effectiveAggregateEnvs.push({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
selectedEnv?.variables.forEach((variable) => {
|
||||
const { key, secret } = variable
|
||||
const currentValue =
|
||||
|
|
@ -495,8 +520,47 @@ export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
|
|||
)
|
||||
|
||||
export function getAggregateEnvs() {
|
||||
const restTabs = getService(RESTTabService)
|
||||
|
||||
const currentEnv = getCurrentEnvironment()
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: currentTab.document.request
|
||||
|
||||
const requestVariables = currentTabRequest?.requestVariables
|
||||
? currentTabRequest.requestVariables
|
||||
: []
|
||||
|
||||
return [
|
||||
...HOPP_SUPPORTED_PREDEFINED_VARIABLES.map(({ key, getValue }) => {
|
||||
return <AggregateEnvironment>{
|
||||
key,
|
||||
currentValue: getValue(),
|
||||
initialValue: getValue(),
|
||||
secret: false,
|
||||
sourceEnv: currentEnv.name,
|
||||
}
|
||||
}),
|
||||
|
||||
...requestVariables
|
||||
.map(({ key, value, active }) => {
|
||||
if (active) {
|
||||
return <AggregateEnvironment>{
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
.filter((v): v is AggregateEnvironment => v !== undefined),
|
||||
|
||||
...currentEnv.variables.map((x) => {
|
||||
let currentValue = ""
|
||||
if (!x.secret) {
|
||||
|
|
@ -528,9 +592,49 @@ export function getAggregateEnvs() {
|
|||
}
|
||||
|
||||
export function getAggregateEnvsWithCurrentValue() {
|
||||
const restTabs = getService(RESTTabService)
|
||||
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
const currentEnv = getCurrentEnvironment()
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: currentTab.document.request
|
||||
|
||||
const requestVariables = currentTabRequest?.requestVariables
|
||||
? currentTabRequest.requestVariables
|
||||
: []
|
||||
|
||||
return [
|
||||
...HOPP_SUPPORTED_PREDEFINED_VARIABLES.map(({ key, getValue }) => {
|
||||
return <AggregateEnvironment>{
|
||||
key,
|
||||
currentValue: getValue(),
|
||||
initialValue: getValue(),
|
||||
secret: false,
|
||||
sourceEnv: currentEnv.name,
|
||||
}
|
||||
}),
|
||||
|
||||
...requestVariables
|
||||
.map(({ key, value, active }) => {
|
||||
if (active) {
|
||||
return <AggregateEnvironment>{
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
.filter((v): v is AggregateEnvironment => v !== undefined),
|
||||
|
||||
...currentEnv.variables.map((x, index) => {
|
||||
let currentValue = x.currentValue
|
||||
if (x.secret) {
|
||||
|
|
@ -581,6 +685,22 @@ export const aggregateEnvsWithCurrentValue$: Observable<
|
|||
AggregateEnvironment[]
|
||||
> = combineLatest([currentEnvironment$, globalEnv$]).pipe(
|
||||
map(([selectedEnv, globalEnv]) => {
|
||||
const restTabs = getService(RESTTabService)
|
||||
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: currentTab.document.request
|
||||
|
||||
const requestVariables = currentTabRequest?.requestVariables
|
||||
? currentTabRequest.requestVariables
|
||||
: []
|
||||
|
||||
const results: AggregateEnvironment[] = []
|
||||
|
||||
// Ensure pre-defined variables are prioritised over other environment variables with the same name
|
||||
|
|
@ -594,6 +714,18 @@ export const aggregateEnvsWithCurrentValue$: Observable<
|
|||
})
|
||||
})
|
||||
|
||||
requestVariables.map(({ key, value, active }) => {
|
||||
if (active) {
|
||||
results.push({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
selectedEnv?.variables.map((x, index) => {
|
||||
let currentValue = x.currentValue
|
||||
if (x.secret) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Service } from "dioc"
|
||||
import { Container, Service } from "dioc"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { nextTick } from "vue"
|
||||
import { watch } from "vue"
|
||||
import { reactive, computed } from "vue"
|
||||
|
||||
/**
|
||||
|
|
@ -21,6 +23,12 @@ export type Variable = {
|
|||
export class CurrentValueService extends Service {
|
||||
public static readonly ID = "CURRENT_VALUE_SERVICE"
|
||||
|
||||
constructor(c: Container) {
|
||||
super(c)
|
||||
// Initialize the secret environments map
|
||||
this.watchCurrentEnvironments()
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of current value of environments.
|
||||
* The key is the ID of the environment.
|
||||
|
|
@ -159,4 +167,26 @@ export class CurrentValueService extends Service {
|
|||
})
|
||||
return environments
|
||||
})
|
||||
|
||||
/**
|
||||
* Watches the current environments for changes.
|
||||
* If a secret variable is removed or has an empty key, it will be deleted.
|
||||
*/
|
||||
protected watchCurrentEnvironments() {
|
||||
watch(
|
||||
() => this.environments,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
this.environments.forEach((vars, id) => {
|
||||
const filteredVars = vars.filter((v) => v.key !== "")
|
||||
|
||||
if (filteredVars.length === 0) {
|
||||
this.environments.delete(id)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,14 @@ describe("RequestInspectorService", () => {
|
|||
const req = ref({
|
||||
...getDefaultRESTRequest(),
|
||||
endpoint: "http://example.com/api/data",
|
||||
headers: [{ key: "Cookie", value: "some-cookie", active: true }],
|
||||
headers: [
|
||||
{
|
||||
key: "Cookie",
|
||||
value: "some-cookie",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const result = requestInspector.getInspections(req)
|
||||
|
|
@ -83,7 +90,14 @@ describe("RequestInspectorService", () => {
|
|||
const req = ref({
|
||||
...getDefaultRESTRequest(),
|
||||
endpoint: "http://example.com/api/data",
|
||||
headers: [{ key: "Cookie", value: "some-cookie", active: true }],
|
||||
headers: [
|
||||
{
|
||||
key: "Cookie",
|
||||
value: "some-cookie",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const result = requestInspector.getInspections(req)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { useStreamStatic } from "~/composables/stream"
|
|||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
import { transformInheritedCollectionVariablesToAggregateEnv } from "~/helpers/utils/inheritedCollectionVarTransformer"
|
||||
|
||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||
|
||||
|
|
@ -76,16 +77,17 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: null
|
||||
const collectionVariables =
|
||||
currentTab.document.type === "request" ||
|
||||
currentTab.document.type === "example-response"
|
||||
? transformInheritedCollectionVariablesToAggregateEnv(
|
||||
currentTab.document.inheritedProperties?.variables ?? []
|
||||
)
|
||||
: []
|
||||
|
||||
const environmentVariables = [
|
||||
...(currentTabRequest?.requestVariables ?? []),
|
||||
...this.aggregateEnvsWithValue.value,
|
||||
...collectionVariables,
|
||||
]
|
||||
|
||||
const envKeys = environmentVariables.map((e) => e.key)
|
||||
|
|
@ -192,32 +194,35 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||
const currentSelectedEnvironment = getCurrentEnvironment()
|
||||
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: null
|
||||
const collectionVariables =
|
||||
currentTab.document.type === "request" ||
|
||||
currentTab.document.type === "example-response"
|
||||
? transformInheritedCollectionVariablesToAggregateEnv(
|
||||
currentTab.document.inheritedProperties?.variables ?? [],
|
||||
false
|
||||
)
|
||||
: []
|
||||
|
||||
const environmentVariables =
|
||||
this.filterNonEmptyEnvironmentVariables([
|
||||
// Transform the request variables to environment variables
|
||||
...(currentTabRequest?.requestVariables ?? []).map((env) => ({
|
||||
key: env.key,
|
||||
currentValue: env.value,
|
||||
initialValue: env.value,
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
})),
|
||||
...this.aggregateEnvsWithValue.value,
|
||||
...collectionVariables,
|
||||
])
|
||||
|
||||
environmentVariables.forEach((env) => {
|
||||
let tooltipSourceEnvID = "Global"
|
||||
|
||||
if (env?.sourceEnv === "Global") {
|
||||
tooltipSourceEnvID = "Global"
|
||||
} else {
|
||||
tooltipSourceEnvID =
|
||||
env?.sourceEnv === "CollectionVariable"
|
||||
? env.sourceEnvID!
|
||||
: currentSelectedEnvironment.id
|
||||
}
|
||||
|
||||
const hasSecretEnv = this.secretEnvs.hasSecretValue(
|
||||
env.sourceEnv !== "Global"
|
||||
? currentSelectedEnvironment.id
|
||||
: "Global",
|
||||
tooltipSourceEnvID,
|
||||
env.key
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const DEFAULT_SETTINGS = getDefaultSettings()
|
|||
|
||||
export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
{
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: "Echo",
|
||||
requests: [
|
||||
{
|
||||
|
|
@ -51,13 +51,14 @@ export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
|||
],
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const GQL_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
{
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: "Echo",
|
||||
requests: [
|
||||
{
|
||||
|
|
@ -75,6 +76,7 @@ export const GQL_COLLECTIONS_MOCK: HoppCollection[] = [
|
|||
],
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
HoppRESTRequestResponse,
|
||||
HoppCollection,
|
||||
GlobalEnvironment,
|
||||
CollectionVariable,
|
||||
} from "@hoppscotch/data"
|
||||
import { entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
|
@ -313,6 +314,15 @@ const HoppInheritedPropertySchema = z
|
|||
inheritedHeader: z.union([HoppRESTHeaders, GQLHeader]),
|
||||
})
|
||||
),
|
||||
variables: z
|
||||
.array(
|
||||
z.object({
|
||||
parentID: z.string(),
|
||||
parentName: z.string(),
|
||||
inheritedVariables: z.array(CollectionVariable),
|
||||
})
|
||||
)
|
||||
.catch([]),
|
||||
})
|
||||
.strict()
|
||||
|
||||
|
|
@ -579,6 +589,7 @@ export const REST_TAB_STATE_SCHEMA = z
|
|||
response: entityReference(HoppRESTRequestResponse),
|
||||
saveContext: z.optional(HoppRESTSaveContextSchema),
|
||||
isDirty: z.boolean(),
|
||||
inheritedProperties: z.optional(HoppInheritedPropertySchema),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Service } from "dioc"
|
||||
import { reactive, computed } from "vue"
|
||||
import { Container, Service } from "dioc"
|
||||
import { nextTick } from "vue"
|
||||
import { reactive, computed, watch } from "vue"
|
||||
|
||||
/**
|
||||
* Defines a secret environment variable.
|
||||
|
|
@ -19,6 +20,12 @@ export type SecretVariable = {
|
|||
export class SecretEnvironmentService extends Service {
|
||||
public static readonly ID = "SECRET_ENVIRONMENT_SERVICE"
|
||||
|
||||
constructor(c: Container) {
|
||||
super(c)
|
||||
// Initialize the secret environments map
|
||||
this.watchSecretEnvironments()
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of secret environments.
|
||||
* The key is the ID of the secret environment.
|
||||
|
|
@ -137,4 +144,26 @@ export class SecretEnvironmentService extends Service {
|
|||
})
|
||||
return secretEnvironments
|
||||
})
|
||||
|
||||
/**
|
||||
* Watches the secret environments for changes.
|
||||
* If a secret variable is removed or has an empty key, it will be deleted.
|
||||
*/
|
||||
protected watchSecretEnvironments() {
|
||||
watch(
|
||||
() => this.secretEnvironments,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
this.secretEnvironments.forEach((secretVars, id) => {
|
||||
const filteredVars = secretVars.filter((v) => v.key !== "")
|
||||
|
||||
if (filteredVars.length === 0) {
|
||||
this.secretEnvironments.delete(id)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Ref, computed, effectScope, markRaw, ref, watch } from "vue"
|
|||
import { getI18n } from "~/modules/i18n"
|
||||
import MiniSearch from "minisearch"
|
||||
import {
|
||||
cascadeParentCollectionForHeaderAuth,
|
||||
cascadeParentCollectionForProperties,
|
||||
graphqlCollectionStore,
|
||||
restCollectionStore,
|
||||
} from "~/newstore/collections"
|
||||
|
|
@ -318,11 +318,6 @@ export class CollectionsSpotlightSearcherService
|
|||
|
||||
if (!req) return
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
folderPath.join("/"),
|
||||
"rest"
|
||||
)
|
||||
|
||||
this.restTab.createNewTab(
|
||||
{
|
||||
type: "request",
|
||||
|
|
@ -333,10 +328,10 @@ export class CollectionsSpotlightSearcherService
|
|||
folderPath: folderPath.join("/"),
|
||||
requestIndex: reqIndex,
|
||||
},
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
inheritedProperties: cascadeParentCollectionForProperties(
|
||||
folderPath.join("/"),
|
||||
"rest"
|
||||
),
|
||||
},
|
||||
true
|
||||
)
|
||||
|
|
@ -350,10 +345,6 @@ export class CollectionsSpotlightSearcherService
|
|||
|
||||
if (!req) return
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
folderPath.join("/"),
|
||||
"graphql"
|
||||
)
|
||||
this.gqlTab.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
|
|
@ -363,10 +354,10 @@ export class CollectionsSpotlightSearcherService
|
|||
cursorPosition: 0,
|
||||
request: req,
|
||||
isDirty: false,
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
inheritedProperties: cascadeParentCollectionForProperties(
|
||||
folderPath.join("/"),
|
||||
"graphql"
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,12 +217,12 @@ export class TeamsSpotlightSearcherService
|
|||
|
||||
if (!selectedRequest) return
|
||||
|
||||
const collectionID = result.id
|
||||
const collectionID = selectedRequest.collectionID
|
||||
|
||||
if (!collectionID) return
|
||||
|
||||
inheritedProperties =
|
||||
this.teamsSearch.cascadeParentCollectionForHeaderAuthForSearchResults(
|
||||
this.teamsSearch.cascadeParentCollectionForPropertiesForSearchResults(
|
||||
collectionID
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
HoppCollection,
|
||||
HoppCollectionVariable,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
|
|
@ -15,6 +16,7 @@ import {
|
|||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { HoppTestData, HoppTestResult } from "~/helpers/types/HoppTestResult"
|
||||
import { HoppTab } from "../tab"
|
||||
import { populateValuesInInheritedCollectionVars } from "~/helpers/utils/inheritedCollectionVarTransformer"
|
||||
|
||||
export type TestRunnerOptions = {
|
||||
stopRef: Ref<boolean>
|
||||
|
|
@ -59,6 +61,7 @@ export class TestRunnerService extends Service {
|
|||
headers: collection.headers,
|
||||
folders: [],
|
||||
requests: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
this.runTestCollection(tab, collection, options)
|
||||
|
|
@ -87,7 +90,9 @@ export class TestRunnerService extends Service {
|
|||
options: TestRunnerOptions,
|
||||
parentPath: number[] = [],
|
||||
parentHeaders?: HoppRESTHeaders,
|
||||
parentAuth?: HoppRESTRequest["auth"]
|
||||
parentAuth?: HoppRESTRequest["auth"],
|
||||
parentVariables: HoppCollection["variables"] = [],
|
||||
parentID?: string
|
||||
) {
|
||||
try {
|
||||
// Compute inherited auth and headers for this collection
|
||||
|
|
@ -101,6 +106,17 @@ export class TestRunnerService extends Service {
|
|||
...collection.headers,
|
||||
]
|
||||
|
||||
const inheritedVariables = [
|
||||
...(populateValuesInInheritedCollectionVars(
|
||||
parentVariables,
|
||||
parentID || collection._ref_id || collection.id
|
||||
) || []),
|
||||
...(populateValuesInInheritedCollectionVars(
|
||||
collection.variables,
|
||||
collection._ref_id || collection.id
|
||||
) || []),
|
||||
]
|
||||
|
||||
// Process folders progressively
|
||||
for (let i = 0; i < collection.folders.length; i++) {
|
||||
if (options.stopRef?.value) {
|
||||
|
|
@ -129,7 +145,9 @@ export class TestRunnerService extends Service {
|
|||
options,
|
||||
currentPath,
|
||||
inheritedHeaders,
|
||||
inheritedAuth
|
||||
inheritedAuth,
|
||||
inheritedVariables,
|
||||
collection._ref_id || collection.id
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +183,8 @@ export class TestRunnerService extends Service {
|
|||
finalRequest,
|
||||
collection,
|
||||
options,
|
||||
currentPath
|
||||
currentPath,
|
||||
inheritedVariables
|
||||
)
|
||||
|
||||
if (options.delay && options.delay > 0) {
|
||||
|
|
@ -255,7 +274,8 @@ export class TestRunnerService extends Service {
|
|||
request: TestRunnerRequest,
|
||||
collection: HoppCollection,
|
||||
options: TestRunnerOptions,
|
||||
path: number[]
|
||||
path: number[],
|
||||
inheritedVariables: HoppCollectionVariable[] = []
|
||||
) {
|
||||
if (options.stopRef?.value) {
|
||||
throw new Error("Test execution stopped")
|
||||
|
|
@ -270,7 +290,8 @@ export class TestRunnerService extends Service {
|
|||
|
||||
const results = await runTestRunnerRequest(
|
||||
request,
|
||||
options.keepVariableValues
|
||||
options.keepVariableValues,
|
||||
inheritedVariables
|
||||
)
|
||||
|
||||
if (options.stopRef?.value) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import V6_VERSION from "./v/6"
|
|||
import V7_VERSION from "./v/7"
|
||||
import V8_VERSION from "./v/8"
|
||||
import V9_VERSION from "./v/9"
|
||||
import V10_VERSION from "./v/10"
|
||||
|
||||
export { CollectionVariable } from "./v/10"
|
||||
|
||||
import { z } from "zod"
|
||||
import { translateToNewRequest } from "../rest"
|
||||
|
|
@ -20,7 +23,7 @@ const versionedObject = z.object({
|
|||
})
|
||||
|
||||
export const HoppCollection = createVersionedEntity({
|
||||
latestVersion: 9,
|
||||
latestVersion: 10,
|
||||
versionMap: {
|
||||
1: V1_VERSION,
|
||||
2: V2_VERSION,
|
||||
|
|
@ -31,6 +34,7 @@ export const HoppCollection = createVersionedEntity({
|
|||
7: V7_VERSION,
|
||||
8: V8_VERSION,
|
||||
9: V9_VERSION,
|
||||
10: V10_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
const versionCheck = versionedObject.safeParse(data)
|
||||
|
|
@ -46,7 +50,11 @@ export const HoppCollection = createVersionedEntity({
|
|||
|
||||
export type HoppCollection = InferredEntity<typeof HoppCollection>
|
||||
|
||||
export const CollectionSchemaVersion = 9
|
||||
export type HoppCollectionVariable = InferredEntity<
|
||||
typeof HoppCollection
|
||||
>["variables"][number]
|
||||
|
||||
export const CollectionSchemaVersion = 10
|
||||
|
||||
/**
|
||||
* Generates a Collection object. This ignores the version number object
|
||||
|
|
@ -74,6 +82,7 @@ export function translateToNewRESTCollection(x: any): HoppCollection {
|
|||
|
||||
const auth = x.auth ?? { authType: "inherit", authActive: true }
|
||||
const headers = x.headers ?? []
|
||||
const variables = x.variables ?? []
|
||||
|
||||
const obj = makeCollection({
|
||||
name,
|
||||
|
|
@ -81,6 +90,7 @@ export function translateToNewRESTCollection(x: any): HoppCollection {
|
|||
requests,
|
||||
auth,
|
||||
headers,
|
||||
variables,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
|
@ -102,6 +112,7 @@ export function translateToNewGQLCollection(x: any): HoppCollection {
|
|||
|
||||
const auth = x.auth ?? { authType: "inherit", authActive: true }
|
||||
const headers = x.headers ?? []
|
||||
const variables = x.variables ?? []
|
||||
|
||||
const obj = makeCollection({
|
||||
name,
|
||||
|
|
@ -109,6 +120,7 @@ export function translateToNewGQLCollection(x: any): HoppCollection {
|
|||
requests,
|
||||
auth,
|
||||
headers,
|
||||
variables,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
|
|
|||
54
packages/hoppscotch-data/src/collection/v/10.ts
Normal file
54
packages/hoppscotch-data/src/collection/v/10.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { defineVersion, entityRefUptoVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import { HoppCollection } from ".."
|
||||
import { v9_baseCollectionSchema } from "./9"
|
||||
|
||||
export const CollectionVariable = z.object({
|
||||
key: z.string(),
|
||||
initialValue: z.string(),
|
||||
currentValue: z.string(),
|
||||
secret: z.boolean(),
|
||||
})
|
||||
|
||||
export type CollectionVariable = z.infer<typeof CollectionVariable>
|
||||
|
||||
export const v10_baseCollectionSchema = v9_baseCollectionSchema.extend({
|
||||
v: z.literal(10),
|
||||
variables: z.array(CollectionVariable),
|
||||
})
|
||||
|
||||
type Input = z.input<typeof v10_baseCollectionSchema> & {
|
||||
folders: Input[]
|
||||
}
|
||||
|
||||
type Output = z.output<typeof v10_baseCollectionSchema> & {
|
||||
folders: Output[]
|
||||
}
|
||||
|
||||
export const V10_SCHEMA = v10_baseCollectionSchema.extend({
|
||||
folders: z.lazy(() => z.array(entityRefUptoVersion(HoppCollection, 10))),
|
||||
}) as z.ZodType<Output, z.ZodTypeDef, Input>
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V10_SCHEMA,
|
||||
up(old: z.infer<typeof V10_SCHEMA>) {
|
||||
const result: z.infer<typeof V10_SCHEMA> = {
|
||||
...old,
|
||||
v: 10 as const,
|
||||
variables: [],
|
||||
folders: old.folders.map((folder) => {
|
||||
const result = HoppCollection.safeParseUpToVersion(folder, 10)
|
||||
|
||||
if (result.type !== "ok") {
|
||||
throw new Error("Failed to migrate child collections")
|
||||
}
|
||||
|
||||
return result.value
|
||||
}),
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
|
@ -2,7 +2,6 @@ import { z } from "zod"
|
|||
import { defineVersion } from "verzod"
|
||||
import { V1_SCHEMA } from "./1"
|
||||
|
||||
// add initialValue and currentValue to the schema and delete value and add it to initialValue and currentValue
|
||||
export const V2_SCHEMA = V1_SCHEMA.extend({
|
||||
v: z.literal(2),
|
||||
variables: z.array(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
mutation CreateGQLChildUserCollection(
|
||||
$title: String!
|
||||
$parentUserCollectionID: ID!
|
||||
$data: String
|
||||
) {
|
||||
createGQLChildUserCollection(
|
||||
title: $title
|
||||
parentUserCollectionID: $parentUserCollectionID
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mutation CreateGQLRootUserCollection($title: String!) {
|
||||
createGQLRootUserCollection(title: $title) {
|
||||
mutation CreateGQLRootUserCollection($title: String!, $data: String) {
|
||||
createGQLRootUserCollection(title: $title, data: $data) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
mutation CreateRESTChildUserCollection(
|
||||
$title: String!
|
||||
$parentUserCollectionID: ID!
|
||||
$data: String
|
||||
) {
|
||||
createRESTChildUserCollection(
|
||||
title: $title
|
||||
parentUserCollectionID: $parentUserCollectionID
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mutation CreateRESTRootUserCollection($title: String!) {
|
||||
createRESTRootUserCollection(title: $title) {
|
||||
mutation CreateRESTRootUserCollection($title: String!, $data: String) {
|
||||
createRESTRootUserCollection(title: $title, data: $data) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
mutation UpdateUserCollection(
|
||||
$userCollectionID: ID!
|
||||
$newTitle: String
|
||||
$data: String
|
||||
) {
|
||||
updateUserCollection(
|
||||
userCollectionID: $userCollectionID
|
||||
newTitle: $newTitle
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +53,9 @@ import {
|
|||
UpdateRestUserRequestDocument,
|
||||
UpdateRestUserRequestMutation,
|
||||
UpdateRestUserRequestMutationVariables,
|
||||
UpdateUserCollectionDocument,
|
||||
UpdateUserCollectionMutation,
|
||||
UpdateUserCollectionMutationVariables,
|
||||
UpdateUserCollectionOrderDocument,
|
||||
UpdateUserCollectionOrderMutation,
|
||||
UpdateUserCollectionOrderMutationVariables,
|
||||
|
|
@ -68,22 +71,24 @@ import {
|
|||
UserRequestUpdatedDocument,
|
||||
} from "../../api/generated/graphql"
|
||||
|
||||
export const createRESTRootUserCollection = (title: string) =>
|
||||
export const createRESTRootUserCollection = (title: string, data?: string) =>
|
||||
runMutation<
|
||||
CreateRestRootUserCollectionMutation,
|
||||
CreateRestRootUserCollectionMutationVariables,
|
||||
""
|
||||
>(CreateRestRootUserCollectionDocument, {
|
||||
title,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const createGQLRootUserCollection = (title: string) =>
|
||||
export const createGQLRootUserCollection = (title: string, data?: string) =>
|
||||
runMutation<
|
||||
CreateGqlRootUserCollectionMutation,
|
||||
CreateGqlRootUserCollectionMutationVariables,
|
||||
""
|
||||
>(CreateGqlRootUserCollectionDocument, {
|
||||
title,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const createRESTUserRequest = (
|
||||
|
|
@ -118,7 +123,8 @@ export const createGQLUserRequest = (
|
|||
|
||||
export const createRESTChildUserCollection = (
|
||||
title: string,
|
||||
parentUserCollectionID: string
|
||||
parentUserCollectionID: string,
|
||||
data?: string
|
||||
) =>
|
||||
runMutation<
|
||||
CreateRestChildUserCollectionMutation,
|
||||
|
|
@ -127,11 +133,13 @@ export const createRESTChildUserCollection = (
|
|||
>(CreateRestChildUserCollectionDocument, {
|
||||
title,
|
||||
parentUserCollectionID,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const createGQLChildUserCollection = (
|
||||
title: string,
|
||||
parentUserCollectionID: string
|
||||
parentUserCollectionID: string,
|
||||
data?: string
|
||||
) =>
|
||||
runMutation<
|
||||
CreateGqlChildUserCollectionMutation,
|
||||
|
|
@ -140,6 +148,7 @@ export const createGQLChildUserCollection = (
|
|||
>(CreateGqlChildUserCollectionDocument, {
|
||||
title,
|
||||
parentUserCollectionID,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const deleteUserCollection = (userCollectionID: string) =>
|
||||
|
|
@ -161,6 +170,17 @@ export const renameUserCollection = (
|
|||
""
|
||||
>(RenameUserCollectionDocument, { userCollectionID, newTitle })()
|
||||
|
||||
export const updateUserCollection = (
|
||||
userCollectionID: string,
|
||||
newTitle?: string,
|
||||
data?: string
|
||||
) =>
|
||||
runMutation<
|
||||
UpdateUserCollectionMutation,
|
||||
UpdateUserCollectionMutationVariables,
|
||||
""
|
||||
>(UpdateUserCollectionDocument, { userCollectionID, newTitle, data })()
|
||||
|
||||
export const moveUserCollection = (
|
||||
sourceCollectionID: string,
|
||||
destinationCollectionID?: string
|
||||
|
|
|
|||
|
|
@ -130,11 +130,12 @@ function exportedCollectionToHoppCollection(
|
|||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: restCollection.id,
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: restCollection.name,
|
||||
folders: restCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
|
|
@ -182,6 +183,7 @@ function exportedCollectionToHoppCollection(
|
|||
}),
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
}
|
||||
} else {
|
||||
const gqlCollection = collection as ExportedUserCollectionGQL
|
||||
|
|
@ -192,11 +194,12 @@ function exportedCollectionToHoppCollection(
|
|||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: gqlCollection.id,
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: gqlCollection.name,
|
||||
folders: gqlCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
|
|
@ -224,6 +227,7 @@ function exportedCollectionToHoppCollection(
|
|||
}),
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -366,6 +370,7 @@ function setupUserCollectionCreatedSubscription() {
|
|||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
|
|
@ -374,17 +379,19 @@ function setupUserCollectionCreatedSubscription() {
|
|||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 9,
|
||||
v: 10,
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
})
|
||||
: addRESTCollection({
|
||||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 9,
|
||||
v: 10,
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
})
|
||||
|
||||
const localIndex = collectionStore.value.state.length - 1
|
||||
|
|
@ -587,12 +594,13 @@ function setupUserCollectionDuplicatedSubscription() {
|
|||
)
|
||||
|
||||
// Incoming data transformed to the respective internal representations
|
||||
const { auth, headers } =
|
||||
const { auth, headers, variables } =
|
||||
data && data != "null"
|
||||
? JSON.parse(data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
const folders = transformDuplicatedCollections(childCollectionsJSONStr)
|
||||
|
|
@ -607,9 +615,10 @@ function setupUserCollectionDuplicatedSubscription() {
|
|||
name,
|
||||
folders,
|
||||
requests,
|
||||
v: 9,
|
||||
v: 10,
|
||||
auth,
|
||||
headers: addDescriptionField(headers),
|
||||
variables: variables ?? [],
|
||||
}
|
||||
|
||||
// only folders will have parent collection id
|
||||
|
|
@ -1023,10 +1032,14 @@ function transformDuplicatedCollections(
|
|||
requests: userRequests,
|
||||
title: name,
|
||||
}) => {
|
||||
const { auth, headers } =
|
||||
const { auth, headers, variables } =
|
||||
data && data !== "null"
|
||||
? JSON.parse(data)
|
||||
: { auth: { authType: "inherit", authActive: true }, headers: [] }
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
const folders = transformDuplicatedCollections(childCollectionsJSONStr)
|
||||
|
||||
|
|
@ -1037,9 +1050,10 @@ function transformDuplicatedCollections(
|
|||
name,
|
||||
folders,
|
||||
requests,
|
||||
v: 9,
|
||||
v: 10,
|
||||
auth,
|
||||
headers: addDescriptionField(headers),
|
||||
variables: variables ?? [],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import {
|
|||
settingsStore,
|
||||
} from "@hoppscotch/common/newstore/settings"
|
||||
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
generateUniqueRefId,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import { getSyncInitFunction } from "../../lib/sync"
|
||||
|
||||
|
|
@ -25,6 +29,7 @@ import {
|
|||
moveUserCollection,
|
||||
moveUserRequest,
|
||||
renameUserCollection,
|
||||
updateUserCollection,
|
||||
updateUserCollectionOrder,
|
||||
} from "./collections.api"
|
||||
|
||||
|
|
@ -47,27 +52,84 @@ const recursivelySyncCollections = async (
|
|||
|
||||
// if parentUserCollectionID does not exist, create the collection as a root collection
|
||||
if (!parentUserCollectionID) {
|
||||
const res = await createRESTRootUserCollection(collection.name)
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
const res = await createRESTRootUserCollection(
|
||||
collection.name,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
parentCollectionID = res.right.createRESTRootUserCollection.id
|
||||
|
||||
const returnedData = res.right.createRESTRootUserCollection.data
|
||||
? JSON.parse(res.right.createRESTRootUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = parentCollectionID
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
collection.variables = returnedData.variables
|
||||
removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath)
|
||||
} else {
|
||||
parentCollectionID = undefined
|
||||
}
|
||||
} else {
|
||||
// if parentUserCollectionID exists, create the collection as a child collection
|
||||
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
const res = await createRESTChildUserCollection(
|
||||
collection.name,
|
||||
parentUserCollectionID
|
||||
parentUserCollectionID,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
const childCollectionId = res.right.createRESTChildUserCollection.id
|
||||
|
||||
const returnedData = res.right.createRESTChildUserCollection.data
|
||||
? JSON.parse(res.right.createRESTChildUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
collection.id = childCollectionId
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
parentCollectionID = childCollectionId
|
||||
collection.variables = returnedData.variables
|
||||
|
||||
removeDuplicateRESTCollectionOrFolder(
|
||||
childCollectionId,
|
||||
|
|
@ -155,8 +217,15 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
[collectionIndex]
|
||||
)?.id
|
||||
|
||||
if (collectionID && collection.name) {
|
||||
renameUserCollection(collectionID, collection.name)
|
||||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
variables: collection.variables,
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
updateUserCollection(collectionID, collection.name, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async addFolder({ name, path }) {
|
||||
|
|
@ -195,9 +264,15 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
)?.id
|
||||
|
||||
const folderName = folder.name
|
||||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
variables: folder.variables,
|
||||
_ref_id: folder._ref_id,
|
||||
}
|
||||
|
||||
if (folderID && folderName) {
|
||||
renameUserCollection(folderID, folderName)
|
||||
if (folderID) {
|
||||
updateUserCollection(folderID, folderName, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async removeFolder({ folderID }) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import {
|
|||
settingsStore,
|
||||
} from "@hoppscotch/common/newstore/settings"
|
||||
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
generateUniqueRefId,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import { getSyncInitFunction } from "../../lib/sync"
|
||||
|
||||
|
|
@ -22,6 +26,7 @@ import {
|
|||
deleteUserRequest,
|
||||
editGQLUserRequest,
|
||||
renameUserCollection,
|
||||
updateUserCollection,
|
||||
} from "./collections.api"
|
||||
|
||||
import * as E from "fp-ts/Either"
|
||||
|
|
@ -44,12 +49,41 @@ const recursivelySyncCollections = async (
|
|||
|
||||
// if parentUserCollectionID does not exist, create the collection as a root collection
|
||||
if (!parentUserCollectionID) {
|
||||
const res = await createGQLRootUserCollection(collection.name)
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id ?? generateUniqueRefId("coll"),
|
||||
}
|
||||
const res = await createGQLRootUserCollection(
|
||||
collection.name,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
parentCollectionID = res.right.createGQLRootUserCollection.id
|
||||
|
||||
const returnedData = res.right.createGQLRootUserCollection.data
|
||||
? JSON.parse(res.right.createGQLRootUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = parentCollectionID
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
collection.variables = returnedData.variables
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
parentCollectionID,
|
||||
collectionPath
|
||||
|
|
@ -59,15 +93,44 @@ const recursivelySyncCollections = async (
|
|||
}
|
||||
} else {
|
||||
// if parentUserCollectionID exists, create the collection as a child collection
|
||||
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id ?? generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
const res = await createGQLChildUserCollection(
|
||||
collection.name,
|
||||
parentUserCollectionID
|
||||
parentUserCollectionID,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
const childCollectionId = res.right.createGQLChildUserCollection.id
|
||||
|
||||
const returnedData = res.right.createGQLChildUserCollection.data
|
||||
? JSON.parse(res.right.createGQLChildUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = childCollectionId
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
parentCollectionID = childCollectionId
|
||||
collection.variables = returnedData.variables
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
childCollectionId,
|
||||
|
|
@ -158,8 +221,15 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
[collectionIndex]
|
||||
)?.id
|
||||
|
||||
if (collectionID && collection.name) {
|
||||
renameUserCollection(collectionID, collection.name)
|
||||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
variables: collection.variables,
|
||||
_ref_id: collection._ref_id ?? generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
updateUserCollection(collectionID, collection.name, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async addFolder({ name, path }) {
|
||||
|
|
@ -197,8 +267,15 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
path.split("/").map((index) => parseInt(index))
|
||||
)?.id
|
||||
|
||||
if (folderBackendId && folder.name) {
|
||||
renameUserCollection(folderBackendId, folder.name)
|
||||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
variables: folder.variables,
|
||||
_ref_id: folder._ref_id ?? generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
if (folderBackendId) {
|
||||
updateUserCollection(folderBackendId, folder.name, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async removeFolder({ folderID }) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import {
|
|||
settingsStore,
|
||||
} from "@hoppscotch/common/newstore/settings"
|
||||
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
generateUniqueRefId,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import { getSyncInitFunction } from "@lib/sync"
|
||||
|
||||
|
|
@ -52,6 +56,8 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
const res = await createGQLRootUserCollection(
|
||||
collection.name,
|
||||
|
|
@ -69,11 +75,15 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: collection._ref_id ?? generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = parentCollectionID
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
collection.variables = returnedData.variables
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
parentCollectionID,
|
||||
|
|
@ -91,6 +101,8 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
const res = await createGQLChildUserCollection(
|
||||
|
|
@ -110,12 +122,16 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: collection._ref_id ?? generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = childCollectionId
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
parentCollectionID = childCollectionId
|
||||
collection.variables = returnedData.variables
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
childCollectionId,
|
||||
|
|
@ -209,6 +225,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
variables: collection.variables,
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
|
|
@ -253,6 +271,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
variables: folder.variables,
|
||||
_ref_id: folder._ref_id,
|
||||
}
|
||||
|
||||
if (folderBackendId) {
|
||||
|
|
|
|||
|
|
@ -135,12 +135,13 @@ function exportedCollectionToHoppCollection(
|
|||
auth: { authType: "inherit", authActive: false },
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: restCollection.id,
|
||||
_ref_id: data._ref_id ?? generateUniqueRefId("coll"),
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: restCollection.name,
|
||||
folders: restCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
|
|
@ -188,6 +189,7 @@ function exportedCollectionToHoppCollection(
|
|||
}),
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
}
|
||||
} else {
|
||||
const gqlCollection = collection as ExportedUserCollectionGQL
|
||||
|
|
@ -199,12 +201,13 @@ function exportedCollectionToHoppCollection(
|
|||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: gqlCollection.id,
|
||||
_ref_id: data._ref_id ?? generateUniqueRefId("coll"),
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: gqlCollection.name,
|
||||
folders: gqlCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
|
|
@ -232,6 +235,7 @@ function exportedCollectionToHoppCollection(
|
|||
}),
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -375,6 +379,7 @@ function setupUserCollectionCreatedSubscription() {
|
|||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
|
|
@ -383,19 +388,21 @@ function setupUserCollectionCreatedSubscription() {
|
|||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 9,
|
||||
v: 10,
|
||||
_ref_id: data._ref_id,
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
})
|
||||
: addRESTCollection({
|
||||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 9,
|
||||
v: 10,
|
||||
_ref_id: data._ref_id,
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
})
|
||||
|
||||
const localIndex = collectionStore.value.state.length - 1
|
||||
|
|
@ -598,12 +605,13 @@ function setupUserCollectionDuplicatedSubscription() {
|
|||
)
|
||||
|
||||
// Incoming data transformed to the respective internal representations
|
||||
const { auth, headers } =
|
||||
const { auth, headers, variables } =
|
||||
data && data != "null"
|
||||
? JSON.parse(data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
// Duplicated collection will have a unique ref id
|
||||
const _ref_id = generateUniqueRefId("coll")
|
||||
|
|
@ -620,10 +628,11 @@ function setupUserCollectionDuplicatedSubscription() {
|
|||
name,
|
||||
folders,
|
||||
requests,
|
||||
v: 9,
|
||||
v: 10,
|
||||
_ref_id,
|
||||
auth,
|
||||
headers: addDescriptionField(headers),
|
||||
variables: variables ?? [],
|
||||
}
|
||||
|
||||
// only folders will have parent collection id
|
||||
|
|
@ -1037,10 +1046,14 @@ function transformDuplicatedCollections(
|
|||
requests: userRequests,
|
||||
title: name,
|
||||
}) => {
|
||||
const { auth, headers } =
|
||||
const { auth, headers, variables } =
|
||||
data && data !== "null"
|
||||
? JSON.parse(data)
|
||||
: { auth: { authType: "inherit", authActive: true }, headers: [] }
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
const _ref_id = generateUniqueRefId("coll")
|
||||
|
||||
|
|
@ -1054,9 +1067,10 @@ function transformDuplicatedCollections(
|
|||
folders,
|
||||
requests,
|
||||
_ref_id,
|
||||
v: 9,
|
||||
v: 10,
|
||||
auth,
|
||||
headers: addDescriptionField(headers),
|
||||
variables: variables ?? [],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
const res = await createRESTRootUserCollection(
|
||||
|
|
@ -74,6 +75,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +83,7 @@ const recursivelySyncCollections = async (
|
|||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
collection.variables = returnedData.variables
|
||||
removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath)
|
||||
} else {
|
||||
parentCollectionID = undefined
|
||||
|
|
@ -93,6 +96,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +117,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +126,7 @@ const recursivelySyncCollections = async (
|
|||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
parentCollectionID = childCollectionId
|
||||
collection.variables = returnedData.variables
|
||||
|
||||
removeDuplicateRESTCollectionOrFolder(
|
||||
childCollectionId,
|
||||
|
|
@ -211,6 +217,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
variables: collection.variables,
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
|
|
@ -256,6 +264,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
variables: folder.variables,
|
||||
_ref_id: folder._ref_id,
|
||||
}
|
||||
if (folderID) {
|
||||
updateUserCollection(folderID, folderName, JSON.stringify(data))
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import {
|
|||
settingsStore,
|
||||
} from "@hoppscotch/common/newstore/settings"
|
||||
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
generateUniqueRefId,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import { getSyncInitFunction } from "@lib/sync"
|
||||
|
||||
|
|
@ -52,6 +56,8 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
const res = await createGQLRootUserCollection(
|
||||
collection.name,
|
||||
|
|
@ -69,11 +75,15 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = parentCollectionID
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
collection.variables = returnedData.variables
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
parentCollectionID,
|
||||
|
|
@ -91,6 +101,8 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
const res = await createGQLChildUserCollection(
|
||||
|
|
@ -110,12 +122,16 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
collection.id = childCollectionId
|
||||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
parentCollectionID = childCollectionId
|
||||
collection.variables = returnedData.variables
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
childCollectionId,
|
||||
|
|
@ -209,6 +225,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
variables: collection.variables,
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
|
|
@ -253,6 +271,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
variables: folder.variables,
|
||||
_ref_id: folder._ref_id,
|
||||
}
|
||||
|
||||
if (folderBackendId) {
|
||||
|
|
|
|||
|
|
@ -135,12 +135,13 @@ function exportedCollectionToHoppCollection(
|
|||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: restCollection.id,
|
||||
_ref_id: data._ref_id ?? generateUniqueRefId("coll"),
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: restCollection.name,
|
||||
folders: restCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
|
|
@ -188,6 +189,7 @@ function exportedCollectionToHoppCollection(
|
|||
}),
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
}
|
||||
} else {
|
||||
const gqlCollection = collection as ExportedUserCollectionGQL
|
||||
|
|
@ -199,12 +201,13 @@ function exportedCollectionToHoppCollection(
|
|||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: gqlCollection.id,
|
||||
_ref_id: data._ref_id ?? generateUniqueRefId("coll"),
|
||||
v: 9,
|
||||
v: 10,
|
||||
name: gqlCollection.name,
|
||||
folders: gqlCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
|
|
@ -232,6 +235,7 @@ function exportedCollectionToHoppCollection(
|
|||
}),
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -375,6 +379,7 @@ function setupUserCollectionCreatedSubscription() {
|
|||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
variables: [],
|
||||
}
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
|
|
@ -383,19 +388,21 @@ function setupUserCollectionCreatedSubscription() {
|
|||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 9,
|
||||
v: 10,
|
||||
_ref_id: data._ref_id,
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
})
|
||||
: addRESTCollection({
|
||||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 9,
|
||||
v: 10,
|
||||
_ref_id: data._ref_id,
|
||||
auth: data.auth,
|
||||
headers: addDescriptionField(data.headers),
|
||||
variables: data.variables ?? [],
|
||||
})
|
||||
|
||||
const localIndex = collectionStore.value.state.length - 1
|
||||
|
|
@ -598,12 +605,13 @@ function setupUserCollectionDuplicatedSubscription() {
|
|||
)
|
||||
|
||||
// Incoming data transformed to the respective internal representations
|
||||
const { auth, headers } =
|
||||
const { auth, headers, variables } =
|
||||
data && data != "null"
|
||||
? JSON.parse(data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
// Duplicated collection will have a unique ref id
|
||||
const _ref_id = generateUniqueRefId("coll")
|
||||
|
|
@ -620,10 +628,11 @@ function setupUserCollectionDuplicatedSubscription() {
|
|||
name,
|
||||
folders,
|
||||
requests,
|
||||
v: 9,
|
||||
v: 10,
|
||||
_ref_id,
|
||||
auth,
|
||||
headers: addDescriptionField(headers),
|
||||
variables: variables ?? [],
|
||||
}
|
||||
|
||||
// only folders will have parent collection id
|
||||
|
|
@ -1037,10 +1046,14 @@ function transformDuplicatedCollections(
|
|||
requests: userRequests,
|
||||
title: name,
|
||||
}) => {
|
||||
const { auth, headers } =
|
||||
const { auth, headers, variables } =
|
||||
data && data !== "null"
|
||||
? JSON.parse(data)
|
||||
: { auth: { authType: "inherit", authActive: true }, headers: [] }
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
variables: [],
|
||||
}
|
||||
|
||||
const _ref_id = generateUniqueRefId("coll")
|
||||
|
||||
|
|
@ -1054,9 +1067,10 @@ function transformDuplicatedCollections(
|
|||
folders,
|
||||
requests,
|
||||
_ref_id,
|
||||
v: 9,
|
||||
v: 10,
|
||||
auth,
|
||||
headers: addDescriptionField(headers),
|
||||
variables: variables ?? [],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
const res = await createRESTRootUserCollection(
|
||||
|
|
@ -74,6 +75,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +83,11 @@ const recursivelySyncCollections = async (
|
|||
collection._ref_id = returnedData._ref_id ?? generateUniqueRefId("coll")
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath)
|
||||
collection.variables = returnedData.variables
|
||||
removeDuplicateRESTCollectionOrFolder(
|
||||
parentCollectionID,
|
||||
`${collectionPath}`
|
||||
)
|
||||
} else {
|
||||
parentCollectionID = undefined
|
||||
}
|
||||
|
|
@ -93,6 +99,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
variables: collection.variables ?? [],
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +120,7 @@ const recursivelySyncCollections = async (
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
_ref_id: generateUniqueRefId("coll"),
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +129,7 @@ const recursivelySyncCollections = async (
|
|||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
parentCollectionID = childCollectionId
|
||||
collection.variables = returnedData.variables
|
||||
|
||||
removeDuplicateRESTCollectionOrFolder(
|
||||
childCollectionId,
|
||||
|
|
@ -211,6 +220,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
variables: collection.variables,
|
||||
_ref_id: collection._ref_id,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
|
|
@ -256,6 +267,8 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
|||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
variables: folder.variables,
|
||||
_ref_id: folder._ref_id,
|
||||
}
|
||||
if (folderID) {
|
||||
updateUserCollection(folderID, folderName, JSON.stringify(data))
|
||||
|
|
|
|||
Loading…
Reference in a new issue