@igstack/app-catalog-frontend-core 0.3.1-alpha-20260405015231 → 0.3.1-alpha-20260724172703
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/__tests__/integration/harness/MockBackendVerifier.d.ts +3 -3
- package/dist/esm/__tests__/integration/mock-backend/MockBackendConfigurer.d.ts +4 -4
- package/dist/esm/__tests__/integration/mock-backend/MockDb.d.ts +11 -7
- package/dist/esm/api/infra/trpc.d.ts +3 -3
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.d.ts +2 -3
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.js +2 -4
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.js.map +1 -1
- package/dist/esm/modules/appCatalog/hooks/useAppCounts.d.ts +2 -2
- package/dist/esm/modules/appCatalog/hooks/useAppCounts.js +3 -3
- package/dist/esm/modules/appCatalog/hooks/useAppCounts.js.map +1 -1
- package/dist/esm/modules/appCatalog/hooks/useUpdateApp.d.ts +9 -0
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js +1 -13
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/AppDetailModal.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/components/MarkdownText.d.ts +18 -0
- package/dist/esm/modules/appCatalog/ui/components/MarkdownText.js +19 -0
- package/dist/esm/modules/appCatalog/ui/components/MarkdownText.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/components/ScreenshotGallery.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/components/ScreenshotGallery.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js +12 -14
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/TierVariantsSection.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/components/TierVariantsSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/filters/FilterBar.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/filters/FilterBar.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.d.ts +3 -3
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js +19 -19
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogTable.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/grid/appCatalogUtils.d.ts +2 -2
- package/dist/esm/modules/appCatalog/ui/hooks/useKeyboardNavigation.d.ts +3 -3
- package/dist/esm/modules/appCatalog/ui/hooks/useKeyboardNavigation.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js +20 -12
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js.map +1 -1
- package/dist/esm/modules/appCatalog/utils/resolveHelpers.d.ts +5 -2
- package/dist/esm/modules/appCatalog/utils/resolveHelpers.js +4 -4
- package/dist/esm/modules/appCatalog/utils/resolveHelpers.js.map +1 -1
- package/dist/esm/modules/appCatalog/utils/searchApps.d.ts +11 -6
- package/dist/esm/modules/appCatalog/utils/searchApps.js +15 -14
- package/dist/esm/modules/appCatalog/utils/searchApps.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/integration/appCatalog.integration.test.ts +3 -3
- package/src/__tests__/integration/harness/MockBackendVerifier.ts +5 -5
- package/src/__tests__/integration/mock-backend/MockBackendConfigurer.ts +16 -12
- package/src/__tests__/integration/mock-backend/MockDb.ts +30 -22
- package/src/__tests__/modules/appCatalog/ScreenshotPreview.test.tsx +4 -5
- package/src/__tests__/modules/appCatalog/utils/searchApps.test.ts +28 -30
- package/src/__tests__/modules/gallery/EscapeDismissal.integration.test.tsx +3 -4
- package/src/modules/appCatalog/context/AppCatalogContext.tsx +4 -8
- package/src/modules/appCatalog/hooks/useAppCounts.ts +5 -5
- package/src/modules/appCatalog/ui/components/AccessRequestSection.tsx +3 -20
- package/src/modules/appCatalog/ui/components/AppDetailModal.tsx +14 -11
- package/src/modules/appCatalog/ui/components/MarkdownText.tsx +42 -0
- package/src/modules/appCatalog/ui/components/ScreenshotGallery.tsx +2 -2
- package/src/modules/appCatalog/ui/components/SearchAndFilterHeader.tsx +6 -2
- package/src/modules/appCatalog/ui/components/SubResourcesSection.tsx +17 -17
- package/src/modules/appCatalog/ui/components/TierVariantsSection.tsx +2 -2
- package/src/modules/appCatalog/ui/filters/FilterBar.tsx +2 -2
- package/src/modules/appCatalog/ui/grid/AppCatalogGrid.tsx +34 -37
- package/src/modules/appCatalog/ui/grid/AppCatalogTable.tsx +3 -3
- package/src/modules/appCatalog/ui/grid/appCatalogUtils.ts +2 -2
- package/src/modules/appCatalog/ui/hooks/useKeyboardNavigation.ts +3 -3
- package/src/modules/appCatalog/ui/pages/AppCatalogPage.tsx +24 -14
- package/src/modules/appCatalog/utils/resolveHelpers.ts +13 -10
- package/src/modules/appCatalog/utils/searchApps.ts +36 -31
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AppForCatalog,
|
|
3
2
|
GroupingTagDefinition,
|
|
3
|
+
Resource,
|
|
4
4
|
} from '@igstack/app-catalog-backend-core'
|
|
5
5
|
import type { ColumnDef } from '@tanstack/react-table'
|
|
6
6
|
import {
|
|
@@ -39,13 +39,13 @@ import { useKeyboardNavigation } from '../hooks/useKeyboardNavigation'
|
|
|
39
39
|
import { highlightText } from '../../utils/searchApps'
|
|
40
40
|
import { TierVariantsSection } from '../components/TierVariantsSection'
|
|
41
41
|
import { SubResourcesSection } from '../components/SubResourcesSection'
|
|
42
|
-
import {
|
|
42
|
+
import { getChildResources } from '../../utils/resolveHelpers'
|
|
43
43
|
|
|
44
44
|
export interface AppCatalogGridProps {
|
|
45
|
-
apps:
|
|
45
|
+
apps: Resource[]
|
|
46
46
|
selectedAppSlug?: string
|
|
47
47
|
groupingDefinition?: GroupingTagDefinition
|
|
48
|
-
onAppClick?: (app:
|
|
48
|
+
onAppClick?: (app: Resource) => void
|
|
49
49
|
/** Whether search is active (affects group sorting) */
|
|
50
50
|
hasSearch?: boolean
|
|
51
51
|
/** Search query for highlighting matches */
|
|
@@ -91,13 +91,7 @@ function HighlightedText({
|
|
|
91
91
|
)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function AppIcon({
|
|
95
|
-
app,
|
|
96
|
-
className,
|
|
97
|
-
}: {
|
|
98
|
-
app: AppForCatalog
|
|
99
|
-
className?: string
|
|
100
|
-
}) {
|
|
94
|
+
function AppIcon({ app, className }: { app: Resource; className?: string }) {
|
|
101
95
|
const [imageError, setImageError] = React.useState(false)
|
|
102
96
|
|
|
103
97
|
// Use iconName from backend if available
|
|
@@ -127,7 +121,7 @@ function AppIcon({
|
|
|
127
121
|
)
|
|
128
122
|
}
|
|
129
123
|
|
|
130
|
-
function AppScreenshot({ app }: { app:
|
|
124
|
+
function AppScreenshot({ app }: { app: Resource }) {
|
|
131
125
|
const [imageError, setImageError] = React.useState(false)
|
|
132
126
|
const [isLoadingImage, setIsLoadingImage] = React.useState(true)
|
|
133
127
|
|
|
@@ -172,11 +166,11 @@ function AppScreenshot({ app }: { app: AppForCatalog }) {
|
|
|
172
166
|
)
|
|
173
167
|
}
|
|
174
168
|
|
|
175
|
-
function TiersAndSubResourcesPanel({ app }: { app:
|
|
176
|
-
const {
|
|
169
|
+
function TiersAndSubResourcesPanel({ app }: { app: Resource }) {
|
|
170
|
+
const { resources } = useAppCatalogContext()
|
|
177
171
|
const appSubResources = React.useMemo(
|
|
178
|
-
() =>
|
|
179
|
-
[
|
|
172
|
+
() => getChildResources(resources, app.slug),
|
|
173
|
+
[resources, app.slug],
|
|
180
174
|
)
|
|
181
175
|
|
|
182
176
|
return (
|
|
@@ -200,13 +194,13 @@ function AppDetails({
|
|
|
200
194
|
onAppClick,
|
|
201
195
|
onClosePanel,
|
|
202
196
|
}: {
|
|
203
|
-
app:
|
|
204
|
-
onAppClick?: (app:
|
|
197
|
+
app: Resource
|
|
198
|
+
onAppClick?: (app: Resource) => void
|
|
205
199
|
onClosePanel: () => void
|
|
206
200
|
}) {
|
|
207
201
|
const [isGalleryOpen, setIsGalleryOpen] = React.useState(false)
|
|
208
202
|
const [galleryInitialIndex, setGalleryInitialIndex] = React.useState(0)
|
|
209
|
-
const { approvalMethods,
|
|
203
|
+
const { approvalMethods, resources: allResources } = useAppCatalogContext()
|
|
210
204
|
const { recordClick } = useAppClickHistory()
|
|
211
205
|
const updateApp = useUpdateApp()
|
|
212
206
|
const [draftSource, setDraftSource] = React.useState<string | null>(null)
|
|
@@ -258,7 +252,7 @@ function AppDetails({
|
|
|
258
252
|
|
|
259
253
|
// Find replacement app if deprecated
|
|
260
254
|
const replacementApp = app.deprecated?.replacementSlug
|
|
261
|
-
?
|
|
255
|
+
? allResources.find((a) => a.slug === app.deprecated?.replacementSlug)
|
|
262
256
|
: null
|
|
263
257
|
|
|
264
258
|
return (
|
|
@@ -621,11 +615,11 @@ function AppDetails({
|
|
|
621
615
|
|
|
622
616
|
interface GroupedApps {
|
|
623
617
|
groupName: string
|
|
624
|
-
apps:
|
|
618
|
+
apps: Resource[]
|
|
625
619
|
}
|
|
626
620
|
|
|
627
621
|
function groupApps(
|
|
628
|
-
apps:
|
|
622
|
+
apps: Resource[],
|
|
629
623
|
groupingDef?: GroupingTagDefinition,
|
|
630
624
|
hasSearch?: boolean,
|
|
631
625
|
): GroupedApps[] {
|
|
@@ -642,8 +636,8 @@ function groupApps(
|
|
|
642
636
|
return [{ groupName: 'All Apps', apps: sortedApps }]
|
|
643
637
|
}
|
|
644
638
|
|
|
645
|
-
const grouped = new Map<string,
|
|
646
|
-
const ungrouped:
|
|
639
|
+
const grouped = new Map<string, Resource[]>()
|
|
640
|
+
const ungrouped: Resource[] = []
|
|
647
641
|
|
|
648
642
|
for (const app of apps) {
|
|
649
643
|
const matchingTag = app.tags?.find((tag) =>
|
|
@@ -720,11 +714,11 @@ export function AppCatalogGrid({
|
|
|
720
714
|
onAppClick,
|
|
721
715
|
})
|
|
722
716
|
|
|
723
|
-
// Build a map of
|
|
724
|
-
const {
|
|
717
|
+
// Build a map of parentSlug -> matched child resource displayName for search annotation
|
|
718
|
+
const { resources: allResources2 } = useAppCatalogContext()
|
|
725
719
|
const matchedSubResourceMap = React.useMemo(() => {
|
|
726
720
|
const map = new Map<string, string>()
|
|
727
|
-
if (!searchQuery?.trim() ||
|
|
721
|
+
if (!searchQuery?.trim() || allResources2.length === 0) return map
|
|
728
722
|
const queryTerms = searchQuery
|
|
729
723
|
.trim()
|
|
730
724
|
.toLowerCase()
|
|
@@ -733,22 +727,25 @@ export function AppCatalogGrid({
|
|
|
733
727
|
const allTermsMatch = (text: string): boolean =>
|
|
734
728
|
queryTerms.every((term) => text.includes(term))
|
|
735
729
|
|
|
736
|
-
for (const
|
|
737
|
-
if (
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
|
|
730
|
+
for (const r of allResources2) {
|
|
731
|
+
if (!r.parentSlug) continue
|
|
732
|
+
if (map.has(r.parentSlug)) continue
|
|
733
|
+
const nameMatch = allTermsMatch(r.displayName.toLowerCase())
|
|
734
|
+
const aliasMatch = (r.aliases ?? []).some((a) =>
|
|
735
|
+
allTermsMatch(a.toLowerCase()),
|
|
736
|
+
)
|
|
737
|
+
const descMatch = r.description
|
|
738
|
+
? allTermsMatch(r.description.toLowerCase())
|
|
742
739
|
: false
|
|
743
740
|
if (nameMatch || aliasMatch || descMatch) {
|
|
744
|
-
map.set(
|
|
741
|
+
map.set(r.parentSlug, r.displayName)
|
|
745
742
|
}
|
|
746
743
|
}
|
|
747
744
|
return map
|
|
748
|
-
}, [searchQuery,
|
|
745
|
+
}, [searchQuery, allResources2])
|
|
749
746
|
|
|
750
747
|
// Define columns
|
|
751
|
-
const columns = React.useMemo<ColumnDef<
|
|
748
|
+
const columns = React.useMemo<ColumnDef<Resource>[]>(
|
|
752
749
|
() => [
|
|
753
750
|
{
|
|
754
751
|
id: 'application',
|
|
@@ -856,7 +853,7 @@ export function AppCatalogGrid({
|
|
|
856
853
|
}
|
|
857
854
|
}, [selectedAppSlug, rowRefs])
|
|
858
855
|
|
|
859
|
-
const handleAppClick = (app:
|
|
856
|
+
const handleAppClick = (app: Resource) => {
|
|
860
857
|
onAppClick?.(app)
|
|
861
858
|
}
|
|
862
859
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Resource } from '@igstack/app-catalog-backend-core'
|
|
2
2
|
import type { ColumnDef } from '@tanstack/react-table'
|
|
3
3
|
import {
|
|
4
4
|
flexRender,
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
import { getAppUrl } from './appCatalogUtils'
|
|
18
18
|
|
|
19
19
|
export interface AppCatalogTableProps {
|
|
20
|
-
apps:
|
|
20
|
+
apps: Resource[]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function AppCatalogTable({ apps }: AppCatalogTableProps) {
|
|
24
|
-
const columns = useMemo<ColumnDef<
|
|
24
|
+
const columns = useMemo<ColumnDef<Resource>[]>(
|
|
25
25
|
() => [
|
|
26
26
|
{
|
|
27
27
|
slug: 'name',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Resource } from '@igstack/app-catalog-backend-core'
|
|
2
2
|
|
|
3
|
-
export function getAppUrl(app:
|
|
3
|
+
export function getAppUrl(app: Resource): string {
|
|
4
4
|
return app.appUrl || '#'
|
|
5
5
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Resource } from '@igstack/app-catalog-backend-core'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
4
|
export interface UseKeyboardNavigationProps {
|
|
5
|
-
apps:
|
|
5
|
+
apps: Resource[]
|
|
6
6
|
selectedAppSlug?: string
|
|
7
|
-
onAppClick?: (app:
|
|
7
|
+
onAppClick?: (app: Resource) => void
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function useKeyboardNavigation({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Resource } from '@igstack/app-catalog-backend-core'
|
|
2
2
|
import { X } from 'lucide-react'
|
|
3
3
|
import { useDeferredValue, useEffect, useMemo, useState } from 'react'
|
|
4
4
|
import { Button } from '~/ui/button'
|
|
@@ -14,15 +14,14 @@ import { useAppCatalogContext } from '../../context/AppCatalogContext'
|
|
|
14
14
|
import { useAppClickHistory } from '../../hooks/useAppClickHistory'
|
|
15
15
|
import { useAppCounts } from '../../hooks/useAppCounts'
|
|
16
16
|
import { useUrlSyncedState } from '../../hooks/useUrlSyncedState'
|
|
17
|
-
import {
|
|
17
|
+
import { searchResources } from '../../utils/searchApps'
|
|
18
18
|
import { OnboardingCard } from '../components/OnboardingCard'
|
|
19
19
|
import { useAppCatalogFilters } from '../context/AppCatalogFiltersContext'
|
|
20
20
|
import { FilterBar } from '../filters/FilterBar'
|
|
21
21
|
import { AppCatalogGrid } from '../grid/AppCatalogGrid'
|
|
22
22
|
|
|
23
23
|
export function AppCatalogPage() {
|
|
24
|
-
const {
|
|
25
|
-
useAppCatalogContext()
|
|
24
|
+
const { resources, isLoadingApps, tagsDefinitions } = useAppCatalogContext()
|
|
26
25
|
const { state: filterState, actions } = useAppCatalogFilters()
|
|
27
26
|
const { getTopApps } = useAppClickHistory()
|
|
28
27
|
|
|
@@ -49,8 +48,14 @@ export function AppCatalogPage() {
|
|
|
49
48
|
void getTopApps(10).then(setTopAppSlugs)
|
|
50
49
|
}, [getTopApps])
|
|
51
50
|
|
|
51
|
+
// Get root resources for filtering (children handled internally by searchResources)
|
|
52
|
+
const rootResources = useMemo(
|
|
53
|
+
() => resources.filter((r) => !r.parentSlug),
|
|
54
|
+
[resources],
|
|
55
|
+
)
|
|
56
|
+
|
|
52
57
|
const filteredApps = useMemo(() => {
|
|
53
|
-
let result =
|
|
58
|
+
let result = rootResources
|
|
54
59
|
|
|
55
60
|
// Step 1: Filter deprecated apps (if not showing them)
|
|
56
61
|
if (!filterState.showDeprecated) {
|
|
@@ -76,22 +81,27 @@ export function AppCatalogPage() {
|
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
// Step 3: Apply search (using deferred value)
|
|
79
|
-
|
|
84
|
+
// Pass all resources so children contribute to parent scoring
|
|
85
|
+
const childResources = resources.filter((r) => r.parentSlug)
|
|
86
|
+
result = searchResources(
|
|
87
|
+
[...result, ...childResources],
|
|
88
|
+
deferredSearchValue,
|
|
89
|
+
)
|
|
80
90
|
|
|
81
91
|
return result
|
|
82
92
|
}, [
|
|
83
|
-
|
|
93
|
+
rootResources,
|
|
94
|
+
resources,
|
|
84
95
|
deferredSearchValue,
|
|
85
96
|
filterState.recentMode,
|
|
86
97
|
filterState.tagFilters,
|
|
87
98
|
filterState.showDeprecated,
|
|
88
99
|
topAppSlugs,
|
|
89
|
-
subResources,
|
|
90
100
|
])
|
|
91
101
|
|
|
92
102
|
// Calculate counts for FilterBar
|
|
93
103
|
const { allCount, recentCount, deprecatedCount } = useAppCounts({
|
|
94
|
-
apps,
|
|
104
|
+
apps: rootResources,
|
|
95
105
|
topAppSlugs,
|
|
96
106
|
searchValue: deferredSearchValue,
|
|
97
107
|
})
|
|
@@ -103,7 +113,7 @@ export function AppCatalogPage() {
|
|
|
103
113
|
}
|
|
104
114
|
}, [filteredApps, setSelectedAppSlug])
|
|
105
115
|
|
|
106
|
-
const handleAppClick = (app:
|
|
116
|
+
const handleAppClick = (app: Resource) => {
|
|
107
117
|
setSelectedAppSlug(app.slug)
|
|
108
118
|
}
|
|
109
119
|
|
|
@@ -115,12 +125,12 @@ export function AppCatalogPage() {
|
|
|
115
125
|
|
|
116
126
|
// Calculate total apps count (respecting showDeprecated setting)
|
|
117
127
|
const totalAppsCount = useMemo(() => {
|
|
118
|
-
let count =
|
|
128
|
+
let count = rootResources.length
|
|
119
129
|
if (!filterState.showDeprecated) {
|
|
120
|
-
count =
|
|
130
|
+
count = rootResources.filter((app) => !app.deprecated).length
|
|
121
131
|
}
|
|
122
132
|
return count
|
|
123
|
-
}, [
|
|
133
|
+
}, [rootResources, filterState.showDeprecated])
|
|
124
134
|
|
|
125
135
|
if (isLoadingApps) {
|
|
126
136
|
return <div className="py-6 text-muted-foreground">Loading…</div>
|
|
@@ -140,7 +150,7 @@ export function AppCatalogPage() {
|
|
|
140
150
|
totalCount={allCount}
|
|
141
151
|
recentCount={recentCount}
|
|
142
152
|
deprecatedCount={deprecatedCount}
|
|
143
|
-
apps={
|
|
153
|
+
apps={rootResources}
|
|
144
154
|
/>
|
|
145
155
|
</div>
|
|
146
156
|
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Group,
|
|
3
|
-
Person,
|
|
4
|
-
SubResource,
|
|
5
|
-
} from '@igstack/app-catalog-backend-core'
|
|
1
|
+
import type { Group, Person, Resource } from '@igstack/app-catalog-backend-core'
|
|
6
2
|
|
|
7
3
|
export function getPersonBySlug(
|
|
8
4
|
persons: Person[],
|
|
@@ -18,9 +14,16 @@ export function getGroupBySlug(
|
|
|
18
14
|
return groups.find((g) => g.slug === slug)
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
export function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
):
|
|
25
|
-
return
|
|
17
|
+
export function getChildResources(
|
|
18
|
+
resources: Resource[],
|
|
19
|
+
parentSlug: string,
|
|
20
|
+
): Resource[] {
|
|
21
|
+
return resources.filter((r) => r.parentSlug === parentSlug)
|
|
26
22
|
}
|
|
23
|
+
|
|
24
|
+
export function getRootResources(resources: Resource[]): Resource[] {
|
|
25
|
+
return resources.filter((r) => !r.parentSlug)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @deprecated Use getChildResources instead */
|
|
29
|
+
export const getSubResourcesForApp = getChildResources
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AppForCatalog,
|
|
3
|
-
SubResource,
|
|
4
|
-
} from '@igstack/app-catalog-backend-core'
|
|
1
|
+
import type { Resource } from '@igstack/app-catalog-backend-core'
|
|
5
2
|
|
|
6
3
|
export interface SearchMatch {
|
|
7
4
|
/** Field where the match occurred */
|
|
@@ -19,12 +16,15 @@ export interface SearchMatch {
|
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
export interface SearchResult {
|
|
22
|
-
app:
|
|
19
|
+
app: Resource
|
|
23
20
|
match: SearchMatch
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
/**
|
|
27
|
-
* Search and sort
|
|
24
|
+
* Search and sort resources by relevance with highlighting support.
|
|
25
|
+
* Only root resources (no parentSlug) are scored and returned.
|
|
26
|
+
* Child resources (with parentSlug) contribute to their parent's score.
|
|
27
|
+
*
|
|
28
28
|
* Priority order:
|
|
29
29
|
* 0. Exact match in abbreviation
|
|
30
30
|
* 1. Exact match in displayName
|
|
@@ -41,19 +41,21 @@ export interface SearchResult {
|
|
|
41
41
|
* 12. Teams
|
|
42
42
|
* 13. Description
|
|
43
43
|
*
|
|
44
|
-
* @param
|
|
44
|
+
* @param resources - Array of all resources (root + children)
|
|
45
45
|
* @param searchQuery - Search query string
|
|
46
|
-
* @returns Filtered and sorted array of
|
|
46
|
+
* @returns Filtered and sorted array of root resources
|
|
47
47
|
*/
|
|
48
|
-
export function
|
|
49
|
-
|
|
48
|
+
export function searchResources(
|
|
49
|
+
resources: Resource[],
|
|
50
50
|
searchQuery: string,
|
|
51
|
-
|
|
52
|
-
): AppForCatalog[] {
|
|
51
|
+
): Resource[] {
|
|
53
52
|
const normalizedQuery = searchQuery.trim().toLowerCase()
|
|
54
53
|
|
|
54
|
+
// Separate root resources from children
|
|
55
|
+
const rootResources = resources.filter((r) => !r.parentSlug)
|
|
56
|
+
|
|
55
57
|
if (normalizedQuery === '') {
|
|
56
|
-
return
|
|
58
|
+
return rootResources
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// Split query into terms for multi-word matching (AND logic)
|
|
@@ -63,18 +65,18 @@ export function searchApps(
|
|
|
63
65
|
const allTermsMatch = (text: string): boolean =>
|
|
64
66
|
queryTerms.every((term) => text.includes(term))
|
|
65
67
|
|
|
66
|
-
// Build
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const list =
|
|
71
|
-
list.push(
|
|
72
|
-
|
|
68
|
+
// Build children lookup: parentSlug -> Resource[]
|
|
69
|
+
const childrenByParent = new Map<string, Resource[]>()
|
|
70
|
+
for (const r of resources) {
|
|
71
|
+
if (r.parentSlug) {
|
|
72
|
+
const list = childrenByParent.get(r.parentSlug) ?? []
|
|
73
|
+
list.push(r)
|
|
74
|
+
childrenByParent.set(r.parentSlug, list)
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
// Filter and score
|
|
77
|
-
const scoredApps =
|
|
78
|
+
// Filter and score root resources
|
|
79
|
+
const scoredApps = rootResources
|
|
78
80
|
.map((app): SearchResult | null => {
|
|
79
81
|
const name = app.displayName.toLowerCase()
|
|
80
82
|
const abbreviation = app.abbreviation?.toLowerCase() || ''
|
|
@@ -143,15 +145,15 @@ export function searchApps(
|
|
|
143
145
|
return { app, match: { field: 'description', type: 'contains' } }
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
// Check
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
const subMatch =
|
|
150
|
-
(
|
|
151
|
-
allTermsMatch(
|
|
152
|
-
|
|
153
|
-
(
|
|
154
|
-
? allTermsMatch(
|
|
148
|
+
// Check child resources (name, aliases, description) — supports multi-word queries
|
|
149
|
+
const children = childrenByParent.get(app.slug)
|
|
150
|
+
if (children) {
|
|
151
|
+
const subMatch = children.some(
|
|
152
|
+
(r) =>
|
|
153
|
+
allTermsMatch(r.displayName.toLowerCase()) ||
|
|
154
|
+
(r.aliases ?? []).some((a) => allTermsMatch(a.toLowerCase())) ||
|
|
155
|
+
(r.description
|
|
156
|
+
? allTermsMatch(r.description.toLowerCase())
|
|
155
157
|
: false),
|
|
156
158
|
)
|
|
157
159
|
if (subMatch) {
|
|
@@ -259,3 +261,6 @@ export function highlightText(
|
|
|
259
261
|
|
|
260
262
|
return segments
|
|
261
263
|
}
|
|
264
|
+
|
|
265
|
+
/** @deprecated Use searchResources instead */
|
|
266
|
+
export const searchApps = searchResources
|