feat: CLI collection runner command generation UI flow (#4141)
Co-authored-by: nivedin <nivedinp@gmail.com>
This commit is contained in:
@@ -191,7 +191,8 @@
|
|||||||
"save_as": "Save as",
|
"save_as": "Save as",
|
||||||
"save_to_collection": "Save to Collection",
|
"save_to_collection": "Save to Collection",
|
||||||
"select": "Select a Collection",
|
"select": "Select a Collection",
|
||||||
"select_location": "Select location"
|
"select_location": "Select location",
|
||||||
|
"details": "Details"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
||||||
@@ -309,7 +310,9 @@
|
|||||||
"value": "Value",
|
"value": "Value",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
"variables": "Variables",
|
"variables": "Variables",
|
||||||
"variable_list": "Variable List"
|
"variable_list": "Variable List",
|
||||||
|
"properties": "Environment Properties",
|
||||||
|
"details": "Details"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"authproviders_load_error": "Unable to load auth providers",
|
"authproviders_load_error": "Unable to load auth providers",
|
||||||
@@ -1062,5 +1065,18 @@
|
|||||||
"generate_new_token": "Generate new token",
|
"generate_new_token": "Generate new token",
|
||||||
"generate_modal_title": "New Personal Access Token",
|
"generate_modal_title": "New Personal Access Token",
|
||||||
"deletion_success": "The access token {label} has been deleted"
|
"deletion_success": "The access token {label} has been deleted"
|
||||||
|
},
|
||||||
|
"collection_runner": {
|
||||||
|
"collection_id": "Collection ID",
|
||||||
|
"environment_id": "Environment ID",
|
||||||
|
"cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.",
|
||||||
|
"cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.",
|
||||||
|
"include_active_environment": "Include active environment:",
|
||||||
|
"cli": "CLI",
|
||||||
|
"ui": "Runner (coming soon)",
|
||||||
|
"cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.",
|
||||||
|
"cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.",
|
||||||
|
"cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.",
|
||||||
|
"run_collection": "Run collection"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ declare module 'vue' {
|
|||||||
CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default']
|
CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default']
|
||||||
CollectionsProperties: typeof import('./components/collections/Properties.vue')['default']
|
CollectionsProperties: typeof import('./components/collections/Properties.vue')['default']
|
||||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||||
|
CollectionsRunner: typeof import('./components/collections/Runner.vue')['default']
|
||||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
||||||
@@ -72,6 +73,7 @@ declare module 'vue' {
|
|||||||
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
||||||
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
||||||
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
|
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
|
||||||
|
EnvironmentsProperties: typeof import('./components/environments/Properties.vue')['default']
|
||||||
EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
|
EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
|
||||||
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
|
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
|
||||||
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
|
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
dropItemID = ''
|
dropItemID = ''
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@contextmenu.prevent="options?.tippy.show()"
|
@contextmenu.prevent="options?.tippy?.show()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex min-w-0 flex-1 items-center justify-center"
|
class="flex min-w-0 flex-1 items-center justify-center"
|
||||||
@@ -73,6 +73,14 @@
|
|||||||
class="hidden group-hover:inline-flex"
|
class="hidden group-hover:inline-flex"
|
||||||
@click="emit('add-folder')"
|
@click="emit('add-folder')"
|
||||||
/>
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="collectionsType === 'team-collections'"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconPlaySquare"
|
||||||
|
:title="t('collection_runner.run_collection')"
|
||||||
|
class="hidden group-hover:inline-flex"
|
||||||
|
@click="emit('run-collection', props.id)"
|
||||||
|
/>
|
||||||
<span>
|
<span>
|
||||||
<tippy
|
<tippy
|
||||||
ref="options"
|
ref="options"
|
||||||
@@ -97,6 +105,7 @@
|
|||||||
@keyup.delete="deleteAction?.$el.click()"
|
@keyup.delete="deleteAction?.$el.click()"
|
||||||
@keyup.x="exportAction?.$el.click()"
|
@keyup.x="exportAction?.$el.click()"
|
||||||
@keyup.p="propertiesAction?.$el.click()"
|
@keyup.p="propertiesAction?.$el.click()"
|
||||||
|
@keyup.t="runCollectionAction?.$el.click()"
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
@@ -172,6 +181,19 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-if="collectionsType === 'team-collections'"
|
||||||
|
ref="runCollectionAction"
|
||||||
|
:icon="IconPlaySquare"
|
||||||
|
:label="t('collection_runner.run_collection')"
|
||||||
|
:shortcut="['T']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('run-collection', props.id)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
@@ -197,26 +219,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
|
||||||
import IconFilePlus from "~icons/lucide/file-plus"
|
|
||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
|
||||||
import IconDownload from "~icons/lucide/download"
|
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
|
||||||
import IconEdit from "~icons/lucide/edit"
|
|
||||||
import IconFolder from "~icons/lucide/folder"
|
|
||||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
|
||||||
import IconSettings2 from "~icons/lucide/settings-2"
|
|
||||||
import { ref, computed, watch } from "vue"
|
|
||||||
import { HoppCollection } from "@hoppscotch/data"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { HoppCollection } from "@hoppscotch/data"
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||||
import {
|
import {
|
||||||
changeCurrentReorderStatus,
|
changeCurrentReorderStatus,
|
||||||
currentReorderingStatus$,
|
currentReorderingStatus$,
|
||||||
} from "~/newstore/reordering"
|
} from "~/newstore/reordering"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||||
|
import IconDownload from "~icons/lucide/download"
|
||||||
|
import IconEdit from "~icons/lucide/edit"
|
||||||
|
import IconFilePlus from "~icons/lucide/file-plus"
|
||||||
|
import IconFolder from "~icons/lucide/folder"
|
||||||
|
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||||
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
|
import IconPlaySquare from "~icons/lucide/play-square"
|
||||||
|
import IconSettings2 from "~icons/lucide/settings-2"
|
||||||
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
|
||||||
type CollectionType = "my-collections" | "team-collections"
|
type CollectionType = "my-collections" | "team-collections"
|
||||||
type FolderType = "collection" | "folder"
|
type FolderType = "collection" | "folder"
|
||||||
@@ -267,16 +290,18 @@ const emit = defineEmits<{
|
|||||||
(event: "dragging", payload: boolean): void
|
(event: "dragging", payload: boolean): void
|
||||||
(event: "update-collection-order", payload: DataTransfer): void
|
(event: "update-collection-order", payload: DataTransfer): void
|
||||||
(event: "update-last-collection-order", payload: DataTransfer): void
|
(event: "update-last-collection-order", payload: DataTransfer): void
|
||||||
|
(event: "run-collection", collectionID: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<HTMLDivElement | null>(null)
|
||||||
const requestAction = ref<HTMLButtonElement | null>(null)
|
const requestAction = ref<HTMLButtonElement | null>(null)
|
||||||
const folderAction = ref<HTMLButtonElement | null>(null)
|
const folderAction = ref<HTMLButtonElement | null>(null)
|
||||||
const edit = ref<HTMLButtonElement | null>(null)
|
const edit = ref<HTMLButtonElement | null>(null)
|
||||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||||
const exportAction = ref<HTMLButtonElement | null>(null)
|
const exportAction = ref<HTMLButtonElement | null>(null)
|
||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
const propertiesAction = ref<TippyComponent | null>(null)
|
const propertiesAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const runCollectionAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
const dragging = ref(false)
|
const dragging = ref(false)
|
||||||
const ordering = ref(false)
|
const ordering = ref(false)
|
||||||
@@ -319,7 +344,7 @@ watch(
|
|||||||
() => props.exportLoading,
|
() => props.exportLoading,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
options.value!.tippy.hide()
|
options.value!.tippy?.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<HoppSmartTab :id="'headers'" :label="`${t('tab.headers')}`">
|
<HoppSmartTab id="headers" :label="`${t('tab.headers')}`">
|
||||||
<HttpHeaders
|
<HttpHeaders
|
||||||
v-model="editableCollection"
|
v-model="editableCollection"
|
||||||
:is-collection-property="true"
|
:is-collection-property="true"
|
||||||
@@ -24,15 +24,13 @@
|
|||||||
{{ t("helpers.collection_properties_header") }}
|
{{ t("helpers.collection_properties_header") }}
|
||||||
</div>
|
</div>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
|
||||||
:id="'authorization'"
|
<HoppSmartTab id="authorization" :label="`${t('tab.authorization')}`">
|
||||||
:label="`${t('tab.authorization')}`"
|
|
||||||
>
|
|
||||||
<HttpAuthorization
|
<HttpAuthorization
|
||||||
v-model="editableCollection.auth"
|
v-model="editableCollection.auth"
|
||||||
:is-collection-property="true"
|
:is-collection-property="true"
|
||||||
:is-root-collection="editingProperties?.isRootCollection"
|
:is-root-collection="editingProperties.isRootCollection"
|
||||||
:inherited-properties="editingProperties?.inheritedProperties"
|
:inherited-properties="editingProperties.inheritedProperties"
|
||||||
:source="source"
|
:source="source"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -42,23 +40,77 @@
|
|||||||
{{ t("helpers.collection_properties_authorization") }}
|
{{ t("helpers.collection_properties_authorization") }}
|
||||||
</div>
|
</div>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
|
|
||||||
|
<HoppSmartTab
|
||||||
|
v-if="showDetails"
|
||||||
|
:id="'details'"
|
||||||
|
:label="t('collection.details')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex flex-shrink-0 items-center justify-between border-b border-dividerLight bg-primary pl-4"
|
||||||
|
>
|
||||||
|
<span>{{ t("collection_runner.collection_id") }}</span>
|
||||||
|
|
||||||
|
<!-- TODO: Make it point to the section about accessing collections via the ID -->
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
to="https://docs.hoppscotch.io/documentation/clients/cli"
|
||||||
|
blank
|
||||||
|
:title="t('app.wiki')"
|
||||||
|
:icon="IconHelpCircle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between py-2 px-4 rounded-md bg-primaryLight select-text"
|
||||||
|
>
|
||||||
|
<div class="text-secondaryDark">
|
||||||
|
{{ editingProperties.path }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HoppButtonSecondary
|
||||||
|
filled
|
||||||
|
:icon="copyIcon"
|
||||||
|
@click="copyCollectionID"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||||
|
>
|
||||||
|
<icon-lucide-info class="svg-icons mr-2" />
|
||||||
|
{{ t("collection_runner.cli_collection_id_description") }}
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<div class="flex gap-x-2">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
|
v-if="activeTabIsDetails"
|
||||||
|
:label="t('action.copy')"
|
||||||
|
:icon="copyIcon"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="copyCollectionID"
|
||||||
|
/>
|
||||||
|
<HoppButtonPrimary
|
||||||
|
v-else
|
||||||
:label="t('action.save')"
|
:label="t('action.save')"
|
||||||
:loading="loadingState"
|
:loading="loadingState"
|
||||||
outline
|
outline
|
||||||
@click="saveEditedCollection"
|
@click="saveEditedCollection"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('action.cancel')"
|
:label="activeTabIsDetails ? t('action.close') : t('action.cancel')"
|
||||||
outline
|
outline
|
||||||
filled
|
filled
|
||||||
@click="hideModal"
|
@click="hideModal"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartModal>
|
</HoppSmartModal>
|
||||||
</template>
|
</template>
|
||||||
@@ -72,13 +124,18 @@ import {
|
|||||||
HoppRESTAuth,
|
HoppRESTAuth,
|
||||||
HoppRESTHeaders,
|
HoppRESTHeaders,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { PersistenceService } from "~/services/persistence"
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
|
||||||
const persistenceService = useService(PersistenceService)
|
const persistenceService = useService(PersistenceService)
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -93,18 +150,21 @@ export type EditingProperties = {
|
|||||||
type HoppCollectionAuth = HoppRESTAuth | HoppGQLAuth
|
type HoppCollectionAuth = HoppRESTAuth | HoppGQLAuth
|
||||||
type HoppCollectionHeaders = HoppRESTHeaders | GQLHeader[]
|
type HoppCollectionHeaders = HoppRESTHeaders | GQLHeader[]
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
loadingState: boolean
|
loadingState: boolean
|
||||||
editingProperties: EditingProperties | null
|
editingProperties: EditingProperties
|
||||||
source: "REST" | "GraphQL"
|
source: "REST" | "GraphQL"
|
||||||
modelValue: string
|
modelValue: string
|
||||||
|
showDetails: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
show: false,
|
show: false,
|
||||||
loadingState: false,
|
loadingState: false,
|
||||||
editingProperties: null,
|
showDetails: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,15 +188,22 @@ const editableCollection = ref<{
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
IconCopy,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
const activeTabIsDetails = computed(() => activeTab.value === "details")
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
editableCollection,
|
editableCollection,
|
||||||
(updatedEditableCollection) => {
|
(updatedEditableCollection) => {
|
||||||
if (props.show && props.editingProperties) {
|
if (props.show && props.editingProperties) {
|
||||||
const unsavedCollectionProperties: EditingProperties = {
|
const unsavedCollectionProperties: EditingProperties = {
|
||||||
collection: updatedEditableCollection,
|
collection: updatedEditableCollection,
|
||||||
isRootCollection: props.editingProperties?.isRootCollection ?? false,
|
isRootCollection: props.editingProperties.isRootCollection ?? false,
|
||||||
path: props.editingProperties?.path,
|
path: props.editingProperties.path,
|
||||||
inheritedProperties: props.editingProperties?.inheritedProperties,
|
inheritedProperties: props.editingProperties.inheritedProperties,
|
||||||
}
|
}
|
||||||
persistenceService.setLocalConfig(
|
persistenceService.setLocalConfig(
|
||||||
"unsaved_collection_properties",
|
"unsaved_collection_properties",
|
||||||
@@ -154,7 +221,13 @@ const activeTab = useVModel(props, "modelValue", emit)
|
|||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show && props.editingProperties?.collection) {
|
// `Details` tab doesn't exist for personal workspace, hence switching to the `Headers` tab
|
||||||
|
// The modal can appear empty while switching from a team workspace with `Details` as the active tab
|
||||||
|
if (activeTab.value === "details" && !props.showDetails) {
|
||||||
|
activeTab.value = "headers"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show && props.editingProperties.collection) {
|
||||||
editableCollection.value.auth = clone(
|
editableCollection.value.auth = clone(
|
||||||
props.editingProperties.collection.auth as HoppCollectionAuth
|
props.editingProperties.collection.auth as HoppCollectionAuth
|
||||||
)
|
)
|
||||||
@@ -194,4 +267,11 @@ const hideModal = () => {
|
|||||||
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copyCollectionID = () => {
|
||||||
|
copyToClipboard(props.editingProperties.path)
|
||||||
|
copyIcon.value = IconCheck
|
||||||
|
|
||||||
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
149
packages/hoppscotch-common/src/components/collections/Runner.vue
Normal file
149
packages/hoppscotch-common/src/components/collections/Runner.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartModal
|
||||||
|
dialog
|
||||||
|
:title="t('collection_runner.run_collection')"
|
||||||
|
@close="closeModal"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppSmartTabs v-model="activeTab">
|
||||||
|
<HoppSmartTab id="cli" :label="t('collection_runner.cli')">
|
||||||
|
<div class="space-y-4 p-4">
|
||||||
|
<p
|
||||||
|
class="p-4 mb-4 border rounded-md text-amber-500 border-amber-600"
|
||||||
|
>
|
||||||
|
{{ cliCommandGenerationDescription }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div v-if="environmentID" class="flex gap-x-2 items-center">
|
||||||
|
<HoppSmartCheckbox
|
||||||
|
:on="includeEnvironmentID"
|
||||||
|
@change="toggleIncludeEnvironment"
|
||||||
|
/>
|
||||||
|
<span class="truncate"
|
||||||
|
>{{ t("collection_runner.include_active_environment") }}
|
||||||
|
<span class="text-secondaryDark">{{
|
||||||
|
activeEnvironment
|
||||||
|
}}</span></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="p-4 rounded-md bg-primaryLight text-secondaryDark select-text"
|
||||||
|
>
|
||||||
|
{{ generatedCLICommand }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
|
||||||
|
<HoppSmartTab id="runner" disabled :label="t('collection_runner.ui')" />
|
||||||
|
</HoppSmartTabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:label="`${t('action.copy')}`"
|
||||||
|
:icon="copyIcon"
|
||||||
|
outline
|
||||||
|
@click="copyCLICommandToClipboard"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="`${t('action.close')}`"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="closeModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
|
import { computed, ref } from "vue"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
|
import { SelectedEnvironmentIndex } from "~/newstore/environments"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
collectionID: string
|
||||||
|
environmentID?: string | null
|
||||||
|
selectedEnvironmentIndex: SelectedEnvironmentIndex
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "hide-modal"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const includeEnvironmentID = ref(false)
|
||||||
|
const activeTab = ref("cli")
|
||||||
|
|
||||||
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
IconCopy,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
const activeEnvironment = computed(() => {
|
||||||
|
const selectedEnv = props.selectedEnvironmentIndex
|
||||||
|
|
||||||
|
if (selectedEnv.type === "TEAM_ENV") {
|
||||||
|
return selectedEnv.environment.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCloudInstance = window.location.hostname === "hoppscotch.io"
|
||||||
|
|
||||||
|
const cliCommandGenerationDescription = computed(() => {
|
||||||
|
if (isCloudInstance) {
|
||||||
|
return t("collection_runner.cli_command_generation_description_cloud")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.VITE_BACKEND_API_URL) {
|
||||||
|
return t("collection_runner.cli_command_generation_description_sh")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t(
|
||||||
|
"collection_runner.cli_command_generation_description_sh_with_server_url_placeholder"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const generatedCLICommand = computed(() => {
|
||||||
|
const { collectionID, environmentID } = props
|
||||||
|
|
||||||
|
const environmentFlag =
|
||||||
|
includeEnvironmentID.value && environmentID ? `-e ${environmentID}` : ""
|
||||||
|
|
||||||
|
const serverUrl = import.meta.env.VITE_BACKEND_API_URL?.endsWith("/v1")
|
||||||
|
? // Removing `/v1` prefix
|
||||||
|
import.meta.env.VITE_BACKEND_API_URL.slice(0, -3)
|
||||||
|
: "<server_url>"
|
||||||
|
|
||||||
|
const serverFlag = isCloudInstance ? "" : `--server ${serverUrl}`
|
||||||
|
|
||||||
|
return `hopp test ${collectionID} ${environmentFlag} --token <access_token> ${serverFlag}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleIncludeEnvironment = () => {
|
||||||
|
includeEnvironmentID.value = !includeEnvironmentID.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyCLICommandToClipboard = () => {
|
||||||
|
copyToClipboard(generatedCLICommand.value)
|
||||||
|
copyIcon.value = IconCheck
|
||||||
|
|
||||||
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -129,6 +129,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@run-collection="emit('run-collection', $event)"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
handleCollectionClick({
|
handleCollectionClick({
|
||||||
@@ -218,6 +219,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@run-collection="emit('run-collection', $event)"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
handleCollectionClick({
|
handleCollectionClick({
|
||||||
@@ -586,6 +588,7 @@ const emit = defineEmits<{
|
|||||||
(event: "expand-team-collection", payload: string): void
|
(event: "expand-team-collection", payload: string): void
|
||||||
(event: "display-modal-add"): void
|
(event: "display-modal-add"): void
|
||||||
(event: "display-modal-import-export"): void
|
(event: "display-modal-import-export"): void
|
||||||
|
(event: "run-collection", collectionID: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const getPath = (path: string) => {
|
const getPath = (path: string) => {
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
@display-modal-add="displayModalAdd(true)"
|
@display-modal-add="displayModalAdd(true)"
|
||||||
@display-modal-import-export="displayModalImportExport(true)"
|
@display-modal-import-export="displayModalImportExport(true)"
|
||||||
@collection-click="handleCollectionClick"
|
@collection-click="handleCollectionClick"
|
||||||
|
@run-collection="runCollectionHandler"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight"
|
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight"
|
||||||
@@ -164,42 +165,26 @@
|
|||||||
v-model="collectionPropertiesModalActiveTab"
|
v-model="collectionPropertiesModalActiveTab"
|
||||||
:show="showModalEditProperties"
|
:show="showModalEditProperties"
|
||||||
:editing-properties="editingProperties"
|
:editing-properties="editingProperties"
|
||||||
|
:show-details="collectionsType.type === 'team-collections'"
|
||||||
source="REST"
|
source="REST"
|
||||||
@hide-modal="displayModalEditProperties(false)"
|
@hide-modal="displayModalEditProperties(false)"
|
||||||
@set-collection-properties="setCollectionProperties"
|
@set-collection-properties="setCollectionProperties"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- `selectedCollectionID` is guaranteed to be a string when `showCollectionsRunnerModal` is `true` -->
|
||||||
|
<CollectionsRunner
|
||||||
|
v-if="showCollectionsRunnerModal"
|
||||||
|
:collection-i-d="selectedCollectionID!"
|
||||||
|
:environment-i-d="activeEnvironmentID"
|
||||||
|
:selected-environment-index="selectedEnvironmentIndex"
|
||||||
|
@hide-modal="showCollectionsRunnerModal = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onMounted, PropType, ref, watch } from "vue"
|
|
||||||
import { useToast } from "@composables/toast"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { useToast } from "@composables/toast"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
|
||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import {
|
|
||||||
addRESTCollection,
|
|
||||||
addRESTFolder,
|
|
||||||
editRESTCollection,
|
|
||||||
editRESTFolder,
|
|
||||||
editRESTRequest,
|
|
||||||
moveRESTRequest,
|
|
||||||
removeRESTCollection,
|
|
||||||
removeRESTFolder,
|
|
||||||
removeRESTRequest,
|
|
||||||
restCollections$,
|
|
||||||
saveRESTRequestAs,
|
|
||||||
updateRESTRequestOrder,
|
|
||||||
updateRESTCollectionOrder,
|
|
||||||
moveRESTFolder,
|
|
||||||
navigateToFolderWithIndexPath,
|
|
||||||
restCollectionStore,
|
|
||||||
cascadeParentCollectionForHeaderAuth,
|
|
||||||
} from "~/newstore/collections"
|
|
||||||
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
|
||||||
import {
|
import {
|
||||||
HoppCollection,
|
HoppCollection,
|
||||||
HoppRESTAuth,
|
HoppRESTAuth,
|
||||||
@@ -207,51 +192,82 @@ import {
|
|||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
makeCollection,
|
makeCollection,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
import { cloneDeep, debounce, isEqual } from "lodash-es"
|
import { cloneDeep, debounce, isEqual } from "lodash-es"
|
||||||
|
import { PropType, computed, nextTick, onMounted, ref, watch } from "vue"
|
||||||
|
import { useReadonlyStream, useStream } from "~/composables/stream"
|
||||||
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import {
|
import {
|
||||||
createNewRootCollection,
|
getCompleteCollectionTree,
|
||||||
|
teamCollToHoppRESTColl,
|
||||||
|
} from "~/helpers/backend/helpers"
|
||||||
|
import {
|
||||||
createChildCollection,
|
createChildCollection,
|
||||||
|
createNewRootCollection,
|
||||||
deleteCollection,
|
deleteCollection,
|
||||||
moveRESTTeamCollection,
|
moveRESTTeamCollection,
|
||||||
updateOrderRESTTeamCollection,
|
updateOrderRESTTeamCollection,
|
||||||
updateTeamCollection,
|
updateTeamCollection,
|
||||||
} from "~/helpers/backend/mutations/TeamCollection"
|
} from "~/helpers/backend/mutations/TeamCollection"
|
||||||
import {
|
import {
|
||||||
updateTeamRequest,
|
|
||||||
createRequestInCollection,
|
createRequestInCollection,
|
||||||
deleteTeamRequest,
|
deleteTeamRequest,
|
||||||
moveRESTTeamRequest,
|
moveRESTTeamRequest,
|
||||||
updateOrderRESTTeamRequest,
|
updateOrderRESTTeamRequest,
|
||||||
|
updateTeamRequest,
|
||||||
} from "~/helpers/backend/mutations/TeamRequest"
|
} from "~/helpers/backend/mutations/TeamRequest"
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
|
||||||
import { Collection as NodeCollection } from "./MyCollections.vue"
|
|
||||||
import {
|
import {
|
||||||
getCompleteCollectionTree,
|
getFoldersByPath,
|
||||||
teamCollToHoppRESTColl,
|
resetTeamRequestsContext,
|
||||||
} from "~/helpers/backend/helpers"
|
resolveSaveContextOnCollectionReorder,
|
||||||
import { platform } from "~/platform"
|
updateInheritedPropertiesForAffectedRequests,
|
||||||
|
updateSaveContextForAffectedRequests,
|
||||||
|
} from "~/helpers/collection/collection"
|
||||||
import {
|
import {
|
||||||
getRequestsByPath,
|
getRequestsByPath,
|
||||||
resolveSaveContextOnRequestReorder,
|
resolveSaveContextOnRequestReorder,
|
||||||
} from "~/helpers/collection/request"
|
} from "~/helpers/collection/request"
|
||||||
import {
|
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||||
getFoldersByPath,
|
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
||||||
resolveSaveContextOnCollectionReorder,
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
updateSaveContextForAffectedRequests,
|
|
||||||
updateInheritedPropertiesForAffectedRequests,
|
|
||||||
resetTeamRequestsContext,
|
|
||||||
} from "~/helpers/collection/collection"
|
|
||||||
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
|
||||||
import { TeamWorkspace, WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
|
||||||
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
||||||
import { PersistenceService } from "~/services/persistence"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
|
import {
|
||||||
|
addRESTCollection,
|
||||||
|
addRESTFolder,
|
||||||
|
cascadeParentCollectionForHeaderAuth,
|
||||||
|
editRESTCollection,
|
||||||
|
editRESTFolder,
|
||||||
|
editRESTRequest,
|
||||||
|
moveRESTFolder,
|
||||||
|
moveRESTRequest,
|
||||||
|
navigateToFolderWithIndexPath,
|
||||||
|
removeRESTCollection,
|
||||||
|
removeRESTFolder,
|
||||||
|
removeRESTRequest,
|
||||||
|
restCollectionStore,
|
||||||
|
restCollections$,
|
||||||
|
saveRESTRequestAs,
|
||||||
|
updateRESTCollectionOrder,
|
||||||
|
updateRESTRequestOrder,
|
||||||
|
} from "~/newstore/collections"
|
||||||
|
import {
|
||||||
|
selectedEnvironmentIndex$,
|
||||||
|
setSelectedEnvironmentIndex,
|
||||||
|
} from "~/newstore/environments"
|
||||||
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
||||||
|
import { platform } from "~/platform"
|
||||||
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
||||||
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { TeamWorkspace, WorkspaceService } from "~/services/workspace.service"
|
||||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||||
|
import { Collection as NodeCollection } from "./MyCollections.vue"
|
||||||
import { EditingProperties } from "./Properties.vue"
|
import { EditingProperties } from "./Properties.vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -339,6 +355,16 @@ const teamLoadingCollections = useReadonlyStream(
|
|||||||
teamCollectionAdapter.loadingCollections$,
|
teamCollectionAdapter.loadingCollections$,
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
const teamEnvironmentAdapter = new TeamEnvironmentAdapter(undefined)
|
||||||
|
const teamEnvironmentList = useReadonlyStream(
|
||||||
|
teamEnvironmentAdapter.teamEnvironmentList$,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const selectedEnvironmentIndex = useStream(
|
||||||
|
selectedEnvironmentIndex$,
|
||||||
|
{ type: "NO_ENV_SELECTED" },
|
||||||
|
setSelectedEnvironmentIndex
|
||||||
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cascadeParentCollectionForHeaderAuthForSearchResults,
|
cascadeParentCollectionForHeaderAuthForSearchResults,
|
||||||
@@ -482,6 +508,8 @@ watch(
|
|||||||
switchToMyCollections()
|
switchToMyCollections()
|
||||||
} else if (newWorkspace.type === "team") {
|
} else if (newWorkspace.type === "team") {
|
||||||
updateSelectedTeam(newWorkspace)
|
updateSelectedTeam(newWorkspace)
|
||||||
|
|
||||||
|
teamEnvironmentAdapter.changeTeamID(newWorkspace.teamID)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -630,6 +658,10 @@ const showModalEditProperties = ref(false)
|
|||||||
const showConfirmModal = ref(false)
|
const showConfirmModal = ref(false)
|
||||||
const showTeamModalAdd = ref(false)
|
const showTeamModalAdd = ref(false)
|
||||||
|
|
||||||
|
const showCollectionsRunnerModal = ref(false)
|
||||||
|
const selectedCollectionID = ref<string | null>(null)
|
||||||
|
const activeEnvironmentID = ref<string | null | undefined>(null)
|
||||||
|
|
||||||
const displayModalAdd = (show: boolean) => {
|
const displayModalAdd = (show: boolean) => {
|
||||||
showModalAdd.value = show
|
showModalAdd.value = show
|
||||||
|
|
||||||
@@ -2251,6 +2283,27 @@ const setCollectionProperties = (newCollection: {
|
|||||||
displayModalEditProperties(false)
|
displayModalEditProperties(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runCollectionHandler = (collectionID: string) => {
|
||||||
|
selectedCollectionID.value = collectionID
|
||||||
|
showCollectionsRunnerModal.value = true
|
||||||
|
|
||||||
|
const activeWorkspace = workspace.value
|
||||||
|
const currentEnv = selectedEnvironmentIndex.value
|
||||||
|
|
||||||
|
if (["NO_ENV_SELECTED", "MY_ENV"].includes(currentEnv.type)) {
|
||||||
|
activeEnvironmentID.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeWorkspace.type === "team" && currentEnv.type === "TEAM_ENV") {
|
||||||
|
activeEnvironmentID.value = teamEnvironmentList.value.find(
|
||||||
|
(env) =>
|
||||||
|
env.teamID === activeWorkspace.teamID &&
|
||||||
|
env.environment.id === currentEnv.environment.id
|
||||||
|
)?.environment.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resolveConfirmModal = (title: string | null) => {
|
const resolveConfirmModal = (title: string | null) => {
|
||||||
if (title === `${t("confirm.remove_collection")}`) onRemoveCollection()
|
if (title === `${t("confirm.remove_collection")}`) onRemoveCollection()
|
||||||
else if (title === `${t("confirm.remove_request")}`) onRemoveRequest()
|
else if (title === `${t("confirm.remove_request")}`) onRemoveRequest()
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartModal
|
||||||
|
dialog
|
||||||
|
:full-width-body="true"
|
||||||
|
:title="t('environment.properties')"
|
||||||
|
@close="hideModal"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppSmartTabs
|
||||||
|
v-model="activeTab"
|
||||||
|
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
||||||
|
render-inactive-tabs
|
||||||
|
>
|
||||||
|
<HoppSmartTab id="details" :label="t('environment.details')">
|
||||||
|
<div
|
||||||
|
class="flex flex-shrink-0 items-center justify-between border-b border-dividerLight bg-primary pl-4"
|
||||||
|
>
|
||||||
|
<span>{{ t("collection_runner.environment_id") }}</span>
|
||||||
|
|
||||||
|
<!-- TODO: Make it point to the section about accessing environments via the ID -->
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
to="https://docs.hoppscotch.io/documentation/clients/cli"
|
||||||
|
blank
|
||||||
|
:title="t('app.wiki')"
|
||||||
|
:icon="IconHelpCircle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between py-2 px-4 rounded-md bg-primaryLight select-text"
|
||||||
|
>
|
||||||
|
<div class="text-secondaryDark">
|
||||||
|
{{ environmentID }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HoppButtonSecondary
|
||||||
|
filled
|
||||||
|
:icon="copyTextIcon"
|
||||||
|
@click="copyText"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||||
|
>
|
||||||
|
<icon-lucide-info class="svg-icons mr-2" />
|
||||||
|
{{ t("collection_runner.cli_environment_id_description") }}
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
</HoppSmartTabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex gap-x-2 items-center">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:label="t('action.copy')"
|
||||||
|
:icon="copyIcon"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="copyEnvironmentID"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('action.close')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="hideModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||||
|
import { toRef } from "vue"
|
||||||
|
import { useCopyResponse } from "~/composables/lens-actions"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
|
||||||
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: string
|
||||||
|
environmentID: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const environmentIDRef = toRef(props, "environmentID")
|
||||||
|
|
||||||
|
const { copyIcon: copyTextIcon, copyResponse: copyText } =
|
||||||
|
useCopyResponse(environmentIDRef)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "hide-modal"): void
|
||||||
|
(e: "update:modelValue"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const activeTab = useVModel(props, "modelValue", emit)
|
||||||
|
|
||||||
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
IconCopy,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
const hideModal = () => {
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyEnvironmentID = () => {
|
||||||
|
copyToClipboard(props.environmentID)
|
||||||
|
copyIcon.value = IconCheck
|
||||||
|
|
||||||
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -40,7 +40,8 @@
|
|||||||
@keyup.d="duplicate!.$el.click()"
|
@keyup.d="duplicate!.$el.click()"
|
||||||
@keyup.j="exportAsJsonEl!.$el.click()"
|
@keyup.j="exportAsJsonEl!.$el.click()"
|
||||||
@keyup.delete="deleteAction!.$el.click()"
|
@keyup.delete="deleteAction!.$el.click()"
|
||||||
@keyup.escape="options!.tippy().hide()"
|
@keyup.p="propertiesAction!.$el.click()"
|
||||||
|
@keyup.escape="options!.tippy?.hide()"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="edit"
|
ref="edit"
|
||||||
@@ -94,6 +95,18 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="propertiesAction"
|
||||||
|
:icon="IconSettings2"
|
||||||
|
:label="t('action.properties')"
|
||||||
|
:shortcut="['P']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('show-environment-properties')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
@@ -108,26 +121,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
|
||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import {
|
import {
|
||||||
deleteTeamEnvironment,
|
deleteTeamEnvironment,
|
||||||
createDuplicateEnvironment as duplicateEnvironment,
|
createDuplicateEnvironment as duplicateEnvironment,
|
||||||
} from "~/helpers/backend/mutations/TeamEnvironment"
|
} from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
|
||||||
import IconEdit from "~icons/lucide/edit"
|
|
||||||
import IconCopy from "~icons/lucide/copy"
|
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
|
||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
|
||||||
import { TippyComponent } from "vue-tippy"
|
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
|
||||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
import { useService } from "dioc/vue"
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconEdit from "~icons/lucide/edit"
|
||||||
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
|
import IconSettings2 from "~icons/lucide/settings-2"
|
||||||
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -139,6 +154,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "edit-environment"): void
|
(e: "edit-environment"): void
|
||||||
|
(e: "show-environment-properties"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const secretEnvironmentService = useService(SecretEnvironmentService)
|
const secretEnvironmentService = useService(SecretEnvironmentService)
|
||||||
@@ -156,6 +172,7 @@ const edit = ref<typeof HoppSmartItem>()
|
|||||||
const duplicate = ref<typeof HoppSmartItem>()
|
const duplicate = ref<typeof HoppSmartItem>()
|
||||||
const deleteAction = ref<typeof HoppSmartItem>()
|
const deleteAction = ref<typeof HoppSmartItem>()
|
||||||
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||||
|
const propertiesAction = ref<typeof HoppSmartItem>()
|
||||||
|
|
||||||
const removeEnvironment = () => {
|
const removeEnvironment = () => {
|
||||||
pipe(
|
pipe(
|
||||||
|
|||||||
@@ -86,6 +86,9 @@
|
|||||||
:environment="environment"
|
:environment="environment"
|
||||||
:is-viewer="team?.role === 'VIEWER'"
|
:is-viewer="team?.role === 'VIEWER'"
|
||||||
@edit-environment="editEnvironment(environment)"
|
@edit-environment="editEnvironment(environment)"
|
||||||
|
@show-environment-properties="
|
||||||
|
showEnvironmentProperties(environment.environment.id)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="loading" class="flex flex-col items-center justify-center p-4">
|
<div v-if="loading" class="flex flex-col items-center justify-center p-4">
|
||||||
@@ -116,6 +119,12 @@
|
|||||||
environment-type="TEAM_ENV"
|
environment-type="TEAM_ENV"
|
||||||
@hide-modal="displayModalImportExport(false)"
|
@hide-modal="displayModalImportExport(false)"
|
||||||
/>
|
/>
|
||||||
|
<EnvironmentsProperties
|
||||||
|
v-if="showEnvironmentsPropertiesModal"
|
||||||
|
v-model="environmentsPropertiesModalActiveTab"
|
||||||
|
:environment-i-d="selectedEnvironmentID!"
|
||||||
|
@hide-modal="showEnvironmentsPropertiesModal = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -149,6 +158,10 @@ const editingEnvironment = ref<TeamEnvironment | null>(null)
|
|||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
const secretOptionSelected = ref(false)
|
const secretOptionSelected = ref(false)
|
||||||
|
|
||||||
|
const showEnvironmentsPropertiesModal = ref(false)
|
||||||
|
const environmentsPropertiesModalActiveTab = ref("details")
|
||||||
|
const selectedEnvironmentID = ref<string | null>(null)
|
||||||
|
|
||||||
const isTeamViewer = computed(() => props.team?.role === "VIEWER")
|
const isTeamViewer = computed(() => props.team?.role === "VIEWER")
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
@@ -187,6 +200,11 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showEnvironmentProperties = (environmentID: string) => {
|
||||||
|
showEnvironmentsPropertiesModal.value = true
|
||||||
|
selectedEnvironmentID.value = environmentID
|
||||||
|
}
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.team.environment.edit",
|
"modals.team.environment.edit",
|
||||||
({ envName, variableName, isSecret }) => {
|
({ envName, variableName, isSecret }) => {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface ImportMetaEnv {
|
|||||||
|
|
||||||
readonly VITE_BACKEND_GQL_URL: string
|
readonly VITE_BACKEND_GQL_URL: string
|
||||||
readonly VITE_BACKEND_WS_URL: string
|
readonly VITE_BACKEND_WS_URL: string
|
||||||
|
readonly VITE_BACKEND_API_URL: string
|
||||||
|
|
||||||
readonly VITE_SENTRY_DSN?: string
|
readonly VITE_SENTRY_DSN?: string
|
||||||
readonly VITE_SENTRY_ENVIRONMENT?: string
|
readonly VITE_SENTRY_ENVIRONMENT?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user