fix: resolve multiple UI issues and account for description field in syncing context (#4309)

* fix: collection properties header bulk update editor bug

* chore: add migration step while resolving `headers` in the syncing context

Resolve type errors.

* fix: prevent inifinite loading state in add environments modal

Toggle back the loading state if attempting to create an environment from a team workspace without specifying a name.

* fix: tab change when clicking computed auth

* fix: ensure tab change action works in GQL headers view

`Go to Authorization tab` action.

* chore: account for REST params while adding description fields

Writing to store after pulling from the syncing context.

---------

Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
Nivedin
2024-08-30 12:15:19 +05:30
committed by GitHub
parent 9ad6a419c1
commit 5a2eed60c9
6 changed files with 73 additions and 29 deletions

View File

@@ -17,6 +17,7 @@
<HttpHeaders <HttpHeaders
v-model="editableCollection" v-model="editableCollection"
:is-collection-property="true" :is-collection-property="true"
@change-tab="changeOptionTab"
/> />
<div <div
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0" class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
@@ -136,6 +137,7 @@ import { PersistenceService } from "~/services/persistence"
import IconCheck from "~icons/lucide/check" import IconCheck from "~icons/lucide/check"
import IconCopy from "~icons/lucide/copy" import IconCopy from "~icons/lucide/copy"
import IconHelpCircle from "~icons/lucide/help-circle" import IconHelpCircle from "~icons/lucide/help-circle"
import { RESTOptionTabs } from "../http/RequestOptions.vue"
const persistenceService = useService(PersistenceService) const persistenceService = useService(PersistenceService)
const t = useI18n() const t = useI18n()
@@ -268,6 +270,10 @@ const hideModal = () => {
emit("hide-modal") emit("hide-modal")
} }
const changeOptionTab = (e: RESTOptionTabs) => {
activeTab.value = e
}
const copyCollectionID = () => { const copyCollectionID = () => {
copyToClipboard(props.editingProperties.path) copyToClipboard(props.editingProperties.path)
copyIcon.value = IconCheck copyIcon.value = IconCheck

View File

@@ -354,7 +354,9 @@ const saveEnvironment = async () => {
isLoading.value = true isLoading.value = true
if (!editingName.value) { if (!editingName.value) {
isLoading.value = false
toast.error(`${t("environment.invalid_name")}`) toast.error(`${t("environment.invalid_name")}`)
return return
} }

View File

@@ -127,19 +127,15 @@
:icon="masking ? IconEye : IconEyeOff" :icon="masking ? IconEye : IconEyeOff"
@click="toggleMask()" @click="toggleMask()"
/> />
<HoppButtonSecondary <div v-else class="aspect-square w-8"></div>
v-else
v-tippy="{ theme: 'tooltip' }"
:icon="IconArrowUpRight"
:title="t('request.go_to_authorization_tab')"
class="cursor-auto text-primary hover:text-primary"
/>
</span> </span>
<span> <span>
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:icon="IconArrowUpRight" :icon="IconArrowUpRight"
:title="t('request.go_to_authorization_tab')" :title="t('request.go_to_authorization_tab')"
@click="changeTab"
/> />
</span> </span>
</div> </div>
@@ -235,6 +231,7 @@ import { useColorMode } from "@composables/theming"
import { useToast } from "@composables/toast" import { useToast } from "@composables/toast"
import { import {
GQLHeader, GQLHeader,
HoppGQLAuth,
HoppGQLRequest, HoppGQLRequest,
parseRawKeyValueEntriesE, parseRawKeyValueEntriesE,
rawKeyValueEntriesToString, rawKeyValueEntriesToString,
@@ -267,6 +264,7 @@ import IconLock from "~icons/lucide/lock"
import IconPlus from "~icons/lucide/plus" import IconPlus from "~icons/lucide/plus"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import IconWrapText from "~icons/lucide/wrap-text" import IconWrapText from "~icons/lucide/wrap-text"
import { GQLOptionTabs } from "./RequestOptions.vue"
const colorMode = useColorMode() const colorMode = useColorMode()
const t = useI18n() const t = useI18n()
@@ -281,6 +279,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: HoppGQLRequest): void (e: "update:modelValue", value: HoppGQLRequest): void
(e: "change-tab", value: GQLOptionTabs): void
}>() }>()
const request = useVModel(props, "modelValue", emit) const request = useVModel(props, "modelValue", emit)
@@ -645,7 +644,7 @@ const inheritedProperties = computed(() => {
const computedAuthHeader = getComputedAuthHeaders( const computedAuthHeader = getComputedAuthHeaders(
request.value, request.value,
props.inheritedProperties.auth.inheritedAuth props.inheritedProperties.auth.inheritedAuth as HoppGQLAuth
)[0] )[0]
if ( if (
@@ -678,8 +677,5 @@ const mask = (header: any) => {
return header.header.value return header.header.value
} }
// const changeTab = (tab: ComputedHeader["source"]) => { const changeTab = () => emit("change-tab", "authorization")
// if (tab === "auth") emit("change-tab", "authorization")
// else emit("change-tab", "bodyParams")
// }
</script> </script>

View File

@@ -37,6 +37,7 @@
<GraphqlHeaders <GraphqlHeaders
v-model="request" v-model="request"
:inherited-properties="inheritedProperties" :inherited-properties="inheritedProperties"
@change-tab="changeOptionTab"
/> />
</HoppSmartTab> </HoppSmartTab>
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`"> <HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
@@ -261,6 +262,11 @@ const saveRequest = () => {
const clearGQLQuery = () => { const clearGQLQuery = () => {
request.value.query = "" request.value.query = ""
} }
const changeOptionTab = (e: GQLOptionTabs) => {
selectedOptionTab.value = e
}
defineActionHandler("request.send-cancel", runQuery) defineActionHandler("request.send-cancel", runQuery)
defineActionHandler("request.save", saveRequest) defineActionHandler("request.save", saveRequest)
defineActionHandler("request.save-as", () => { defineActionHandler("request.save-as", () => {

View File

@@ -49,8 +49,14 @@
/> />
</div> </div>
</div> </div>
<div v-if="bulkMode" class="h-full relative w-full flex flex-col flex-1"> <div v-if="bulkMode" class="h-full relative w-full flex flex-col flex-1">
<div ref="bulkEditor" class="absolute inset-0"></div> <div
ref="bulkEditor"
:class="{
'absolute inset-0': !isCollectionProperty,
}"
></div>
</div> </div>
<div v-else> <div v-else>
<draggable <draggable

View File

@@ -48,8 +48,11 @@ import {
} from "@hoppscotch/common/newstore/collections" } from "@hoppscotch/common/newstore/collections"
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient" import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
import { import {
GQLHeader,
HoppCollection, HoppCollection,
HoppGQLRequest, HoppGQLRequest,
HoppRESTHeaders,
HoppRESTParam,
HoppRESTRequest, HoppRESTRequest,
} from "@hoppscotch/data" } from "@hoppscotch/data"
import { gqlCollectionsSyncer } from "./gqlCollections.sync" import { gqlCollectionsSyncer } from "./gqlCollections.sync"
@@ -100,12 +103,22 @@ type ExportedUserCollectionGQL = {
data: string data: string
} }
function addDescriptionField(
candidate: HoppRESTHeaders | GQLHeader[] | HoppRESTParam[]
) {
return candidate.map((item) => ({
...item,
description: "description" in item ? item.description : "",
}))
}
function exportedCollectionToHoppCollection( function exportedCollectionToHoppCollection(
collection: ExportedUserCollectionREST | ExportedUserCollectionGQL, collection: ExportedUserCollectionREST | ExportedUserCollectionGQL,
collectionType: "REST" | "GQL" collectionType: "REST" | "GQL"
): HoppCollection { ): HoppCollection {
if (collectionType == "REST") { if (collectionType == "REST") {
const restCollection = collection as ExportedUserCollectionREST const restCollection = collection as ExportedUserCollectionREST
const data = const data =
restCollection.data && restCollection.data !== "null" restCollection.data && restCollection.data !== "null"
? JSON.parse(restCollection.data) ? JSON.parse(restCollection.data)
@@ -113,9 +126,10 @@ function exportedCollectionToHoppCollection(
auth: { authType: "inherit", authActive: false }, auth: { authType: "inherit", authActive: false },
headers: [], headers: [],
} }
return { return {
id: restCollection.id, id: restCollection.id,
v: 2, v: 3,
name: restCollection.name, name: restCollection.name,
folders: restCollection.folders.map((folder) => folders: restCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
@@ -140,26 +154,31 @@ function exportedCollectionToHoppCollection(
testScript, testScript,
requestVariables, requestVariables,
} = request } = request
const resolvedParams = addDescriptionField(params)
const resolvedHeaders = addDescriptionField(headers)
return { return {
v, v,
id, id,
name, name,
endpoint, endpoint,
method, method,
params, params: resolvedParams,
requestVariables: requestVariables, requestVariables,
auth, auth,
headers, headers: resolvedHeaders,
body, body,
preRequestScript, preRequestScript,
testScript, testScript,
} }
}), }),
auth: data.auth, auth: data.auth,
headers: data.headers, headers: addDescriptionField(data.headers),
} }
} else { } else {
const gqlCollection = collection as ExportedUserCollectionGQL const gqlCollection = collection as ExportedUserCollectionGQL
const data = const data =
gqlCollection.data && gqlCollection.data !== "null" gqlCollection.data && gqlCollection.data !== "null"
? JSON.parse(gqlCollection.data) ? JSON.parse(gqlCollection.data)
@@ -170,25 +189,34 @@ function exportedCollectionToHoppCollection(
return { return {
id: gqlCollection.id, id: gqlCollection.id,
v: 2, v: 3,
name: gqlCollection.name, name: gqlCollection.name,
folders: gqlCollection.folders.map((folder) => folders: gqlCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
), ),
requests: gqlCollection.requests.map( requests: gqlCollection.requests.map((request) => {
({ v, auth, headers, name, id, query, url, variables }) => ({ const requestParsedResult = HoppGQLRequest.safeParse(request)
if (requestParsedResult.type === "ok") {
return requestParsedResult.value
}
const { v, auth, headers, name, id, query, url, variables } = request
const resolvedHeaders = addDescriptionField(headers)
return {
id, id,
v, v,
auth, auth,
headers, headers: resolvedHeaders,
name, name,
query, query,
url, url,
variables, variables,
}) }
) as HoppGQLRequest[], }),
auth: data.auth, auth: data.auth,
headers: data.headers, headers: addDescriptionField(data.headers),
} }
} }
} }
@@ -349,17 +377,17 @@ function setupUserCollectionCreatedSubscription() {
name: res.right.userCollectionCreated.title, name: res.right.userCollectionCreated.title,
folders: [], folders: [],
requests: [], requests: [],
v: 2, v: 3,
auth: data.auth, auth: data.auth,
headers: data.headers, headers: addDescriptionField(data.headers),
}) })
: addRESTCollection({ : addRESTCollection({
name: res.right.userCollectionCreated.title, name: res.right.userCollectionCreated.title,
folders: [], folders: [],
requests: [], requests: [],
v: 2, v: 3,
auth: data.auth, auth: data.auth,
headers: data?.headers, headers: addDescriptionField(data.headers),
}) })
const localIndex = collectionStore.value.state.length - 1 const localIndex = collectionStore.value.state.length - 1