Skip to content

Open virtual assistant on NotFound #3124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 16, 2025
Merged

Open virtual assistant on NotFound #3124

merged 18 commits into from
Jul 16, 2025

Conversation

justinorringer
Copy link
Contributor

@justinorringer justinorringer commented Jun 16, 2025

https://issues.redhat.com/browse/RHCLOUD-32515

Summary by Sourcery

Enable the Virtual Assistant to open on any route (including 404s) by wiring Jotai-powered state for its visibility and initial input, updating its route configuration, and adding a prompt link on the NotFound page.

New Features:

  • Allow users to launch the Virtual Assistant from the NotFound page with a prefilled admin contact message.

Enhancements:

  • Use Jotai atoms to manage the Virtual Assistant’s open state and start input across components.
  • Expand the set of viable routes to include a catch-all (‘*’) for 404s.
  • Pass the open state and input control props into the Scalprum AstroVirtualAssistant component.

Chores:

  • Add new Jotai atoms (virtualAssistantOpenAtom and virtualAssistantStartInputAtom).
  • Initialize the new virtual assistant atoms in chromeStore.

Summary by Sourcery

Enable the Virtual Assistant to open on any route (including 404s) by introducing global Jotai state, expanding route configuration, and adding launch prompts on the NotFound and empty search pages.

New Features:

  • Allow launching the Virtual Assistant from the NotFound page with a prefilled admin contact message.
  • Add a link in the empty search state to start a Virtual Assistant conversation.

Enhancements:

  • Manage the assistant’s visibility and initial input using Jotai atoms across all routes.
  • Extend route matching to include a catch-all route and wire state props into the Scalprum AstroVirtualAssistant component.
  • Introduce a SilentErrorBoundary to suppress runtime errors in the assistant.
  • Extract launch logic into a reusable useVirtualAssistant hook.

Tests:

  • Mock the virtualAssistant module and feature flags in Cypress wrappers and add ChromeAuthContext and FeatureFlagsProvider to tests.

Chores:

  • Add and initialize new Jotai atoms for assistant visibility, open state, and start input in chromeStore.

Copy link
Contributor

sourcery-ai bot commented Jun 16, 2025

Reviewer's Guide

This PR wires Jotai atoms to manage the Virtual Assistant’s open state and initial input, extends its route configuration to catch-all/404 routes, introduces a useVirtualAssistant hook and error boundary, enhances 404 and empty search pages with VA launch controls, and updates Cypress tests to mock and verify the new assistant behavior.

Entity relationship diagram for new Virtual Assistant atoms in chromeStore

erDiagram
    chromeStore ||--o{ virtualAssistantShowAssistantAtom : contains
    chromeStore ||--o{ virtualAssistantOpenAtom : contains
    chromeStore ||--o{ virtualAssistantStartInputAtom : contains
    virtualAssistantShowAssistantAtom {
      boolean value
    }
    virtualAssistantOpenAtom {
      boolean value
    }
    virtualAssistantStartInputAtom {
      string or undefined value
    }
Loading

Class diagram for new and updated Virtual Assistant state management

classDiagram
    class useVirtualAssistant {
      +openVA(startInput: string)
    }
    class virtualAssistantOpenAtom {
      <<atom<boolean>>
    }
    class virtualAssistantShowAssistantAtom {
      <<atom<boolean>>
    }
    class virtualAssistantStartInputAtom {
      <<atom<string | undefined>>
    }
    useVirtualAssistant --> virtualAssistantOpenAtom : uses
    useVirtualAssistant --> virtualAssistantShowAssistantAtom : uses
    useVirtualAssistant --> virtualAssistantStartInputAtom : uses
Loading

Class diagram for updated VirtualAssistant route component

classDiagram
    class VirtualAssistant {
      +isOpen: boolean
      +setOpen(open: boolean): void
      +startInput: string
      +setStartInput(message: string): void
      +showAssistant: boolean
      +setShowAssistant(show: boolean): void
    }
    class ScalprumComponent {
      +scope: string
      +module: string
      +showAssistant: boolean
      +isOpen: boolean
      +setOpen(open: boolean): void
      +startInput: string
      +setStartInput(message: string): void
    }
    VirtualAssistant --> ScalprumComponent : passes props
Loading

Class diagram for SilentErrorBoundary component

classDiagram
    class SilentErrorBoundary {
      -hasError: boolean
      -error: any
      -errorInfo: any
      +render(): ReactNode
      +componentDidCatch(error, errorInfo): void
      +static getDerivedStateFromError(): object
    }
Loading

File-Level Changes

Change Details Files
Integrate Jotai state and expand VA routing
  • Import and use virtualAssistantOpenAtom, virtualAssistantShowAssistantAtom, virtualAssistantStartInputAtom
  • Use useLocation and matchRoutes to enable assistant on any viable route including catch-all
  • Apply useFlag to toggle new config-driven rendering path
  • Assemble and spread ScalprumComponentProps with open/show/input control into AstroVirtualAssistant
  • Wrap assistant in SilentErrorBoundary when config flag is enabled
src/components/Routes/VirtualAssistant.tsx
Add and initialize Virtual Assistant atoms
  • Define virtualAssistantShowAssistantAtom, virtualAssistantOpenAtom, virtualAssistantStartInputAtom
  • Initialize these atoms in chromeStore with default values
src/state/atoms/virtualAssistantAtom.ts
src/state/chromeStore.ts
Introduce VA utility hook and error boundary
  • Implement useVirtualAssistant hook to open assistant with prefilled input
  • Add SilentErrorBoundary to silently catch and suppress VA loading errors
src/hooks/useVirtualAssistant.ts
src/components/Routes/SilentErrorBoundary.tsx
Enable launching VA from 404 and empty search states
  • Update NotFoundRoute to set visibility atom, conditionally render Contact Admin button calling openVA
  • Enhance EmptySearchState to include a clickable VA link invoking openVA when flag enabled
src/components/NotFoundRoute/NotFoundRoute.tsx
src/components/Search/EmptySearchState.tsx
Update Cypress tests to mock and verify VA behavior
  • Add FeatureFlagsProvider and ChromeAuthContext to test wrappers
  • Mock virtualAssistant module entries and intercept feature flag APIs
  • Adjust DefaultLayout, ChromeRoute, and HelpTopicManager tests to include VA config
cypress/component/ChromeRoutes/ChromeRoute.cy.tsx
cypress/component/helptopics/HelpTopicManager.cy.tsx
cypress/component/DefaultLayout.cy.js
cypress/support/component.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @justinorringer - I've reviewed your changes - here's some feedback:

  • The element in NotFoundRoute is used as a button without an href—consider swapping to a PatternFly or a native with proper ARIA attributes for accessibility.
  • Catching 404s by adding '*' to viableRoutes may inadvertently match every route—consider using useLocation or a dedicated catch-all instead of maintaining a static list.
  • You recreate the virtualAssistantProps object on every render, which may remount the Scalprum component; wrap it in useMemo to avoid unnecessary re-initialization.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The <a> element in NotFoundRoute is used as a button without an href—consider swapping to a PatternFly <Button> or a native <button> with proper ARIA attributes for accessibility.
- Catching 404s by adding '*' to `viableRoutes` may inadvertently match every route—consider using `useLocation` or a dedicated catch-all <Route> instead of maintaining a static list.
- You recreate the `virtualAssistantProps` object on every render, which may remount the Scalprum component; wrap it in `useMemo` to avoid unnecessary re-initialization.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

const viableRoutes = ['/', '/insights/*', '/settings/*', '/subscriptions/overview/*', '/subscriptions/inventory/*', '/subscriptions/usage/*'];
const [isOpen, setOpen] = useAtom(virtualAssistantOpenAtom);
const [startInput, setStartInput] = useAtom(virtualAssistantStartInputAtom);
// TODO: If a route that results in a 404 is not in this list, the Virtual Assistant will not be displayed. :(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure about the best way to get around the viableRoutes on 404 (I added the wildcard for testing)

Add the isHidden var to jotai and check it here? https://github.com/RedHatInsights/insights-chrome/blob/master/src/components/ChromeRoute/ChromeRoute.tsx#L35

Copy link
Contributor

Choose a reason for hiding this comment

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

Met with Justin offline. Due to the nature of the VA (and it being a federated module) - we can simply render a scalprum component for VA on the 404 page (and in other locations) as the URL will not be predictable.

There is one issue we will need to solve however around state management (so on pages where the VA is already present and open we do not reset the chat history/reopen it). We can manage that state inside the VA itself like we have done for the notifications panel singleton instance.

@florkbr
Copy link
Contributor

florkbr commented Jun 17, 2025

@justinorringer we may want to mark this a draft so it's not accidentally merged (wildcard in viable routes shouldn't be merged).

Also, I see some lint failures reported by the github action:

src/components/NotFoundRoute/NotFoundRoute.tsx
  10:10  error  'isOpen' is assigned a value but never used          @typescript-eslint/no-unused-vars
  11:10  error  'startInput' is assigned a value but never used      @typescript-eslint/no-unused-vars
  13:85  error  Insert `;`                                           prettier/prettier
  15:4   error  Insert `;`                                           prettier/prettier
  17:10  error  Insert `(⏎····`                                      prettier/prettier
  18:5   error  Insert `··`                                          prettier/prettier
  19:1   error  Insert `··`                                          prettier/prettier
  20:7   error  Insert `··`                                          prettier/prettier
  21:5   error  Insert `··`                                          prettier/prettier
  22:3   error  Replace `</EmptyState>` with `··</EmptyState>⏎··);`  prettier/prettier

Can we also include the JIRA in the description?

@florkbr
Copy link
Contributor

florkbr commented Jun 18, 2025

/retest

@justinorringer
Copy link
Contributor Author

@florkbr @Hyperkid123 mind taking a look at the error boundary solution to fix the tests (not ideal)

@@ -0,0 +1,5 @@
import { atom } from 'jotai';

export const virtualAssistantShowAssistantAtom = atom(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that it's a good practice to define types for atoms. Even though TS will implicitly define its type by itself, it wouldn't catch errors if the value was accidentally changed

@Hyperkid123
Copy link
Contributor

@justinorringer the 404 page looks wierd now:
Screenshot 2025-06-23 at 10 27 56

No relevant errors in console. we should only see the not found component, not be "error" component.

@catastrophe-brandon catastrophe-brandon marked this pull request as draft June 23, 2025 15:52
@justinorringer justinorringer marked this pull request as ready for review July 9, 2025 20:42
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @justinorringer - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/components/Search/EmptySearchState.tsx:36` </location>
<code_context>
+          {isOpenConfig ? (
+            <Content component="p" className="pf-v6-u-text-color-subtle">
+              Try searching Hybrid Cloud help or start a conversation with our{' '}
+              <a
+                onClick={() => {
+                  openVA('');
+                }}
+              >
+                Virtual Assistant.
+              </a>
</code_context>

<issue_to_address>
Using an <a> tag without href for a button-like action may affect accessibility.

Since the <a> tag is used for an action, either use a <button> element or add role="button" and keyboard event handlers to ensure accessibility.
</issue_to_address>

### Comment 2
<location> `src/components/NotFoundRoute/NotFoundRoute.tsx:23` </location>
<code_context>
+
+  return (
+    <>
+      {isOpenConfig ? (
+        <EmptyState id="not-found">
+          <EmptyStateBody>
</code_context>

<issue_to_address>
Consider extracting the conditional button and rendering a single <EmptyState> to avoid duplicating markup.

```jsx
// Extract the conditional button into a small variable (or helper component)
// and render a single <EmptyState> tree:

const NotFoundRoute = () => {
  const setShowAssistant = useSetAtom(virtualAssistantShowAssistantAtom);
  const { openVA } = useVirtualAssistant();
  const isOpenConfig = useFlag('platform.virtual-assistant.is-open-config');

  useEffect(() => {
    setShowAssistant(true);
  }, [setShowAssistant]);

  // only render the Button if isOpenConfig is true
  const actionButton = isOpenConfig && (
    <Button
      onClick={() => openVA(`Contact my org admin.`)}
      className="pf-v6-c-button pf-m-link"
    >
      Contact your org admin with the Virtual Assistant.
    </Button>
  );

  return (
    <EmptyState id="not-found">
      <EmptyStateBody>
        <InvalidObject />
        {actionButton}
      </EmptyStateBody>
    </EmptyState>
  );
};
```
This removes the duplicated `<EmptyState>` branches while preserving all behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -69,6 +81,9 @@ const Wrapper = ({
const [scalprumConfig, setScalprumConfig] = useAtom(scalprumConfigAtom);
const setModuleRoutes = useSetAtom(moduleRoutesAtom);
useEffect(() => {
console.log('Setting scalprum config and module routes in Wrapper');
Copy link
Contributor

Choose a reason for hiding this comment

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

@justinorringer can we remove these console log statements?

@@ -31,6 +33,27 @@ const defaultUser: ChromeUser = {
},
};

const chromeAuthContextValue: ChromeAuthContextValue = {
ssoUrl: '',
doOffline: () => Promise.resolve(),
Copy link
Contributor

@florkbr florkbr Jul 16, 2025

Choose a reason for hiding this comment

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

It might be nice to move this chromeAuthContextValue to a test-helper utility somewhere in the future in case other tests need this shape going forward. Not blocking.

@@ -47,6 +47,11 @@ const initialScalprumConfig = {
appId: 'TestApp',
manifestLocation: '/foo/bar.json',
},
virtualAssistant: {
name: 'virtualAssistant',
Copy link
Contributor

@florkbr florkbr Jul 16, 2025

Choose a reason for hiding this comment

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

@justinorringer since we had to touch so many of these specs that setup the scalprum context - it may be nice to move this to a shared setup as well (in case we need to remove it in the future, or add more site wide modules). Not blocking

],
});
});

it('should render not found route if permissions are not met', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to make sure I understand the test scenario. Would this cover something like a non-admin trying to view /iam/users which is restricted to org admins or users with appropriate assigned permissions?

@florkbr
Copy link
Contributor

florkbr commented Jul 16, 2025

Konflux EC failure:

✕ [Violation] tasks.required_untrusted_task_found
  ImageRef: quay.io/redhat-user-workloads/hcc-platex-services-tenant/insights-chrome@sha256:e9def1dd7ea47987f47d29b01814b2db917a16bd5af2c3bd6bb506e5ff0b25da
  Reason: Required task "git-clone-oci-ta" is required and present but not from a trusted task
  Term: git-clone-oci-ta
  Title: All required tasks are from trusted tasks
  Description: Ensure that the all required tasks are resolved from trusted tasks. To exclude this rule add
  "tasks.required_untrusted_task_found:git-clone-oci-ta" to the `exclude` section of the policy configuration.
  Solution: Make sure all required tasks in the build pipeline are resolved from trusted tasks.

✕ [Violation] tasks.required_untrusted_task_found
  ImageRef: quay.io/redhat-user-workloads/hcc-platex-services-tenant/insights-chrome-dev@sha256:ab4bda4963b9ca1b77e0e0855073ab31331d6051b67d09331052d1b77ef81fc4
  Reason: Required task "git-clone-oci-ta" is required and present but not from a trusted task
  Term: git-clone-oci-ta
  Title: All required tasks are from trusted tasks
  Description: Ensure that the all required tasks are resolved from trusted tasks. To exclude this rule add
  "tasks.required_untrusted_task_found:git-clone-oci-ta" to the `exclude` section of the policy configuration.
  Solution: Make sure all required tasks in the build pipeline are resolved from trusted tasks.

Looking into it...

@florkbr florkbr merged commit 36a647f into master Jul 16, 2025
16 of 18 checks passed
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.

5 participants