Skip to content

Knowledge Base #602

@SvenVw

Description

@SvenVw

Context

A public knowledge base reduces ticket volume by letting users self-serve common questions (e.g., "How do I import parcels from RVO?"). Agents can also link KB articles when replying to tickets, ensuring consistent answers.

Depends on: #599 (MVP — support routes and layout must exist)

Scope

In Scope

  • Schema: kb_articles table (title, slug, body as Markdown, category, is_published, view_count, author_id)
  • Article CRUD: create, update, publish/unpublish, delete (soft)
  • Categories: grouping articles by topic (percelen, bemesting, account, etc.)
  • Full-text search: pg_trgm fuzzy matching on title + body with GIN indexes
  • Public browse UI (/support/kb): categorized article list with search
  • Article view (/support/kb/$article_id): rendered Markdown with feedback widget
  • Article feedback: "Was dit artikel nuttig?" thumbs up/down (stored per article)
  • Agent KB editor (/admin/support/kb): create/edit articles with Markdown preview
  • Link articles in replies: agent can reference KB article in a ticket reply
  • View count tracking: increment on each unique view

Acceptance Criteria

  • KB articles can be created and edited in Markdown by agents/admins
  • Published articles are visible at /support/kb for all authenticated users
  • Articles are organized by category with clear navigation
  • Search finds articles by fuzzy matching on title and body (debounced, as-you-type)
  • Article view renders Markdown properly (headings, code blocks, links, tables, images)
  • Users can give feedback (👍/👎) on articles
  • Agents can link to a KB article from the reply box
  • Draft (unpublished) articles are only visible to agents in the editor
  • View counts are tracked per article
  • pg_trgm extension is enabled and GIN indexes are created via migration

Technical Implementation

1. Schema

export const kbArticles = fdmHelpdeskSchema.table(
    "kb_articles",
    {
        article_id: text().primaryKey(),
        slug: text().notNull().unique(),
        title: text().notNull(),
        body: text().notNull(),                    // Markdown content
        category: text().notNull().default("general"),
        is_published: boolean().notNull().default(false),
        author_id: text().notNull(),              // Agent who created it
        view_count: integer().notNull().default(0),
        created: timestamp({ withTimezone: true }).notNull().defaultNow(),
        updated: timestamp({ withTimezone: true }),
    },
    (table) => [
        index("kb_category_idx").on(table.category),
        index("kb_published_idx").on(table.is_published),
        // GIN trigram indexes for fuzzy search
        index("kb_title_trgm_idx").using("gin", sql`${table.title} gin_trgm_ops`),
        index("kb_body_trgm_idx").using("gin", sql`${table.body} gin_trgm_ops`),
    ],
)

2. Search Function

export async function searchArticles(fdm: FdmType, query: string, limit = 10) {
    return fdm.select().from(kbArticles)
        .where(and(
            eq(kbArticles.is_published, true),
            or(
                sql`${kbArticles.title} % ${query}`,
                sql`${kbArticles.body} % ${query}`,
            ),
        ))
        .orderBy(sql`similarity(${kbArticles.title}, ${query}) DESC`)
        .limit(limit)
}

3. App Routes

Route Purpose
support.kb.tsx KB listing with categories and search
support.kb.$slug.tsx Article view (rendered Markdown + feedback)
admin.support.kb.tsx KB article list (published/draft)
admin.support.kb.new.tsx Create article (Markdown editor)
admin.support.kb.$slug.edit.tsx Edit article

4. UI — Public KB (/support/kb)

┌────────────────────────────────────────────────────────────┐
│  Kennisbank                        🔍 Zoek in artikelen... │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  Veelgestelde vragen                                       │
│  ├── 📄 Hoe importeer ik percelen via de RVO-koppeling?   │
│  ├── 📄 Hoe werkt het bemestingsadvies?                   │
│  └── 📄 Kan ik mijn bedrijf delen met een adviseur?       │
│                                                            │
│  Account & inloggen                                        │
│  ├── 📄 Hoe wijzig ik mijn wachtwoord?                    │
│  └── 📄 Ik kan niet inloggen                              │
│                                                            │
│  ── Article view ──                                        │
│  [Rendered Markdown content]                               │
│                                                            │
│  Was dit artikel nuttig?  [👍 Ja]  [👎 Nee]               │
└────────────────────────────────────────────────────────────┘

5. UI — Agent Editor (/admin/support/kb)

┌────────────────────────────────────────────────────────────┐
│  Kennisbank beheer                    [+ Nieuw artikel]    │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  Titel   [Hoe importeer ik percelen via de RVO-koppeling?] │
│  Slug    [hoe-importeer-ik-percelen-rvo                  ] │
│  Categorie [Veelgestelde vragen ▾]                         │
│                                                            │
│  ┌─ Editor (Markdown) ──────┐  ┌─ Preview ─────────────┐ │
│  │ ## Stap 1: Ga naar...    │  │ Stap 1: Ga naar...    │ │
│  │                          │  │                        │ │
│  │ Klik op **RVO-koppeling**│  │ Klik op RVO-koppeling  │ │
│  │ in het menu.             │  │ in het menu.           │ │
│  └──────────────────────────┘  └────────────────────────┘ │
│                                                            │
│  [💾 Opslaan als concept]  [🌐 Publiceren]                │
└────────────────────────────────────────────────────────────┘

6. Dependencies

  • react-markdown (already in fdm-app)
  • remark-gfm (for GitHub-flavored Markdown: tables, strikethrough)
  • pg_trgm PostgreSQL extension (enable via migration)

7. Suggested Default Categories

Category Description
veelgesteld Veelgestelde vragen (FAQ)
aan-de-slag Aan de slag / Getting started
percelen Percelen & kaart
bemesting Bemesting & advies
account Account & inloggen

Testing Requirements

  • Integration tests: Create article → publish → verify visible in search → feedback
  • Unit tests: Slug generation, Markdown rendering (no XSS), search query building
  • Manual test: Search finds article with typo (trigram fuzzy), verify categories render

Definition of Done

Users can browse and search a knowledge base to find answers before creating a ticket. Agents can create and maintain articles in Markdown with a live preview.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions