Files
supabase/apps/docs/resources/globalSearch/globalSearchModel.ts
Illia Basalaiev ce5cce5030 replace github discussions with local guides in the docs search (#42335)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

feature

## What is the current behavior?

Currently, old GitHub discussions appear in the docs search instead of
troubleshooting guides in docs/guides/troubleshooting

## What is the new behavior?

Local troubleshooting guides appear in the search

## Additional context

<img width="958" height="846" alt="CleanShot 2026-01-31 at 23 37 33@2x"
src="https://github.com/user-attachments/assets/445fab5d-764a-4b4d-b4ef-c29ab675a9ae"
/>


**troubleshooting.ts** - New source loader that reads local MDX files
from content/troubleshooting/ directly instead of fetching from GitHub
Discussions API
- Generates correct docs paths: /guides/troubleshooting/{slug}
- Uses type = 'troubleshooting' for proper search result mapping
- Sets slug: undefined to avoid trailing # in URLs
- Checksum includes title/topics/keywords so metadata-only changes
trigger re-indexing
- Left comments for review 

**index.ts** - Replaced GitHub discussion sources with local
troubleshooting sources
- Removed GitHubDiscussionLoader, fetchDiscussions,
buildGithubUrlToSlugMap imports
- Added fetchTroubleshootingSources and TroubleshootingSource
- Updated SearchSource type union

**globalSearchModel.ts** - Changed type mapping from
'github-discussions' to 'troubleshooting'

**generate-embeddings.ts** - Removed GitHub App env vars from required
list (DOCS_GITHUB_APP_ID, DOCS_GITHUB_APP_INSTALLATION_ID,
DOCS_GITHUB_APP_PRIVATE_KEY) since they're no longer needed


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Local troubleshooting articles are now indexed and appear directly in
search results for easier access to step‑by‑step guidance.
* Search UI now recognizes a Troubleshooting page type and shows
appropriate icons/sections.

* **Refactor**
* Search sourcing switched from external discussion feeds to local
troubleshooting sources to improve relevance and indexing consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Illia Basalaiev <illiab@IMB3.local>
Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
2026-02-23 13:54:40 +01:00

141 lines
4.5 KiB
TypeScript

import { type RootQueryTypeSearchDocsArgs } from '~/__generated__/graphql'
import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import { openAI } from '~/lib/openAi'
import { supabase, type DatabaseCorrected } from '~/lib/supabase'
import { isFeatureEnabled } from '../../../../packages/common/enabled-features'
import { GuideModel } from '../guide/guideModel'
import {
DB_METADATA_TAG_PLATFORM_CLI,
ReferenceCLICommandModel,
} from '../reference/referenceCLIModel'
import { ReferenceManagementApiModel } from '../reference/referenceManagementApiModel'
import { ReferenceSDKFunctionModel, SDKLanguageValues } from '../reference/referenceSDKModel'
import { TroubleshootingModel } from '../troubleshooting/troubleshootingModel'
import { SearchResultInterface } from './globalSearchInterface'
type SearchFunction = 'search_content' | 'search_content_nimbus'
type SearchHybridFunction = 'search_content_hybrid' | 'search_content_hybrid_nimbus'
export abstract class SearchResultModel {
static async search(
args: RootQueryTypeSearchDocsArgs,
requestedFields: Array<string>
): Promise<Result<SearchResultModel[], ApiErrorGeneric>> {
const query = args.query.trim()
const includeFullContent = requestedFields.includes('content')
const embeddingResult = await openAI().createContentEmbedding(query)
const useAltSearchIndex = !isFeatureEnabled('search:fullIndex')
const searchFunction: SearchFunction = useAltSearchIndex
? 'search_content_nimbus'
: 'search_content'
return embeddingResult.flatMapAsync(async ({ embedding }) => {
const matchResult = new Result(
await supabase().rpc(searchFunction, {
embedding,
include_full_content: includeFullContent,
max_result: args.limit ?? undefined,
})
)
.map((matches) =>
matches
.map(createModelFromMatch)
.filter((item): item is SearchResultInterface => item !== null)
)
.mapError(convertPostgrestToApiError)
return matchResult
})
}
static async searchHybrid(
args: RootQueryTypeSearchDocsArgs,
requestedFields: Array<string>
): Promise<Result<SearchResultModel[], ApiErrorGeneric>> {
const query = args.query.trim()
const includeFullContent = requestedFields.includes('content')
const embeddingResult = await openAI().createContentEmbedding(query)
const useAltSearchIndex = !isFeatureEnabled('search:fullIndex')
const searchFunction: SearchHybridFunction = useAltSearchIndex
? 'search_content_hybrid_nimbus'
: 'search_content_hybrid'
return embeddingResult.flatMapAsync(async ({ embedding }) => {
const matchResult = new Result(
await supabase().rpc(searchFunction, {
query_text: query,
query_embedding: embedding,
include_full_content: includeFullContent,
max_result: args.limit ?? 30,
})
)
.map((matches) =>
matches
.map(createModelFromMatch)
.filter((item): item is SearchResultInterface => item !== null)
)
.mapError(convertPostgrestToApiError)
return matchResult
})
}
}
function createModelFromMatch({
type,
page_title,
href,
content,
metadata,
subsections,
}: DatabaseCorrected['public']['Functions']['search_content']['Returns'][number]): SearchResultInterface | null {
switch (type) {
case 'markdown':
return new GuideModel({
title: page_title,
href,
content,
subsections,
})
case 'reference':
const { language } = metadata
if (language && SDKLanguageValues.includes(language)) {
return new ReferenceSDKFunctionModel({
title: page_title,
href,
content,
language,
methodName: metadata.methodName,
})
} else if (metadata.platform === DB_METADATA_TAG_PLATFORM_CLI) {
return new ReferenceCLICommandModel({
title: page_title,
href,
content,
subsections,
})
// TODO [Charis 2025-06-09] replace with less hacky check
} else if (metadata.subtitle?.startsWith('Management API Reference')) {
return new ReferenceManagementApiModel({
title: page_title,
href,
content,
})
} else {
return null
}
case 'troubleshooting':
return new TroubleshootingModel({
title: page_title,
href,
content,
})
default:
return null
}
}