feat: import collections from URL (#2262)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
// TODO: Add validation to output
|
||||
const fetchGist = (
|
||||
url: string
|
||||
): TO.TaskOption<HoppCollection<HoppRESTRequest>> =>
|
||||
): TO.TaskOption<HoppCollection<HoppRESTRequest>[]> =>
|
||||
pipe(
|
||||
TO.tryCatch(() =>
|
||||
axios.get(`https://api.github.com/gists/${url.split("/").pop()}`, {
|
||||
@@ -30,8 +30,10 @@ const fetchGist = (
|
||||
)
|
||||
|
||||
export default defineImporter({
|
||||
id: "gist",
|
||||
name: "import.from_gist",
|
||||
icon: "github",
|
||||
applicableTo: ["my-collections", "team-collections"],
|
||||
steps: [
|
||||
step({
|
||||
stepName: "URL_IMPORT",
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { translateToNewRESTCollection } from "@hoppscotch/data"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import {
|
||||
translateToNewRESTCollection,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import _isPlainObject from "lodash/isPlainObject"
|
||||
import { step } from "../steps"
|
||||
import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { safeParseJSON } from "~/helpers/functional/json"
|
||||
|
||||
export default defineImporter({
|
||||
id: "hoppscotch",
|
||||
name: "import.from_json",
|
||||
icon: "folder-plus",
|
||||
applicableTo: ["my-collections", "team-collections", "url-import"],
|
||||
steps: [
|
||||
step({
|
||||
stepName: "FILE_IMPORT",
|
||||
@@ -19,16 +28,40 @@ export default defineImporter({
|
||||
] as const,
|
||||
importer: ([content]) =>
|
||||
pipe(
|
||||
E.tryCatch(
|
||||
() => {
|
||||
const x = JSON.parse(content)
|
||||
|
||||
return Array.isArray(x)
|
||||
? x.map((coll: any) => translateToNewRESTCollection(coll))
|
||||
: [translateToNewRESTCollection(x)]
|
||||
},
|
||||
() => IMPORTER_INVALID_FILE_FORMAT
|
||||
safeParseJSON(content),
|
||||
O.chain(
|
||||
flow(
|
||||
makeCollectionsArray,
|
||||
RA.map(
|
||||
flow(
|
||||
O.fromPredicate(isValidCollection),
|
||||
O.map(translateToNewRESTCollection)
|
||||
)
|
||||
),
|
||||
O.sequenceArray,
|
||||
O.map(RA.toArray)
|
||||
)
|
||||
),
|
||||
TE.fromEither
|
||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
),
|
||||
})
|
||||
|
||||
/**
|
||||
* checks if a value is a plain object
|
||||
*/
|
||||
const isPlainObject = (value: any): value is object => _isPlainObject(value)
|
||||
|
||||
/**
|
||||
* checks if a collection matches the schema for a hoppscotch collection.
|
||||
* as of now we are only checking if the collection has a "v" key in it.
|
||||
*/
|
||||
const isValidCollection = (
|
||||
collection: unknown
|
||||
): collection is HoppCollection<HoppRESTRequest> =>
|
||||
isPlainObject(collection) && "v" in collection
|
||||
|
||||
/**
|
||||
* convert single collection object into an array so it can be handled the same as multiple collections
|
||||
*/
|
||||
const makeCollectionsArray = (collections: unknown | unknown[]): unknown[] =>
|
||||
Array.isArray(collections) ? collections : [collections]
|
||||
|
||||
@@ -13,3 +13,10 @@ export const RESTCollectionImporters = [
|
||||
GistImporter,
|
||||
MyCollectionsImporter,
|
||||
] as const
|
||||
|
||||
export const URLImporters = [
|
||||
HoppRESTCollImporter,
|
||||
OpenAPIImporter,
|
||||
PostmanImporter,
|
||||
InsomniaImporter,
|
||||
] as const
|
||||
|
||||
@@ -13,10 +13,18 @@ type HoppImporter<T, StepsType, Errors> = (
|
||||
stepValues: StepsOutputList<StepsType>
|
||||
) => TE.TaskEither<Errors, T>
|
||||
|
||||
type HoppImporterApplicableTo = Array<
|
||||
"team-collections" | "my-collections" | "url-import"
|
||||
>
|
||||
|
||||
/**
|
||||
* Definition for importers
|
||||
*/
|
||||
type HoppImporterDefinition<T, Y, E> = {
|
||||
/**
|
||||
* the id
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* Name of the importer, shown on the Select Importer dropdown
|
||||
*/
|
||||
@@ -30,7 +38,7 @@ type HoppImporterDefinition<T, Y, E> = {
|
||||
/**
|
||||
* Identifier for the importer
|
||||
*/
|
||||
applicableTo?: Array<"team-collections" | "my-collections">
|
||||
applicableTo: HoppImporterApplicableTo
|
||||
|
||||
/**
|
||||
* The importer function, It is a Promise because its supposed to be loaded in lazily (dynamic imports ?)
|
||||
@@ -47,10 +55,11 @@ type HoppImporterDefinition<T, Y, E> = {
|
||||
* Defines a Hoppscotch importer
|
||||
*/
|
||||
export const defineImporter = <ReturnType, StepType, Errors>(input: {
|
||||
id: string
|
||||
name: string
|
||||
icon: string
|
||||
importer: HoppImporter<ReturnType, StepType, Errors>
|
||||
applicableTo?: Array<"team-collections" | "my-collections">
|
||||
applicableTo: HoppImporterApplicableTo
|
||||
steps: StepType
|
||||
}) => {
|
||||
return <HoppImporterDefinition<ReturnType, StepType, Errors>>{
|
||||
|
||||
@@ -210,7 +210,9 @@ const getHoppCollections = (doc: InsomniaDoc) =>
|
||||
)
|
||||
|
||||
export default defineImporter({
|
||||
id: "insomnia",
|
||||
name: "import.from_insomnia",
|
||||
applicableTo: ["my-collections", "team-collections", "url-import"],
|
||||
icon: "insomnia",
|
||||
steps: [
|
||||
step({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { defineImporter } from "."
|
||||
import { getRESTCollection } from "~/newstore/collections"
|
||||
|
||||
export default defineImporter({
|
||||
id: "myCollections",
|
||||
name: "import.from_my_collections",
|
||||
icon: "user",
|
||||
applicableTo: ["team-collections"],
|
||||
|
||||
@@ -27,7 +27,7 @@ import * as RA from "fp-ts/ReadonlyArray"
|
||||
import { step } from "../steps"
|
||||
import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
|
||||
const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||
|
||||
// TODO: URL Import Support
|
||||
|
||||
@@ -586,7 +586,9 @@ const parseOpenAPIDocContent = (str: string) =>
|
||||
)
|
||||
|
||||
export default defineImporter({
|
||||
id: "openapi",
|
||||
name: "import.from_openapi",
|
||||
applicableTo: ["my-collections", "team-collections", "url-import"],
|
||||
icon: "file",
|
||||
steps: [
|
||||
step({
|
||||
@@ -603,7 +605,6 @@ export default defineImporter({
|
||||
fileContent,
|
||||
parseOpenAPIDocContent,
|
||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT),
|
||||
|
||||
// Try validating, else the importer is invalid file format
|
||||
TE.chainW((obj) =>
|
||||
pipe(
|
||||
@@ -613,7 +614,6 @@ export default defineImporter({
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
// Deference the references
|
||||
TE.chainW((obj) =>
|
||||
pipe(
|
||||
@@ -623,7 +623,6 @@ export default defineImporter({
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
TE.chainW(convertOpenApiDocToHopp)
|
||||
),
|
||||
})
|
||||
|
||||
@@ -297,7 +297,9 @@ const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection<HoppRESTRequest> =>
|
||||
export const getHoppCollection = (coll: PMCollection) => getHoppFolder(coll)
|
||||
|
||||
export default defineImporter({
|
||||
id: "postman",
|
||||
name: "import.from_postman",
|
||||
applicableTo: ["my-collections", "team-collections", "url-import"],
|
||||
icon: "postman",
|
||||
steps: [
|
||||
step({
|
||||
|
||||
Reference in New Issue
Block a user