diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json
index 905844c33f6..60d5619e54c 100644
--- a/packages/hoppscotch-common/locales/en.json
+++ b/packages/hoppscotch-common/locales/en.json
@@ -281,7 +281,7 @@
"updated": "Environment updated",
"value": "Value",
"variable": "Variable",
- "variables":"Variables",
+ "variables": "Variables",
"variable_list": "Variable List"
},
"error": {
@@ -961,7 +961,8 @@
"success_invites": "Success invites",
"title": "Workspaces",
"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": {
"deleted": "Environment Deleted",
diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts
index df8e8a8fa78..52d1e50f389 100644
--- a/packages/hoppscotch-common/src/components.d.ts
+++ b/packages/hoppscotch-common/src/components.d.ts
@@ -31,6 +31,7 @@ declare module 'vue' {
AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default']
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
+ AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default']
AppSupport: typeof import('./components/app/Support.vue')['default']
Collections: typeof import('./components/collections/index.vue')['default']
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
@@ -57,6 +58,7 @@ declare module 'vue' {
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
+ CollectionsTeamSearchResults: typeof import('./components/collections/TeamSearchResults.vue')['default']
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
Embeds: typeof import('./components/embeds/index.vue')['default']
@@ -131,6 +133,7 @@ declare module 'vue' {
HttpRequest: typeof import('./components/http/Request.vue')['default']
HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default']
HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default']
+ HttpRequestVariables: typeof import('./components/http/RequestVariables.vue')['default']
HttpResponse: typeof import('./components/http/Response.vue')['default']
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
diff --git a/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTTeamRequestEntry.vue b/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTTeamRequestEntry.vue
new file mode 100644
index 00000000000..109367c6df4
--- /dev/null
+++ b/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTTeamRequestEntry.vue
@@ -0,0 +1,32 @@
+
+
+
+
+ {{ title }}
+
+
+
+
+ {{ request.method.toUpperCase() }}
+
+
+ {{ request.name }}
+
+
+
+
+
diff --git a/packages/hoppscotch-common/src/components/app/spotlight/index.vue b/packages/hoppscotch-common/src/components/app/spotlight/index.vue
index 5081bfad13e..7da96ea7834 100644
--- a/packages/hoppscotch-common/src/components/app/spotlight/index.vue
+++ b/packages/hoppscotch-common/src/components/app/spotlight/index.vue
@@ -111,6 +111,7 @@ import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.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 {
SwitchWorkspaceSpotlightSearcherService,
@@ -144,6 +145,7 @@ useService(SwitchEnvSpotlightSearcherService)
useService(WorkspaceSpotlightSearcherService)
useService(SwitchWorkspaceSpotlightSearcherService)
useService(InterceptorSpotlightSearcherService)
+useService(TeamsSpotlightSearcherService)
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
useService(searcher)
diff --git a/packages/hoppscotch-common/src/components/collections/TeamCollections.vue b/packages/hoppscotch-common/src/components/collections/TeamCollections.vue
index fa8f71cb4ee..0a99acad73e 100644
--- a/packages/hoppscotch-common/src/components/collections/TeamCollections.vue
+++ b/packages/hoppscotch-common/src/components/collections/TeamCollections.vue
@@ -283,7 +283,15 @@
+
+
+
+
+ ({ type: "my-collections", selectedTeam: undefined }),
required: true,
},
+ filterText: {
+ type: String as PropType,
+ default: "",
+ required: true,
+ },
teamCollectionList: {
type: Array as PropType,
default: () => [],
diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue
index 39b4b74ad77..068e0f03dfc 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 (collectionsType.value.type === "team-collections") {
+ const selectedTeamID = collectionsType.value.selectedTeam?.id
+ const debouncedSearch = debounce(searchTeams, 400)
+
+ selectedTeamID &&
+ debouncedSearch(newFilterText, selectedTeamID)?.catch((_) => {})
+ }
+ },
+ {
+ immediate: true,
+ }
+)
+
watch(
() => myTeams.value,
(newTeams) => {
@@ -1330,13 +1358,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 +1388,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 00000000000..e97e77fd6aa
--- /dev/null
+++ b/packages/hoppscotch-common/src/helpers/teams/TeamsSearch.service.ts
@@ -0,0 +1,469 @@
+import { ref } from "vue"
+import { runGQLQuery } from "../backend/GQLClient"
+import {
+ 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"
+
+type CollectionSearchNode =
+ | {
+ type: "request"
+ title: string
+ method: string
+ id: string
+ // parent collections
+ path: CollectionSearchNode[]
+ }
+ | {
+ type: "collection"
+ title: string
+ id: string
+ // parent collections
+ path: CollectionSearchNode[]
+ }
+
+function convertToTeamCollection(
+ node: CollectionSearchNode,
+ existingCollections: Record<
+ string,
+ TeamCollection & { parentID: string | null }
+ >,
+ existingRequests: Record<
+ string,
+ {
+ id: string
+ collectionID: string
+ title: string
+ request: {
+ name: string
+ method: string
+ }
+ }
+ >
+) {
+ 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,
+ },
+ }
+
+ 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,
+ }
+
+ 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
+
+ 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
+ )
+
+ 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<
+ string,
+ TeamCollection & { parentID: string | null }
+ > = {}
+
+ searchResultsRequests: Record<
+ string,
+ {
+ id: string
+ collectionID: string
+ title: string
+ request: {
+ name: string
+ method: string
+ }
+ }
+ > = {}
+
+ // 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) => {
+ this.teamsSearchResultsLoading.value = true
+
+ this.searchResultsCollections = {}
+ this.searchResultsRequests = {}
+
+ try {
+ const searchResponse = await axios.get(
+ `${this.endpoint}/team-collection/search/${teamID}/${query}`,
+ {
+ withCredentials: true,
+ }
+ )
+
+ if (searchResponse.status !== 200) {
+ return
+ }
+
+ const searchResults = searchResponse.data.data as CollectionSearchNode[]
+
+ searchResults
+ .map((node) => {
+ const { existingCollections, existingRequests } =
+ convertToTeamCollection(node, {}, {})
+
+ 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)
+ }
+}
+
+const getSingleCollection = (collectionID: string) =>
+ runGQLQuery({
+ query: GetSingleCollectionDocument,
+ variables: {
+ collectionID,
+ },
+ })
+
+const getSingleRequest = (requestID: string) =>
+ runGQLQuery({
+ query: GetSingleRequestDocument,
+ variables: {
+ requestID,
+ },
+ })
+
+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 00000000000..fa108a4758d
--- /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(() => {
+ watch(
+ query,
+ (query) => {
+ const debouncedSearch = debounce(this.teamsSearch.searchTeams, 400)
+
+ 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,
+ })
+ }
+ }
+}
]