feat: import environments from insomnia (#3625)

Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
Akash K
2023-12-13 19:15:39 +05:30
committed by GitHub
parent 3ae49ca483
commit a8cc569786
5 changed files with 172 additions and 10 deletions

View File

@@ -392,7 +392,8 @@
"hoppscotch_environment": "Hoppscotch Environment",
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file",
"postman_environment": "Postman Environment",
"postman_environment_description": "Import Postman Environment JSON file",
"postman_environment_description": "Import Postman Environment from a JSON file",
"insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file",
"environments_from_gist": "Import From Gist",
"environments_from_gist_description": "Import Hoppscotch Environments From Gist",
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist"
@@ -968,4 +969,4 @@
"team": "Team Workspace",
"title": "Workspaces"
}
}
}

View File

@@ -18,16 +18,22 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo
import { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv"
import * as E from "fp-ts/Either"
import { appendEnvironments, environments$ } from "~/newstore/environments"
import {
appendEnvironments,
addGlobalEnvVariable,
environments$,
} from "~/newstore/environments"
import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
import { GQLError } from "~/helpers/backend/GQLClient"
import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql"
import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv"
import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv"
import IconFolderPlus from "~icons/lucide/folder-plus"
import IconPostman from "~icons/hopp/postman"
import IconInsomnia from "~icons/hopp/insomnia"
import IconUser from "~icons/lucide/user"
import { initializeDownloadCollection } from "~/helpers/import-export/export"
import { computed } from "vue"
@@ -136,6 +142,51 @@ const PostmanEnvironmentsImport: ImporterOrExporter = {
}),
}
const insomniaEnvironmentsImport: ImporterOrExporter = {
metadata: {
id: "import.from_insomnia",
name: "import.from_insomnia",
icon: IconInsomnia,
title: "import.from_json",
applicableTo: ["personal-workspace", "team-workspace"],
disabled: false,
},
component: FileSource({
acceptedFileTypes: "application/json",
caption: "import.insomnia_environment_description",
onImportFromFile: async (environments) => {
const res = await insomniaEnvImporter(environments)()
if (E.isLeft(res)) {
showImportFailedError()
return
}
const globalEnvIndex = res.right.findIndex(
(env) => env.name === "Base Environment"
)
const globalEnv =
globalEnvIndex !== -1 ? res.right[globalEnvIndex] : undefined
// remove the global env from the environments array to prevent it from being imported twice
if (globalEnvIndex !== -1) {
res.right.splice(globalEnvIndex, 1)
}
handleImportToStore(res.right, globalEnv)
platform.analytics?.logEvent({
type: "HOPP_IMPORT_ENVIRONMENT",
platform: "rest",
workspaceType: isTeamEnvironment.value ? "team" : "personal",
})
emit("hide-modal")
},
}),
}
const EnvironmentsImportFromGIST: ImporterOrExporter = {
metadata: {
id: "import.environments_from_gist",
@@ -255,6 +306,7 @@ const importerModules = [
HoppEnvironmentsImport,
EnvironmentsImportFromGIST,
PostmanEnvironmentsImport,
insomniaEnvironmentsImport,
]
const exporterModules = computed(() => {
@@ -271,7 +323,17 @@ const showImportFailedError = () => {
toast.error(t("import.failed").toString())
}
const handleImportToStore = async (environments: Environment[]) => {
const handleImportToStore = async (
environments: Environment[],
globalEnv?: Environment
) => {
// if there's a global env, add them to the store
if (globalEnv) {
globalEnv.variables.forEach(({ key, value }) => {
addGlobalEnvVariable({ key, value })
})
}
if (props.environmentType === "MY_ENV") {
appendEnvironments(environments)
toast.success(t("state.file_imported"))

View File

@@ -0,0 +1,16 @@
import yaml from "js-yaml"
import * as O from "fp-ts/Option"
import { safeParseJSON } from "./json"
import { pipe } from "fp-ts/function"
export const safeParseYAML = (str: string) => O.tryCatch(() => yaml.load(str))
export const safeParseJSONOrYAML = (str: string) =>
pipe(
str,
safeParseJSON,
O.match(
() => safeParseYAML(str),
(data) => O.of(data)
)
)

View File

@@ -1,5 +1,5 @@
import { convert, ImportRequest } from "insomnia-importers"
import { pipe, flow } from "fp-ts/function"
import { pipe } from "fp-ts/function"
import {
HoppRESTAuth,
HoppRESTHeader,
@@ -12,10 +12,10 @@ import {
makeCollection,
} from "@hoppscotch/data"
import * as A from "fp-ts/Array"
import * as S from "fp-ts/string"
import * as TO from "fp-ts/TaskOption"
import * as TE from "fp-ts/TaskEither"
import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { replaceInsomniaTemplating } from "./insomniaEnv"
// TODO: Insomnia allows custom prefixes for Bearer token auth, Hoppscotch doesn't. We just ignore the prefix for now
@@ -32,10 +32,8 @@ type InsomniaRequestResource = ImportRequest & { _type: "request" }
const parseInsomniaDoc = (content: string) =>
TO.tryCatch(() => convert(content))
const replaceVarTemplating = flow(
S.replace(/{{\s*/g, "<<"),
S.replace(/\s*}}/g, ">>")
)
const replaceVarTemplating = (expression: string) =>
replaceInsomniaTemplating(expression)
const getFoldersIn = (
folder: InsomniaFolderResource | null,

View File

@@ -0,0 +1,85 @@
import * as TE from "fp-ts/TaskEither"
import * as O from "fp-ts/Option"
import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { z } from "zod"
import { Environment } from "@hoppscotch/data"
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
const insomniaResourcesSchema = z.object({
resources: z.array(
z
.object({
_type: z.string(),
})
.passthrough()
),
})
const insomniaEnvSchema = z.object({
_type: z.literal("environment"),
name: z.string(),
data: z.record(z.string()),
})
export const replaceInsomniaTemplating = (expression: string) => {
const regex = /\{\{ _\.([^}]+) \}\}/g
return expression.replaceAll(regex, "<<$1>>")
}
export const insomniaEnvImporter = (content: string) => {
const parsedContent = safeParseJSONOrYAML(content)
if (O.isNone(parsedContent)) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
const validationResult = insomniaResourcesSchema.safeParse(
parsedContent.value
)
if (!validationResult.success) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
const insomniaEnvs = validationResult.data.resources
.filter((resource) => resource._type === "environment")
.map((envResource) => {
const envResourceData = envResource.data as Record<string, unknown>
const stringifiedData: Record<string, string> = {}
Object.keys(envResourceData).forEach((key) => {
stringifiedData[key] = String(envResourceData[key])
})
return { ...envResource, data: stringifiedData }
})
const environments: Environment[] = []
insomniaEnvs.forEach((insomniaEnv) => {
const parsedInsomniaEnv = insomniaEnvSchema.safeParse(insomniaEnv)
if (parsedInsomniaEnv.success) {
const environment: Environment = {
name: parsedInsomniaEnv.data.name,
variables: Object.entries(parsedInsomniaEnv.data.data).map(
([key, value]) => ({ key, value })
),
}
environments.push(environment)
}
})
const processedEnvironments = environments.map((env) => ({
...env,
variables: env.variables.map((variable) => ({
...variable,
value: replaceInsomniaTemplating(variable.value),
})),
}))
return TE.right(processedEnvironments)
}