Skip to content

Add knowledgebase tab + storybook journey and tests#272

Merged
apinkert merged 3 commits intoRedHatInsights:masterfrom
apinkert:kb-tab
Mar 23, 2026
Merged

Add knowledgebase tab + storybook journey and tests#272
apinkert merged 3 commits intoRedHatInsights:masterfrom
apinkert:kb-tab

Conversation

@apinkert
Copy link
Copy Markdown
Collaborator

@apinkert apinkert commented Mar 20, 2026

For RHCLOUD-45777, RHCLOUD-45244, and RHCLOUD-45249. This tab is currently using mocked data as the API has not been implemented. I went ahead and added both the user journey and the stories related to it since it won't be visible on stage (hidden behind unleash feature flag)

Screen.Recording.2026-03-20.at.12.28.40.PM.mov

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

Summary by CodeRabbit

  • New Features

    • Knowledgebase panel: full browsing UI with searchable articles, bundle-scope toggle (All vs bundle), pagination, article tags, and external-link behavior (opens in new tab).
  • Updates

    • Terminology changed from "Knowledge base" to "Knowledgebase".
    • New UI labels: panel description, search placeholder, results count, and no-results messaging.

Walkthrough

Adds a Knowledgebase feature to the Help Panel: feature-flag support in Storybook, new i18n messages, a rebuilt KBPanel with search, bundle-scoped filtering and pagination, mock-data support, and extensive Storybook component and user-journey tests.

Changes

Cohort / File(s) Summary
Feature Flag (storybook)
.storybook/hooks/unleash.js
useFlag now accepts flagName and checks ENABLED_FLAGS; useFlags returns enabled flags as { name, enabled: true }. Adds platform.chrome.help-panel_knowledge-base to enabled list.
i18n Messages
src/Messages.ts
Renamed helpPanel.kb.title default from "Knowledge base" → "Knowledgebase". Added helpPanel.kb.description, helpPanel.kb.searchPlaceholder, helpPanel.kb.articlesCount, and helpPanel.kb.noArticlesFound message entries.
Help Panel Tabs / Titles
src/components/HelpPanel/HelpPanelCustomTabs.tsx
Sub-tabs updated to use i18n intl.formatMessage(messages.knowledgeBaseTitle) for the KB tab title and aria-label; useIntl() added to SubTabs.
KBPanel Component
src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx
Reimplemented KBPanel: adds internal state (search, scope toggle, pagination), bundle-awareness via useChrome().getBundleData() & getAvailableBundles(), derives articles from bundleRecommendedContent (or mockArticles prop), deduplicates/merges bundle tags, filters by search + bundle tag, renders DataList of external links, shows results count, empty state, and Pagination. Prop mockArticles?: KnowledgebaseArticle[] added.
Component Storybook Tests
src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx
New Storybook stories and helpers for KBPanel (KBPanelWrapper, waitForLoadingComplete, expectVisibleTitles) with 9 interactive scenarios: default, search, no-results, clear search, bundle scope toggle, pagination, bundle tags view, external-link validation, and combined search+scope. Also WithRealData variant.
User-Journey Tests
src/user-journeys/HelpPanelKBPanel.stories.tsx
New end-to-end Storybook user-journey with a manual test plus 8 sequential step stories that exercise page load, opening the Help Panel, navigating to Knowledgebase, searching, clearing search, toggling bundle scope, viewing articles, and searching within bundle scope. Uses MSW handlers and shared helpers.
Test Data / Helpers
src/user-journeys/_shared/helpPanelJourneyHelpers.ts
Added export const mockKBArticles — an array of ~15 mock knowledgebase article objects with id, title, url, and bundleTags for use in stories/tests.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User as User
participant KB as KBPanel
(React UI)
participant Chrome as ChromeAPI
(useChrome)
participant Data as RecommendedContent
(bundleRecommendedContent / mock)

User->>KB: open Help Panel / select Knowledgebase / type query / toggle scope / paginate
KB->>Chrome: getBundleData() / getAvailableBundles()
Chrome-->>KB: bundle info
KB->>Data: read bundleRecommendedContent or mockArticles
Data-->>KB: list of articles (deduped, with bundleTags)
KB->>KB: filter by search + scope, paginate
KB-->>User: render paginated external links, bundle tag labels, results count, empty-state

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a knowledgebase tab along with Storybook journey and tests, which is well-supported by the file changes.
Description check ✅ Passed The description is well-related to the changeset, referencing specific Jira tickets, explaining the use of mocked data pending API implementation, and noting the feature flag-based visibility.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx (1)

107-115: Keep one Storybook case on the real article source.

KBPanelWrapper always injects mockKBArticles, so every play test skips getAllKBArticles() and the real tag mapping from recommendedContentConfig—the code path the live Help Panel uses. Adding one story without mockArticles would catch source/filter regressions much earlier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx` around lines 107
- 115, The Story currently always injects mockKBArticles into KBPanel (via the
story using <KBPanel setNewActionTitle={() => {}} mockArticles={mockKBArticles}
/>) which bypasses getAllKBArticles() and the real tag mapping from
recommendedContentConfig; add one Storybook case that does NOT pass mockArticles
(i.e., render <KBPanel setNewActionTitle={...} />) so KBPanel uses its real data
source and exercises getAllKBArticles() and the recommendedContentConfig
tag/filter logic, keeping the existing mock-based story for isolated tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/HelpPanel/HelpPanelCustomTabs.tsx`:
- Around line 91-94: Replace the hard-coded title 'Knowledgebase' used for the
TabType.kb entry in HelpPanelCustomTabs with the i18n message key defined in
Messages.ts (helpPanel.kb.title) and use that same localized string for the
tab's aria-label; update the object that sets title: 'Knowledgebase' (for
TabType.kb) to pull the translated message via the app's i18n helper (the same
accessor used elsewhere) and ensure aria-label references that translated value
instead of a literal.

In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx`:
- Around line 41-59: getAllKBArticles currently flattens
bundleRecommendedContent 1:1 and yields duplicate KB entries when the same
article appears under multiple bundle keys; update getAllKBArticles to
deduplicate by a stable key (e.g., item.url or item.id) and, when duplicates are
found, merge/union their bundleTags into a single KnowledgebaseArticle before
pushing to the kbArticles array so downstream bundle filtering works correctly
(ensure the generated id remains stable, e.g., derived from the URL or article
identifier rather than bundleId-index). Keep the "static" && "kb" filter but
perform the dedupe-and-merge step while iterating bundleRecommendedContent so
returned KnowledgebaseArticle objects have combined bundleTags and no duplicate
entries.

In `@src/user-journeys/HelpPanelKBPanel.stories.tsx`:
- Around line 129-136: The test currently only checks for the presence of the
count label using waitFor + canvas.queryByText(/Knowledgebase articles \(/i)
which can pass even when results are wrong; update the assertions to verify
filtered results themselves by asserting the exact expected count text (e.g.,
"Knowledgebase articles (N)") or by asserting a representative article title is
present after each search/toggle action. Locate the waitFor blocks and
assertions in HelpPanelKBPanel.stories.tsx (the calls using waitFor,
TEST_TIMEOUTS.ELEMENT_WAIT, and canvas.queryByText) and replace/augment the
generic existence checks with assertions that validate the actual result set for
the searches/toggles mentioned (also apply the same change to the other similar
blocks around the comment ranges 210-235 and 330-336). Ensure the assertions run
after the same waitFor so timing is preserved.

---

Nitpick comments:
In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx`:
- Around line 107-115: The Story currently always injects mockKBArticles into
KBPanel (via the story using <KBPanel setNewActionTitle={() => {}}
mockArticles={mockKBArticles} />) which bypasses getAllKBArticles() and the real
tag mapping from recommendedContentConfig; add one Storybook case that does NOT
pass mockArticles (i.e., render <KBPanel setNewActionTitle={...} />) so KBPanel
uses its real data source and exercises getAllKBArticles() and the
recommendedContentConfig tag/filter logic, keeping the existing mock-based story
for isolated tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6456faeb-06b4-44bb-9ca0-b4a7da5ff000

📥 Commits

Reviewing files that changed from the base of the PR and between 10e41c2 and 0699e68.

📒 Files selected for processing (7)
  • .storybook/hooks/unleash.js
  • src/Messages.ts
  • src/components/HelpPanel/HelpPanelCustomTabs.tsx
  • src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx
  • src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx
  • src/user-journeys/HelpPanelKBPanel.stories.tsx
  • src/user-journeys/_shared/helpPanelJourneyHelpers.ts

Comment thread src/components/HelpPanel/HelpPanelCustomTabs.tsx
Comment thread src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx Outdated
Comment thread src/user-journeys/HelpPanelKBPanel.stories.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/user-journeys/HelpPanelKBPanel.stories.tsx (1)

348-357: ⚠️ Potential issue | 🟡 Minor

Assert on filtered results, not just the count label.

This assertion only verifies the count label exists but doesn't validate the actual search results. If the search or bundle filter is broken, this test could still pass with incorrect results.

Consider asserting on a representative article title or the exact count like other steps do:
,

💡 Suggested improvement
     // Verify search is applied with bundle filter
-    // Note: Actual results depend on bundle KB articles and their tags
     await waitFor(
       () => {
-        // Just verify the search executed and count updated
-        const count = canvas.queryByText(/Knowledgebase articles \(/i);
-        expect(count).toBeInTheDocument();
+        // Verify results contain expected article or count
+        const articles = canvas.queryAllByRole('link', {
+          name: /Red Hat/i,
+        });
+        expect(articles.length).toBeGreaterThanOrEqual(0); // 0 is valid if no matches in bundle
       },
       { timeout: TEST_TIMEOUTS.ELEMENT_WAIT }
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/user-journeys/HelpPanelKBPanel.stories.tsx` around lines 348 - 357, The
test currently only checks that the "Knowledgebase articles (" label exists by
using canvas.queryByText inside the waitFor (with TEST_TIMEOUTS.ELEMENT_WAIT),
which doesn't validate filtered results; update the assertion in the waitFor
used in HelpPanelKBPanel.stories.tsx to assert on the actual filtered output —
for example, wait for a representative article title to appear (use
canvas.getByText or canvas.findByText for a known article name) or assert the
exact count in the label (e.g., match "Knowledgebase articles (N)") so the test
fails if the search or bundle filter returns incorrect results; keep the waitFor
wrapper and timeout intact but replace the loose queryByText/count existence
check with a concrete result assertion.
🧹 Nitpick comments (3)
src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx (2)

42-73: Deduplication logic looks good but ID generation could cause collisions.

The deduplication by URL and bundleTags merging addresses the previous review feedback. However, the urlHash derivation using item.url.split('/').pop() may not be unique across different KB articles if they have the same trailing path segment (e.g., different articles ending in /index or numeric IDs).

Consider using a hash of the full URL or the URL itself as the ID for guaranteed uniqueness:

♻️ Suggested improvement for stable unique IDs
         } else {
-          // Create new entry with stable ID based on URL
-          const urlHash = item.url.split('/').pop() || item.url;
+          // Create new entry with stable ID based on full URL
+          const urlHash = encodeURIComponent(item.url);
           articlesByUrl.set(item.url, {
             id: `kb-${urlHash}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx` around lines 42 - 73, In
getAllKBArticles, the current id generation using url.split('/').pop() can
collide; replace that logic to produce a stable unique id per article by either
using the full URL string (e.g., id: `kb-${item.url}`) or a deterministic hash
of the full URL (compute inside getAllKBArticles) when creating the new
KnowledgebaseArticle entry; update the id assignment in the branch that sets
articlesByUrl.set(item.url, { id: ..., title: item.title, url: item.url,
bundleTags: ... }) so IDs are guaranteed unique across different URLs.

92-99: TypeScript suppressions are safe but should be tracked.

The @ts-ignore comments for getBundleData and getAvailableBundles indicate missing type definitions in the chrome API types. The optional chaining with fallback values (|| {} and || []) provides adequate runtime safety.

Consider opening an issue to add proper type definitions for these methods to eliminate the suppressions in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx` around lines 92 - 99, The
code uses `@ts-ignore` for chrome?.getBundleData and chrome?.getAvailableBundles
when extracting bundleId and availableBundles; instead of leaving suppressions,
open an issue to add proper TypeScript definitions for these methods and then
remove the `@ts-ignore` comments: add or augment the chrome namespace/type
declarations to include getBundleData(): { bundleId?: string } | undefined and
getAvailableBundles(): any[] (or a more specific type), update the KBPanel.tsx
usages (bundleId and availableBundles) to rely on the new types, and remove the
eslint/ts-ignore lines so the code is properly typed.
src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx (1)

445-492: Consider extracting shared wrapper logic to reduce duplication.

The WithRealData story duplicates the context provider setup and chrome API mocking from KBPanelWrapper. While this duplication is understandable given the different mockArticles behavior, consider refactoring to share the common setup:

♻️ Optional refactor to reduce duplication
+const KBPanelWrapperBase = ({
+  bundle = 'insights',
+  useMockArticles = true,
+}: {
+  bundle?: string;
+  useMockArticles?: boolean;
+}) => {
+  // ... shared setup logic ...
+  return (
+    <IntlProvider locale="en" defaultLocale="en">
+      <QuickStartContextProvider value={quickStartContextValue}>
+        <div style={{ height: '600px', width: '400px' }}>
+          <KBPanel
+            setNewActionTitle={() => {}}
+            mockArticles={useMockArticles ? mockKBArticles : undefined}
+          />
+        </div>
+      </QuickStartContextProvider>
+    </IntlProvider>
+  );
+};

 export const WithRealData: Story = {
-  render: () => {
-    // ... duplicated setup ...
-  },
+  render: () => <KBPanelWrapperBase useMockArticles={false} />,
   play: async ({ canvasElement }) => {
     // ... assertions ...
   },
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx` around lines 445
- 492, WithRealData duplicates the context/provider and chrome API mocking found
in KBPanelWrapper; extract those common pieces into a shared helper/wrapper
component (e.g., KBPanelStoryWrapper) that creates quickStartContextValue via
useValuesForQuickStartContext, sets up the window.insights.chrome
getBundleData/getAvailableBundles overrides, and renders IntlProvider +
QuickStartContextProvider around KBPanel; update WithRealData to reuse
KBPanelStoryWrapper and only pass the differing bits (like mockArticles or
setNewActionTitle) so the chrome mocking and quick-start context logic is
centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/user-journeys/HelpPanelKBPanel.stories.tsx`:
- Around line 247-255: The conditional around the articles assertion is
redundant and doesn't validate bundle filtering; update the test in
HelpPanelKBPanel.stories.tsx to remove the if block and assert a meaningful
expectation directly (e.g., expect(articles.length).toBeGreaterThan(0) or a
specific minimum) after obtaining articles via
canvasElement.querySelectorAll('[data-ouia-component-id="help-panel-kb-articles-list"]
a'), so the test will fail if zero articles are returned when some are expected.

---

Duplicate comments:
In `@src/user-journeys/HelpPanelKBPanel.stories.tsx`:
- Around line 348-357: The test currently only checks that the "Knowledgebase
articles (" label exists by using canvas.queryByText inside the waitFor (with
TEST_TIMEOUTS.ELEMENT_WAIT), which doesn't validate filtered results; update the
assertion in the waitFor used in HelpPanelKBPanel.stories.tsx to assert on the
actual filtered output — for example, wait for a representative article title to
appear (use canvas.getByText or canvas.findByText for a known article name) or
assert the exact count in the label (e.g., match "Knowledgebase articles (N)")
so the test fails if the search or bundle filter returns incorrect results; keep
the waitFor wrapper and timeout intact but replace the loose queryByText/count
existence check with a concrete result assertion.

---

Nitpick comments:
In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx`:
- Around line 445-492: WithRealData duplicates the context/provider and chrome
API mocking found in KBPanelWrapper; extract those common pieces into a shared
helper/wrapper component (e.g., KBPanelStoryWrapper) that creates
quickStartContextValue via useValuesForQuickStartContext, sets up the
window.insights.chrome getBundleData/getAvailableBundles overrides, and renders
IntlProvider + QuickStartContextProvider around KBPanel; update WithRealData to
reuse KBPanelStoryWrapper and only pass the differing bits (like mockArticles or
setNewActionTitle) so the chrome mocking and quick-start context logic is
centralized.

In `@src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx`:
- Around line 42-73: In getAllKBArticles, the current id generation using
url.split('/').pop() can collide; replace that logic to produce a stable unique
id per article by either using the full URL string (e.g., id: `kb-${item.url}`)
or a deterministic hash of the full URL (compute inside getAllKBArticles) when
creating the new KnowledgebaseArticle entry; update the id assignment in the
branch that sets articlesByUrl.set(item.url, { id: ..., title: item.title, url:
item.url, bundleTags: ... }) so IDs are guaranteed unique across different URLs.
- Around line 92-99: The code uses `@ts-ignore` for chrome?.getBundleData and
chrome?.getAvailableBundles when extracting bundleId and availableBundles;
instead of leaving suppressions, open an issue to add proper TypeScript
definitions for these methods and then remove the `@ts-ignore` comments: add or
augment the chrome namespace/type declarations to include getBundleData(): {
bundleId?: string } | undefined and getAvailableBundles(): any[] (or a more
specific type), update the KBPanel.tsx usages (bundleId and availableBundles) to
rely on the new types, and remove the eslint/ts-ignore lines so the code is
properly typed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ceadb0f7-c2d2-4ee9-9245-c161497267d9

📥 Commits

Reviewing files that changed from the base of the PR and between 0699e68 and 0032e5c.

📒 Files selected for processing (4)
  • src/components/HelpPanel/HelpPanelCustomTabs.tsx
  • src/components/HelpPanel/HelpPanelTabs/KBPanel.stories.tsx
  • src/components/HelpPanel/HelpPanelTabs/KBPanel.tsx
  • src/user-journeys/HelpPanelKBPanel.stories.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/HelpPanel/HelpPanelCustomTabs.tsx

Comment thread src/user-journeys/HelpPanelKBPanel.stories.tsx
@apinkert apinkert merged commit ae0f8eb into RedHatInsights:master Mar 23, 2026
10 checks passed
@apinkert apinkert deleted the kb-tab branch March 23, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants