diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index 45f20231..541ec7d6 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -724,6 +724,7 @@ "duplicate_name_error": "Same name response already exists", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "headers": "Headers", + "request_headers": "Request Headers", "html": "HTML", "image": "Image", "json": "JSON", @@ -1224,10 +1225,12 @@ "advanced_settings": "Advanced Settings", "stop_on_error": "Stop run if an error occurs", "persist_responses": "Persist responses", + "keep_variable_values": "Keep variable values", "collection_not_found": "Collection not found. May be deleted or moved.", "empty_collection": "Collection is empty. Add requests to run.", "no_response_persist": "The collection runner is presently configured not to persist responses. This setting prevents showing the response data. To modify this behavior, initiate a new run configuration.", "select_request": "Select a request to see response and test results", + "response_body_lost_rerun": "Response body is lost. Run again the collection to get the response body.", "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 2b1ed997..aafb1837 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -219,7 +219,7 @@ declare module 'vue' { IconLucideRss: (typeof import("~icons/lucide/rss"))["default"] IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideUsers: typeof import('~icons/lucide/users')['default'] - IconLucideVerified: (typeof import("~icons/lucide/verified"))["default"] + IconLucideVerified: typeof import('~icons/lucide/verified')['default'] IconLucideX: typeof import('~icons/lucide/x')['default'] ImportExportBase: typeof import('./components/importExport/Base.vue')['default'] ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/collections/TeamCollections.vue b/packages/hoppscotch-common/src/components/collections/TeamCollections.vue index 1174b19e..5e3e3d7e 100644 --- a/packages/hoppscotch-common/src/components/collections/TeamCollections.vue +++ b/packages/hoppscotch-common/src/components/collections/TeamCollections.vue @@ -136,7 +136,12 @@ }) } " - @run-collection="emit('run-collection', $event)" + @run-collection=" + emit('run-collection', { + collectionID: node.data.data.data.id, + path: node.id, + }) + " @click=" () => { handleCollectionClick({ @@ -233,7 +238,12 @@ }) } " - @run-collection="emit('run-collection', $event)" + @run-collection=" + emit('run-collection', { + collectionID: node.data.data.data.id, + path: node.id, + }) + " @click=" () => { handleCollectionClick({ @@ -662,7 +672,10 @@ const emit = defineEmits<{ (event: "expand-team-collection", payload: string): void (event: "display-modal-add"): void (event: "display-modal-import-export"): void - (event: "run-collection", collectionID: string): void + ( + event: "run-collection", + payload: { collectionID: string; path: string } + ): void }>() const getPath = (path: string) => { diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue index 65ad9976..6c083b60 100644 --- a/packages/hoppscotch-common/src/components/collections/index.vue +++ b/packages/hoppscotch-common/src/components/collections/index.vue @@ -109,7 +109,8 @@ @run-collection=" runCollectionHandler({ type: 'team-collections', - collectionID: $event, + collectionID: $event.collectionID, + path: $event.path, }) " @share-request="shareRequest" @@ -2866,8 +2867,28 @@ const setCollectionProperties = (newCollection: { displayModalEditProperties(false) } -const runCollectionHandler = (payload: CollectionRunnerData) => { - collectionRunnerData.value = payload +const runCollectionHandler = ( + payload: CollectionRunnerData & { + path?: string + } +) => { + if (payload.path && collectionsType.value.type === "team-collections") { + const inheritedProperties = + teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(payload.path) + + if (inheritedProperties) { + collectionRunnerData.value = { + type: "team-collections", + collectionID: payload.collectionID, + inheritedProperties: inheritedProperties, + } + } + } else { + collectionRunnerData.value = { + type: "my-collections", + collectionID: payload.collectionID, + } + } showCollectionsRunnerModal.value = true } diff --git a/packages/hoppscotch-common/src/components/http/test/Response.vue b/packages/hoppscotch-common/src/components/http/test/Response.vue index f561aaa1..85af6e6a 100644 --- a/packages/hoppscotch-common/src/components/http/test/Response.vue +++ b/packages/hoppscotch-common/src/components/http/test/Response.vue @@ -3,19 +3,41 @@ + + + diff --git a/packages/hoppscotch-common/src/components/http/test/ResultRequest.vue b/packages/hoppscotch-common/src/components/http/test/ResultRequest.vue index e09bd278..24803789 100644 --- a/packages/hoppscotch-common/src/components/http/test/ResultRequest.vue +++ b/packages/hoppscotch-common/src/components/http/test/ResultRequest.vue @@ -1,7 +1,8 @@ @@ -120,7 +122,7 @@ diff --git a/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue b/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue index 844bd973..32911d68 100644 --- a/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue +++ b/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue @@ -37,6 +37,18 @@ > + + + @@ -50,11 +62,11 @@ import { import { useI18n } from "@composables/i18n" import { useVModel } from "@vueuse/core" import { HoppRequestDocument } from "~/helpers/rest/document" -import { TestRunnerRequest } from "~/services/test-runner/test-runner.service" const props = defineProps<{ - document: HoppRequestDocument | TestRunnerRequest + document: HoppRequestDocument isEditable: boolean + isTestRunner?: boolean }>() const emit = defineEmits<{ @@ -65,7 +77,6 @@ const emit = defineEmits<{ const doc = useVModel(props, "document", emit) const isSavable = computed(() => { - if (doc.value.type === "test-response") return false return doc.value.response?.type === "success" && doc.value.saveContext }) @@ -104,6 +115,11 @@ const maybeHeaders = computed(() => { return doc.value.response.headers }) +const requestHeaders = computed(() => { + if (!props.isTestRunner || !doc.value) return null + return doc.value.request.headers +}) + const validLenses = computed(() => { if (!doc.value.response) return [] return getSuitableLenses(doc.value.response) @@ -120,8 +136,6 @@ watch( "results", ] - if (doc.value.type === "test-response") return - const { responseTabPreference } = doc.value if ( @@ -137,7 +151,7 @@ watch( ) watch(selectedLensTab, (newLensID) => { - if (doc.value.type === "test-response") return + if (props.isTestRunner) return doc.value.responseTabPreference = newLensID }) diff --git a/packages/hoppscotch-common/src/helpers/RequestRunner.ts b/packages/hoppscotch-common/src/helpers/RequestRunner.ts index a97ae5ab..4f9f4f95 100644 --- a/packages/hoppscotch-common/src/helpers/RequestRunner.ts +++ b/packages/hoppscotch-common/src/helpers/RequestRunner.ts @@ -40,6 +40,10 @@ import { HoppRESTResponse } from "./types/HoppRESTResponse" import { HoppTestData, HoppTestResult } from "./types/HoppTestResult" import { getEffectiveRESTRequest } from "./utils/EffectiveURL" import { isJSONContentType } from "./utils/contenttypes" +import { + getTemporaryVariables, + setTemporaryVariables, +} from "./runner/temp_envs" const secretEnvironmentService = getService(SecretEnvironmentService) @@ -74,10 +78,12 @@ export const combineEnvVariables = (variables: { environments: { selected: Environment["variables"] global: Environment["variables"] + temp?: Environment["variables"] } requestVariables: Environment["variables"] }) => [ ...variables.requestVariables, + ...(variables.environments.temp ?? []), ...variables.environments.selected, ...variables.environments.global, ] @@ -377,7 +383,17 @@ function updateEnvsAfterTestScript(runResult: E.Right) { return updatedRunResult } -export function runTestRunnerRequest(request: HoppRESTRequest): Promise< +/** + * Run the test runner request + * @param request The request to run + * @param persistEnv Whether to persist the environment variables after running the test script + * @returns The response and the test result + */ + +export function runTestRunnerRequest( + request: HoppRESTRequest, + persistEnv = true +): Promise< | E.Left<"script_fail"> | E.Right<{ response: HoppRESTResponse @@ -399,7 +415,10 @@ export function runTestRunnerRequest(request: HoppRESTRequest): Promise< v: 1, name: "Env", variables: combineEnvVariables({ - environments: envs.right, + environments: { + ...envs.right, + temp: !persistEnv ? getTemporaryVariables() : [], + }, requestVariables: [], }), }) @@ -431,7 +450,18 @@ export function runTestRunnerRequest(request: HoppRESTRequest): Promise< runResult.right ) - updateEnvsAfterTestScript(runResult) + // Update the environment variables after running the test script when persistEnv is true. else store the updated environment variables in the store as a temporary variable. + if (persistEnv) { + updateEnvsAfterTestScript(runResult) + } else { + // Combine global and selected environment changes + const allChanges = [ + ...runResult.right.envs.global, + ...runResult.right.envs.selected, + ] + + setTemporaryVariables(allChanges) + } return E.right({ response: res, diff --git a/packages/hoppscotch-common/src/helpers/auth/index.ts b/packages/hoppscotch-common/src/helpers/auth/index.ts index 1a1c049c..397eed80 100644 --- a/packages/hoppscotch-common/src/helpers/auth/index.ts +++ b/packages/hoppscotch-common/src/helpers/auth/index.ts @@ -14,7 +14,8 @@ export const replaceTemplateStringsInObjectValues = < const restTabsService = getService(RESTTabService) const requestVariables = - source === "REST" + source === "REST" && + restTabsService.currentActiveTab.value.document.type === "request" ? restTabsService.currentActiveTab.value.document.request.requestVariables.map( ({ key, value }) => ({ key, diff --git a/packages/hoppscotch-common/src/helpers/backend/helpers.ts b/packages/hoppscotch-common/src/helpers/backend/helpers.ts index 1df00b89..ca9f06a6 100644 --- a/packages/hoppscotch-common/src/helpers/backend/helpers.ts +++ b/packages/hoppscotch-common/src/helpers/backend/helpers.ts @@ -216,12 +216,15 @@ export const teamCollToHoppRESTColl = ( headers: [], } + const { auth, headers } = parseCollectionData(data) + return makeCollection({ + id: coll.id, name: coll.title, folders: coll.children?.map(teamCollToHoppRESTColl) ?? [], requests: coll.requests?.map((x) => x.request) ?? [], - auth: data.auth ?? { authType: "inherit", authActive: true }, - headers: data.headers ?? [], + auth: auth ?? { authType: "inherit", authActive: true }, + headers: headers ?? [], }) } diff --git a/packages/hoppscotch-common/src/helpers/preRequest.ts b/packages/hoppscotch-common/src/helpers/preRequest.ts index 611536c5..0b06f37d 100644 --- a/packages/hoppscotch-common/src/helpers/preRequest.ts +++ b/packages/hoppscotch-common/src/helpers/preRequest.ts @@ -63,7 +63,7 @@ const unsecretEnvironments = ( } } -export const getCombinedEnvVariables = () => { +export const getCombinedEnvVariables = (temp?: Environment["variables"]) => { const reformedVars = unsecretEnvironments( getCurrentEnvironment(), getGlobalVariables() @@ -71,6 +71,7 @@ export const getCombinedEnvVariables = () => { return { global: cloneDeep(reformedVars.global), selected: cloneDeep(reformedVars.selected), + temp: temp ? cloneDeep(temp) : [], } } @@ -79,6 +80,7 @@ export const getFinalEnvsFromPreRequest = ( envs: { global: Environment["variables"] selected: Environment["variables"] + temp: Environment["variables"] } ): Promise> => runPreRequestScript(script, envs) diff --git a/packages/hoppscotch-common/src/helpers/rest/document.ts b/packages/hoppscotch-common/src/helpers/rest/document.ts index 517d631f..5793333e 100644 --- a/packages/hoppscotch-common/src/helpers/rest/document.ts +++ b/packages/hoppscotch-common/src/helpers/rest/document.ts @@ -127,6 +127,12 @@ export type HoppTestRunnerDocument = { */ collectionID: string + /** + * Selected request id + * (if any) + */ + selectedRequestPath?: string + /** * The request as it is in the document */ @@ -166,6 +172,12 @@ export type HoppTestRunnerDocument = { * (atleast as far as we can say) */ isDirty: boolean + + /** + * The inherited properties from the parent collection also the collection itself + * (if any) - Used for team collections + */ + inheritedProperties?: HoppInheritedProperty } export type HoppRequestDocument = { diff --git a/packages/hoppscotch-common/src/helpers/runner/temp_envs.ts b/packages/hoppscotch-common/src/helpers/runner/temp_envs.ts new file mode 100644 index 00000000..88772573 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/runner/temp_envs.ts @@ -0,0 +1,20 @@ +import { ref } from "vue" +import { GlobalEnvironmentVariable } from "@hoppscotch/data" + +export const temporaryVariables = ref([]) + +export function getTemporaryVariables() { + return temporaryVariables.value +} + +export function setTemporaryVariables(variables: GlobalEnvironmentVariable[]) { + temporaryVariables.value = variables +} + +export function clearTemporaryVariables() { + temporaryVariables.value = [] +} + +export function addTemporaryVariable(variable: GlobalEnvironmentVariable) { + temporaryVariables.value.push(variable) +} diff --git a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts index e3675754..f5d3fbb4 100644 --- a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts +++ b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts @@ -537,6 +537,7 @@ export const REST_TAB_STATE_SCHEMA = z response: z.nullable(HoppRESTResponseSchema), testResults: z.optional(z.nullable(HoppTestResultSchema)), isDirty: z.boolean(), + inheritedProperties: z.optional(HoppInheritedPropertySchema), }), z.object({ // !Versioned entity diff --git a/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts b/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts index 95019465..992b4df2 100644 --- a/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts +++ b/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts @@ -268,7 +268,10 @@ export class TestRunnerService extends Service { error: undefined, }) - const results = await runTestRunnerRequest(request) + const results = await runTestRunnerRequest( + request, + options.keepVariableValues + ) if (options.stopRef?.value) { throw new Error("Test execution stopped") diff --git a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts index 52c29a36..0d29f9ce 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts @@ -598,14 +598,15 @@ function setupUserCollectionDuplicatedSubscription() { ) // Incoming data transformed to the respective internal representations - const { auth, headers, _ref_id } = + const { auth, headers } = data && data != "null" ? JSON.parse(data) : { auth: { authType: "inherit", authActive: false }, headers: [], - _ref_id: generateUniqueRefId("coll"), } + // Duplicated collection will have a unique ref id + const _ref_id = generateUniqueRefId("coll") const folders = transformDuplicatedCollections(childCollectionsJSONStr) @@ -1041,6 +1042,8 @@ function transformDuplicatedCollections( ? JSON.parse(data) : { auth: { authType: "inherit", authActive: false }, headers: [] } + const _ref_id = generateUniqueRefId("coll") + const folders = transformDuplicatedCollections(childCollectionsJSONStr) const requests = transformDuplicatedCollectionRequests(userRequests) @@ -1050,6 +1053,7 @@ function transformDuplicatedCollections( name, folders, requests, + _ref_id, v: 5, auth, headers: addDescriptionField(headers),