Skip to content

Commit 2d2e653

Browse files
nivedinCopilotjamesgeorge007
authored
feat: collection variables (hoppscotch#5325)
Co-authored-by: Copilot <[email protected]> Co-authored-by: jamesgeorge007 <[email protected]>
1 parent ad974db commit 2d2e653

File tree

77 files changed

+2688
-765
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+2688
-765
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{
2+
"id": "cmeicx49r00xylb1jxektmknk",
3+
"_ref_id": "coll_meicx3z7_a1cb5e72-cd1b-414b-adc2-7d601ca0936d",
4+
"v": 10,
5+
"name": "coll-with-variables",
6+
"folders": [
7+
{
8+
"id": "cmeicy6fu00xzlb1jqmmqbjdm",
9+
"_ref_id": "coll_meie14lh_818ea8a2-9839-4a1c-8cce-cc7565b5f594",
10+
"v": 10,
11+
"name": "folder-1",
12+
"folders": [],
13+
"requests": [
14+
{
15+
"v": "15",
16+
"id": "cmeicyhnn00y1lb1j8d80g7ys",
17+
"name": "request-1",
18+
"method": "GET",
19+
"endpoint": "https://echo.hoppscotch.io",
20+
"params": [
21+
{
22+
"key": "collection-var-1",
23+
"value": "<<collection-variable-1>>",
24+
"active": true,
25+
"description": ""
26+
},
27+
{
28+
"key": "collection-var-2",
29+
"value": "<<collection-variable-2>>",
30+
"active": true,
31+
"description": ""
32+
},
33+
{
34+
"key": "folder-var-1",
35+
"value": "<<folder-variable-1>>",
36+
"active": true,
37+
"description": ""
38+
},
39+
{
40+
"key": "folder-var-2",
41+
"value": "<<folder-variable-2>>",
42+
"active": true,
43+
"description": ""
44+
}
45+
],
46+
"headers": [],
47+
"preRequestScript": "",
48+
"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});",
49+
"auth": {
50+
"authType": "inherit",
51+
"authActive": true
52+
},
53+
"body": {
54+
"contentType": null,
55+
"body": null
56+
},
57+
"requestVariables": [],
58+
"responses": {}
59+
}
60+
],
61+
"auth": {
62+
"authType": "inherit",
63+
"authActive": true
64+
},
65+
"headers": [],
66+
"variables": [
67+
{
68+
"key": "folder-variable-1",
69+
"secret": false,
70+
"currentValue": "",
71+
"initialValue": "folder-variable-1-initial-value"
72+
},
73+
{
74+
"key": "folder-variable-2",
75+
"secret": false,
76+
"currentValue": "",
77+
"initialValue": "folder-variable-2-initial-value"
78+
}
79+
]
80+
}
81+
],
82+
"requests": [],
83+
"auth": {
84+
"authType": "none",
85+
"authActive": true
86+
},
87+
"headers": [],
88+
"variables": [
89+
{
90+
"key": "collection-variable-1",
91+
"secret": false,
92+
"currentValue": "",
93+
"initialValue": "collection-var-1-initial-value"
94+
},
95+
{
96+
"key": "collection-variable-2",
97+
"secret": false,
98+
"currentValue": "",
99+
"initialValue": "collection-var-2-initial-value"
100+
}
101+
]
102+
}

packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"v": 6,
33
"id": "cm9wmuzj46s3imbs891pdamv4",
4-
"name": "Multiple child collections with authorization & headers set at each level",
4+
"name": "Multiple child collections with authorization, headers and variables set at each level",
55
"folders": [
66
{
77
"v": 6,
@@ -688,5 +688,13 @@
688688
"description": ""
689689
}
690690
],
691+
"variables": [
692+
{
693+
"key": "collection-variable",
694+
"currentValue": "collection-variable-value",
695+
"initialValue": "collection-variable-value",
696+
"secret": false
697+
}
698+
],
691699
"_ref_id": "coll_m9wn4jl9_aa8a3bc2-a96f-4cac-86f3-2df4bb355cc8"
692700
}

packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/valid-mixed-versions-coll.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,6 @@
8686
"authActive": true
8787
},
8888
"headers": [],
89+
"variables": [],
8990
"_ref_id": "coll_mbhuxoci_a8fc710e-04c1-489c-a183-7f16946a7225"
9091
}

packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts

Lines changed: 183 additions & 41 deletions
Large diffs are not rendered by default.

packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,47 @@ import {
55
transformWorkspaceEnvironment,
66
} from "../../utils/workspace-access";
77
import {
8-
TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK,
9-
TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK,
8+
TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK,
9+
TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK,
1010
TRANSFORMED_ENVIRONMENT_V0_FORMAT_MOCK,
1111
TRANSFORMED_ENVIRONMENT_V1_FORMAT_MOCK,
1212
TRANSFORMED_ENVIRONMENT_V2_FORMAT_MOCK,
1313
TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK,
14-
WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK,
15-
WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK,
14+
WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK,
15+
WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK,
1616
WORKSPACE_ENVIRONMENT_V0_FORMAT_MOCK,
1717
WORKSPACE_ENVIRONMENT_V1_FORMAT_MOCK,
1818
WORKSPACE_ENVIRONMENT_V2_FORMAT_MOCK,
19-
WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK,
19+
WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK,
2020
} from "./fixtures/workspace-access.mock";
2121

2222
describe("workspace-access", () => {
2323
describe("transformWorkspaceCollection", () => {
2424
test("Successfully transforms collection data with deeply nested collections and authorization/headers set at each level to the `HoppCollection` format", () => {
2525
expect(
2626
transformWorkspaceCollections(
27-
WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK
27+
WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK
2828
)
29-
).toEqual(TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK);
29+
).toEqual(
30+
TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK
31+
);
3032
});
3133

3234
test("Successfully transforms collection data with multiple child collections and authorization/headers set at each level to the `HoppCollection` format", () => {
3335
expect(
3436
transformWorkspaceCollections(
35-
WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK
37+
WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_VARIABLES_MOCK
3638
)
3739
).toEqual(TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK);
3840
});
3941

4042
test("Adds the default value for `auth` & `header` fields while transforming collections without authorization/headers set at certain levels", () => {
4143
expect(
4244
transformWorkspaceCollections(
43-
WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK
45+
WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK
4446
)
4547
).toEqual(
46-
TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK
48+
TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_VARIABLES_AT_CERTAIN_LEVELS_MOCK
4749
);
4850
});
4951
});

packages/hoppscotch-cli/src/types/request.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { Environment, HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
1+
import {
2+
Environment,
3+
HoppCollection,
4+
HoppCollectionVariable,
5+
HoppRESTRequest,
6+
} from "@hoppscotch/data";
27
import { z } from "zod";
38

49
import { TestReport } from "../interfaces/response";
@@ -37,5 +42,6 @@ export type ProcessRequestParams = {
3742
envs: HoppEnvs;
3843
path: string;
3944
delay: number;
40-
legacySandbox: boolean;
45+
legacySandbox?: boolean;
46+
collectionVariables?: HoppCollectionVariable[];
4147
};

packages/hoppscotch-cli/src/utils/collections.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,18 @@ const processCollection = async (
115115
for (const request of collection.requests) {
116116
const _request = preProcessRequest(request as HoppRESTRequest, collection);
117117
const requestPath = `${path}/${_request.name}`;
118+
119+
const collectionVariables = collection.variables.filter(
120+
(variable) => variable.key && variable.key.trim() !== ""
121+
);
122+
118123
const processRequestParams: ProcessRequestParams = {
119124
path: requestPath,
120125
request: _request,
121126
envs,
122127
delay,
123128
legacySandbox,
129+
collectionVariables,
124130
};
125131

126132
// Request processing initiated message.
@@ -161,6 +167,20 @@ const processCollection = async (
161167
updatedFolder.headers.push(...filteredHeaders);
162168
}
163169

170+
if (updatedFolder.variables?.length) {
171+
// Filter out variable entries present in the parent collection under the same name
172+
// This ensures the folder variables take precedence over the collection variables
173+
const filteredVariables = collection.variables.filter(
174+
(collectionVariableEntries) => {
175+
return !updatedFolder.variables.some(
176+
(folderVariableEntries) =>
177+
folderVariableEntries.key === collectionVariableEntries.key
178+
);
179+
}
180+
);
181+
updatedFolder.variables.push(...filteredVariables);
182+
}
183+
164184
await processCollection(
165185
updatedFolder,
166186
`${path}/${updatedFolder.name}`,

packages/hoppscotch-cli/src/utils/getters.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
EnvironmentVariable,
3+
HoppCollectionVariable,
34
HoppRESTHeader,
45
HoppRESTParam,
56
HoppRESTRequestVariables,
@@ -269,11 +270,13 @@ export const getResourceContents = async (
269270
*
270271
* @param {HoppRESTRequestVariables} requestVariables - Incoming request variables.
271272
* @param {EnvironmentVariable[]} environmentVariables - Incoming environment variables.
273+
* @param {HoppCollectionVariable[]} collectionVariables - Optional collection variables to be included.
272274
* @returns {EnvironmentVariable[]} The resolved list of variables that conforms to the shape of environment variables.
273275
*/
274276
export const getResolvedVariables = (
275277
requestVariables: HoppRESTRequestVariables,
276-
environmentVariables: EnvironmentVariable[]
278+
environmentVariables: EnvironmentVariable[],
279+
collectionVariables: HoppCollectionVariable[] = []
277280
): EnvironmentVariable[] => {
278281
// Transforming request variables to the shape of environment variables
279282
const activeRequestVariables = requestVariables
@@ -287,11 +290,21 @@ export const getResolvedVariables = (
287290

288291
const requestVariableKeys = activeRequestVariables.map(({ key }) => key);
289292

290-
// Request variables have higher priority, hence filtering out environment variables with the same keys
291-
const filteredEnvironmentVariables = environmentVariables.filter(
293+
// Request variables have higher priority, hence filtering out collection variables with the same keys
294+
const filteredCollectionVariables = collectionVariables.filter(
292295
({ key }) => !requestVariableKeys.includes(key)
293296
);
294297

298+
const collectionVariableKeys = filteredCollectionVariables.map(
299+
({ key }) => key
300+
);
301+
302+
// Filtering out environment variables that have keys present in request or collection variables
303+
const filteredEnvironmentVariables = environmentVariables.filter(
304+
({ key }) =>
305+
![...requestVariableKeys, ...collectionVariableKeys].includes(key)
306+
);
307+
295308
// Setting currentValue to initialValue for environment variables
296309
// because the exported file might not have the currentValue field
297310
const processedEnvironmentVariables = filteredEnvironmentVariables.map(
@@ -304,5 +317,19 @@ export const getResolvedVariables = (
304317
})
305318
);
306319

307-
return [...activeRequestVariables, ...processedEnvironmentVariables];
320+
const processedCollectionVariables = filteredCollectionVariables.map(
321+
({ key, initialValue, currentValue, secret }) => ({
322+
key,
323+
initialValue,
324+
currentValue:
325+
currentValue && currentValue !== "" ? currentValue : initialValue,
326+
secret,
327+
})
328+
);
329+
330+
return [
331+
...activeRequestVariables,
332+
...processedCollectionVariables,
333+
...processedEnvironmentVariables,
334+
];
308335
};

packages/hoppscotch-cli/src/utils/pre-request.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
parseTemplateString,
88
parseTemplateStringE,
99
generateJWTToken,
10+
HoppCollectionVariable,
1011
} from "@hoppscotch/data";
1112
import { runPreRequestScript } from "@hoppscotch/js-sandbox/node";
1213
import * as A from "fp-ts/Array";
@@ -46,7 +47,8 @@ import { calculateHawkHeader } from "@hoppscotch/data";
4647
export const preRequestScriptRunner = (
4748
request: HoppRESTRequest,
4849
envs: HoppEnvs,
49-
legacySandbox: boolean
50+
legacySandbox: boolean,
51+
collectionVariables?: HoppCollectionVariable[]
5052
): TE.TaskEither<
5153
HoppCLIError,
5254
{ effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs }
@@ -67,7 +69,7 @@ export const preRequestScriptRunner = (
6769
),
6870
TE.chainW((env) =>
6971
TE.tryCatch(
70-
() => getEffectiveRESTRequest(request, env),
72+
() => getEffectiveRESTRequest(request, env, collectionVariables),
7173
(reason) => error({ code: "PRE_REQUEST_SCRIPT_ERROR", data: reason })
7274
)
7375
),
@@ -93,7 +95,8 @@ export const preRequestScriptRunner = (
9395
*/
9496
export async function getEffectiveRESTRequest(
9597
request: HoppRESTRequest,
96-
environment: Environment
98+
environment: Environment,
99+
collectionVariables?: HoppCollectionVariable[]
97100
): Promise<
98101
E.Either<
99102
HoppCLIError,
@@ -104,7 +107,8 @@ export async function getEffectiveRESTRequest(
104107

105108
const resolvedVariables = getResolvedVariables(
106109
request.requestVariables,
107-
envVariables
110+
envVariables,
111+
collectionVariables
108112
);
109113

110114
// Parsing final headers with applied ENVs.

packages/hoppscotch-cli/src/utils/request.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ export const processRequest =
202202
params: ProcessRequestParams
203203
): T.Task<{ envs: HoppEnvs; report: RequestReport }> =>
204204
async () => {
205-
const { envs, path, request, delay, legacySandbox } = params;
205+
const { envs, path, request, delay, legacySandbox, collectionVariables } =
206+
params;
206207

207208
// Initialising updatedEnvs with given parameter envs, will eventually get updated.
208209
const result = {
@@ -236,7 +237,8 @@ export const processRequest =
236237
const preRequestRes = await preRequestScriptRunner(
237238
request,
238239
processedEnvs,
239-
legacySandbox
240+
legacySandbox,
241+
collectionVariables
240242
)();
241243
if (E.isLeft(preRequestRes)) {
242244
printPreRequestRunner.fail();

0 commit comments

Comments
 (0)