From 5afa1a5f23b2f0b994ce05e04c4f23561601bb8f Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Mon, 27 Jul 2026 16:14:41 +0600 Subject: [PATCH 01/12] feat(common): implement current env value fallback to initial value (#6503) Co-authored-by: nivedin --- .../components/collections/ImportExport.vue | 2 +- .../PublishDocSnapshotPreview.vue | 11 +- .../collections/documentation/index.vue | 6 +- .../collections/graphql/Collection.vue | 2 +- .../components/collections/graphql/Folder.vue | 2 +- .../collections/graphql/ImportExport.vue | 2 +- .../src/components/collections/index.vue | 6 +- .../src/components/environments/Add.vue | 10 +- .../components/environments/ImportExport.vue | 10 +- .../src/components/environments/Selector.vue | 66 ++++++---- .../src/components/environments/index.vue | 4 +- .../components/environments/my/Details.vue | 4 +- .../components/environments/teams/Details.vue | 4 +- .../src/components/http/Codegen.vue | 6 +- .../src/components/http/Headers.vue | 6 +- .../src/components/http/RequestTab.vue | 8 +- .../components/http/example/ResponseTab.vue | 7 +- .../src/components/smart/EnvInput.vue | 13 +- .../src/composables/useMockServer.ts | 8 +- .../src/helpers/RequestRunner.ts | 124 +++++++++--------- ...s.spec.ts => clientLocalVariables.spec.ts} | 23 ++-- .../src/helpers/auth/index.ts | 46 +++---- .../src/helpers/backend/helpers.ts | 2 +- ...etVariables.ts => clientLocalVariables.ts} | 102 +++++--------- .../src/helpers/collection/collection.ts | 4 +- .../editor/extensions/HoppEnvironment.ts | 67 ++++------ .../import-export/export/environment.ts | 4 +- .../import-export/export/environments.ts | 4 +- .../import-export/export/gqlCollections.ts | 2 +- .../import-export/export/myCollections.ts | 2 +- .../__tests__/scriptEnvWriteback.spec.ts | 79 +++++++++++ .../utils/__tests__/secretMask.spec.ts | 19 +++ .../__tests__/variablePrecedence.spec.ts | 102 +++++++++++++- .../src/helpers/utils/environments.ts | 45 ++++++- .../src/helpers/utils/scriptEnvWriteback.ts | 37 ++++++ .../src/helpers/utils/secretMask.ts | 13 ++ .../sync/collections/gqlCollections.sync.ts | 12 +- .../src/lib/sync/collections/import.ts | 6 +- .../src/lib/sync/collections/sync.ts | 12 +- .../src/lib/sync/environments/sync.ts | 16 +-- .../hoppscotch-common/src/pages/import.vue | 2 +- .../src/pages/view/_id/_version.vue | 11 +- .../__tests__/environment.inspector.spec.ts | 43 ++++++ .../inspectors/environment.inspector.ts | 43 ++---- .../hoppscotch-data/src/environment/index.ts | 32 +++-- 45 files changed, 660 insertions(+), 369 deletions(-) rename packages/hoppscotch-common/src/helpers/__tests__/{secretVariables.spec.ts => clientLocalVariables.spec.ts} (97%) rename packages/hoppscotch-common/src/helpers/{secretVariables.ts => clientLocalVariables.ts} (58%) create mode 100644 packages/hoppscotch-common/src/helpers/utils/__tests__/scriptEnvWriteback.spec.ts create mode 100644 packages/hoppscotch-common/src/helpers/utils/__tests__/secretMask.spec.ts create mode 100644 packages/hoppscotch-common/src/helpers/utils/scriptEnvWriteback.ts create mode 100644 packages/hoppscotch-common/src/helpers/utils/secretMask.ts diff --git a/packages/hoppscotch-common/src/components/collections/ImportExport.vue b/packages/hoppscotch-common/src/components/collections/ImportExport.vue index 051886f2f34..24e19eb4a8c 100644 --- a/packages/hoppscotch-common/src/components/collections/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/collections/ImportExport.vue @@ -45,7 +45,7 @@ import { flushUnmatchedRefIdsFromTree, populateLocalStoresFromCollectionTree, stripCollectionTreeForStore, -} from "~/helpers/secretVariables" +} from "~/helpers/clientLocalVariables" import IconInsomnia from "~icons/hopp/insomnia" import IconPostman from "~icons/hopp/postman" diff --git a/packages/hoppscotch-common/src/components/collections/documentation/PublishDocSnapshotPreview.vue b/packages/hoppscotch-common/src/components/collections/documentation/PublishDocSnapshotPreview.vue index 4ecc9a479d4..6b81de21f90 100644 --- a/packages/hoppscotch-common/src/components/collections/documentation/PublishDocSnapshotPreview.vue +++ b/packages/hoppscotch-common/src/components/collections/documentation/PublishDocSnapshotPreview.vue @@ -429,14 +429,9 @@ const fetchSnapshotPreview = async () => { const parsedVars = typeof rawEnvVars === "string" ? JSON.parse(rawEnvVars) : rawEnvVars if (Array.isArray(parsedVars)) { - snapshotEnvironmentVariables.value = parsedVars.map((v) => { - const normalized = translateToNewEnvironmentVariables(v) - // Ensure currentValue falls back to initialValue - return { - ...normalized, - currentValue: normalized.currentValue || normalized.initialValue, - } - }) + snapshotEnvironmentVariables.value = parsedVars.map((v) => + translateToNewEnvironmentVariables(v) + ) } } catch (e) { console.error("Error parsing snapshot environment variables:", e) diff --git a/packages/hoppscotch-common/src/components/collections/documentation/index.vue b/packages/hoppscotch-common/src/components/collections/documentation/index.vue index 6100f360f6e..16dfdf72be9 100644 --- a/packages/hoppscotch-common/src/components/collections/documentation/index.vue +++ b/packages/hoppscotch-common/src/components/collections/documentation/index.vue @@ -270,7 +270,7 @@ import { import { updateTeamCollection } from "~/helpers/backend/mutations/TeamCollection" import { updateTeamRequest } from "~/helpers/backend/mutations/TeamRequest" -import { stripSecretVariableValuesForWire } from "~/helpers/secretVariables" +import { stripClientLocalValuesForWire } from "~/helpers/clientLocalVariables" import { CollectionDataProps, getSingleTeamCollectionJSON, @@ -736,7 +736,7 @@ const saveCollectionDocumentation = async () => { const data: CollectionDataProps = { auth: collection.auth || { authType: "inherit", authActive: true }, headers: collection.headers || [], - variables: stripSecretVariableValuesForWire(collection.variables || []), + variables: stripClientLocalValuesForWire(collection.variables || []), description: documentationDescription.value, preRequestScript: collection.preRequestScript || "", testScript: collection.testScript || "", @@ -832,7 +832,7 @@ const saveCollectionDocumentationById = async ( const data: CollectionDataProps = { auth: collectionData.auth || { authType: "inherit", authActive: true }, headers: collectionData.headers || [], - variables: stripSecretVariableValuesForWire( + variables: stripClientLocalValuesForWire( collectionData.variables || [] ), description: documentation, diff --git a/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue b/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue index 14803af4914..b901d246e10 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/Collection.vue @@ -250,7 +250,7 @@ import { useService } from "dioc/vue" import { computed, ref } from "vue" import { Picked } from "~/helpers/types/HoppPicked" import { removeGraphqlCollection } from "~/newstore/collections" -import { flushLocalStoresForCollectionTree } from "~/helpers/secretVariables" +import { flushLocalStoresForCollectionTree } from "~/helpers/clientLocalVariables" import { handleTokenValidation } from "~/helpers/handleTokenValidation" import { GQLTabService } from "~/services/tab/graphql" import IconCheckCircle from "~icons/lucide/check-circle" diff --git a/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue b/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue index 2b710b144f2..f134c0a42bf 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue @@ -235,7 +235,7 @@ import { computed, ref } from "vue" import { handleTokenValidation } from "~/helpers/handleTokenValidation" import { Picked } from "~/helpers/types/HoppPicked" import { removeGraphqlFolder } from "~/newstore/collections" -import { flushLocalStoresForCollectionTree } from "~/helpers/secretVariables" +import { flushLocalStoresForCollectionTree } from "~/helpers/clientLocalVariables" import { GQLTabService } from "~/services/tab/graphql" import IconCheckCircle from "~icons/lucide/check-circle" import IconCopy from "~icons/lucide/copy" diff --git a/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue b/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue index f1b72ccc904..9733bab961b 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue @@ -40,7 +40,7 @@ import { ensureRefIds, populateLocalStoresFromCollectionTree, stripCollectionTreeForStore, -} from "~/helpers/secretVariables" +} from "~/helpers/clientLocalVariables" const t = useI18n() const toast = useToast() diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue index efa2785e95f..d0b24107099 100644 --- a/packages/hoppscotch-common/src/components/collections/index.vue +++ b/packages/hoppscotch-common/src/components/collections/index.vue @@ -412,8 +412,8 @@ import { flushLocalStoresForCollectionTree, flushLocalStoresForTeamCollectionTree, stripCollectionTreeForStore, - stripSecretVariableValuesForWire, -} from "~/helpers/secretVariables" + stripClientLocalValuesForWire, +} from "~/helpers/clientLocalVariables" const t = useI18n() const toast = useToast() @@ -3615,7 +3615,7 @@ const setCollectionProperties = (newCollection: { currentEnvironmentValueService.addEnvironment(storeKey, nonSecretVariables) - collection.variables = stripSecretVariableValuesForWire(filteredVariables) + collection.variables = stripClientLocalValuesForWire(filteredVariables) } if (collectionsType.value.type === "my-collections") { diff --git a/packages/hoppscotch-common/src/components/environments/Add.vue b/packages/hoppscotch-common/src/components/environments/Add.vue index 1a9ec22c80d..7f47f6d8b8b 100644 --- a/packages/hoppscotch-common/src/components/environments/Add.vue +++ b/packages/hoppscotch-common/src/components/environments/Add.vue @@ -79,7 +79,7 @@ import { useToast } from "~/composables/toast" import { GQLError } from "~/helpers/backend/GQLClient" import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment" import { getEnvActionErrorMessage } from "~/helpers/error-messages" -import { stripSecretVariableValuesForWire } from "~/helpers/secretVariables" +import { stripClientLocalValuesForWire } from "~/helpers/clientLocalVariables" import { setGlobalEnvVariables, updateEnvironment, @@ -206,7 +206,7 @@ const addEnvironment = async () => { await pipe( updateTeamEnvironment( - JSON.stringify(stripSecretVariableValuesForWire(newVariables)), + JSON.stringify(stripClientLocalValuesForWire(newVariables)), scope.value.environment.id, scope.value.environment.environment.name ), @@ -223,8 +223,10 @@ const addEnvironment = async () => { key: editingName.value, currentValue: editingValue.value, isSecret: false, - varIndex: - scope.value.environment.environment.variables.length - 1, + // The new variable is appended at index `length` of the + // pre-append array; `length - 1` collided with the previous + // last variable's slot. + varIndex: scope.value.environment.environment.variables.length, } ) } diff --git a/packages/hoppscotch-common/src/components/environments/ImportExport.vue b/packages/hoppscotch-common/src/components/environments/ImportExport.vue index b1a168d5365..583234acca0 100644 --- a/packages/hoppscotch-common/src/components/environments/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/environments/ImportExport.vue @@ -13,8 +13,8 @@ import { Environment, generateUniqueRefId } from "@hoppscotch/data" import { populateLocalStoresFromVariables, promoteInitialValueForImport, - stripSecretVariableValuesForWire, -} from "~/helpers/secretVariables" + stripClientLocalValuesForWire, +} from "~/helpers/clientLocalVariables" import * as E from "fp-ts/Either" import { ref } from "vue" @@ -418,7 +418,7 @@ const handleImportToStore = async ( ]) // Append stripped imports; varIndex aligns with the hydrated entries. - stripSecretVariableValuesForWire(importedGlobals).forEach( + stripClientLocalValuesForWire(importedGlobals).forEach( ({ key, initialValue, currentValue, secret }) => { addGlobalEnvVariable({ key, initialValue, currentValue, secret }) } @@ -446,7 +446,7 @@ const handleImportToStore = async ( const strippedEnvironments = envsWithIds.map((env) => ({ ...env, - variables: stripSecretVariableValuesForWire(env.variables), + variables: stripClientLocalValuesForWire(env.variables), })) appendEnvironments(strippedEnvironments) toast.success(t("state.file_imported")) @@ -462,7 +462,7 @@ const importToTeams = async (content: Environment[]) => { for (const [, env] of content.entries()) { const res = createTeamEnvironment( - JSON.stringify(stripSecretVariableValuesForWire(env.variables)), + JSON.stringify(stripClientLocalValuesForWire(env.variables)), props.teamId as string, env.name )() diff --git a/packages/hoppscotch-common/src/components/environments/Selector.vue b/packages/hoppscotch-common/src/components/environments/Selector.vue index a553ab2de17..590e2e40487 100644 --- a/packages/hoppscotch-common/src/components/environments/Selector.vue +++ b/packages/hoppscotch-common/src/components/environments/Selector.vue @@ -258,16 +258,10 @@ {{ variable.key }} - - + {{ variable.initialValue }} - - + {{ variable.currentValue }}
@@ -327,16 +321,10 @@ {{ variable.key }} - - + {{ variable.initialValue }} - - + {{ variable.currentValue }}
{ - return (globalVals.value?.variables ?? []).map((variable, index) => ({ +// Resolve each variable's display values. Secrets are masked by length (an +// unset secret renders empty, not a fixed `********`) so the popover matches the +// env tooltip and never claims a value exists when it doesn't. +const resolveDisplayVariable = ( + variable: Environment["variables"][number], + envID: string, + index: number +) => { + if (variable.secret) { + const secretValue = + secretEnvironmentService.getSecretEnvironmentVariableValue(envID, index) + return { + ...variable, + initialValue: maskSecretValue(secretValue?.initialValue), + currentValue: maskSecretValue(secretValue?.value), + } + } + return { ...variable, currentValue: currentEnvironmentValueService.getEnvironmentVariableValue( - "Global", + envID, index ) ?? "", - })) + } +} + +const globalEnvs = computed(() => { + return (globalVals.value?.variables ?? []).map((variable, index) => + resolveDisplayVariable(variable, "Global", index) + ) }) const environmentVariables = computed(() => { if (selectedEnv.value.variables && selectedEnv.value.id) { - return selectedEnv.value.variables.map((variable, index) => ({ - ...variable, - currentValue: - currentEnvironmentValueService.getEnvironmentVariableValue( - selectedEnv.value.id ?? "", - index - ) ?? "", - })) + const envID = selectedEnv.value.id + return selectedEnv.value.variables.map((variable, index) => + resolveDisplayVariable(variable, envID, index) + ) } return [] }) diff --git a/packages/hoppscotch-common/src/components/environments/index.vue b/packages/hoppscotch-common/src/components/environments/index.vue index a74cafac6dc..5f36045284d 100644 --- a/packages/hoppscotch-common/src/components/environments/index.vue +++ b/packages/hoppscotch-common/src/components/environments/index.vue @@ -57,7 +57,7 @@