feat: team search in workspace search and spotlight (#3896)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
@@ -281,7 +281,7 @@
|
|||||||
"updated": "Environment updated",
|
"updated": "Environment updated",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
"variables":"Variables",
|
"variables": "Variables",
|
||||||
"variable_list": "Variable List"
|
"variable_list": "Variable List"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -961,7 +961,8 @@
|
|||||||
"success_invites": "Success invites",
|
"success_invites": "Success invites",
|
||||||
"title": "Workspaces",
|
"title": "Workspaces",
|
||||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace."
|
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace.",
|
||||||
|
"search_title": "Team Requests"
|
||||||
},
|
},
|
||||||
"team_environment": {
|
"team_environment": {
|
||||||
"deleted": "Environment Deleted",
|
"deleted": "Environment Deleted",
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<span class="flex flex-1 items-center space-x-2">
|
||||||
|
<template v-for="(title, index) in collectionTitles" :key="index">
|
||||||
|
<span class="block" :class="{ truncate: index !== 0 }">
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<icon-lucide-chevron-right class="flex flex-shrink-0" />
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-if="request"
|
||||||
|
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold"
|
||||||
|
:style="{ color: getMethodLabelColor(request.method) }"
|
||||||
|
>
|
||||||
|
{{ request.method.toUpperCase() }}
|
||||||
|
</span>
|
||||||
|
<span v-if="request" class="block">
|
||||||
|
{{ request.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
collectionTitles: string[]
|
||||||
|
request: {
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
@@ -111,6 +111,7 @@ import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/
|
|||||||
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
||||||
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
||||||
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
||||||
|
import { TeamsSpotlightSearcherService } from "~/services/spotlight/searchers/teamRequest.searcher"
|
||||||
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
||||||
import {
|
import {
|
||||||
SwitchWorkspaceSpotlightSearcherService,
|
SwitchWorkspaceSpotlightSearcherService,
|
||||||
@@ -144,6 +145,7 @@ useService(SwitchEnvSpotlightSearcherService)
|
|||||||
useService(WorkspaceSpotlightSearcherService)
|
useService(WorkspaceSpotlightSearcherService)
|
||||||
useService(SwitchWorkspaceSpotlightSearcherService)
|
useService(SwitchWorkspaceSpotlightSearcherService)
|
||||||
useService(InterceptorSpotlightSearcherService)
|
useService(InterceptorSpotlightSearcherService)
|
||||||
|
useService(TeamsSpotlightSearcherService)
|
||||||
|
|
||||||
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
|
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
|
||||||
useService(searcher)
|
useService(searcher)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="hasNoTeamAccess"
|
v-if="hasNoTeamAccess || isShowingSearchResults"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
disabled
|
disabled
|
||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
@@ -36,8 +36,9 @@
|
|||||||
v-if="!saveRequest"
|
v-if="!saveRequest"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:disabled="
|
:disabled="
|
||||||
collectionsType.type === 'team-collections' &&
|
(collectionsType.type === 'team-collections' &&
|
||||||
collectionsType.selectedTeam === undefined
|
collectionsType.selectedTeam === undefined) ||
|
||||||
|
isShowingSearchResults
|
||||||
"
|
"
|
||||||
:icon="IconImport"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
|
||||||
:collection-move-loading="collectionMoveLoading"
|
:collection-move-loading="collectionMoveLoading"
|
||||||
:is-last-item="node.data.isLastItem"
|
:is-last-item="node.data.isLastItem"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
@@ -128,6 +129,14 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
handleCollectionClick({
|
||||||
|
collectionID: node.id,
|
||||||
|
isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<CollectionsCollection
|
<CollectionsCollection
|
||||||
v-if="node.data.type === 'folders'"
|
v-if="node.data.type === 'folders'"
|
||||||
@@ -137,7 +146,7 @@
|
|||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
|
||||||
:collection-move-loading="collectionMoveLoading"
|
:collection-move-loading="collectionMoveLoading"
|
||||||
:is-last-item="node.data.isLastItem"
|
:is-last-item="node.data.isLastItem"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
@@ -209,6 +218,15 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
handleCollectionClick({
|
||||||
|
// for the folders, we get a path, so we need to get the last part of the path which is the folder id
|
||||||
|
collectionID: node.id.split('/').pop() as string,
|
||||||
|
isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<CollectionsRequest
|
<CollectionsRequest
|
||||||
v-if="node.data.type === 'requests'"
|
v-if="node.data.type === 'requests'"
|
||||||
@@ -218,7 +236,7 @@
|
|||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:duplicate-loading="duplicateLoading"
|
:duplicate-loading="duplicateLoading"
|
||||||
:is-active="isActiveRequest(node.data.data.data.id)"
|
:is-active="isActiveRequest(node.data.data.data.id)"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
|
||||||
:request-move-loading="requestMoveLoading"
|
:request-move-loading="requestMoveLoading"
|
||||||
:is-last-item="node.data.isLastItem"
|
:is-last-item="node.data.isLastItem"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
@@ -283,7 +301,15 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="node === null"
|
v-if="filterText.length !== 0 && teamCollectionList.length === 0"
|
||||||
|
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-lucide-search class="svg-icons opacity-75" />
|
||||||
|
</template>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-else-if="node === null"
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
@@ -394,6 +420,11 @@ const props = defineProps({
|
|||||||
default: () => ({ type: "my-collections", selectedTeam: undefined }),
|
default: () => ({ type: "my-collections", selectedTeam: undefined }),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
filterText: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
default: "",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
teamCollectionList: {
|
teamCollectionList: {
|
||||||
type: Array as PropType<TeamCollection[]>,
|
type: Array as PropType<TeamCollection[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -436,6 +467,8 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isShowingSearchResults = computed(() => props.filterText.length > 0)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
event: "add-request",
|
event: "add-request",
|
||||||
@@ -543,6 +576,14 @@ const emit = defineEmits<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
): void
|
): void
|
||||||
|
(
|
||||||
|
event: "collection-click",
|
||||||
|
payload: {
|
||||||
|
// if the collection is open or not in the tree
|
||||||
|
isOpen: boolean
|
||||||
|
collectionID: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
(event: "select", payload: Picked | null): void
|
(event: "select", payload: Picked | null): void
|
||||||
(event: "expand-team-collection", payload: string): void
|
(event: "expand-team-collection", payload: string): void
|
||||||
(event: "display-modal-add"): void
|
(event: "display-modal-add"): void
|
||||||
@@ -555,6 +596,18 @@ const getPath = (path: string) => {
|
|||||||
return pathArray.join("/")
|
return pathArray.join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCollectionClick = (payload: {
|
||||||
|
collectionID: string
|
||||||
|
isOpen: boolean
|
||||||
|
}) => {
|
||||||
|
const { collectionID, isOpen } = payload
|
||||||
|
|
||||||
|
emit("collection-click", {
|
||||||
|
collectionID,
|
||||||
|
isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const teamCollectionsList = toRef(props, "teamCollectionList")
|
const teamCollectionsList = toRef(props, "teamCollectionList")
|
||||||
|
|
||||||
const hasNoTeamAccess = computed(
|
const hasNoTeamAccess = computed(
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="flex w-full bg-transparent px-4 py-2 h-8"
|
class="flex w-full bg-transparent px-4 py-2 h-8"
|
||||||
:placeholder="t('action.search')"
|
:placeholder="t('action.search')"
|
||||||
:disabled="collectionsType.type === 'team-collections'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CollectionsMyCollections
|
<CollectionsMyCollections
|
||||||
@@ -58,8 +57,15 @@
|
|||||||
<CollectionsTeamCollections
|
<CollectionsTeamCollections
|
||||||
v-else
|
v-else
|
||||||
:collections-type="collectionsType"
|
:collections-type="collectionsType"
|
||||||
:team-collection-list="teamCollectionList"
|
:team-collection-list="
|
||||||
:team-loading-collections="teamLoadingCollections"
|
filterTexts.length > 0 ? teamsSearchResults : teamCollectionList
|
||||||
|
"
|
||||||
|
:team-loading-collections="
|
||||||
|
filterTexts.length > 0
|
||||||
|
? collectionsBeingLoadedFromSearch
|
||||||
|
: teamLoadingCollections
|
||||||
|
"
|
||||||
|
:filter-text="filterTexts"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:duplicate-loading="duplicateLoading"
|
:duplicate-loading="duplicateLoading"
|
||||||
:save-request="saveRequest"
|
:save-request="saveRequest"
|
||||||
@@ -87,6 +93,7 @@
|
|||||||
@expand-team-collection="expandTeamCollection"
|
@expand-team-collection="expandTeamCollection"
|
||||||
@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"
|
||||||
/>
|
/>
|
||||||
<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"
|
||||||
@@ -199,7 +206,7 @@ import {
|
|||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
makeCollection,
|
makeCollection,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { cloneDeep, isEqual } from "lodash-es"
|
import { cloneDeep, debounce, isEqual } from "lodash-es"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import {
|
import {
|
||||||
createNewRootCollection,
|
createNewRootCollection,
|
||||||
@@ -240,6 +247,7 @@ import { WorkspaceService } from "~/services/workspace.service"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -336,6 +344,50 @@ const teamLoadingCollections = useReadonlyStream(
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
cascadeParentCollectionForHeaderAuthForSearchResults,
|
||||||
|
searchTeams,
|
||||||
|
teamsSearchResults,
|
||||||
|
teamsSearchResultsLoading,
|
||||||
|
expandCollection,
|
||||||
|
expandingCollections,
|
||||||
|
} = useService(TeamSearchService)
|
||||||
|
|
||||||
|
watch(teamsSearchResults, (newSearchResults) => {
|
||||||
|
if (newSearchResults.length === 1 && filterTexts.value.length > 0) {
|
||||||
|
expandCollection(newSearchResults[0].id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const debouncedSearch = debounce(searchTeams, 400)
|
||||||
|
|
||||||
|
const collectionsBeingLoadedFromSearch = computed(() => {
|
||||||
|
const collections = []
|
||||||
|
|
||||||
|
if (teamsSearchResultsLoading.value) {
|
||||||
|
collections.push("root")
|
||||||
|
}
|
||||||
|
|
||||||
|
collections.push(...expandingCollections.value)
|
||||||
|
|
||||||
|
return collections
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
filterTexts,
|
||||||
|
(newFilterText) => {
|
||||||
|
if (collectionsType.value.type === "team-collections") {
|
||||||
|
const selectedTeamID = collectionsType.value.selectedTeam?.id
|
||||||
|
|
||||||
|
selectedTeamID &&
|
||||||
|
debouncedSearch(newFilterText, selectedTeamID)?.catch((_) => {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => myTeams.value,
|
() => myTeams.value,
|
||||||
(newTeams) => {
|
(newTeams) => {
|
||||||
@@ -364,7 +416,28 @@ const switchToMyCollections = () => {
|
|||||||
teamCollectionAdapter.changeTeamID(null)
|
teamCollectionAdapter.changeTeamID(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* right now, for search results, we rely on collection click + isOpen to expand the collection
|
||||||
|
*/
|
||||||
|
const handleCollectionClick = (payload: {
|
||||||
|
collectionID: string
|
||||||
|
isOpen: boolean
|
||||||
|
}) => {
|
||||||
|
if (
|
||||||
|
filterTexts.value.length > 0 &&
|
||||||
|
teamsSearchResults.value.length &&
|
||||||
|
payload.isOpen
|
||||||
|
) {
|
||||||
|
expandCollection(payload.collectionID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const expandTeamCollection = (collectionID: string) => {
|
const expandTeamCollection = (collectionID: string) => {
|
||||||
|
if (filterTexts.value.length > 0 && teamsSearchResults.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
teamCollectionAdapter.expandCollection(collectionID)
|
teamCollectionAdapter.expandCollection(collectionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1330,13 +1403,25 @@ const selectRequest = (selectedRequest: {
|
|||||||
let possibleTab = null
|
let possibleTab = null
|
||||||
|
|
||||||
if (collectionsType.value.type === "team-collections") {
|
if (collectionsType.value.type === "team-collections") {
|
||||||
const { auth, headers } =
|
let inheritedProperties: HoppInheritedProperty | undefined = undefined
|
||||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
|
|
||||||
|
|
||||||
possibleTab = tabs.getTabRefWithSaveContext({
|
if (filterTexts.value.length > 0) {
|
||||||
|
const collectionID = folderPath.split("/").at(-1)
|
||||||
|
|
||||||
|
if (!collectionID) return
|
||||||
|
|
||||||
|
inheritedProperties =
|
||||||
|
cascadeParentCollectionForHeaderAuthForSearchResults(collectionID)
|
||||||
|
} else {
|
||||||
|
inheritedProperties =
|
||||||
|
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
tabs.setActiveTab(possibleTab.value.id)
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
@@ -1348,10 +1433,7 @@ const selectRequest = (selectedRequest: {
|
|||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
collectionID: folderPath,
|
collectionID: folderPath,
|
||||||
},
|
},
|
||||||
inheritedProperties: {
|
inheritedProperties: inheritedProperties,
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,610 @@
|
|||||||
|
import { ref } from "vue"
|
||||||
|
import { runGQLQuery } from "../backend/GQLClient"
|
||||||
|
import {
|
||||||
|
GetCollectionChildrenDocument,
|
||||||
|
GetCollectionRequestsDocument,
|
||||||
|
GetSingleCollectionDocument,
|
||||||
|
GetSingleRequestDocument,
|
||||||
|
} from "../backend/graphql"
|
||||||
|
import { TeamCollection } from "./TeamCollection"
|
||||||
|
import { HoppRESTAuth, HoppRESTHeader } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||||
|
import { TeamRequest } from "./TeamRequest"
|
||||||
|
import { Service } from "dioc"
|
||||||
|
import axios from "axios"
|
||||||
|
import { Ref } from "vue"
|
||||||
|
|
||||||
|
type CollectionSearchMeta = {
|
||||||
|
isSearchResult?: boolean
|
||||||
|
insertedWhileExpanding?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionSearchNode =
|
||||||
|
| {
|
||||||
|
type: "request"
|
||||||
|
title: string
|
||||||
|
method: string
|
||||||
|
id: string
|
||||||
|
// parent collections
|
||||||
|
path: CollectionSearchNode[]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "collection"
|
||||||
|
title: string
|
||||||
|
id: string
|
||||||
|
// parent collections
|
||||||
|
path: CollectionSearchNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SearchCollection = TeamCollection & {
|
||||||
|
parentID: string | null
|
||||||
|
meta?: CollectionSearchMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SearchRequest = {
|
||||||
|
id: string
|
||||||
|
collectionID: string
|
||||||
|
title: string
|
||||||
|
request: {
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
}
|
||||||
|
meta?: CollectionSearchMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToTeamCollection(
|
||||||
|
node: CollectionSearchNode & {
|
||||||
|
meta?: CollectionSearchMeta
|
||||||
|
},
|
||||||
|
existingCollections: Record<string, _SearchCollection>,
|
||||||
|
existingRequests: Record<string, _SearchRequest>
|
||||||
|
) {
|
||||||
|
if (node.type === "request") {
|
||||||
|
existingRequests[node.id] = {
|
||||||
|
id: node.id,
|
||||||
|
collectionID: node.path[0].id,
|
||||||
|
title: node.title,
|
||||||
|
request: {
|
||||||
|
name: node.title,
|
||||||
|
method: node.method,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
isSearchResult: node.meta?.isSearchResult || false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.path[0]) {
|
||||||
|
// add parent collections to the collections array recursively
|
||||||
|
convertToTeamCollection(
|
||||||
|
node.path[0],
|
||||||
|
existingCollections,
|
||||||
|
existingRequests
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
existingCollections[node.id] = {
|
||||||
|
id: node.id,
|
||||||
|
title: node.title,
|
||||||
|
children: [],
|
||||||
|
requests: [],
|
||||||
|
data: null,
|
||||||
|
parentID: node.path[0]?.id,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: node.meta?.isSearchResult || false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.path[0]) {
|
||||||
|
// add parent collections to the collections array recursively
|
||||||
|
convertToTeamCollection(
|
||||||
|
node.path[0],
|
||||||
|
existingCollections,
|
||||||
|
existingRequests
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
existingCollections,
|
||||||
|
existingRequests,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToTeamTree(
|
||||||
|
collections: (TeamCollection & { parentID: string | null })[],
|
||||||
|
requests: TeamRequest[]
|
||||||
|
) {
|
||||||
|
const collectionTree: TeamCollection[] = []
|
||||||
|
|
||||||
|
collections.forEach((collection) => {
|
||||||
|
const parentCollection = collection.parentID
|
||||||
|
? collections.find((c) => c.id === collection.parentID)
|
||||||
|
: null
|
||||||
|
|
||||||
|
const isAlreadyInserted = parentCollection?.children?.find(
|
||||||
|
(c) => c.id === collection.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAlreadyInserted) return
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
parentCollection.children = parentCollection.children || []
|
||||||
|
parentCollection.children.push(collection)
|
||||||
|
} else {
|
||||||
|
collectionTree.push(collection)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
requests.forEach((request) => {
|
||||||
|
const parentCollection = collections.find(
|
||||||
|
(c) => c.id === request.collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
const isAlreadyInserted = parentCollection?.requests?.find(
|
||||||
|
(r) => r.id === request.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAlreadyInserted) return
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
parentCollection.requests = parentCollection.requests || []
|
||||||
|
parentCollection.requests.push({
|
||||||
|
id: request.id,
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
title: request.title,
|
||||||
|
request: request.request,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return collectionTree
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TeamSearchService extends Service {
|
||||||
|
public static readonly ID = "TeamSearchService"
|
||||||
|
|
||||||
|
public endpoint = import.meta.env.VITE_BACKEND_API_URL
|
||||||
|
|
||||||
|
public teamsSearchResultsLoading = ref(false)
|
||||||
|
public teamsSearchResults = ref<TeamCollection[]>([])
|
||||||
|
public teamsSearchResultsFormattedForSpotlight = ref<
|
||||||
|
{
|
||||||
|
collectionTitles: string[]
|
||||||
|
request: {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
>([])
|
||||||
|
|
||||||
|
searchResultsCollections: Record<string, _SearchCollection> = {}
|
||||||
|
searchResultsRequests: Record<string, _SearchRequest> = {}
|
||||||
|
|
||||||
|
expandingCollections: Ref<string[]> = ref([])
|
||||||
|
expandedCollections: Ref<string[]> = ref([])
|
||||||
|
|
||||||
|
// FUTURE-TODO: ideally this should return the search results / formatted results instead of directly manipulating the result set
|
||||||
|
// eg: do the spotlight formatting in the spotlight searcher and not here
|
||||||
|
searchTeams = async (query: string, teamID: string) => {
|
||||||
|
if (!query.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.teamsSearchResultsLoading.value = true
|
||||||
|
|
||||||
|
this.searchResultsCollections = {}
|
||||||
|
this.searchResultsRequests = {}
|
||||||
|
this.expandedCollections.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const searchResponse = await axios.get(
|
||||||
|
`${
|
||||||
|
this.endpoint
|
||||||
|
}/team-collection/search/${teamID}?searchQuery=${encodeURIComponent(
|
||||||
|
query
|
||||||
|
)}}`,
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (searchResponse.status !== 200) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResults = searchResponse.data.data as CollectionSearchNode[]
|
||||||
|
|
||||||
|
searchResults
|
||||||
|
.map((node) => {
|
||||||
|
const { existingCollections, existingRequests } =
|
||||||
|
convertToTeamCollection(
|
||||||
|
{
|
||||||
|
...node,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
collections: existingCollections,
|
||||||
|
requests: existingRequests,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forEach(({ collections, requests }) => {
|
||||||
|
this.searchResultsCollections = {
|
||||||
|
...this.searchResultsCollections,
|
||||||
|
...collections,
|
||||||
|
}
|
||||||
|
this.searchResultsRequests = {
|
||||||
|
...this.searchResultsRequests,
|
||||||
|
...requests,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const collectionFetchingPromises = Object.values(
|
||||||
|
this.searchResultsCollections
|
||||||
|
).map((col) => {
|
||||||
|
return getSingleCollection(col.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestFetchingPromises = Object.values(
|
||||||
|
this.searchResultsRequests
|
||||||
|
).map((req) => {
|
||||||
|
return getSingleRequest(req.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const collectionResponses = await Promise.all(collectionFetchingPromises)
|
||||||
|
const requestResponses = await Promise.all(requestFetchingPromises)
|
||||||
|
|
||||||
|
requestResponses.map((res) => {
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = res.right.request
|
||||||
|
|
||||||
|
if (!request) return
|
||||||
|
|
||||||
|
this.searchResultsRequests[request.id] = {
|
||||||
|
id: request.id,
|
||||||
|
title: request.title,
|
||||||
|
request: JSON.parse(request.request) as TeamRequest["request"],
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
collectionResponses.map((res) => {
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = res.right.collection
|
||||||
|
|
||||||
|
if (!collection) return
|
||||||
|
|
||||||
|
this.searchResultsCollections[collection.id].data =
|
||||||
|
collection.data ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
const collectionTree = convertToTeamTree(
|
||||||
|
Object.values(this.searchResultsCollections),
|
||||||
|
// asserting because we've already added the missing properties after fetching the full details
|
||||||
|
Object.values(this.searchResultsRequests) as TeamRequest[]
|
||||||
|
)
|
||||||
|
|
||||||
|
this.teamsSearchResults.value = collectionTree
|
||||||
|
|
||||||
|
this.teamsSearchResultsFormattedForSpotlight.value = Object.values(
|
||||||
|
this.searchResultsRequests
|
||||||
|
).map((request) => {
|
||||||
|
return formatTeamsSearchResultsForSpotlight(
|
||||||
|
{
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
name: request.title,
|
||||||
|
method: request.request.method,
|
||||||
|
id: request.id,
|
||||||
|
},
|
||||||
|
Object.values(this.searchResultsCollections)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.teamsSearchResultsLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
cascadeParentCollectionForHeaderAuthForSearchResults = (
|
||||||
|
collectionID: string
|
||||||
|
): HoppInheritedProperty => {
|
||||||
|
const defaultInheritedAuth: HoppInheritedProperty["auth"] = {
|
||||||
|
parentID: "",
|
||||||
|
parentName: "",
|
||||||
|
inheritedAuth: {
|
||||||
|
authType: "none",
|
||||||
|
authActive: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultInheritedHeaders: HoppInheritedProperty["headers"] = []
|
||||||
|
|
||||||
|
const collection = Object.values(this.searchResultsCollections).find(
|
||||||
|
(col) => col.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!collection)
|
||||||
|
return { auth: defaultInheritedAuth, headers: defaultInheritedHeaders }
|
||||||
|
|
||||||
|
const inheritedAuthData = this.findInheritableParentAuth(collectionID)
|
||||||
|
const inheritedHeadersData = this.findInheritableParentHeaders(collectionID)
|
||||||
|
|
||||||
|
return {
|
||||||
|
auth: E.isRight(inheritedAuthData)
|
||||||
|
? inheritedAuthData.right
|
||||||
|
: defaultInheritedAuth,
|
||||||
|
headers: E.isRight(inheritedHeadersData)
|
||||||
|
? Object.values(inheritedHeadersData.right)
|
||||||
|
: defaultInheritedHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findInheritableParentAuth = (
|
||||||
|
collectionID: string
|
||||||
|
): E.Either<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
parentID: string
|
||||||
|
parentName: string
|
||||||
|
inheritedAuth: HoppRESTAuth
|
||||||
|
}
|
||||||
|
> => {
|
||||||
|
const collection = Object.values(this.searchResultsCollections).find(
|
||||||
|
(col) => col.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return E.left("PARENT_NOT_FOUND" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
// has inherited data
|
||||||
|
if (collection.data) {
|
||||||
|
const parentInheritedData = JSON.parse(collection.data) as {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeader[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const inheritedAuth = parentInheritedData.auth
|
||||||
|
|
||||||
|
if (inheritedAuth.authType !== "inherit") {
|
||||||
|
return E.right({
|
||||||
|
parentID: collectionID,
|
||||||
|
parentName: collection.title,
|
||||||
|
inheritedAuth: inheritedAuth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collection.parentID) {
|
||||||
|
return E.left("PARENT_INHERITED_DATA_NOT_FOUND")
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findInheritableParentAuth(collection.parentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
findInheritableParentHeaders = (
|
||||||
|
collectionID: string,
|
||||||
|
existingHeaders: Record<
|
||||||
|
string,
|
||||||
|
HoppInheritedProperty["headers"][number]
|
||||||
|
> = {}
|
||||||
|
): E.Either<
|
||||||
|
string,
|
||||||
|
Record<string, HoppInheritedProperty["headers"][number]>
|
||||||
|
> => {
|
||||||
|
const collection = Object.values(this.searchResultsCollections).find(
|
||||||
|
(col) => col.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return E.left("PARENT_NOT_FOUND" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if it has headers to inherit, if yes, add it to the existing headers
|
||||||
|
if (collection.data) {
|
||||||
|
const parentInheritedData = JSON.parse(collection.data) as {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeader[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const inheritedHeaders = parentInheritedData.headers
|
||||||
|
|
||||||
|
if (inheritedHeaders) {
|
||||||
|
inheritedHeaders.forEach((header) => {
|
||||||
|
if (!existingHeaders[header.key]) {
|
||||||
|
existingHeaders[header.key] = {
|
||||||
|
parentID: collection.id,
|
||||||
|
parentName: collection.title,
|
||||||
|
inheritedHeader: header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collection.parentID) {
|
||||||
|
return this.findInheritableParentHeaders(
|
||||||
|
collection.parentID,
|
||||||
|
existingHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(existingHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
expandCollection = async (collectionID: string) => {
|
||||||
|
if (this.expandingCollections.value.includes(collectionID)) return
|
||||||
|
|
||||||
|
const collectionToExpand = Object.values(
|
||||||
|
this.searchResultsCollections
|
||||||
|
).find((col) => col.id === collectionID)
|
||||||
|
|
||||||
|
const isAlreadyExpanded =
|
||||||
|
this.expandedCollections.value.includes(collectionID)
|
||||||
|
|
||||||
|
// only allow search result collections to be expanded
|
||||||
|
if (
|
||||||
|
isAlreadyExpanded ||
|
||||||
|
!collectionToExpand ||
|
||||||
|
!(
|
||||||
|
collectionToExpand.meta?.isSearchResult ||
|
||||||
|
collectionToExpand.meta?.insertedWhileExpanding
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.expandingCollections.value.push(collectionID)
|
||||||
|
|
||||||
|
const childCollectionsPromise = getCollectionChildCollections(collectionID)
|
||||||
|
const childRequestsPromise = getCollectionChildRequests(collectionID)
|
||||||
|
|
||||||
|
const [childCollections, childRequests] = await Promise.all([
|
||||||
|
childCollectionsPromise,
|
||||||
|
childRequestsPromise,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (E.isLeft(childCollections)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (E.isLeft(childRequests)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
childCollections.right.collection?.children
|
||||||
|
.map((child) => ({
|
||||||
|
id: child.id,
|
||||||
|
title: child.title,
|
||||||
|
data: child.data ?? null,
|
||||||
|
children: [],
|
||||||
|
requests: [],
|
||||||
|
}))
|
||||||
|
.forEach((child) => {
|
||||||
|
this.searchResultsCollections[child.id] = {
|
||||||
|
...child,
|
||||||
|
parentID: collectionID,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: false,
|
||||||
|
insertedWhileExpanding: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
childRequests.right.requestsInCollection
|
||||||
|
.map((request) => ({
|
||||||
|
id: request.id,
|
||||||
|
collectionID: collectionID,
|
||||||
|
title: request.title,
|
||||||
|
request: JSON.parse(request.request) as TeamRequest["request"],
|
||||||
|
}))
|
||||||
|
.forEach((request) => {
|
||||||
|
this.searchResultsRequests[request.id] = {
|
||||||
|
...request,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: false,
|
||||||
|
insertedWhileExpanding: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.teamsSearchResults.value = convertToTeamTree(
|
||||||
|
Object.values(this.searchResultsCollections),
|
||||||
|
// asserting because we've already added the missing properties after fetching the full details
|
||||||
|
Object.values(this.searchResultsRequests) as TeamRequest[]
|
||||||
|
)
|
||||||
|
|
||||||
|
// remove the collection after expanding
|
||||||
|
this.expandingCollections.value = this.expandingCollections.value.filter(
|
||||||
|
(colID) => colID !== collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
this.expandedCollections.value.push(collectionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSingleCollection = (collectionID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetSingleCollectionDocument,
|
||||||
|
variables: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getSingleRequest = (requestID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetSingleRequestDocument,
|
||||||
|
variables: {
|
||||||
|
requestID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getCollectionChildCollections = (collectionID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetCollectionChildrenDocument,
|
||||||
|
variables: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getCollectionChildRequests = (collectionID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetCollectionRequestsDocument,
|
||||||
|
variables: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatTeamsSearchResultsForSpotlight = (
|
||||||
|
request: {
|
||||||
|
collectionID: string
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
id: string
|
||||||
|
},
|
||||||
|
parentCollections: (TeamCollection & { parentID: string | null })[]
|
||||||
|
) => {
|
||||||
|
let collectionTitles: string[] = []
|
||||||
|
|
||||||
|
let parentCollectionID: string | null = request.collectionID
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!parentCollectionID) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentCollection = parentCollections.find(
|
||||||
|
(col) => col.id === parentCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!parentCollection) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionTitles = [parentCollection.title, ...collectionTitles]
|
||||||
|
parentCollectionID = parentCollection.parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionTitles,
|
||||||
|
request: {
|
||||||
|
name: request.name,
|
||||||
|
method: request.method,
|
||||||
|
id: request.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { Service } from "dioc"
|
||||||
|
import {
|
||||||
|
SpotlightSearcher,
|
||||||
|
SpotlightSearcherResult,
|
||||||
|
SpotlightSearcherSessionState,
|
||||||
|
SpotlightService,
|
||||||
|
} from ".."
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
import { Ref, computed, effectScope, markRaw, watch } from "vue"
|
||||||
|
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
||||||
|
import { cloneDeep, debounce } from "lodash-es"
|
||||||
|
import IconFolder from "~icons/lucide/folder"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
import RESTTeamRequestEntry from "~/components/app/spotlight/entry/RESTTeamRequestEntry.vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
export class TeamsSpotlightSearcherService
|
||||||
|
extends Service
|
||||||
|
implements SpotlightSearcher
|
||||||
|
{
|
||||||
|
public static readonly ID = "TEAMS_SPOTLIGHT_SEARCHER_SERVICE"
|
||||||
|
|
||||||
|
private t = getI18n()
|
||||||
|
|
||||||
|
public searcherID = "teams"
|
||||||
|
public searcherSectionTitle = this.t("team.search_title")
|
||||||
|
|
||||||
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
|
|
||||||
|
private readonly teamsSearch = this.bind(TeamSearchService)
|
||||||
|
|
||||||
|
private readonly workspaceService = this.bind(WorkspaceService)
|
||||||
|
|
||||||
|
private readonly tabs = this.bind(RESTTabService)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.spotlight.registerSearcher(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
createSearchSession(
|
||||||
|
query: Readonly<Ref<string>>
|
||||||
|
): [Ref<SpotlightSearcherSessionState>, () => void] {
|
||||||
|
const isTeamWorkspace = computed(
|
||||||
|
() => this.workspaceService.currentWorkspace.value.type === "team"
|
||||||
|
)
|
||||||
|
|
||||||
|
const scopeHandle = effectScope()
|
||||||
|
|
||||||
|
scopeHandle.run(() => {
|
||||||
|
const debouncedSearch = debounce(this.teamsSearch.searchTeams, 400)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
query,
|
||||||
|
(query) => {
|
||||||
|
if (this.workspaceService.currentWorkspace.value.type === "team") {
|
||||||
|
const teamID = this.workspaceService.currentWorkspace.value.teamID
|
||||||
|
debouncedSearch(query, teamID)?.catch((_) => {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSessionEnd = () => {
|
||||||
|
scopeHandle.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultObj = computed<SpotlightSearcherSessionState>(() => {
|
||||||
|
return isTeamWorkspace.value
|
||||||
|
? {
|
||||||
|
loading: this.teamsSearch.teamsSearchResultsLoading.value,
|
||||||
|
results:
|
||||||
|
this.teamsSearch.teamsSearchResultsFormattedForSpotlight.value.map(
|
||||||
|
(result) => ({
|
||||||
|
id: result.request.id,
|
||||||
|
icon: markRaw(IconFolder),
|
||||||
|
score: 1, // make a better scoring system for this
|
||||||
|
text: {
|
||||||
|
type: "custom",
|
||||||
|
component: markRaw(RESTTeamRequestEntry),
|
||||||
|
componentProps: {
|
||||||
|
collectionTitles: result.collectionTitles,
|
||||||
|
request: result.request,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
loading: false,
|
||||||
|
results: [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return [resultObj, onSessionEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
onResultSelect(result: SpotlightSearcherResult): void {
|
||||||
|
let inheritedProperties: HoppInheritedProperty | undefined = undefined
|
||||||
|
|
||||||
|
const selectedRequest = this.teamsSearch.searchResultsRequests[result.id]
|
||||||
|
|
||||||
|
if (!selectedRequest) return
|
||||||
|
|
||||||
|
const collectionID = result.id
|
||||||
|
|
||||||
|
if (!collectionID) return
|
||||||
|
|
||||||
|
inheritedProperties =
|
||||||
|
this.teamsSearch.cascadeParentCollectionForHeaderAuthForSearchResults(
|
||||||
|
collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
const possibleTab = this.tabs.getTabRefWithSaveContext({
|
||||||
|
originLocation: "team-collection",
|
||||||
|
requestID: result.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (possibleTab) {
|
||||||
|
this.tabs.setActiveTab(possibleTab.value.id)
|
||||||
|
} else {
|
||||||
|
this.tabs.createNewTab({
|
||||||
|
request: cloneDeep(selectedRequest.request as HoppRESTRequest),
|
||||||
|
isDirty: false,
|
||||||
|
saveContext: {
|
||||||
|
originLocation: "team-collection",
|
||||||
|
requestID: selectedRequest.id,
|
||||||
|
collectionID: selectedRequest.collectionID,
|
||||||
|
},
|
||||||
|
inheritedProperties: inheritedProperties,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user