,
default: () => [],
@@ -436,6 +467,8 @@ const props = defineProps({
},
})
+const isShowingSearchResults = computed(() => props.filterText.length > 0)
+
const emit = defineEmits<{
(
event: "add-request",
@@ -543,6 +576,14 @@ const emit = defineEmits<{
}
}
): 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: "expand-team-collection", payload: string): void
(event: "display-modal-add"): void
@@ -555,6 +596,18 @@ const getPath = (path: string) => {
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 hasNoTeamAccess = computed(
diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue
index a22f99895b..9a53a69542 100644
--- a/packages/hoppscotch-common/src/components/collections/index.vue
+++ b/packages/hoppscotch-common/src/components/collections/index.vue
@@ -24,7 +24,6 @@
autocomplete="off"
class="flex w-full bg-transparent px-4 py-2 h-8"
:placeholder="t('action.search')"
- :disabled="collectionsType.type === 'team-collections'"
/>
{
+ 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(
() => myTeams.value,
(newTeams) => {
@@ -364,7 +416,28 @@ const switchToMyCollections = () => {
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) => {
+ if (filterTexts.value.length > 0 && teamsSearchResults.value) {
+ return
+ }
+
teamCollectionAdapter.expandCollection(collectionID)
}
@@ -1330,13 +1403,25 @@ const selectRequest = (selectedRequest: {
let possibleTab = null
if (collectionsType.value.type === "team-collections") {
- const { auth, headers } =
- teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
+ let inheritedProperties: HoppInheritedProperty | undefined = undefined
- 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",
requestID: requestIndex,
})
+
if (possibleTab) {
tabs.setActiveTab(possibleTab.value.id)
} else {
@@ -1348,10 +1433,7 @@ const selectRequest = (selectedRequest: {
requestID: requestIndex,
collectionID: folderPath,
},
- inheritedProperties: {
- auth,
- headers,
- },
+ inheritedProperties: inheritedProperties,
})
}
} else {
diff --git a/packages/hoppscotch-common/src/helpers/teams/TeamsSearch.service.ts b/packages/hoppscotch-common/src/helpers/teams/TeamsSearch.service.ts
new file mode 100644
index 0000000000..6776231a26
--- /dev/null
+++ b/packages/hoppscotch-common/src/helpers/teams/TeamsSearch.service.ts
@@ -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,
+ existingRequests: Record
+) {
+ 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([])
+ public teamsSearchResultsFormattedForSpotlight = ref<
+ {
+ collectionTitles: string[]
+ request: {
+ id: string
+ name: string
+ method: string
+ }
+ }[]
+ >([])
+
+ searchResultsCollections: Record = {}
+ searchResultsRequests: Record = {}
+
+ expandingCollections: Ref = ref([])
+ expandedCollections: Ref = 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
+ > => {
+ 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,
+ },
+ }
+}
diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts
new file mode 100644
index 0000000000..8513bb83d3
--- /dev/null
+++ b/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts
@@ -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, () => 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(() => {
+ 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,
+ })
+ }
+ }
+}
]