feat: collection import summaries (#4489)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
@@ -502,8 +502,11 @@
|
||||
"from_file": "Import from File",
|
||||
"from_gist": "Import from Gist",
|
||||
"from_gist_description": "Import from Gist URL",
|
||||
"from_gist_import_summary": "All hoppscotch features are imported.",
|
||||
"from_hoppscotch_importer_summary": "All hoppscotch features are imported.",
|
||||
"from_insomnia": "Import from Insomnia",
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_insomnia_import_summary": "Collections and Requests will be imported.",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Import from Personal Collections",
|
||||
@@ -512,12 +515,15 @@
|
||||
"from_all_collections_description": "Import any collection from Another Workspace to the current workspace",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_openapi_import_summary": "Collections ( will be created from tags ), Requests and response examples will be imported.",
|
||||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_postman_import_summary": "Collections, Requests and response examples will be imported.",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Enter Gist URL",
|
||||
"from_har": "Import from HAR",
|
||||
"from_har_description": "Import from HAR file",
|
||||
"from_har_import_summary": "Requests will be imported to a default collection.",
|
||||
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist",
|
||||
"hoppscotch_environment": "Hoppscotch Environment",
|
||||
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file",
|
||||
@@ -532,7 +538,13 @@
|
||||
"title": "Import",
|
||||
"file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of {sizeLimit}MB. Only the first {files} selected will be imported",
|
||||
"file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of {sizeLimit}MB. Please select another file.",
|
||||
"success": "Successfully imported"
|
||||
"success": "Successfully imported",
|
||||
"import_summary_collections_title": "Collections",
|
||||
"import_summary_requests_title": "Requests",
|
||||
"import_summary_responses_title": "Responses",
|
||||
"import_summary_pre_request_scripts_title": "Pre-request scripts",
|
||||
"import_summary_test_scripts_title": "Test scripts",
|
||||
"import_summary_not_supported_by_hoppscotch_import": "We do not support importing {featureLabel} from this source right now."
|
||||
},
|
||||
"inspections": {
|
||||
"description": "Inspect possible errors",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script setup lang="ts">
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { PropType, computed, ref } from "vue"
|
||||
import { PropType, Ref, computed, ref } from "vue"
|
||||
|
||||
import { FileSource } from "~/helpers/import-export/import/import-sources/FileSource"
|
||||
import { UrlSource } from "~/helpers/import-export/import/import-sources/UrlSource"
|
||||
@@ -106,7 +106,6 @@ const handleImportToStore = async (collections: HoppCollection[]) => {
|
||||
|
||||
if (E.isRight(importResult)) {
|
||||
toast.success(t("state.file_imported"))
|
||||
emit("hide-modal")
|
||||
} else {
|
||||
toast.error(t("import.failed"))
|
||||
}
|
||||
@@ -175,6 +174,24 @@ const isTeamWorkspace = computed(() => {
|
||||
return props.collectionsType.type === "team-collections"
|
||||
})
|
||||
|
||||
const currentImportSummary: Ref<{
|
||||
showImportSummary: boolean
|
||||
importedCollections: HoppCollection[] | null
|
||||
}> = ref({
|
||||
showImportSummary: false,
|
||||
importedCollections: null,
|
||||
})
|
||||
|
||||
const setCurrentImportSummary = (collections: HoppCollection[]) => {
|
||||
currentImportSummary.value.importedCollections = collections
|
||||
currentImportSummary.value.showImportSummary = true
|
||||
}
|
||||
|
||||
const unsetCurrentImportSummary = () => {
|
||||
currentImportSummary.value.importedCollections = null
|
||||
currentImportSummary.value.showImportSummary = false
|
||||
}
|
||||
|
||||
const HoppRESTImporter: ImporterOrExporter = {
|
||||
metadata: {
|
||||
id: "hopp_rest",
|
||||
@@ -183,7 +200,9 @@ const HoppRESTImporter: ImporterOrExporter = {
|
||||
icon: IconFolderPlus,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace", "url-import"],
|
||||
format: "hoppscotch",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
component: FileSource({
|
||||
caption: "import.from_file",
|
||||
acceptedFileTypes: ".json",
|
||||
@@ -194,6 +213,8 @@ const HoppRESTImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
importer: "import.from_json",
|
||||
@@ -202,10 +223,13 @@ const HoppRESTImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isRESTImporterInProgress.value = false
|
||||
},
|
||||
description: "import.from_hoppscotch_importer_summary",
|
||||
isLoading: isRESTImporterInProgress,
|
||||
}),
|
||||
}
|
||||
@@ -218,6 +242,7 @@ const HoppAllCollectionImporter: ImporterOrExporter = {
|
||||
icon: IconUser,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace"],
|
||||
format: "hoppscotch",
|
||||
},
|
||||
onSelect() {
|
||||
if (!currentUser.value) {
|
||||
@@ -227,19 +252,26 @@ const HoppAllCollectionImporter: ImporterOrExporter = {
|
||||
|
||||
return false
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
component: defineStep("all_collection_import", AllCollectionImport, () => ({
|
||||
loading: isAllCollectionImporterInProgress.value,
|
||||
async onImportCollection(content) {
|
||||
isAllCollectionImporterInProgress.value = true
|
||||
|
||||
await handleImportToStore([content])
|
||||
try {
|
||||
await handleImportToStore([content])
|
||||
setCurrentImportSummary([content])
|
||||
|
||||
// our analytics consider this as an export event, so keeping compatibility with that
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_EXPORT_COLLECTION",
|
||||
exporter: "import_to_teams",
|
||||
platform: "rest",
|
||||
})
|
||||
// our analytics consider this as an export event, so keeping compatibility with that
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_EXPORT_COLLECTION",
|
||||
exporter: "import_to_teams",
|
||||
platform: "rest",
|
||||
})
|
||||
} catch (e) {
|
||||
showImportFailedError()
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isAllCollectionImporterInProgress.value = false
|
||||
},
|
||||
@@ -254,7 +286,9 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
icon: IconOpenAPI,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace", "url-import"],
|
||||
format: "openapi",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
supported_sources: [
|
||||
{
|
||||
id: "file_import",
|
||||
@@ -263,6 +297,7 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
step: FileSource({
|
||||
caption: "import.from_file",
|
||||
acceptedFileTypes: ".json, .yaml, .yml",
|
||||
description: "import.from_openapi_import_summary",
|
||||
onImportFromFile: async (content) => {
|
||||
isOpenAPIImporterInProgress.value = true
|
||||
|
||||
@@ -271,6 +306,8 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
platform: "rest",
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
@@ -279,6 +316,8 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isOpenAPIImporterInProgress.value = false
|
||||
@@ -292,6 +331,7 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
icon: IconLink,
|
||||
step: UrlSource({
|
||||
caption: "import.from_url",
|
||||
description: "import.from_openapi_import_summary",
|
||||
onImportFromURL: async (content) => {
|
||||
isOpenAPIImporterInProgress.value = true
|
||||
|
||||
@@ -300,6 +340,8 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
platform: "rest",
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
@@ -308,6 +350,8 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isOpenAPIImporterInProgress.value = false
|
||||
@@ -326,10 +370,13 @@ const HoppPostmanImporter: ImporterOrExporter = {
|
||||
icon: IconPostman,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace", "url-import"],
|
||||
format: "postman",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
component: FileSource({
|
||||
caption: "import.from_file",
|
||||
acceptedFileTypes: ".json",
|
||||
description: "import.from_postman_import_summary",
|
||||
onImportFromFile: async (content) => {
|
||||
isPostmanImporterInProgress.value = true
|
||||
|
||||
@@ -338,6 +385,8 @@ const HoppPostmanImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
platform: "rest",
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
@@ -346,6 +395,8 @@ const HoppPostmanImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isPostmanImporterInProgress.value = false
|
||||
@@ -362,10 +413,13 @@ const HoppInsomniaImporter: ImporterOrExporter = {
|
||||
icon: IconInsomnia,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace", "url-import"],
|
||||
format: "insomnia",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
component: FileSource({
|
||||
caption: "import.from_file",
|
||||
acceptedFileTypes: ".json",
|
||||
description: "import.from_insomnia_import_summary",
|
||||
onImportFromFile: async (content) => {
|
||||
isInsomniaImporterInProgress.value = true
|
||||
|
||||
@@ -374,6 +428,8 @@ const HoppInsomniaImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
platform: "rest",
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
@@ -382,6 +438,8 @@ const HoppInsomniaImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isInsomniaImporterInProgress.value = false
|
||||
@@ -398,9 +456,12 @@ const HoppGistImporter: ImporterOrExporter = {
|
||||
icon: IconGithub,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace", "url-import"],
|
||||
format: "hoppscotch",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
component: GistSource({
|
||||
caption: "import.from_url",
|
||||
description: "import.from_gist_import_summary",
|
||||
onImportFromGist: async (content) => {
|
||||
if (E.isLeft(content)) {
|
||||
showImportFailedError()
|
||||
@@ -414,6 +475,8 @@ const HoppGistImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
platform: "rest",
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
@@ -422,6 +485,8 @@ const HoppGistImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isGistImporterInProgress.value = false
|
||||
@@ -439,7 +504,9 @@ const HoppMyCollectionsExporter: ImporterOrExporter = {
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace"],
|
||||
isLoading: isHoppMyCollectionExporterInProgress,
|
||||
format: "hoppscotch",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
action: async () => {
|
||||
if (!myCollections.value.length) {
|
||||
return toast.error(t("error.no_collections_to_export"))
|
||||
@@ -478,6 +545,7 @@ const HoppTeamCollectionsExporter: ImporterOrExporter = {
|
||||
applicableTo: ["team-workspace"],
|
||||
isLoading: isHoppTeamCollectionExporterInProgress,
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
action: async () => {
|
||||
isHoppTeamCollectionExporterInProgress.value = true
|
||||
if (
|
||||
@@ -574,10 +642,13 @@ const HARImporter: ImporterOrExporter = {
|
||||
icon: IconFile,
|
||||
disabled: false,
|
||||
applicableTo: ["personal-workspace", "team-workspace"],
|
||||
format: "har",
|
||||
},
|
||||
importSummary: currentImportSummary,
|
||||
component: FileSource({
|
||||
caption: "import.from_file",
|
||||
acceptedFileTypes: ".har",
|
||||
description: "import.from_har_import_summary",
|
||||
onImportFromFile: async (content) => {
|
||||
isHarImporterInProgress.value = true
|
||||
|
||||
@@ -586,6 +657,8 @@ const HARImporter: ImporterOrExporter = {
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
importer: "import.from_har",
|
||||
@@ -594,6 +667,8 @@ const HARImporter: ImporterOrExporter = {
|
||||
})
|
||||
} else {
|
||||
showImportFailedError()
|
||||
|
||||
unsetCurrentImportSummary()
|
||||
}
|
||||
|
||||
isHarImporterInProgress.value = false
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<template #actions>
|
||||
<HoppButtonSecondary
|
||||
v-if="hasPreviousStep"
|
||||
v-if="hasPreviousStep && !isImportSummaryStep"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.go_back')"
|
||||
:icon="IconArrowLeft"
|
||||
@@ -23,13 +23,14 @@
|
||||
import IconArrowLeft from "~icons/lucide/arrow-left"
|
||||
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { PropType, ref } from "vue"
|
||||
import { computed, PropType, ref, watch } from "vue"
|
||||
|
||||
import { useSteps, defineStep } from "~/composables/step-components"
|
||||
import ImportExportList from "./ImportExportList.vue"
|
||||
|
||||
import ImportExportSourcesList from "./ImportExportSourcesList.vue"
|
||||
import { ImporterOrExporter } from "~/components/importExport/types"
|
||||
import ImportSummary from "~/components/importExport/ImportExportSteps/ImportSummary.vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -60,6 +61,10 @@ const {
|
||||
hasPreviousStep,
|
||||
} = useSteps()
|
||||
|
||||
const isImportSummaryStep = computed(() => {
|
||||
return currentStep.value.id.startsWith("import_summary_")
|
||||
})
|
||||
|
||||
const selectedImporterID = ref<string | null>(null)
|
||||
const selectedSourceID = ref<string | null>(null)
|
||||
|
||||
@@ -154,10 +159,50 @@ const chooseImportSource = defineStep(
|
||||
addStep(chooseImporterOrExporter)
|
||||
addStep(chooseImportSource)
|
||||
|
||||
const selectedImporterImportSummary = computed(() => {
|
||||
const importer = props.importerModules.find(
|
||||
(i) => i.metadata.id === selectedImporterID.value
|
||||
)
|
||||
|
||||
if (!importer?.importSummary) return null
|
||||
|
||||
return importer.importSummary
|
||||
})
|
||||
|
||||
watch(
|
||||
selectedImporterImportSummary,
|
||||
(val) => {
|
||||
if (val?.value.showImportSummary) {
|
||||
goToStep(`import_summary_${selectedImporterID.value}`)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
props.importerModules.forEach((importer) => {
|
||||
if (importer.component) {
|
||||
addStep(importer.component)
|
||||
}
|
||||
|
||||
const importSummary = importer.importSummary
|
||||
|
||||
if (!importSummary) {
|
||||
return
|
||||
}
|
||||
|
||||
if (importSummary.value) {
|
||||
addStep({
|
||||
id: `import_summary_${importer.metadata.id}`,
|
||||
component: ImportSummary,
|
||||
props: () => ({
|
||||
collections: importSummary.value.importedCollections,
|
||||
importFormat: importer.metadata.format,
|
||||
"on-close": () => {
|
||||
emit("hide-modal")
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasFile,
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle class="svg-icons" />
|
||||
</span>
|
||||
<span>
|
||||
{{ t(`${caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
<div>
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasFile,
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle class="svg-icons" />
|
||||
</span>
|
||||
<span>
|
||||
{{ t(`${caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="flex flex-col ml-10 border border-dashed rounded border-dividerDark"
|
||||
>
|
||||
<p v-if="description" class="ml-10 mt-2 text-secondaryLight">
|
||||
{{ t(description) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col border border-dashed rounded border-dividerDark">
|
||||
<input
|
||||
id="inputChooseFileToImportFrom"
|
||||
ref="inputChooseFileToImportFrom"
|
||||
@@ -71,9 +75,11 @@ const props = withDefaults(
|
||||
caption: string
|
||||
acceptedFileTypes: string
|
||||
loading?: boolean
|
||||
description?: string
|
||||
}>(),
|
||||
{
|
||||
loading: false,
|
||||
description: undefined,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<script setup lang="ts">
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { computed, Ref, ref, watch } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import { SupportedImportFormat } from "./../types"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type Feature =
|
||||
| "collections"
|
||||
| "requests"
|
||||
| "responses"
|
||||
| "preRequestScripts"
|
||||
| "testScripts"
|
||||
|
||||
type FeatureStatus =
|
||||
| "SUPPORTED"
|
||||
| "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT"
|
||||
| "NOT_SUPPORTED_BY_SOURCE"
|
||||
|
||||
type FeatureWithCount = {
|
||||
count: number
|
||||
label: string
|
||||
id: Feature
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
importFormat: SupportedImportFormat
|
||||
collections: HoppCollection[]
|
||||
onClose: () => void
|
||||
}>()
|
||||
|
||||
const importSourceAndSupportedFeatures: Record<
|
||||
SupportedImportFormat,
|
||||
Record<Feature, FeatureStatus>
|
||||
> = {
|
||||
hoppscotch: {
|
||||
collections: "SUPPORTED",
|
||||
requests: "SUPPORTED",
|
||||
responses: "SUPPORTED",
|
||||
preRequestScripts: "SUPPORTED",
|
||||
testScripts: "SUPPORTED",
|
||||
},
|
||||
postman: {
|
||||
collections: "SUPPORTED",
|
||||
requests: "SUPPORTED",
|
||||
responses: "SUPPORTED",
|
||||
preRequestScripts: "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT",
|
||||
testScripts: "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT",
|
||||
},
|
||||
insomnia: {
|
||||
collections: "SUPPORTED",
|
||||
requests: "SUPPORTED",
|
||||
responses: "NOT_SUPPORTED_BY_SOURCE",
|
||||
preRequestScripts: "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT",
|
||||
testScripts: "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT",
|
||||
},
|
||||
openapi: {
|
||||
collections: "SUPPORTED",
|
||||
requests: "SUPPORTED",
|
||||
responses: "SUPPORTED",
|
||||
preRequestScripts: "NOT_SUPPORTED_BY_SOURCE",
|
||||
testScripts: "NOT_SUPPORTED_BY_SOURCE",
|
||||
},
|
||||
har: {
|
||||
collections: "SUPPORTED",
|
||||
requests: "SUPPORTED",
|
||||
responses: "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT",
|
||||
preRequestScripts: "NOT_SUPPORTED_BY_SOURCE",
|
||||
testScripts: "NOT_SUPPORTED_BY_SOURCE",
|
||||
},
|
||||
}
|
||||
|
||||
const featuresWithCount: Ref<FeatureWithCount[]> = ref([])
|
||||
|
||||
const countCollections = (collections: HoppCollection[]) => {
|
||||
let collectionCount = 0
|
||||
let requestCount = 0
|
||||
let preRequestScriptsCount = 0
|
||||
let testScriptsCount = 0
|
||||
let responseCount = 0
|
||||
|
||||
const flattenHoppCollections = (_collections: HoppCollection[]) => {
|
||||
_collections.forEach((collection) => {
|
||||
collectionCount++
|
||||
|
||||
collection.requests.forEach((request) => {
|
||||
requestCount++
|
||||
|
||||
const _request = request as HoppRESTRequest
|
||||
|
||||
preRequestScriptsCount += !!_request.preRequestScript?.trim() ? 1 : 0
|
||||
testScriptsCount += !!_request.testScript?.trim() ? 1 : 0
|
||||
|
||||
responseCount += _request.responses
|
||||
? Object.values(_request.responses).length
|
||||
: 0
|
||||
})
|
||||
|
||||
flattenHoppCollections(collection.folders)
|
||||
})
|
||||
}
|
||||
|
||||
flattenHoppCollections(collections)
|
||||
|
||||
return {
|
||||
collectionCount,
|
||||
requestCount,
|
||||
responseCount,
|
||||
preRequestScriptsCount,
|
||||
testScriptsCount,
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
props.collections,
|
||||
(collections) => {
|
||||
const {
|
||||
collectionCount,
|
||||
requestCount,
|
||||
responseCount,
|
||||
preRequestScriptsCount,
|
||||
testScriptsCount,
|
||||
} = countCollections(collections)
|
||||
|
||||
featuresWithCount.value = [
|
||||
{
|
||||
count: collectionCount,
|
||||
label: "import.import_summary_collections_title",
|
||||
id: "collections" as const,
|
||||
},
|
||||
{
|
||||
count: requestCount,
|
||||
label: "import.import_summary_requests_title",
|
||||
id: "requests" as const,
|
||||
},
|
||||
{
|
||||
count: responseCount,
|
||||
label: "import.import_summary_responses_title",
|
||||
id: "responses" as const,
|
||||
},
|
||||
{
|
||||
count: preRequestScriptsCount,
|
||||
label: "import.import_summary_pre_request_scripts_title",
|
||||
id: "preRequestScripts" as const,
|
||||
},
|
||||
{
|
||||
count: testScriptsCount,
|
||||
label: "import.import_summary_test_scripts_title",
|
||||
id: "testScripts" as const,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
|
||||
const featureSupportForImportFormat = computed(() => {
|
||||
return importSourceAndSupportedFeatures[props.importFormat]
|
||||
})
|
||||
|
||||
const visibleFeatures = computed(() => {
|
||||
return featuresWithCount.value.filter((feature) => {
|
||||
return (
|
||||
importSourceAndSupportedFeatures[props.importFormat][feature.id] !==
|
||||
"NOT_SUPPORTED_BY_SOURCE"
|
||||
)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div v-for="feature in visibleFeatures" :key="feature.id">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary"
|
||||
:class="{
|
||||
'text-green-500':
|
||||
featureSupportForImportFormat[feature.id] === 'SUPPORTED',
|
||||
'text-amber-500':
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT',
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle
|
||||
v-if="featureSupportForImportFormat[feature.id] === 'SUPPORTED'"
|
||||
class="svg-icons"
|
||||
/>
|
||||
|
||||
<IconInfo
|
||||
v-else-if="
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT'
|
||||
"
|
||||
class="svg-icons"
|
||||
/>
|
||||
</span>
|
||||
<span>{{ t(feature.label) }}</span>
|
||||
</p>
|
||||
|
||||
<p class="ml-10 text-secondaryLight">
|
||||
<template
|
||||
v-if="featureSupportForImportFormat[feature.id] === 'SUPPORTED'"
|
||||
>
|
||||
{{ feature.count }}
|
||||
{{
|
||||
feature.count != 1
|
||||
? t(feature.label)
|
||||
: t(feature.label).slice(0, -1)
|
||||
}}
|
||||
Imported
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-else-if="
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT'
|
||||
"
|
||||
>
|
||||
{{
|
||||
t("import.import_summary_not_supported_by_hoppscotch_import", {
|
||||
featureLabel: t(feature.label),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
<HoppButtonSecondary
|
||||
class="w-full"
|
||||
:label="t('action.close')"
|
||||
outline
|
||||
filled
|
||||
@click="onClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,19 +1,26 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasURL,
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle class="svg-icons" />
|
||||
</span>
|
||||
<span>
|
||||
{{ t(caption) }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex flex-col ml-10">
|
||||
<div>
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasURL,
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle class="svg-icons" />
|
||||
</span>
|
||||
<span>
|
||||
{{ t(caption) }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p v-if="description" class="ml-10 mt-2 text-secondaryLight">
|
||||
{{ t(description) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="flex flex-col">
|
||||
<input
|
||||
v-model="inputChooseGistToImportFrom"
|
||||
type="url"
|
||||
@@ -49,8 +56,9 @@ const props = withDefaults(
|
||||
caption: string
|
||||
fetchLogic?: (url: string) => Promise<AxiosResponse<any>>
|
||||
loading?: boolean
|
||||
description?: string
|
||||
}>(),
|
||||
{ fetchLogic: undefined, loading: false }
|
||||
{ fetchLogic: undefined, loading: false, description: undefined }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { Component, Ref } from "vue"
|
||||
import { defineStep } from "~/composables/step-components"
|
||||
|
||||
export type SupportedImportFormat =
|
||||
| "hoppscotch"
|
||||
| "postman"
|
||||
| "insomnia"
|
||||
| "openapi"
|
||||
| "har"
|
||||
|
||||
// TODO: move the metadata except disabled and isLoading to importers.ts
|
||||
export type ImporterOrExporter = {
|
||||
metadata: {
|
||||
@@ -11,6 +19,7 @@ export type ImporterOrExporter = {
|
||||
disabled: boolean
|
||||
applicableTo: Array<"personal-workspace" | "team-workspace" | "url-import">
|
||||
isLoading?: Ref<boolean>
|
||||
format?: SupportedImportFormat
|
||||
}
|
||||
supported_sources?: {
|
||||
id: string
|
||||
@@ -18,6 +27,10 @@ export type ImporterOrExporter = {
|
||||
icon: Component
|
||||
step: ReturnType<typeof defineStep>
|
||||
}[]
|
||||
importSummary?: Ref<{
|
||||
showImportSummary: boolean
|
||||
importedCollections: HoppCollection[] | null
|
||||
}>
|
||||
component?: ReturnType<typeof defineStep>
|
||||
action?: (...args: any[]) => any
|
||||
onSelect?: () => boolean
|
||||
|
||||
@@ -9,6 +9,7 @@ export function FileSource(metadata: {
|
||||
caption: string
|
||||
onImportFromFile: (content: string[]) => any | Promise<any>
|
||||
isLoading?: Ref<boolean>
|
||||
description?: string
|
||||
}) {
|
||||
const stepID = uuidv4()
|
||||
|
||||
@@ -17,5 +18,6 @@ export function FileSource(metadata: {
|
||||
caption: metadata.caption,
|
||||
onImportFromFile: metadata.onImportFromFile,
|
||||
loading: metadata.isLoading?.value,
|
||||
description: metadata.description,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ export function GistSource(metadata: {
|
||||
importResult: E.Either<string, string[]>
|
||||
) => any | Promise<any>
|
||||
isLoading?: Ref<boolean>
|
||||
description?: string
|
||||
}) {
|
||||
const stepID = uuidv4()
|
||||
|
||||
return defineStep(stepID, UrlImport, () => ({
|
||||
caption: metadata.caption,
|
||||
description: metadata.description,
|
||||
onImportFromURL: (gistResponse: unknown) => {
|
||||
const fileSchema = z.object({
|
||||
files: z.record(z.object({ content: z.string() })),
|
||||
|
||||
@@ -9,6 +9,7 @@ export function UrlSource(metadata: {
|
||||
onImportFromURL: (content: string) => any | Promise<any>
|
||||
fetchLogic?: (url: string) => Promise<any>
|
||||
isLoading?: Ref<boolean>
|
||||
description: string
|
||||
}) {
|
||||
const stepID = uuidv4()
|
||||
|
||||
@@ -20,5 +21,6 @@ export function UrlSource(metadata: {
|
||||
}
|
||||
},
|
||||
loading: metadata.isLoading?.value,
|
||||
description: metadata.description,
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user