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
v-model="editableCollection"
:is-collection-property="true"
@change-tab="changeOptionTab"
/>
<div
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 IconCopy from "~icons/lucide/copy"
import IconHelpCircle from "~icons/lucide/help-circle"
import { RESTOptionTabs } from "../http/RequestOptions.vue"
const persistenceService = useService(PersistenceService)
const t = useI18n()
@@ -268,6 +270,10 @@ const hideModal = () => {
emit("hide-modal")
}
const changeOptionTab = (e: RESTOptionTabs) => {
activeTab.value = e
}
const copyCollectionID = () => {
copyToClipboard(props.editingProperties.path)
copyIcon.value = IconCheck

View File

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

View File

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

View File

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

View File

@@ -49,8 +49,14 @@
/>
</div>
</div>
<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 v-else>
<draggable

View File

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