Skip to content

Conversation

@nofaralfasi
Copy link
Collaborator

@nofaralfasi nofaralfasi commented Sep 11, 2025

What are the changes introduced in this pull request?

  • Enable inventory synchronization in local Advisor Engine environments.
  • Hide cloud-specific UI elements when use_iop_mode is true.
  • Conditionally display settings based on IOP mode.
  • Show only relevant configuration options for cloud vs. local Advisor environments.

These changes allow inventory synchronization to run in IOP mode while hiding cloud-specific UI components that are not relevant in local Advisor Engine environments.

Considerations taken when implementing this change?

  • The “Sync all inventory status” button is retained for debugging and to handle edge cases.

What are the testing steps for this pull request?

  1. Navigate to the Inventory Upload page (Under Administrator).
  2. With Smart-Proxy using IoP mode enabled:
    In the top section of the page:
    • Only the "Sync all inventory status" button should be visible.
    • All other elements, including:
      • Configure Cloud Connector
      • RH Inventory Description
        should be hidden.
  3. With Smart-Proxy without IoP mode:
    • The page should remain unchanged.

ToDo: Consider whether the "Connectivity test" option in the main kebab menu should also be hidden in IOP mode.

Summary by Sourcery

Enable inventory synchronization in IOP mode and conditionally hide cloud-specific UI components using a new useIopConfig hook, and update component tests to use React Testing Library

New Features:

  • Enable inventory synchronization in IOP mode by removing the skip in the inventory hosts sync job

Enhancements:

  • Introduce a useIopConfig hook to toggle UI components based on IOP mode
  • Hide cloud-specific UI elements (inventory settings, page description, and cloud connector button) when IOP mode is active
  • Refactor PageHeader and ToolbarButtons components to conditionally render content and adjust layout

Tests:

  • Migrate snapshot tests to React Testing Library using rtlHelpers.renderWithStore
  • Add tests for PageHeader, ToolbarButtons, CVECountCell, and InsightsTable covering IOP and non-IOP modes and CSS class checks

Chores:

  • Remove obsolete snapshot fixtures and unused mock API hook files

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 11, 2025

Reviewer's Guide

This PR enables inventory synchronization in IOP mode by removing the previous skip logic and updates the UI to conditionally hide cloud-specific components (settings, descriptions, and connector button) when IOP mode is active, accompanied by a comprehensive overhaul of related tests to use renderWithStore, mock IOP configurations, and remove outdated snapshots.

Sequence diagram for conditional rendering of ToolbarButtons in IOP mode

sequenceDiagram
    participant PageHeader
    participant ToolbarButtons
    participant useIopConfig
    participant CloudConnectorButton
    participant SyncButton
    PageHeader->>ToolbarButtons: Render ToolbarButtons
    ToolbarButtons->>useIopConfig: Check IOP mode
    alt IOP mode enabled
        ToolbarButtons-->>CloudConnectorButton: Do not render
        ToolbarButtons->>SyncButton: Render
    else IOP mode disabled
        ToolbarButtons->>CloudConnectorButton: Render
        ToolbarButtons->>SyncButton: Render
    end
Loading

Class diagram for InventoryHostsSync logic change

classDiagram
    class InventoryHostsSync {
      +plan(organizations)
    }
    InventoryHostsSync --|> QueryInventoryJob
    class ForemanRhCloud {
      +with_iop_smart_proxy?()
    }
    %% Previously: plan() would skip if ForemanRhCloud.with_iop_smart_proxy? was true
    %% Now: plan() always runs, regardless of IOP mode
Loading

Class diagram for conditional UI components in PageHeader

classDiagram
    class PageHeader {
      +useIopConfig()
      +render()
    }
    class InventorySettings
    class PageDescription
    class ToolbarButtons
    PageHeader o-- ToolbarButtons
    PageHeader o-- InventorySettings
    PageHeader o-- PageDescription
    %% InventorySettings and PageDescription only rendered if useIopConfig() is false
    %% ToolbarButtons always rendered
Loading

Class diagram for ToolbarButtons conditional rendering

classDiagram
    class ToolbarButtons {
      +useIopConfig()
      +render()
    }
    class CloudConnectorButton
    class SyncButton
    ToolbarButtons o-- CloudConnectorButton
    ToolbarButtons o-- SyncButton
    %% CloudConnectorButton only rendered if useIopConfig() is false
    %% SyncButton always rendered
Loading

File-Level Changes

Change Details Files
Enable inventory synchronization in IOP mode by removing the skip condition
  • Removed return-if-IoP check in the plan method
  • Allowed InventoryHostsSync to run under IoP smart proxies
lib/inventory_sync/async/inventory_hosts_sync.rb
Conditionally hide cloud-specific UI components when in IOP mode
  • Imported and used useIopConfig hook in PageHeader
  • Wrapped InventorySettings and PageDescription rendering behind IoP mode check
  • Imported and used useIopConfig in ToolbarButtons
  • Wrapped CloudConnectorButton behind IoP mode condition
webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js
webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js
Migrate and enhance component tests for IoP mode and remove outdated snapshots
  • Switched tests to use rtlHelpers.renderWithStore
  • Mocked useIopConfig hook and subscription selector for IoP vs. non-IoP scenarios
  • Added coverage for UI visibility in both modes
  • Deleted obsolete snapshot files and APIHooks mock
webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js
webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js
webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js
webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js
webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap
webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap
webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js

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

@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 there - I've reviewed your changes and they look great!


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.

Copy link
Member

@ShimShtein ShimShtein left a comment

Choose a reason for hiding this comment

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

A couple of small suggestions, but overall looks good.

@nofaralfasi nofaralfasi force-pushed the SAT-36556 branch 4 times, most recently from 2cc7ef5 to 296fbbd Compare September 17, 2025 09:12
@ShimShtein
Copy link
Member

ShimShtein commented Sep 25, 2025

I have just merged theforeman/foreman#10654, using those new helpers we can stop mocking the useAPI function, and just present the correct state to the component. Something similar to

    import { rtlHelpers } from '../../../common/rtlTestHelpers';
    const { renderWithStore } = rtlHelpers;

    test('hides inventory settings and description when in IoP mode', () => {
      renderWithStore(
          <PageHeader />,
          {
            ADVISOR_ENGINE_CONFIG: {
                response: { use_iop_mode: true },
          }
      );

      // Core components should still be present
      expect(screen.getByTestId('settings-warning')).toBeTruthy();
      expect(screen.getByTestId('page-title')).toBeTruthy();
      expect(screen.getByTestId('inventory-filter')).toBeTruthy();
      expect(screen.getByTestId('toolbar-buttons')).toBeTruthy();

      // These components should be hidden in IoP mode
      expect(screen.queryByTestId('inventory-settings')).toBeNull();
      expect(screen.queryByTestId('page-description')).toBeNull();
    });

I won't block the PR on it, but I think it will create more technical debt.

@nofaralfasi nofaralfasi force-pushed the SAT-36556 branch 2 times, most recently from 4769707 to af3e24f Compare September 30, 2025 15:16
@nofaralfasi
Copy link
Collaborator Author

Instead of using the mock, you can render the component with the augmented state:

This will implicitly test the useAdvisorEngineConfig accessor too, which is desired.

@ShimShtein I tried the approach you suggested, but unfortunately, it didn’t work as expected.
Here’s the error message I’m getting:

Unexpected key "ADVISOR_ENGINE_CONFIG" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "bookmarksPF4", "hosts", "notifications", "toasts", "passwordStrength", "breadcrumbBar", "layout", "diffModal", "editor", "templates", "settingRecords", "personalAccessTokens", "confirmModal", "router", "extendable", "auditsPage", "foremanModals", "intervals", "API". Unexpected keys will be ignored.

I also tried nesting it under the API key, but with that setup, the tests are still failing, so it doesn’t seem to help either.
Do you have any other suggestions on how to structure this?

@ShimShtein
Copy link
Member

You need to recreate the structure of the existing API redux tree:
image
Can you please try it? In the ideal world we will have a helper that will put the response exactly where we want it, but for now, we will need to do it manually

@jeremylenz
Copy link
Collaborator

@nofaralfasi
Copy link
Collaborator Author

nofaralfasi commented Oct 27, 2025

You need to recreate the structure of the existing API redux tree:
Can you please try it? In the ideal world we will have a helper that will put the response exactly where we want it, but for now, we will need to do it manually

@ShimShtein I tried it with this code, but I’m still seeing errors for components that should be hidden:

    test('hides inventory settings and description when in IoP mode', () => {
      renderWithStore(<PageHeader />, {
        API: {
          ADVISOR_ENGINE_CONFIG: {
            payload: { url: '/api/v2/rh_cloud/advisor_engine_config' },
            response: { use_iop_mode: true },
            status: 'RESOLVED',
          },
        },
      });

@MariaAga
Copy link
Member

@nofaralfasi I ran the tests with

    test('hides inventory settings and description when in IoP mode', () => {
      renderWithStore(<PageHeader />, {
        API: {
          ADVISOR_ENGINE_CONFIG: {
            payload: { url: '/api/v2/rh_cloud/advisor_engine_config' },
            response: { use_iop_mode: true },
            status: 'RESOLVED',
          },
        },
      });

on my env and they pass. What error do you see?

@nofaralfasi
Copy link
Collaborator Author

What error do you see?

 FAIL  ../../foreman_rh_cloud/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js (5.887 s, 172 MB heap size)
  ● PageHeader › component behavior › hides inventory settings and description when in IoP mode

    expect(received).toBeNull()

    Received: <div data-testid="inventory-settings">InventorySettings</div>

      69 |
      70 |       // These components should be hidden in IoP mode
    > 71 |       expect(screen.queryByTestId('inventory-settings')).toBeNull();
         |                                                          ^
      72 |       expect(screen.queryByTestId('page-description')).toBeNull();
      73 |     });
      74 |

      at Object.<anonymous> (../../foreman_rh_cloud/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js:71:58)

@nofaralfasi nofaralfasi force-pushed the SAT-36556 branch 2 times, most recently from f31853b to 32ef189 Compare November 10, 2025 17:12
@nofaralfasi nofaralfasi marked this pull request as draft November 13, 2025 14:04
@nofaralfasi nofaralfasi changed the title Conditionally hide UI components and enable inventory sync in IOP mode [WIP] Conditionally hide UI components and enable inventory sync in IOP mode Nov 13, 2025
@nofaralfasi nofaralfasi marked this pull request as ready for review November 17, 2025 15:24
Copy link

@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 there - I've reviewed your changes - here's some feedback:

  • The IOP mode mocking setup is duplicated across several test files—consider extracting the useIopConfig mock and renderWithStore setup into a shared test utility to reduce boilerplate.
  • Since multiple components (PageHeader, ToolbarButtons) use the same useIopConfig check, you might extract that conditional into a small HOC or helper to keep UI logic DRY.
  • In the CVECountCell tests you only cover the UnknownIcon path—adding a positive case where the API returns a real count would validate that the component renders the expected number.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The IOP mode mocking setup is duplicated across several test files—consider extracting the `useIopConfig` mock and `renderWithStore` setup into a shared test utility to reduce boilerplate.
- Since multiple components (PageHeader, ToolbarButtons) use the same `useIopConfig` check, you might extract that conditional into a small HOC or helper to keep UI logic DRY.
- In the CVECountCell tests you only cover the UnknownIcon path—adding a positive case where the API returns a real count would validate that the component renders the expected number.

## Individual Comments

### Comment 1
<location> `lib/inventory_sync/async/inventory_hosts_sync.rb:11-12` </location>
<code_context>
       set_callback :step, :around, :create_missing_hosts

       def plan(organizations)
-        # Do not run for local advisor, since we use sub-man id to identify hosts.
-        return if ForemanRhCloud.with_iop_smart_proxy?
         # by default the tasks will be executed concurrently
         super(organizations)
</code_context>

<issue_to_address>
**issue (bug_risk):** Removing the early return for IOP mode may affect host sync logic.

Please verify that running the job in local advisor mode does not result in duplicate host records or other unintended effects, particularly regarding sub-man id usage.
</issue_to_address>

### Comment 2
<location> `webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js:70-79` </location>
<code_context>
+    test('hides inventory settings and description when in IoP mode', () => {
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for unexpected or invalid API responses.

Tests should also cover scenarios where ADVISOR_ENGINE_CONFIG or INVENTORY_SETTINGS API responses are missing, malformed, or contain unexpected values to verify error handling and UI stability.
</issue_to_address>

### Comment 3
<location> `webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js:61-67` </location>
<code_context>
+    expect(screen.getByTestId('sync-button')).toBeTruthy();
+  });
+
+  test('renders nothing when subscription connection is not enabled', () => {
+    useIopConfig.mockReturnValue(false);
+    selectSubscriptionConnectionEnabled.mockReturnValue(false);
+
+    const { container } = renderWithStore(<ToolbarButtons />);
+
+    expect(container.firstChild).toBeNull();
+  });
+
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for error and loading states of subscription connection.

Please add tests for cases where the selector returns undefined, null, or the API status is 'ERROR' or 'PENDING' to verify correct component behavior in these scenarios.

Suggested implementation:

```javascript
  test('renders only sync button when in IOP mode', () => {
    useIopConfig.mockReturnValue(true);
    selectSubscriptionConnectionEnabled.mockReturnValue(true);

    renderWithStore(<ToolbarButtons />);

    expect(screen.queryByTestId('cloud-connector-button')).toBeNull();
    expect(screen.getByTestId('sync-button')).toBeTruthy();
  });

  test('renders nothing when subscription connection is not enabled', () => {
    useIopConfig.mockReturnValue(false);
    selectSubscriptionConnectionEnabled.mockReturnValue(false);

    const { container } = renderWithStore(<ToolbarButtons />);

    expect(container.firstChild).toBeNull();
  });

  test('renders nothing when subscription connection enabled selector returns undefined', () => {
    useIopConfig.mockReturnValue(false);
    selectSubscriptionConnectionEnabled.mockReturnValue(undefined);

    const { container } = renderWithStore(<ToolbarButtons />);

    expect(container.firstChild).toBeNull();
  });

  test('renders nothing when subscription connection enabled selector returns null', () => {
    useIopConfig.mockReturnValue(false);
    selectSubscriptionConnectionEnabled.mockReturnValue(null);

    const { container } = renderWithStore(<ToolbarButtons />);

    expect(container.firstChild).toBeNull();
  });

  test('renders loading indicator when API status is PENDING', () => {
    useIopConfig.mockReturnValue(false);
    selectSubscriptionConnectionEnabled.mockReturnValue(true);
    // Mock API status selector
    jest.spyOn(require('../selectors'), 'selectSubscriptionConnectionStatus').mockReturnValue('PENDING');

    renderWithStore(<ToolbarButtons />);

    expect(screen.getByTestId('subscription-loading')).toBeTruthy();
  });

  test('renders error message when API status is ERROR', () => {
    useIopConfig.mockReturnValue(false);
    selectSubscriptionConnectionEnabled.mockReturnValue(true);
    // Mock API status selector
    jest.spyOn(require('../selectors'), 'selectSubscriptionConnectionStatus').mockReturnValue('ERROR');

    renderWithStore(<ToolbarButtons />);

    expect(screen.getByTestId('subscription-error')).toBeTruthy();
  });


```

- Ensure that your `ToolbarButtons` component renders elements with `data-testid="subscription-loading"` and `data-testid="subscription-error"` for the loading and error states, respectively.
- If the selectors are imported differently, adjust the `jest.spyOn` path accordingly.
- If your component handles loading/error states differently, update the test assertions to match the actual rendered output.
</issue_to_address>

### Comment 4
<location> `webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js:80-57` </location>
<code_context>
+   it('renders UnknownIcon when IoP is enabled but CVE API call fails', () => {
</code_context>

<issue_to_address>
**suggestion (testing):** Add a test for successful CVE API response in IoP mode.

Please add a test to verify correct rendering when the CVE API call succeeds and returns a valid count in IoP mode.
</issue_to_address>

### Comment 5
<location> `webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js:15-16` </location>
<code_context>
+    jest.clearAllMocks();
+  });
+
+  it('renders without crashing', () => {
+    renderWithStore(<InsightsTable {...tableProps} />);
+  });
 });
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding assertions for table content and edge cases.

Add assertions to check correct data rendering, empty data handling, and proper behavior during loading and error states.

Suggested implementation:

```javascript
  it('renders without crashing', () => {
    renderWithStore(<InsightsTable {...tableProps} />);
  });

  it('renders table rows with correct data', () => {
    const { getByText } = renderWithStore(<InsightsTable {...tableProps} />);
    tableProps.data.forEach(row => {
      expect(getByText(row.name)).toBeInTheDocument();
      // Add more assertions for other columns as needed
    });
  });

  it('renders empty state when data is empty', () => {
    const emptyProps = { ...tableProps, data: [] };
    const { getByText } = renderWithStore(<InsightsTable {...emptyProps} />);
    expect(getByText(/no data/i)).toBeInTheDocument();
  });

  it('shows loading indicator when loading', () => {
    const loadingProps = { ...tableProps, loading: true };
    const { getByTestId } = renderWithStore(<InsightsTable {...loadingProps} />);
    expect(getByTestId('loading-indicator')).toBeInTheDocument();
  });

  it('shows error message when error occurs', () => {
    const errorProps = { ...tableProps, error: 'Something went wrong' };
    const { getByText } = renderWithStore(<InsightsTable {...errorProps} />);
    expect(getByText(/something went wrong/i)).toBeInTheDocument();
  });

```

- Ensure that your `InsightsTable` component renders a loading indicator with `data-testid="loading-indicator"` and an empty state message containing "no data" (case-insensitive) when appropriate.
- Adjust the column assertions in the "renders table rows with correct data" test to match your actual data structure and column names.
- If your error message or empty state uses different text, update the test accordingly.
</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.

@nofaralfasi nofaralfasi changed the title [WIP] Conditionally hide UI components and enable inventory sync in IOP mode Conditionally hide UI components and enable inventory sync in IOP mode Nov 17, 2025
- Enable inventory synchronization in local advisor engine environments
- Hide cloud-specific UI elements when use_iop_mode is true
- Conditionally display settings based on iop mode
- Remove APIHooks.js and update tests accordingly

This allows inventory sync to run in IOP mode while hiding cloud-specific
UI components that are not relevant in local advisor engine environments.
Copy link
Collaborator

@jeremylenz jeremylenz left a comment

Choose a reason for hiding this comment

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

LGTM, thanks @nofaralfasi!

Will wait a bit to merge in case any others have additional comments.

@jeremylenz
Copy link
Collaborator

Merging. thanks @nofaralfasi !

@jeremylenz jeremylenz merged commit 269654a into theforeman:develop Dec 1, 2025
19 checks passed
Odilhao pushed a commit to Odilhao/foreman_rh_cloud that referenced this pull request Jan 14, 2026
theforeman#1088)

- Enable inventory synchronization in local advisor engine environments
- Hide cloud-specific UI elements when use_iop_mode is true
- Conditionally display settings based on iop mode
- Remove APIHooks.js and update tests accordingly

This allows inventory sync to run in IOP mode while hiding cloud-specific
UI components that are not relevant in local advisor engine environments.

(cherry picked from commit 269654a)
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