fix: handle edge cases and bugs in collection variables (#5348)
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
a0c2635000
commit
9504369ce1
18 changed files with 609 additions and 538 deletions
|
|
@ -51,9 +51,12 @@
|
|||
<icon-lucide-arrow-up-right class="svg-icons" />
|
||||
</HoppSmartLink>
|
||||
</span>
|
||||
<span v-if="inspector.action" class="flex space-x-2 p-2">
|
||||
<span
|
||||
v-if="inspector.action ? inspector.action.showAction : true"
|
||||
class="flex space-x-2 p-2"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="inspector.action.text"
|
||||
:label="inspector.action?.text"
|
||||
outline
|
||||
filled
|
||||
@click="
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@
|
|||
emit('export-data', node.data.data.data)
|
||||
"
|
||||
@remove-collection="emit('remove-collection', node.id)"
|
||||
@drop-event="dropEvent($event, node.id)"
|
||||
@drop-event="dropEvent($event, node.id, getPath(node.id, false))"
|
||||
@drag-event="dragEvent($event, node.id)"
|
||||
@update-collection-order="
|
||||
updateCollectionOrder($event, {
|
||||
|
|
@ -210,8 +210,12 @@
|
|||
node.data.type === 'folders' &&
|
||||
emit('remove-folder', node.data.data.data.id)
|
||||
"
|
||||
@drop-event="dropEvent($event, node.data.data.data.id)"
|
||||
@drag-event="dragEvent($event, node.data.data.data.id)"
|
||||
@drop-event="
|
||||
dropEvent($event, node.data.data.data.id, getPath(node.id, false))
|
||||
"
|
||||
@drag-event="
|
||||
dragEvent($event, node.data.data.data.id, getPath(node.id, true))
|
||||
"
|
||||
@update-collection-order="
|
||||
updateCollectionOrder($event, {
|
||||
destinationCollectionIndex: node.data.data.data.id,
|
||||
|
|
@ -640,6 +644,8 @@ const emit = defineEmits<{
|
|||
payload: {
|
||||
collectionIndexDragged: string
|
||||
destinationCollectionIndex: string
|
||||
destinationParentPath?: string
|
||||
currentParentIndex?: string
|
||||
}
|
||||
): void
|
||||
(
|
||||
|
|
@ -678,9 +684,9 @@ const emit = defineEmits<{
|
|||
): void
|
||||
}>()
|
||||
|
||||
const getPath = (path: string) => {
|
||||
const getPath = (path: string, pop: boolean = true) => {
|
||||
const pathArray = path.split("/")
|
||||
pathArray.pop()
|
||||
if (pop) pathArray.pop()
|
||||
return pathArray.join("/")
|
||||
}
|
||||
|
||||
|
|
@ -783,17 +789,25 @@ const dragRequest = (
|
|||
dataTransfer.setData("requestIndex", requestIndex)
|
||||
}
|
||||
|
||||
const dragEvent = (dataTransfer: DataTransfer, collectionIndex: string) => {
|
||||
const dragEvent = (
|
||||
dataTransfer: DataTransfer,
|
||||
collectionIndex: string,
|
||||
parentIndex?: string
|
||||
) => {
|
||||
dataTransfer.setData("collectionIndex", collectionIndex)
|
||||
if (parentIndex) dataTransfer.setData("parentIndex", parentIndex)
|
||||
}
|
||||
|
||||
const dropEvent = (
|
||||
dataTransfer: DataTransfer,
|
||||
destinationCollectionIndex: string
|
||||
destinationCollectionIndex: string,
|
||||
destinationParentPath?: string
|
||||
) => {
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
const collectionIndexDragged = dataTransfer.getData("collectionIndex")
|
||||
const currentParentIndex = dataTransfer.getData("parentIndex")
|
||||
|
||||
if (folderPath && requestIndex) {
|
||||
emit("drop-request", {
|
||||
folderPath,
|
||||
|
|
@ -804,6 +818,8 @@ const dropEvent = (
|
|||
emit("drop-collection", {
|
||||
collectionIndexDragged,
|
||||
destinationCollectionIndex,
|
||||
destinationParentPath,
|
||||
currentParentIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2296,8 +2296,15 @@ const isMoveToSameLocation = (
|
|||
const dropCollection = (payload: {
|
||||
collectionIndexDragged: string
|
||||
destinationCollectionIndex: string
|
||||
destinationParentPath?: string
|
||||
currentParentIndex?: string
|
||||
}) => {
|
||||
const { collectionIndexDragged, destinationCollectionIndex } = payload
|
||||
const {
|
||||
collectionIndexDragged,
|
||||
destinationCollectionIndex,
|
||||
destinationParentPath,
|
||||
currentParentIndex,
|
||||
} = payload
|
||||
if (!collectionIndexDragged || !destinationCollectionIndex) return
|
||||
if (collectionIndexDragged === destinationCollectionIndex) return
|
||||
|
||||
|
|
@ -2339,18 +2346,20 @@ const dropCollection = (payload: {
|
|||
"drop"
|
||||
)
|
||||
|
||||
const newCollectionPath = `${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`
|
||||
|
||||
updateSaveContextForAffectedRequests(
|
||||
collectionIndexDragged,
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`
|
||||
newCollectionPath
|
||||
)
|
||||
|
||||
const inheritedProperty = cascadeParentCollectionForProperties(
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`,
|
||||
newCollectionPath,
|
||||
"rest"
|
||||
)
|
||||
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`,
|
||||
newCollectionPath,
|
||||
inheritedProperty,
|
||||
"rest"
|
||||
)
|
||||
|
|
@ -2381,16 +2390,25 @@ const dropCollection = (payload: {
|
|||
1
|
||||
)
|
||||
|
||||
if (destinationParentPath && currentParentIndex) {
|
||||
updateSaveContextForAffectedRequests(
|
||||
currentParentIndex,
|
||||
`${destinationParentPath}`
|
||||
)
|
||||
}
|
||||
|
||||
const inheritedProperty =
|
||||
teamCollectionAdapter.cascadeParentCollectionForProperties(
|
||||
destinationCollectionIndex
|
||||
`${destinationParentPath}/${collectionIndexDragged}`
|
||||
)
|
||||
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
`${destinationCollectionIndex}`,
|
||||
inheritedProperty,
|
||||
"rest"
|
||||
)
|
||||
setTimeout(() => {
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
`${destinationParentPath}/${collectionIndexDragged}`,
|
||||
inheritedProperty,
|
||||
"rest"
|
||||
)
|
||||
}, 300)
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
|
@ -2430,6 +2448,17 @@ const dropToRoot = ({ dataTransfer }: DragEvent) => {
|
|||
collectionIndexDragged,
|
||||
`${rootLength - 1}`
|
||||
)
|
||||
|
||||
const inheritedProperty = cascadeParentCollectionForProperties(
|
||||
`${rootLength - 1}`,
|
||||
"rest"
|
||||
)
|
||||
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
`${rootLength - 1}`,
|
||||
inheritedProperty,
|
||||
"rest"
|
||||
)
|
||||
}
|
||||
|
||||
draggingToRoot.value = false
|
||||
|
|
@ -2989,7 +3018,7 @@ const setCollectionProperties = (newCollection: {
|
|||
"rest",
|
||||
collectionId
|
||||
)
|
||||
}, 200)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
displayModalEditProperties(false)
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
import {
|
||||
GQLHeader,
|
||||
HoppCollection,
|
||||
HoppCollectionVariable,
|
||||
HoppGQLAuth,
|
||||
HoppGQLRequest,
|
||||
HoppRESTAuth,
|
||||
|
|
@ -261,11 +262,13 @@ const convertToInheritedProperties = (
|
|||
): {
|
||||
auth: HoppRESTAuth | HoppGQLAuth
|
||||
headers: Array<HoppRESTHeader | GQLHeader>
|
||||
variables: HoppCollectionVariable[]
|
||||
} => {
|
||||
const collectionLevelAuthAndHeaders = data
|
||||
? (JSON.parse(data) as {
|
||||
auth: HoppRESTAuth | HoppGQLAuth
|
||||
headers: Array<HoppRESTHeader | GQLHeader>
|
||||
variables: HoppCollectionVariable[]
|
||||
})
|
||||
: null
|
||||
|
||||
|
|
@ -276,9 +279,12 @@ const convertToInheritedProperties = (
|
|||
authActive: true,
|
||||
}
|
||||
|
||||
const variables = collectionLevelAuthAndHeaders?.variables ?? []
|
||||
|
||||
return {
|
||||
auth,
|
||||
headers,
|
||||
variables,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -392,33 +392,52 @@ const aggregateEnvs = useReadonlyStream(
|
|||
const tabs = useService(RESTTabService)
|
||||
|
||||
const envVars = computed(() => {
|
||||
// If envs are passed directly as props, mask secrets and return them
|
||||
if (props.envs?.length) {
|
||||
return props.envs.map((x) => {
|
||||
const { key, secret } = x
|
||||
const currentValue = secret ? "********" : x.currentValue
|
||||
const initialValue = secret ? "********" : x.initialValue
|
||||
const sourceEnv = "sourceEnv" in x ? x.sourceEnv : ""
|
||||
return {
|
||||
return props.envs.map(
|
||||
({ key, currentValue, initialValue, secret, sourceEnv }) => ({
|
||||
key,
|
||||
currentValue,
|
||||
initialValue,
|
||||
sourceEnv,
|
||||
currentValue: secret ? "********" : currentValue,
|
||||
initialValue: secret ? "********" : initialValue,
|
||||
sourceEnv: sourceEnv ?? "",
|
||||
secret,
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const currentTab = tabs.currentActiveTab.value
|
||||
const { document } = currentTab
|
||||
const isRequest = document.type === "request"
|
||||
const isExample = document.type === "example-response"
|
||||
|
||||
// variables inherited from the collection if we're in a request or example
|
||||
const collectionVariables =
|
||||
tabs.currentActiveTab.value.document.type === "request" ||
|
||||
tabs.currentActiveTab.value.document.type === "example-response"
|
||||
isRequest || isExample
|
||||
? transformInheritedCollectionVariablesToAggregateEnv(
|
||||
tabs.currentActiveTab.value.document.inheritedProperties?.variables ??
|
||||
[],
|
||||
document.inheritedProperties?.variables ?? [],
|
||||
false
|
||||
)
|
||||
: []
|
||||
|
||||
return [...collectionVariables, ...aggregateEnvs.value]
|
||||
// request-level variables
|
||||
const rawRequestVars = isRequest
|
||||
? document.request.requestVariables
|
||||
: isExample
|
||||
? document.response.originalRequest.requestVariables
|
||||
: []
|
||||
|
||||
// formated request variables
|
||||
const requestVariables = rawRequestVars
|
||||
.filter((v) => v.active)
|
||||
.map(({ key, value }) => ({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
}))
|
||||
|
||||
return [...requestVariables, ...collectionVariables, ...aggregateEnvs.value]
|
||||
})
|
||||
|
||||
function envAutoCompletion(context: CompletionContext) {
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ subscription TeamCollectionMoved($teamID: ID!) {
|
|||
parent {
|
||||
id
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,17 @@ export function resolveSaveContextOnCollectionReorder(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last folder path from the given path.
|
||||
* @param path Path can be folder path or collection path
|
||||
* @returns Get the last folder path from the given path
|
||||
*/
|
||||
const getLastParentFolderPath = (path?: string) => {
|
||||
if (!path) return ""
|
||||
const pathArray = path.split("/")
|
||||
return pathArray.slice(pathArray.length - 1, pathArray.length).join("/")
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve save context for affected requests on drop folder from one to another
|
||||
* @param oldFolderPath
|
||||
|
|
@ -91,13 +102,18 @@ export function updateSaveContextForAffectedRequests(
|
|||
) {
|
||||
const tabService = getService(RESTTabService)
|
||||
const tabs = tabService.getTabsRefTo((tab) => {
|
||||
return (
|
||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
||||
)
|
||||
if (tab.document.type === "test-runner") return false
|
||||
|
||||
return tab.document.saveContext?.originLocation === "user-collection"
|
||||
? tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
||||
: tab.document.saveContext?.originLocation === "team-collection"
|
||||
? tab.document.saveContext.collectionID!.startsWith(oldFolderPath) ||
|
||||
tab.document.saveContext.collectionID === oldFolderPath
|
||||
: false
|
||||
})
|
||||
|
||||
for (const tab of tabs) {
|
||||
if (tab.value.document.type === "test-runner") return
|
||||
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
||||
tab.value.document.saveContext = {
|
||||
...tab.value.document.saveContext,
|
||||
|
|
@ -106,6 +122,16 @@ export function updateSaveContextForAffectedRequests(
|
|||
newFolderPath
|
||||
),
|
||||
}
|
||||
} else if (
|
||||
tab.value.document.saveContext?.originLocation === "team-collection"
|
||||
) {
|
||||
tab.value.document.saveContext = {
|
||||
...tab.value.document.saveContext,
|
||||
collectionID: tab.value.document.saveContext!.collectionID?.replace(
|
||||
oldFolderPath,
|
||||
newFolderPath
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -166,6 +192,32 @@ function removeDuplicatesAndKeepLast(arr: HoppInheritedProperty["headers"]) {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Order collection variables based on their parentPath and parentID
|
||||
* eg: path like 4/0/0 should come before 4/0/1 nad 4 should come before 4/0
|
||||
* @param vars Collection of variables to be ordered
|
||||
* @returns Ordered collection of variables
|
||||
*/
|
||||
const orderCollectionVariables = (
|
||||
vars: HoppInheritedProperty["variables"]
|
||||
): HoppInheritedProperty["variables"] => {
|
||||
return vars.sort((a, b) => {
|
||||
if (a.parentPath && b.parentPath) {
|
||||
return a.parentPath.localeCompare(b.parentPath)
|
||||
}
|
||||
|
||||
if (a.parentPath) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (b.parentPath) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return a.parentID.localeCompare(b.parentID)
|
||||
})
|
||||
}
|
||||
|
||||
export function updateInheritedPropertiesForAffectedRequests(
|
||||
path: string,
|
||||
inheritedProperties: HoppInheritedProperty,
|
||||
|
|
@ -176,6 +228,8 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
||||
|
||||
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
||||
if ("type" in tab.document && tab.document.type === "test-runner")
|
||||
return false
|
||||
const saveContext = tab.document.saveContext
|
||||
|
||||
const saveContextPath =
|
||||
|
|
@ -183,7 +237,12 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||
? saveContext.collectionID
|
||||
: saveContext?.folderPath
|
||||
|
||||
return saveContextPath?.startsWith(path) ?? false
|
||||
return (
|
||||
(saveContextPath?.startsWith(path) ||
|
||||
getLastParentFolderPath(saveContextPath) ===
|
||||
getLastParentFolderPath(path)) ??
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
effectedTabs.map((tab) => {
|
||||
|
|
@ -217,7 +276,8 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||
|
||||
// filter out the headers with the parentID as the path in the inheritedProperties
|
||||
const inheritedHeaders = inheritedProperties.headers.filter(
|
||||
(header) => header.parentID === path
|
||||
(header) =>
|
||||
path.startsWith(header.parentID ?? "") || header.parentID === path
|
||||
)
|
||||
|
||||
// merge the headers with the parentID as the path
|
||||
|
|
@ -228,7 +288,13 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||
tab.value.document.inheritedProperties.headers = mergedHeaders
|
||||
}
|
||||
|
||||
if (tab.value.document.inheritedProperties?.variables && collectionId) {
|
||||
if (tab.value.document.inheritedProperties?.variables && !collectionId) {
|
||||
tab.value.document.inheritedProperties.variables =
|
||||
inheritedProperties.variables
|
||||
} else if (
|
||||
tab.value.document.inheritedProperties?.variables &&
|
||||
collectionId
|
||||
) {
|
||||
const tabInheritedVariables =
|
||||
tab.value.document.inheritedProperties.variables.filter(
|
||||
(variable) => variable.parentID !== collectionId
|
||||
|
|
@ -239,7 +305,9 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||
(variable) => variable.parentID === collectionId
|
||||
)
|
||||
|
||||
const finalVariables = [...inheritedVariables, ...tabInheritedVariables]
|
||||
const finalVariables = orderCollectionVariables([
|
||||
...new Set([...inheritedVariables, ...tabInheritedVariables]),
|
||||
])
|
||||
|
||||
tab.value.document.inheritedProperties.variables = finalVariables
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import {
|
|||
hoverTooltip,
|
||||
} from "@codemirror/view"
|
||||
import { StreamSubscriberFunc } from "@composables/stream"
|
||||
import { parseTemplateStringE } from "@hoppscotch/data"
|
||||
import {
|
||||
HoppRESTRequestVariables,
|
||||
parseTemplateStringE,
|
||||
} from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { Ref, watch } from "vue"
|
||||
|
||||
|
|
@ -22,20 +25,22 @@ import {
|
|||
} from "~/newstore/environments"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
|
||||
import IconEdit from "~icons/lucide/edit?raw"
|
||||
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"
|
||||
import { transformInheritedCollectionVariablesToAggregateEnv } from "~/helpers/utils/inheritedCollectionVarTransformer"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||
|
||||
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"
|
||||
|
|
@ -44,7 +49,6 @@ const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "environment-not-found-highlight"
|
|||
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
const restTabs = getService(RESTTabService)
|
||||
|
||||
/**
|
||||
|
|
@ -56,11 +60,9 @@ const filterNonEmptyEnvironmentVariables = (
|
|||
envs: AggregateEnvironment[]
|
||||
): AggregateEnvironment[] => {
|
||||
const envsMap = new Map<string, AggregateEnvironment>()
|
||||
|
||||
envs.forEach((env) => {
|
||||
if (envsMap.has(env.key)) {
|
||||
const existingEnv = envsMap.get(env.key)
|
||||
|
||||
if (
|
||||
existingEnv?.currentValue === "" &&
|
||||
existingEnv?.initialValue === "" &&
|
||||
|
|
@ -72,7 +74,6 @@ const filterNonEmptyEnvironmentVariables = (
|
|||
envsMap.set(env.key, env)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(envsMap.values())
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +81,8 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
hoverTooltip(
|
||||
(view, pos, side) => {
|
||||
// Check if the current position is inside a comment then disable the tooltip
|
||||
if (isComment(view.state, pos)) {
|
||||
return null
|
||||
}
|
||||
if (isComment(view.state, pos)) return null
|
||||
|
||||
const { from, to, text } = view.state.doc.lineAt(pos)
|
||||
|
||||
// TODO: When Codemirror 6 allows this to work (not make the
|
||||
|
|
@ -98,7 +98,6 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
// Tracking the start and the end of the words
|
||||
let start = pos
|
||||
let end = pos
|
||||
|
||||
while (start > from && /[a-zA-Z0-9-_]+/.test(text[start - from - 1]))
|
||||
start--
|
||||
while (end < to && /[a-zA-Z0-9-_]+/.test(text[end - from])) end++
|
||||
|
|
@ -113,20 +112,15 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
return null
|
||||
|
||||
const parsedEnvKey = text.slice(start - from, end - from)
|
||||
|
||||
const envsWithNoEmptyValues =
|
||||
filterNonEmptyEnvironmentVariables(aggregateEnvs)
|
||||
|
||||
const tooltipEnv = envsWithNoEmptyValues.find(
|
||||
(env) => env.key === parsedEnvKey
|
||||
)
|
||||
|
||||
const currentSelectedEnvironment = getCurrentEnvironment()
|
||||
|
||||
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
||||
|
||||
let envInitialValue = tooltipEnv?.initialValue
|
||||
|
||||
// If the environment is not a request variable, get the current value from the current environment service
|
||||
let envCurrentValue =
|
||||
tooltipEnv?.sourceEnv !== "RequestVariable"
|
||||
|
|
@ -141,16 +135,12 @@ 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"
|
||||
const tooltipSourceEnvID =
|
||||
tooltipEnv?.sourceEnv === "Global"
|
||||
? "Global"
|
||||
: tooltipEnv?.sourceEnv === "CollectionVariable"
|
||||
? tooltipEnv.sourceEnvID!
|
||||
: currentSelectedEnvironment.id
|
||||
}
|
||||
|
||||
const hasSecretStored = secretEnvironmentService.hasSecretValue(
|
||||
tooltipSourceEnvID,
|
||||
|
|
@ -225,13 +215,13 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
| "modals.team.environment.edit"
|
||||
| "modals.global.environment.update" = "modals.my.environment.edit"
|
||||
|
||||
if (tooltipEnv?.sourceEnv === "Global") {
|
||||
if (tooltipEnv?.sourceEnv === "Global")
|
||||
invokeActionType = "modals.global.environment.update"
|
||||
} else if (selectedEnvType === "MY_ENV") {
|
||||
else if (selectedEnvType === "MY_ENV")
|
||||
invokeActionType = "modals.my.environment.edit"
|
||||
} else if (selectedEnvType === "TEAM_ENV") {
|
||||
else if (selectedEnvType === "TEAM_ENV")
|
||||
invokeActionType = "modals.team.environment.edit"
|
||||
} else {
|
||||
else {
|
||||
invokeActionType = "modals.my.environment.edit"
|
||||
}
|
||||
|
||||
|
|
@ -250,9 +240,8 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
}
|
||||
})
|
||||
editIcon.innerHTML = `<span class="inline-flex items-center justify-center my-1">${IconEdit}</span>`
|
||||
if (tooltipEnv?.sourceEnv !== "CollectionVariable") {
|
||||
if (tooltipEnv?.sourceEnv !== "CollectionVariable")
|
||||
tooltip.appendChild(editIcon)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -264,7 +253,6 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
arrow: true,
|
||||
create() {
|
||||
const dom = document.createElement("div")
|
||||
|
||||
const tooltipContainer = document.createElement("div")
|
||||
|
||||
const tooltipHeaderBlock = document.createElement("div")
|
||||
|
|
@ -279,7 +267,6 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
|
||||
const icon = document.createElement("span")
|
||||
icon.innerHTML = envTypeIcon
|
||||
|
||||
const envNameBlock = document.createElement("span")
|
||||
envNameBlock.innerText = envName
|
||||
|
||||
|
|
@ -296,20 +283,20 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
const initialValueBlock = document.createElement("div")
|
||||
initialValueBlock.className = "flex items-center space-x-2"
|
||||
const initialValueTitle = document.createElement("div")
|
||||
const initialValue = document.createElement("span")
|
||||
initialValue.textContent = envInitialValue || ""
|
||||
initialValueTitle.textContent = "Initial"
|
||||
initialValueTitle.className = "font-bold mr-4 "
|
||||
const initialValue = document.createElement("span")
|
||||
initialValue.textContent = envInitialValue || ""
|
||||
initialValueBlock.appendChild(initialValueTitle)
|
||||
initialValueBlock.appendChild(initialValue)
|
||||
|
||||
const currentValueBlock = document.createElement("div")
|
||||
currentValueBlock.className = "flex items-center space-x-2"
|
||||
const currentValueTitle = document.createElement("div")
|
||||
currentValueTitle.textContent = "Current"
|
||||
currentValueTitle.className = "font-bold mr-1.5"
|
||||
const currentValue = document.createElement("span")
|
||||
currentValue.textContent = envCurrentValue || ""
|
||||
currentValueTitle.textContent = "Current "
|
||||
currentValueTitle.className = "font-bold mr-1.5"
|
||||
currentValueBlock.appendChild(currentValueTitle)
|
||||
currentValueBlock.appendChild(currentValue)
|
||||
|
||||
|
|
@ -332,7 +319,6 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
|
||||
function checkEnv(env: string, aggregateEnvs: AggregateEnvironment[]) {
|
||||
let className = HOPP_ENV_HIGHLIGHT_NOT_FOUND
|
||||
|
||||
const envSource = aggregateEnvs.find(
|
||||
(k: { key: string }) => k.key === env.slice(2, -2)
|
||||
)?.sourceEnv
|
||||
|
|
@ -344,9 +330,7 @@ function checkEnv(env: string, aggregateEnvs: AggregateEnvironment[]) {
|
|||
else if (envSource === "Global") className = HOPP_GLOBAL_ENVIRONMENT_HIGHLIGHT
|
||||
else if (envSource !== undefined) className = HOPP_ENVIRONMENT_HIGHLIGHT
|
||||
|
||||
return Decoration.mark({
|
||||
class: `${HOPP_ENV_HIGHLIGHT} ${className}`,
|
||||
})
|
||||
return Decoration.mark({ class: `${HOPP_ENV_HIGHLIGHT} ${className}` })
|
||||
}
|
||||
|
||||
const getMatchDecorator = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
|
|
@ -354,9 +338,7 @@ const getMatchDecorator = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||
regexp: HOPP_ENVIRONMENT_REGEX,
|
||||
decoration: (m, view, pos) => {
|
||||
// Check if the current position is inside a comment then disable the highlight
|
||||
if (isComment(view.state, pos)) {
|
||||
return null
|
||||
}
|
||||
if (isComment(view.state, pos)) return null
|
||||
return checkEnv(m[0], aggregateEnvs)
|
||||
},
|
||||
})
|
||||
|
|
@ -366,9 +348,7 @@ export const environmentHighlightStyle = (
|
|||
) => {
|
||||
const envsWithNoEmptyValues =
|
||||
filterNonEmptyEnvironmentVariables(aggregateEnvs)
|
||||
|
||||
const decorator = getMatchDecorator(envsWithNoEmptyValues)
|
||||
|
||||
return ViewPlugin.define(
|
||||
(view) => ({
|
||||
decorations: decorator.createDeco(view),
|
||||
|
|
@ -376,15 +356,40 @@ export const environmentHighlightStyle = (
|
|||
this.decorations = decorator.updateDeco(u, this.decorations)
|
||||
},
|
||||
}),
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
}
|
||||
{ decorations: (v) => v.decorations }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the request variables and collection variables in AggregateEnvironment type
|
||||
* @param requestVariables Request Variables defined in the request
|
||||
* @param collectionVariables Inherited Collection Variables
|
||||
* @returns Transforms the request and collection variables to AggregateEnvironment type
|
||||
*/
|
||||
const getRequestAndCollectionVariables = (
|
||||
requestVariables: HoppRESTRequestVariables,
|
||||
collectionVariables: HoppInheritedProperty["variables"]
|
||||
) => {
|
||||
const reqVars = requestVariables
|
||||
.filter((v) => v.active)
|
||||
.map(({ key, value }) => ({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
}))
|
||||
|
||||
const collVars = transformInheritedCollectionVariablesToAggregateEnv(
|
||||
collectionVariables,
|
||||
false
|
||||
)
|
||||
|
||||
return [...reqVars, ...collVars]
|
||||
}
|
||||
|
||||
export class HoppEnvironmentPlugin {
|
||||
private compartment = new Compartment()
|
||||
|
||||
private envs: AggregateEnvironment[] = []
|
||||
|
||||
constructor(
|
||||
|
|
@ -392,18 +397,42 @@ 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
|
||||
const currentTabInheritedProperty = currentTab.document.inheritedProperties
|
||||
|
||||
this.envs = [...aggregateEnvs]
|
||||
if (!currentTabRequest || !currentTabInheritedProperty) return
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
environmentHighlightStyle(this.envs),
|
||||
]),
|
||||
})
|
||||
watch(
|
||||
[currentTabRequest, currentTabInheritedProperty],
|
||||
([request, document]) => {
|
||||
const requestAndCollVars = getRequestAndCollectionVariables(
|
||||
request.requestVariables,
|
||||
document.variables
|
||||
)
|
||||
|
||||
this.envs = [...requestAndCollVars, ...aggregateEnvs]
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
environmentHighlightStyle(this.envs),
|
||||
]),
|
||||
})
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
const requestAndCollVars = getRequestAndCollectionVariables(
|
||||
currentTabRequest.requestVariables,
|
||||
currentTabInheritedProperty.variables
|
||||
)
|
||||
|
||||
subscribeToStream(aggregateEnvsWithCurrentValue$, (envs) => {
|
||||
this.envs = [...envs]
|
||||
this.envs = [...requestAndCollVars, ...envs]
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
|
|
@ -424,7 +453,6 @@ export class HoppEnvironmentPlugin {
|
|||
|
||||
export class HoppReactiveEnvPlugin {
|
||||
private compartment = new Compartment()
|
||||
|
||||
private envs: AggregateEnvironment[] = []
|
||||
|
||||
constructor(
|
||||
|
|
@ -435,7 +463,6 @@ export class HoppReactiveEnvPlugin {
|
|||
envsRef,
|
||||
(envs) => {
|
||||
this.envs = envs
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ const generateRequestBodyExampleFromOpenAPIV2BodySchema = (
|
|||
)
|
||||
),
|
||||
O.map((schema) => schema.items as OpenAPIV2.ItemsObject),
|
||||
O.filter((items) => items != null), // Filter out null/undefined items
|
||||
O.filter((items) => items !== null), // Filter out null/undefined items
|
||||
O.map(generateExampleArrayFromOpenAPIV2ItemsObject)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export interface TeamCollection {
|
|||
title: string
|
||||
children: TeamCollection[] | null
|
||||
requests: TeamRequest[] | null
|
||||
data: string | null
|
||||
data?: string | null
|
||||
}
|
||||
|
||||
export const getSingleCollection = (collectionID: string) =>
|
||||
|
|
|
|||
|
|
@ -230,6 +230,10 @@ export default class NewTeamCollectionAdapter {
|
|||
private teamRequestOrderUpdatedSub: WSubscription | null
|
||||
private teamCollectionOrderUpdatedSub: WSubscription | null
|
||||
|
||||
//collection variables current value and secret value
|
||||
private secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
private currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
constructor(private teamID: string | null) {
|
||||
this.collections$ = new BehaviorSubject<TeamCollection[]>([])
|
||||
this.loadingCollections$ = new BehaviorSubject<string[]>([])
|
||||
|
|
@ -534,7 +538,8 @@ export default class NewTeamCollectionAdapter {
|
|||
private async moveCollection(
|
||||
collectionID: string,
|
||||
parentID: string | null,
|
||||
title: string
|
||||
title: string,
|
||||
data?: string | null
|
||||
) {
|
||||
// Remove the collection from the current position
|
||||
this.removeCollection(collectionID)
|
||||
|
|
@ -551,7 +556,7 @@ export default class NewTeamCollectionAdapter {
|
|||
children: null,
|
||||
requests: null,
|
||||
title: title,
|
||||
data: null,
|
||||
data,
|
||||
},
|
||||
parentID ?? null
|
||||
)
|
||||
|
|
@ -851,11 +856,11 @@ export default class NewTeamCollectionAdapter {
|
|||
)
|
||||
|
||||
const { teamCollectionMoved } = result.right
|
||||
const { id, parent, title } = teamCollectionMoved
|
||||
const { id, parent, title, data } = teamCollectionMoved
|
||||
|
||||
const parentID = parent?.id ?? null
|
||||
|
||||
this.moveCollection(id, parentID, title)
|
||||
this.moveCollection(id, parentID, title, data)
|
||||
})
|
||||
|
||||
const [teamRequestOrderUpdated$, teamRequestOrderUpdated] =
|
||||
|
|
@ -1043,17 +1048,13 @@ export default class NewTeamCollectionAdapter {
|
|||
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(
|
||||
return this.secretEnvironmentService.getSecretEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.value
|
||||
}
|
||||
return currentEnvironmentValueService.getEnvironmentVariable(
|
||||
return this.currentEnvironmentValueService.getEnvironmentVariable(
|
||||
collectionID,
|
||||
varIndex
|
||||
)?.currentValue
|
||||
|
|
@ -1190,6 +1191,7 @@ export default class NewTeamCollectionAdapter {
|
|||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
|
||||
variables.push({
|
||||
parentPath: path.slice(0, i + 1).join("/"),
|
||||
parentID: parentFolder.id ?? currentPath,
|
||||
parentName: parentFolder.title,
|
||||
inheritedVariables: this.populateValues(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export type HoppInheritedProperty = {
|
|||
inheritedHeader: HoppRESTHeader | GQLHeader
|
||||
}[]
|
||||
variables: {
|
||||
parentPath?: string
|
||||
parentID: string
|
||||
parentName: string
|
||||
inheritedVariables: HoppCollectionVariable[]
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ import { CurrentValueService } from "~/services/current-environment-value.servic
|
|||
import { getService } from "~/modules/dioc"
|
||||
import { HoppCollectionVariable } from "@hoppscotch/data"
|
||||
|
||||
//collection variables current value and secret value
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
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,
|
||||
|
|
@ -28,16 +28,19 @@ const getCurrentValue = (
|
|||
}
|
||||
|
||||
/**
|
||||
* Function to transform inherited collection variables into an array of `AggregateEnvironment` objects.
|
||||
* Transforms inherited collection variables into a normalized array of `AggregateEnvironment` objects.
|
||||
* Ensures no duplicate keys exist — the last encountered value overrides earlier ones.
|
||||
*
|
||||
* @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.
|
||||
* @param showSecret - Whether to reveal secret values or mask them.
|
||||
* @returns A de-duplicated array of `AggregateEnvironment` objects.
|
||||
*/
|
||||
export const transformInheritedCollectionVariablesToAggregateEnv = (
|
||||
variables: HoppInheritedProperty["variables"],
|
||||
showSecret: boolean = true
|
||||
): AggregateEnvironment[] => {
|
||||
return variables.flatMap(({ parentID, inheritedVariables }) =>
|
||||
// Flatten the inherited variables into a single array
|
||||
const flattened = variables.flatMap(({ parentID, inheritedVariables }) =>
|
||||
inheritedVariables.map(
|
||||
({ currentValue, initialValue, key, secret }, index) => ({
|
||||
key,
|
||||
|
|
@ -50,6 +53,14 @@ export const transformInheritedCollectionVariablesToAggregateEnv = (
|
|||
})
|
||||
)
|
||||
)
|
||||
|
||||
// Later values override earlier ones
|
||||
const mapByKey = new Map<string, AggregateEnvironment>()
|
||||
flattened.forEach((variable) => {
|
||||
mapByKey.set(variable.key, variable)
|
||||
})
|
||||
|
||||
return Array.from(mapByKey.values())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
|||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { CurrentValueService } from "~/services/current-environment-value.service"
|
||||
|
||||
//collection variables current value and secret value
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
|
||||
const defaultRESTCollectionState = {
|
||||
state: [
|
||||
makeCollection({
|
||||
|
|
@ -80,10 +84,6 @@ const getCurrentValue = (
|
|||
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,
|
||||
|
|
@ -167,7 +167,6 @@ export function cascadeParentCollectionForProperties(
|
|||
const parentFolderHeaders = parentFolder.headers as
|
||||
| HoppRESTHeaders
|
||||
| GQLHeader[]
|
||||
|
||||
const parentFolderVariables =
|
||||
parentFolder.variables as HoppCollectionVariable[]
|
||||
|
||||
|
|
@ -182,7 +181,6 @@ export function cascadeParentCollectionForProperties(
|
|||
inheritedAuth: auth.inheritedAuth,
|
||||
}
|
||||
}
|
||||
|
||||
if (parentFolderAuth?.authType !== "inherit") {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
|
|
@ -195,24 +193,17 @@ export function cascadeParentCollectionForProperties(
|
|||
if (parentFolderHeaders) {
|
||||
const activeHeaders = parentFolderHeaders.filter((h) => h.active)
|
||||
activeHeaders.forEach((header) => {
|
||||
const index = headers.findIndex(
|
||||
const idx = headers.findIndex(
|
||||
(h) => h.inheritedHeader?.key === header.key
|
||||
)
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
if (index !== -1) {
|
||||
// Replace the existing header with the same key
|
||||
headers[index] = {
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedHeader: header,
|
||||
}
|
||||
} else {
|
||||
headers.push({
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedHeader: header,
|
||||
})
|
||||
const headerObj = {
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedHeader: header,
|
||||
}
|
||||
if (idx !== -1) headers[idx] = headerObj
|
||||
else headers.push(headerObj)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +211,7 @@ export function cascadeParentCollectionForProperties(
|
|||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
|
||||
variables.push({
|
||||
parentPath: currentPath,
|
||||
parentID: parentFolder._ref_id ?? parentFolder.id ?? currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedVariables: populateValues(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ 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" }
|
||||
|
|
@ -438,19 +437,6 @@ 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
|
||||
|
|
@ -466,18 +452,6 @@ 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 =
|
||||
|
|
@ -520,19 +494,7 @@ 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 }) => {
|
||||
|
|
@ -545,22 +507,6 @@ export function getAggregateEnvs() {
|
|||
}
|
||||
}),
|
||||
|
||||
...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) {
|
||||
|
|
@ -592,22 +538,10 @@ 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 }) => {
|
||||
|
|
@ -620,21 +554,6 @@ export function getAggregateEnvsWithCurrentValue() {
|
|||
}
|
||||
}),
|
||||
|
||||
...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) {
|
||||
|
|
@ -683,97 +602,74 @@ export function getAggregateEnvsWithCurrentValue() {
|
|||
|
||||
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 secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const currentEnvironmentValueService = getService(CurrentValueService)
|
||||
return combineLatest([currentEnvironment$, globalEnv$]).pipe(
|
||||
map(([selectedEnv, globalEnv]) => {
|
||||
const results: AggregateEnvironment[] = []
|
||||
|
||||
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
|
||||
HOPP_SUPPORTED_PREDEFINED_VARIABLES.forEach(({ key, getValue }) => {
|
||||
results.push({
|
||||
key,
|
||||
currentValue: getValue(),
|
||||
initialValue: getValue(),
|
||||
secret: false,
|
||||
sourceEnv: selectedEnv?.name ?? "Global",
|
||||
})
|
||||
})
|
||||
|
||||
requestVariables.map(({ key, value, active }) => {
|
||||
if (active) {
|
||||
// Pre-defined variables
|
||||
HOPP_SUPPORTED_PREDEFINED_VARIABLES.forEach(({ key, getValue }) => {
|
||||
results.push({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
currentValue: getValue(),
|
||||
initialValue: getValue(),
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
sourceEnv: selectedEnv?.name ?? "Global",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
selectedEnv?.variables.map((x, index) => {
|
||||
let currentValue = x.currentValue
|
||||
if (x.secret) {
|
||||
currentValue =
|
||||
secretEnvironmentService.getSecretEnvironmentVariableValue(
|
||||
selectedEnv.id,
|
||||
index
|
||||
) ?? ""
|
||||
}
|
||||
results.push({
|
||||
key: x.key,
|
||||
currentValue:
|
||||
currentEnvironmentValueService.getEnvironmentVariableValue(
|
||||
selectedEnv.id,
|
||||
index
|
||||
) ?? currentValue,
|
||||
initialValue: x.initialValue,
|
||||
secret: x.secret,
|
||||
sourceEnv: selectedEnv.name,
|
||||
})
|
||||
})
|
||||
|
||||
globalEnv.variables.map((x, index) => {
|
||||
let currentValue = x.currentValue
|
||||
if (x.secret) {
|
||||
currentValue =
|
||||
secretEnvironmentService.getSecretEnvironmentVariableValue(
|
||||
"Global",
|
||||
index
|
||||
) ?? ""
|
||||
}
|
||||
results.push({
|
||||
key: x.key,
|
||||
currentValue:
|
||||
currentEnvironmentValueService.getEnvironmentVariableValue(
|
||||
"Global",
|
||||
index
|
||||
) ?? currentValue,
|
||||
initialValue: x.initialValue,
|
||||
secret: x.secret,
|
||||
sourceEnv: "Global",
|
||||
selectedEnv?.variables.map((x, index) => {
|
||||
let currentValue = x.currentValue
|
||||
if (x.secret) {
|
||||
currentValue =
|
||||
secretEnvironmentService.getSecretEnvironmentVariableValue(
|
||||
selectedEnv.id,
|
||||
index
|
||||
) ?? ""
|
||||
}
|
||||
results.push({
|
||||
key: x.key,
|
||||
currentValue:
|
||||
currentEnvironmentValueService.getEnvironmentVariableValue(
|
||||
selectedEnv.id,
|
||||
index
|
||||
) ?? currentValue,
|
||||
initialValue: x.initialValue,
|
||||
secret: x.secret,
|
||||
sourceEnv: selectedEnv.name,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return results
|
||||
}),
|
||||
distinctUntilChanged(isEqual)
|
||||
)
|
||||
globalEnv.variables.map((x, index) => {
|
||||
let currentValue = x.currentValue
|
||||
if (x.secret) {
|
||||
currentValue =
|
||||
secretEnvironmentService.getSecretEnvironmentVariableValue(
|
||||
"Global",
|
||||
index
|
||||
) ?? ""
|
||||
}
|
||||
results.push({
|
||||
key: x.key,
|
||||
currentValue:
|
||||
currentEnvironmentValueService.getEnvironmentVariableValue(
|
||||
"Global",
|
||||
index
|
||||
) ?? currentValue,
|
||||
initialValue: x.initialValue,
|
||||
secret: x.secret,
|
||||
sourceEnv: "Global",
|
||||
})
|
||||
})
|
||||
|
||||
return results
|
||||
}),
|
||||
distinctUntilChanged(isEqual)
|
||||
)
|
||||
})()
|
||||
|
||||
export function getCurrentEnvironment(): Environment {
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export interface InspectorResult {
|
|||
action?: {
|
||||
text: string
|
||||
apply: () => void
|
||||
showAction?: boolean
|
||||
}
|
||||
doc?: {
|
||||
text: string
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ import { transformInheritedCollectionVariablesToAggregateEnv } from "~/helpers/u
|
|||
|
||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||
|
||||
const isENVInString = (str: string) => {
|
||||
return HOPP_ENVIRONMENT_REGEX.test(str)
|
||||
}
|
||||
const isENVInString = (str: string) => HOPP_ENVIRONMENT_REGEX.test(str)
|
||||
|
||||
/**
|
||||
* This inspector is responsible for inspecting the environment variables of a input.
|
||||
|
|
@ -64,19 +62,28 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||
}
|
||||
|
||||
/**
|
||||
* Validates the environment variables in the target array
|
||||
* Looks for environment variables in an array of strings.
|
||||
* Reports variables that are referenced but not defined.
|
||||
* @param target The target array to validate
|
||||
* @param locations The location where results are to be displayed
|
||||
* @returns The results array containing the results of the validation
|
||||
*/
|
||||
private validateEnvironmentVariables = (
|
||||
target: any[],
|
||||
target: string[],
|
||||
locations: InspectorLocation
|
||||
) => {
|
||||
const newErrors: InspectorResult[] = []
|
||||
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
|
||||
// Get the current request or example-response request
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: null
|
||||
|
||||
// inherited collection-level variables
|
||||
const collectionVariables =
|
||||
currentTab.document.type === "request" ||
|
||||
currentTab.document.type === "example-response"
|
||||
|
|
@ -85,70 +92,82 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||
)
|
||||
: []
|
||||
|
||||
const environmentVariables = [
|
||||
...this.aggregateEnvsWithValue.value,
|
||||
...collectionVariables,
|
||||
]
|
||||
// request variables (active only)
|
||||
const requestVariables =
|
||||
currentTabRequest?.requestVariables
|
||||
.filter((v) => v.active)
|
||||
.map(({ key, value }) => ({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})) ?? []
|
||||
|
||||
// combine everything into one list
|
||||
const environmentVariables = [
|
||||
...requestVariables,
|
||||
...collectionVariables,
|
||||
...this.aggregateEnvsWithValue.value,
|
||||
]
|
||||
const envKeys = environmentVariables.map((e) => e.key)
|
||||
|
||||
// Scan each string for <<VAR>> patterns
|
||||
target.forEach((element, index) => {
|
||||
if (isENVInString(element)) {
|
||||
const extractedEnv = element.match(HOPP_ENVIRONMENT_REGEX)
|
||||
if (!isENVInString(element)) return
|
||||
const matches = element.match(HOPP_ENVIRONMENT_REGEX)
|
||||
matches?.forEach((exEnv) => {
|
||||
const formattedExEnv = exEnv.slice(2, -2)
|
||||
const itemLocation: InspectorLocation = {
|
||||
type: locations.type,
|
||||
position:
|
||||
locations.type === "url" ||
|
||||
locations.type === "body" ||
|
||||
locations.type === "response" ||
|
||||
locations.type === "body-content-type-header"
|
||||
? "key"
|
||||
: locations.position,
|
||||
index,
|
||||
key: element,
|
||||
}
|
||||
|
||||
if (extractedEnv) {
|
||||
extractedEnv.forEach((exEnv: string) => {
|
||||
const formattedExEnv = exEnv.slice(2, -2)
|
||||
const itemLocation: InspectorLocation = {
|
||||
type: locations.type,
|
||||
position:
|
||||
locations.type === "url" ||
|
||||
locations.type === "body" ||
|
||||
locations.type === "response" ||
|
||||
locations.type === "body-content-type-header"
|
||||
? "key"
|
||||
: locations.position,
|
||||
index: index,
|
||||
key: element,
|
||||
}
|
||||
if (!envKeys.includes(formattedExEnv)) {
|
||||
newErrors.push({
|
||||
id: `environment-not-found-${newErrors.length}`,
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("inspections.environment.not_found", {
|
||||
environment: exEnv,
|
||||
}),
|
||||
},
|
||||
icon: markRaw(IconPlusCircle),
|
||||
action: {
|
||||
text: this.t("inspections.environment.add_environment"),
|
||||
apply: () => {
|
||||
invokeAction("modals.environment.add", {
|
||||
envName: formattedExEnv,
|
||||
variableName: "",
|
||||
})
|
||||
},
|
||||
},
|
||||
severity: 3,
|
||||
isApplicable: true,
|
||||
locations: itemLocation,
|
||||
doc: {
|
||||
text: this.t("action.learn_more"),
|
||||
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
||||
},
|
||||
})
|
||||
}
|
||||
// If the variable doesn't exist, add an inspection
|
||||
if (!envKeys.includes(formattedExEnv)) {
|
||||
newErrors.push({
|
||||
id: `environment-not-found-${newErrors.length}`,
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("inspections.environment.not_found", {
|
||||
environment: exEnv,
|
||||
}),
|
||||
},
|
||||
icon: markRaw(IconPlusCircle),
|
||||
action: {
|
||||
text: this.t("inspections.environment.add_environment"),
|
||||
apply: () =>
|
||||
invokeAction("modals.environment.add", {
|
||||
envName: formattedExEnv,
|
||||
variableName: "",
|
||||
}),
|
||||
showAction: true,
|
||||
},
|
||||
severity: 3,
|
||||
isApplicable: true,
|
||||
locations: itemLocation,
|
||||
doc: {
|
||||
text: this.t("action.learn_more"),
|
||||
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return newErrors
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the environment list to a list with unique keys with value
|
||||
* Keeps only unique environment variables and prefers ones with values.
|
||||
* @param envs The environment list to be transformed
|
||||
* @returns The transformed environment list with keys with value
|
||||
*/
|
||||
|
|
@ -160,7 +179,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||
envs.forEach((env) => {
|
||||
if (envsMap.has(env.key)) {
|
||||
const existingEnv = envsMap.get(env.key)
|
||||
|
||||
// Replace if existing is empty and this one has a value
|
||||
if (existingEnv?.currentValue === "" && env.currentValue !== "") {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
|
|
@ -173,243 +192,222 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if the environment variables in the target have empty current value or initial value.
|
||||
* Looks for variables that exist but are empty (no value or secret).
|
||||
* Suggests adding a value for them.
|
||||
* @param target The target array to validate
|
||||
* @param locations The location where results are to be displayed
|
||||
* @returns The results array containing the results of the validation
|
||||
*/
|
||||
private validateEmptyEnvironmentVariables = (
|
||||
target: any[],
|
||||
target: string[],
|
||||
locations: InspectorLocation
|
||||
) => {
|
||||
const newErrors: InspectorResult[] = []
|
||||
|
||||
target.forEach((element, index) => {
|
||||
if (isENVInString(element)) {
|
||||
const extractedEnv = element.match(HOPP_ENVIRONMENT_REGEX)
|
||||
if (!isENVInString(element)) return
|
||||
const matches = element.match(HOPP_ENVIRONMENT_REGEX)
|
||||
matches?.forEach((exEnv) => {
|
||||
const formattedExEnv = exEnv.slice(2, -2)
|
||||
const currentSelectedEnvironment = getCurrentEnvironment()
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
|
||||
if (extractedEnv) {
|
||||
extractedEnv.forEach((exEnv: string) => {
|
||||
const formattedExEnv = exEnv.slice(2, -2)
|
||||
const currentSelectedEnvironment = getCurrentEnvironment()
|
||||
// Get current request or example
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: null
|
||||
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
const collectionVariables =
|
||||
currentTab.document.type === "request" ||
|
||||
currentTab.document.type === "example-response"
|
||||
? transformInheritedCollectionVariablesToAggregateEnv(
|
||||
currentTab.document.inheritedProperties?.variables ?? [],
|
||||
false
|
||||
)
|
||||
: []
|
||||
// request variables (active only)
|
||||
const requestVariables =
|
||||
currentTabRequest?.requestVariables
|
||||
.filter((v) => v.active)
|
||||
.map(({ key, value }) => ({
|
||||
key,
|
||||
currentValue: value,
|
||||
initialValue: value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})) ?? []
|
||||
|
||||
const environmentVariables =
|
||||
this.filterNonEmptyEnvironmentVariables([
|
||||
...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(
|
||||
tooltipSourceEnvID,
|
||||
env.key
|
||||
// inherited collection variables
|
||||
const collectionVariables =
|
||||
currentTab.document.type === "request" ||
|
||||
currentTab.document.type === "example-response"
|
||||
? transformInheritedCollectionVariablesToAggregateEnv(
|
||||
currentTab.document.inheritedProperties?.variables ?? [],
|
||||
false
|
||||
)
|
||||
: []
|
||||
|
||||
const hasValue =
|
||||
this.currentEnvs.hasValue(
|
||||
env.sourceEnv !== "Global"
|
||||
? currentSelectedEnvironment.id
|
||||
: "Global",
|
||||
env.key
|
||||
) ||
|
||||
env.currentValue !== "" ||
|
||||
env.initialValue !== ""
|
||||
// Merge all variables
|
||||
const environmentVariables = this.filterNonEmptyEnvironmentVariables([
|
||||
...requestVariables,
|
||||
...collectionVariables,
|
||||
...this.aggregateEnvsWithValue.value,
|
||||
])
|
||||
|
||||
if (env.key === formattedExEnv) {
|
||||
if (env.secret ? !hasSecretEnv : !hasValue) {
|
||||
const itemLocation: InspectorLocation = {
|
||||
type: locations.type,
|
||||
position:
|
||||
locations.type === "url" ||
|
||||
locations.type === "body" ||
|
||||
locations.type === "response" ||
|
||||
locations.type === "body-content-type-header"
|
||||
? "key"
|
||||
: locations.position,
|
||||
index: index,
|
||||
key: element,
|
||||
}
|
||||
// Check each variable for missing values
|
||||
environmentVariables.forEach((env) => {
|
||||
const sourceEnvID =
|
||||
env.sourceEnv === "Global"
|
||||
? "Global"
|
||||
: env.sourceEnv === "CollectionVariable"
|
||||
? env.sourceEnvID!
|
||||
: currentSelectedEnvironment.id
|
||||
|
||||
const currentEnvironmentType = getSelectedEnvironmentType()
|
||||
const hasSecretEnv = this.secretEnvs.hasSecretValue(
|
||||
sourceEnvID,
|
||||
env.key
|
||||
)
|
||||
|
||||
let invokeActionType:
|
||||
| "modals.my.environment.edit"
|
||||
| "modals.team.environment.edit"
|
||||
| "modals.global.environment.update" =
|
||||
"modals.my.environment.edit"
|
||||
const hasValue =
|
||||
this.currentEnvs.hasValue(
|
||||
env.sourceEnv !== "Global"
|
||||
? currentSelectedEnvironment.id
|
||||
: "Global",
|
||||
env.key
|
||||
) ||
|
||||
env.currentValue !== "" ||
|
||||
env.initialValue !== ""
|
||||
|
||||
if (env.sourceEnv === "Global") {
|
||||
invokeActionType = "modals.global.environment.update"
|
||||
} else if (currentEnvironmentType === "MY_ENV") {
|
||||
invokeActionType = "modals.my.environment.edit"
|
||||
} else if (currentEnvironmentType === "TEAM_ENV") {
|
||||
invokeActionType = "modals.team.environment.edit"
|
||||
if (env.key !== formattedExEnv) return
|
||||
|
||||
// Flag variables that are empty
|
||||
if (env.secret ? !hasSecretEnv : !hasValue) {
|
||||
const itemLocation: InspectorLocation = {
|
||||
type: locations.type,
|
||||
position:
|
||||
locations.type === "url" ||
|
||||
locations.type === "body" ||
|
||||
locations.type === "response" ||
|
||||
locations.type === "body-content-type-header"
|
||||
? "key"
|
||||
: locations.position,
|
||||
index,
|
||||
key: element,
|
||||
}
|
||||
|
||||
// Pick the right modal to open for editing
|
||||
const currentEnvironmentType = getSelectedEnvironmentType()
|
||||
const invokeActionType:
|
||||
| "modals.my.environment.edit"
|
||||
| "modals.team.environment.edit"
|
||||
| "modals.global.environment.update" =
|
||||
env.sourceEnv === "Global"
|
||||
? "modals.global.environment.update"
|
||||
: currentEnvironmentType === "TEAM_ENV"
|
||||
? "modals.team.environment.edit"
|
||||
: "modals.my.environment.edit"
|
||||
|
||||
newErrors.push({
|
||||
id: `environment-empty-${newErrors.length}`,
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("inspections.environment.empty_value", {
|
||||
variable: exEnv,
|
||||
}),
|
||||
},
|
||||
icon: markRaw(IconPlusCircle),
|
||||
action: {
|
||||
text: this.t("inspections.environment.add_environment_value"),
|
||||
apply: () => {
|
||||
// If it's a request variable, open the requestVariables tab
|
||||
if (
|
||||
env.sourceEnv === "RequestVariable" &&
|
||||
currentTab.document.type === "request"
|
||||
) {
|
||||
currentTab.document.optionTabPreference = "requestVariables"
|
||||
} else {
|
||||
invokeActionType = "modals.my.environment.edit"
|
||||
invokeAction(invokeActionType, {
|
||||
envName:
|
||||
env.sourceEnv === "Global"
|
||||
? "Global"
|
||||
: currentSelectedEnvironment.name,
|
||||
variableName: formattedExEnv,
|
||||
isSecret: env.secret,
|
||||
})
|
||||
}
|
||||
|
||||
newErrors.push({
|
||||
id: `environment-empty-${newErrors.length}`,
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("inspections.environment.empty_value", {
|
||||
variable: exEnv,
|
||||
}),
|
||||
},
|
||||
icon: markRaw(IconPlusCircle),
|
||||
action: {
|
||||
text: this.t(
|
||||
"inspections.environment.add_environment_value"
|
||||
),
|
||||
apply: () => {
|
||||
if (
|
||||
env.sourceEnv === "RequestVariable" &&
|
||||
currentTab.document.type === "request"
|
||||
) {
|
||||
currentTab.document.optionTabPreference =
|
||||
"requestVariables"
|
||||
} else {
|
||||
invokeAction(invokeActionType, {
|
||||
envName:
|
||||
env.sourceEnv === "Global"
|
||||
? "Global"
|
||||
: currentSelectedEnvironment.name,
|
||||
variableName: formattedExEnv,
|
||||
isSecret: env.secret,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
severity: 2,
|
||||
isApplicable: true,
|
||||
locations: itemLocation,
|
||||
doc: {
|
||||
text: this.t("action.learn_more"),
|
||||
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
showAction: env.sourceEnv !== "CollectionVariable", // skip collection vars for now
|
||||
},
|
||||
severity: 2,
|
||||
isApplicable: true,
|
||||
locations: itemLocation,
|
||||
doc: {
|
||||
text: this.t("action.learn_more"),
|
||||
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return newErrors
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all inspections for a given request and returns a computed list of results.
|
||||
*/
|
||||
getInspections(
|
||||
req: Readonly<Ref<HoppRESTRequest | HoppRESTResponseOriginalRequest>>
|
||||
) {
|
||||
return computed(() => {
|
||||
const results: InspectorResult[] = []
|
||||
|
||||
if (!req.value) return results
|
||||
|
||||
const headers = req.value.headers
|
||||
|
||||
const params = req.value.params
|
||||
|
||||
/**
|
||||
* Validate the environment variables in the URL
|
||||
*/
|
||||
const url = req.value.endpoint
|
||||
const { endpoint, headers, params } = req.value
|
||||
|
||||
// URL check
|
||||
results.push(
|
||||
...this.validateEnvironmentVariables([url], {
|
||||
type: "url",
|
||||
})
|
||||
)
|
||||
results.push(
|
||||
...this.validateEmptyEnvironmentVariables([url], {
|
||||
type: "url",
|
||||
})
|
||||
...this.validateEnvironmentVariables([endpoint], { type: "url" }),
|
||||
...this.validateEmptyEnvironmentVariables([endpoint], { type: "url" })
|
||||
)
|
||||
|
||||
/**
|
||||
* Validate the environment variables in the headers
|
||||
*/
|
||||
const headerKeys = Object.values(headers).map((header) => header.key)
|
||||
// Header keys and values
|
||||
const headerKeys = Object.values(headers).map((h) => h.key)
|
||||
const headerValues = Object.values(headers).map((h) => h.value)
|
||||
|
||||
results.push(
|
||||
...this.validateEnvironmentVariables(headerKeys, {
|
||||
type: "header",
|
||||
position: "key",
|
||||
})
|
||||
)
|
||||
results.push(
|
||||
}),
|
||||
...this.validateEmptyEnvironmentVariables(headerKeys, {
|
||||
type: "header",
|
||||
position: "key",
|
||||
})
|
||||
)
|
||||
|
||||
const headerValues = Object.values(headers).map((header) => header.value)
|
||||
|
||||
results.push(
|
||||
}),
|
||||
...this.validateEnvironmentVariables(headerValues, {
|
||||
type: "header",
|
||||
position: "value",
|
||||
})
|
||||
)
|
||||
results.push(
|
||||
}),
|
||||
...this.validateEmptyEnvironmentVariables(headerValues, {
|
||||
type: "header",
|
||||
position: "value",
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Validate the environment variables in the parameters
|
||||
*/
|
||||
const paramsKeys = Object.values(params).map((param) => param.key)
|
||||
// Parameter keys and values
|
||||
const paramKeys = Object.values(params).map((p) => p.key)
|
||||
const paramValues = Object.values(params).map((p) => p.value)
|
||||
|
||||
results.push(
|
||||
...this.validateEnvironmentVariables(paramsKeys, {
|
||||
...this.validateEnvironmentVariables(paramKeys, {
|
||||
type: "parameter",
|
||||
position: "key",
|
||||
})
|
||||
)
|
||||
results.push(
|
||||
...this.validateEmptyEnvironmentVariables(paramsKeys, {
|
||||
}),
|
||||
...this.validateEmptyEnvironmentVariables(paramKeys, {
|
||||
type: "parameter",
|
||||
position: "key",
|
||||
})
|
||||
)
|
||||
|
||||
const paramsValues = Object.values(params).map((param) => param.value)
|
||||
|
||||
results.push(
|
||||
...this.validateEnvironmentVariables(paramsValues, {
|
||||
}),
|
||||
...this.validateEnvironmentVariables(paramValues, {
|
||||
type: "parameter",
|
||||
position: "value",
|
||||
})
|
||||
)
|
||||
|
||||
results.push(
|
||||
...this.validateEmptyEnvironmentVariables(paramsValues, {
|
||||
}),
|
||||
...this.validateEmptyEnvironmentVariables(paramValues, {
|
||||
type: "parameter",
|
||||
position: "value",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ const HoppInheritedPropertySchema = z
|
|||
variables: z
|
||||
.array(
|
||||
z.object({
|
||||
parentPath: z.optional(z.string()),
|
||||
parentID: z.string(),
|
||||
parentName: z.string(),
|
||||
inheritedVariables: z.array(CollectionVariable),
|
||||
|
|
|
|||
Loading…
Reference in a new issue