mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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>
This commit is contained in:
6 files changed
+122
-29
No files matched your search
@@ -128,7 +128,7 @@ function createModelFromMatch({
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
case 'github-discussions':
|
||||
case 'troubleshooting':
|
||||
return new TroubleshootingModel({
|
||||
title: page_title,
|
||||
href,
|
||||
|
||||
@@ -446,9 +446,6 @@ async function generateEmbeddings() {
|
||||
}
|
||||
|
||||
requireEnvOrThrow([
|
||||
'DOCS_GITHUB_APP_ID',
|
||||
'DOCS_GITHUB_APP_INSTALLATION_ID',
|
||||
'DOCS_GITHUB_APP_PRIVATE_KEY',
|
||||
'NEXT_PUBLIC_MISC_ANON_KEY',
|
||||
'NEXT_PUBLIC_MISC_URL',
|
||||
'NEXT_PUBLIC_SUPABASE_URL',
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { type GuideModel } from '../../../resources/guide/guideModel.js'
|
||||
import { GuideModelLoader } from '../../../resources/guide/guideModelLoader.js'
|
||||
import {
|
||||
GitHubDiscussionLoader,
|
||||
type GitHubDiscussionSource,
|
||||
fetchDiscussions,
|
||||
} from './github-discussion.js'
|
||||
import { LintWarningsGuideLoader, type LintWarningsGuideSource } from './lint-warnings-guide.js'
|
||||
import { MarkdownLoader, type MarkdownSource } from './markdown.js'
|
||||
import { IntegrationLoader, type IntegrationSource, fetchPartners } from './partner-integrations.js'
|
||||
@@ -16,13 +11,14 @@ import {
|
||||
OpenApiReferenceLoader,
|
||||
type OpenApiReferenceSource,
|
||||
} from './reference-doc.js'
|
||||
import { fetchTroubleshootingSources, type TroubleshootingSource } from './troubleshooting.js'
|
||||
|
||||
export type SearchSource =
|
||||
| MarkdownSource
|
||||
| OpenApiReferenceSource
|
||||
| ClientLibReferenceSource
|
||||
| CliReferenceSource
|
||||
| GitHubDiscussionSource
|
||||
| TroubleshootingSource
|
||||
| IntegrationSource
|
||||
| LintWarningsGuideSource
|
||||
|
||||
@@ -150,21 +146,15 @@ export async function fetchAllSources(fullIndex: boolean) {
|
||||
.then((data) => data.flat())
|
||||
: []
|
||||
|
||||
const githubDiscussionSources = fetchDiscussions(
|
||||
'supabase',
|
||||
'supabase',
|
||||
'DIC_kwDODMpXOc4CUvEr' // 'Troubleshooting' category
|
||||
)
|
||||
.then((discussions) =>
|
||||
Promise.all(
|
||||
discussions.map((discussion) =>
|
||||
new GitHubDiscussionLoader('supabase/supabase', discussion).load()
|
||||
)
|
||||
)
|
||||
)
|
||||
// Load troubleshooting articles from local MDX files
|
||||
const troubleshootingSources = fetchTroubleshootingSources()
|
||||
.then((loaders) => Promise.all(loaders.map((loader) => loader.load())))
|
||||
.then((data) => data.flat())
|
||||
|
||||
const sources: SearchSource[] = (
|
||||
// Type assertion required because ReferenceLoader.load() returns Promise<BaseSource[]>
|
||||
// which widens the inferred union type. All concrete sources in this array are valid
|
||||
// SearchSource types (MarkdownSource, OpenApiReferenceSource, etc.).
|
||||
const sources = (
|
||||
await Promise.all([
|
||||
guideSources,
|
||||
lintWarningsGuideSources,
|
||||
@@ -177,9 +167,9 @@ export async function fetchAllSources(fullIndex: boolean) {
|
||||
ktLibReferenceSource,
|
||||
cliReferenceSource,
|
||||
partnerIntegrationSources,
|
||||
githubDiscussionSources,
|
||||
troubleshootingSources,
|
||||
])
|
||||
).flat()
|
||||
).flat() as SearchSource[]
|
||||
|
||||
return sources
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { BaseLoader, BaseSource } from './base.js'
|
||||
import {
|
||||
getAllTroubleshootingEntriesInternal,
|
||||
getArticleSlug,
|
||||
} from '../../../features/docs/Troubleshooting.utils.common.mjs'
|
||||
import type { ITroubleshootingEntry } from '../../../features/docs/Troubleshooting.utils.js'
|
||||
|
||||
/**
|
||||
* Loader for troubleshooting articles from local MDX files.
|
||||
*
|
||||
* The path format is `/guides/troubleshooting/{slug}` where slug is derived
|
||||
* from the filename (e.g., `auth-error-handling.mdx` → `auth-error-handling`).
|
||||
*/
|
||||
export class TroubleshootingLoader extends BaseLoader {
|
||||
type = 'troubleshooting' as const
|
||||
|
||||
constructor(
|
||||
source: string,
|
||||
public entry: ITroubleshootingEntry
|
||||
) {
|
||||
const slug = getArticleSlug(entry)
|
||||
super(source, `/guides/troubleshooting/${slug}`)
|
||||
}
|
||||
|
||||
async load(): Promise<TroubleshootingSource[]> {
|
||||
return [new TroubleshootingSource(this.source, this.path, this.entry)]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search source for a single troubleshooting article.
|
||||
*
|
||||
* Each article becomes one indexed page with a single section containing
|
||||
* the full content. This differs from guide pages which may have multiple
|
||||
* sections based on headings.
|
||||
*/
|
||||
export class TroubleshootingSource extends BaseSource {
|
||||
type = 'troubleshooting' as const
|
||||
|
||||
constructor(
|
||||
source: string,
|
||||
path: string,
|
||||
public entry: ITroubleshootingEntry
|
||||
) {
|
||||
super(source, path)
|
||||
}
|
||||
|
||||
async process() {
|
||||
const { title, topics, keywords } = this.entry.data
|
||||
const content = this.entry.contentWithoutJsx
|
||||
|
||||
// Include title and metadata in checksum so any changes trigger re-indexing.
|
||||
// This ensures updates to title, topics, or keywords are picked up even if
|
||||
// the main content hasn't changed.
|
||||
const checksum = createHash('sha256')
|
||||
.update(JSON.stringify({ title, topics, keywords, content }))
|
||||
.digest('base64')
|
||||
|
||||
const meta = { title, topics, keywords }
|
||||
|
||||
// Troubleshooting articles are single-section pages (no sub-headings indexed).
|
||||
// We explicitly set slug to undefined so the database's `get_full_content_url`
|
||||
// function returns the page URL without a fragment (e.g., no trailing `#slug`).
|
||||
// This is handled in SQL: `CASE WHEN slug IS NULL THEN '' ELSE concat('#', slug) END`
|
||||
const sections = [
|
||||
{
|
||||
heading: title,
|
||||
slug: undefined as string | undefined,
|
||||
content: `# ${title}\n${content}`,
|
||||
},
|
||||
]
|
||||
|
||||
this.checksum = checksum
|
||||
this.meta = meta
|
||||
this.sections = sections
|
||||
|
||||
return { checksum, meta, sections }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full article content formatted for full-text search indexing.
|
||||
* The title is included as a heading to boost its relevance in search results.
|
||||
*/
|
||||
extractIndexedContent(): string {
|
||||
return `# ${this.entry.data.title}\n\n${this.entry.contentWithoutJsx}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all troubleshooting articles from `content/troubleshooting/` directory.
|
||||
*
|
||||
* Each MDX file is parsed, validated, and converted to a TroubleshootingLoader.
|
||||
* Hidden files (prefixed with `_`) are excluded.
|
||||
*
|
||||
* @returns Array of loaders, one per troubleshooting article
|
||||
*/
|
||||
export async function fetchTroubleshootingSources(): Promise<TroubleshootingLoader[]> {
|
||||
const entries = (await getAllTroubleshootingEntriesInternal()) as ITroubleshootingEntry[]
|
||||
return entries.map((entry) => new TroubleshootingLoader('troubleshooting', entry))
|
||||
}
|
||||
@@ -16,6 +16,7 @@ enum PageType {
|
||||
Reference = 'reference',
|
||||
Integration = 'partner-integration',
|
||||
GithubDiscussion = 'github-discussions',
|
||||
Troubleshooting = 'troubleshooting',
|
||||
}
|
||||
|
||||
interface PageSection {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
DocsSearchResultType as PageType,
|
||||
useDocsSearch,
|
||||
type DocsSearchResult as Page,
|
||||
type DocsSearchResultSection as PageSection,
|
||||
DocsSearchResultType as PageType,
|
||||
useDocsSearch,
|
||||
} from 'common'
|
||||
import { Book, ChevronRight, Github, Hash, Loader2, MessageSquare, Search } from 'lucide-react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Button, cn, CommandGroup_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_ } from 'ui'
|
||||
import { Button, CommandGroup_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, cn } from 'ui'
|
||||
import { StatusIcon } from 'ui/src/components/StatusIcon'
|
||||
|
||||
import {
|
||||
@@ -83,6 +83,7 @@ const DocsSearchPage = () => {
|
||||
switch (pageType) {
|
||||
case PageType.Markdown:
|
||||
case PageType.Reference:
|
||||
case PageType.Troubleshooting:
|
||||
if (BASE_PATH === '/docs') {
|
||||
router.push(link)
|
||||
setIsOpen(false)
|
||||
@@ -299,8 +300,9 @@ export function formatSectionUrl(page: Page, section: PageSection) {
|
||||
return `${page.path}#${section.slug ?? ''}`
|
||||
case PageType.Reference:
|
||||
return `${page.path}/${section.slug ?? ''}`
|
||||
case PageType.Troubleshooting:
|
||||
// [Charis] Markdown headings on integrations pages don't have slugs yet
|
||||
case PageType.Integration:
|
||||
// [Charis] Markdown headings on integrations pages don't have slugs yet
|
||||
return page.path
|
||||
default:
|
||||
throw new Error(`Unknown page type '${page.type}'`)
|
||||
@@ -312,6 +314,7 @@ export function getPageIcon(page: Page) {
|
||||
case PageType.Markdown:
|
||||
case PageType.Reference:
|
||||
case PageType.Integration:
|
||||
case PageType.Troubleshooting:
|
||||
return <Book strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
||||
case PageType.GithubDiscussion:
|
||||
return <Github strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
||||
@@ -325,6 +328,7 @@ export function getPageSectionIcon(page: Page) {
|
||||
case PageType.Markdown:
|
||||
case PageType.Reference:
|
||||
case PageType.Integration:
|
||||
case PageType.Troubleshooting:
|
||||
return <Hash strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
||||
case PageType.GithubDiscussion:
|
||||
return <MessageSquare strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
||||
|
||||
Reference in new issue
Block a user