Skip to content

Conversation

@jeremylenz
Copy link
Collaborator

@jeremylenz jeremylenz commented Nov 7, 2025

What are the changes introduced in this pull request?

This is built on top of #1124

This PR redesigns the Inventory Upload UI to provide a simpler, more intuitive user experience. The changes span both frontend React components and backend Rails controllers to deliver a unified workflow for inventory report generation and upload.

UI Improvements:

  • Removed tab navigation from Dashboard (Generate and Upload tabs) in favor of a single unified view
  • Replaced Terminal-style output with modern TaskProgress components showing real-time progress
  • Added report file path display with download button for generated reports
  • Improved status messaging: "Generated/Uploaded" instead of "Generating/Uploading" for completed tasks
  • Enhanced progress indicators with optimistic UI updates for better responsiveness
  • Made first organization accordion expanded by default for easier access
  • Fixed duration display to show actual task execution time (was showing 0 seconds)
  • Applied sentence case and clearer messaging throughout the UI
  • Improved UI responsiveness:
    • Reduced polling interval from 5s to 2s for faster status updates
    • Added immediate polling after task start for instant feedback
    • Implemented optimistic UI that prevents flashing between states
    • Always show Duration and Report saved to fields for consistent layout
    • Keep action buttons visible at all times, using disabled state instead of hiding
    • Synchronized button states to disable/enable together
    • Removed redundant loading spinners from buttons (progress bar provides feedback)

Backend Changes:

  • Replaced shell-based uploader.sh with Ruby HTTP upload implementation for better reliability (Refactor: Replace uploader.sh with Ruby-based HTTP upload #1124)
  • Refactored GenerateReportJob to use HostInventoryReportJob directly
  • Removed obsolete Redux state management and unused components
  • Fixed task-to-organization linking using action_subject for proper task history
  • Added new API endpoints for task progress and report downloads

Code Quality:

  • Removed 1,841 lines of obsolete code (Terminal, Redux reducers, shell scripts)
  • Added 1,331 lines of new, cleaner implementation
  • Better separation of concerns between generation and upload workflows
  • Converted AccountList from class component to modern function component with hooks

Considerations taken when implementing this change?

  • Backward compatibility: Existing tasks and reports continue to work without migration
  • User experience: Focused on making the most common workflow (generate + upload) seamless
  • Performance: Optimistic UI updates provide immediate feedback while tasks run asynchronously
  • Maintainability: Removed complex Redux state management in favor of simpler component-level state
  • Error handling: Improved error messages and status indicators for failed tasks
  • Testing: Comprehensive test coverage for new API endpoints and job classes
  • Responsiveness: UI updates feel instant with 2-second polling + immediate polling on task start

What are the testing steps for this pull request?

Manual Testing:

  1. Navigate to Administer > Inventory Upload in Foreman UI
  2. Verify the Dashboard displays organizations with their latest task status
  3. Click "Generate and upload" for an organization
  4. Verify the TaskProgress component appears with:
    • Progress bar showing task progress
    • Real-time status updates (updates within 2 seconds)
    • Duration counter
    • All buttons remain visible (disabled during task execution)
    • No flashing or jarring UI transitions
  5. After task completes, verify:
    • Status shows "Generated" or "Uploaded"
    • Report file path is displayed
    • Download button works correctly
    • Duration shows actual execution time
    • Buttons re-enable smoothly
  6. Expand/collapse organization accordions
  7. Verify first organization is expanded by default
  8. Test with multiple organizations to ensure task history displays correctly
  9. Test error scenarios (failed uploads, network issues)
  10. Verify no UI flashing when starting new tasks (optimistic UI should persist)

API Testing:

# Test task progress endpoint
curl -u admin:password http://localhost:3000/foreman_inventory_upload/api/tasks/<task_id>

# Test accounts with tasks endpoint
curl -u admin:password http://localhost:3000/foreman_inventory_upload/api/accounts

# Test report download
curl -u admin:password http://localhost:3000/foreman_inventory_upload/api/reports/<org_id> -o report.tar.xz

🤖 Generated with Claude Code

Summary by Sourcery

Redesign the inventory upload workflow by consolidating report generation and upload into a unified Dynflow-based task, replacing the old Dashboard tabs with a modern TaskProgress UI, introducing TaskHistory and API endpoints for task monitoring, and swapping shell-based uploads for a reliable Ruby HTTP implementation.

New Features:

  • Add TaskProgress UI component for real-time generation and upload status display
  • Introduce TaskHistory component to show past inventory tasks
  • Implement UploadReportDirectJob for direct HTTP uploads with SSL certificate handling
  • Expose new API endpoints for current and historical inventory tasks under inventory_upload

Bug Fixes:

  • Correct duration calculation and status messaging for completed tasks
  • Fix task-to-organization linking and sub-action status determination

Enhancements:

  • Combine report generation and upload into a single Dynflow HostInventoryReportJob
  • Replace old Dashboard with unified view, remove Redux and shell scripts, and apply optimistic UI updates
  • Display report file paths with download button and show actual task durations
  • Expand the first organization accordion by default
  • Improve UI responsiveness with faster polling, immediate feedback, and optimistic state management

Tests:

  • Add comprehensive tests for UploadReportDirectJob covering certificate selection, error handling, and proxy use
  • Remove obsolete script generation tests from QueueForUploadJob

Chores:

  • Remove obsolete components and over 1,800 lines of code related to Terminal output, Redux, and shell scripts
  • Convert AccountList to modern React hooks-based function component

jeremylenz and others added 5 commits November 7, 2025 10:13
Eliminates shell script dependency by implementing direct HTTP upload
using RestClient. This provides better error handling, follows Dynflow
patterns more closely, and removes the need for external shell scripts.

Created:
- UploadReportDirectJob - Pure Ruby HTTP upload implementation
- Comprehensive test suite with 14 tests covering error scenarios

Modified:
- QueueForUploadJob - Switch to new upload job
- Controllers and tests - Reference new job class

Deleted:
- UploadReportJob - Old shell-based implementation
- uploader.sh.erb - Bash upload script
- Related tests for old implementation

All 359 tests pass. This is part 1 of 2-part inventory upload redesign.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Use CloudRequest concern instead of direct RestClient calls
  - Include ::ForemanRhCloud::CloudRequest module
  - Replace RestClient::Request.execute with execute_cloud_request
  - Automatically handles proxy configuration, SSL verification, and error logging

- Create FileUpload wrapper class to avoid monkey-patching File
  - Provides content_type attribute for RestClient multipart uploads
  - Delegates file methods (read, path, etc.) to underlying File object
  - Uses respond_to_missing? and method_missing for proper delegation

- Add thread-safety documentation
  - Note that current implementation assumes single upload per organization
  - Matches existing pattern in GenerateReportJob
  - Full fix requires UI changes (tracked for PR #2)

- Fix certificate check to use dig instead of chained fetch
  - Prevents KeyError when owner_details is empty hash
  - Safer handling of missing certificate data

- Add comprehensive error handling tests
  - Test RestClient::InternalServerError handling
  - Test RestClient::Exceptions::Timeout handling
  - Verify progress output shows error messages

- Add proxy configuration test
  - Verify execute_cloud_request is called (brings in proxy support)

- Add file cleanup tests
  - Test file remains when upload aborted due to missing certificate
  - Test file remains when connection is disabled
  - Verify progress output and exit status for aborted uploads

- Add FileUpload wrapper tests
  - Test delegation to underlying file object
  - Test content_type attribute for RestClient compatibility

All 21 tests passing (0 failures, 0 errors)
Backend Changes:
- Remove ProgressOutput from UploadReportDirectJob
  - Simplified plan() and try_execute() methods
  - Removed progress_output, instance_label, clear_task_output methods
  - Now a pure Dynflow action relying on standard task tracking

- Create Tasks API controller
  - New endpoints: GET /api/rh_cloud/inventory_upload/tasks/current
  - New endpoints: GET /api/rh_cloud/inventory_upload/tasks/history
  - Queries ForemanTasks::Task with .for_action_types() and .with_duration

- Update AccountsController to query ForemanTasks::Task
  - Replaced ProgressOutput.get() with direct task queries
  - Returns both old status strings (backward compat) and new task objects
  - New fields: generate_task, upload_task with full task details

Frontend Changes:
- Create TaskProgress component (Patternfly 4)
  - Uses Progress, Card, Badge from @patternfly/react-core
  - Shows task state, progress bar, timestamps, duration
  - Links to foreman-tasks detail page (/foreman_tasks/tasks/:id)
  - EmptyState for when no tasks exist

- Create TaskHistory component
  - DataList showing recent tasks
  - Result icons (success/error/warning)
  - Relative timestamps and duration
  - Links to task details

- Update Dashboard component
  - Replace ReportGenerate/ReportUpload with TaskProgress
  - Remove log polling (no longer needed)
  - Simplified lifecycle methods
  - Uses account.generate_task and account.upload_task

Note: Redux actions and obsolete component removal pending.
This commit is ready for manual testing.
- Convert Dashboard from Redux to native React state with useState
- Remove Dashboard Redux actions, reducers, constants, and selectors
- Remove obsolete components:
  * Terminal - replaced by TaskProgress
  * TabBody - no longer needed
  * ReportGenerate - no longer needed
  * ReportUpload - no longer needed
  * FullScreenModal - no longer needed with TaskProgress
- Simplify NavContainer to remove FullScreenModal dependency
- Update ForemanInventoryUploadReducers to not import dashboard reducers

This simplifies the codebase and removes legacy terminal-based UI components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Eliminates the shell-based report generation wrapper that called rake tasks,
replacing it with direct Ruby invocations for better performance and simplicity.

Backend changes:
- Replace GenerateReportJob (shell wrapper) with HostInventoryReportJob in controllers
- Delete GenerateReportJob and ShellProcess base class entirely
- Update AccountsController to query HostInventoryReportJob tasks
- Update API TasksController current/history endpoints
- Remove obsolete ReportsController#last and UploadsController#last endpoints
- Remove obsolete /reports/last and /uploads/last routes

Frontend changes:
- Add trigger buttons to TaskProgress component for "Generate and Upload" and "Generate Report"
- Remove unused activeTab state from Dashboard (tabs don't need state)
- Add organizationId and taskType props to TaskProgress
- Delete obsolete Dashboard test files (DashboardActions.test.js, DashboardSelectors.test.js)
- Fix linting issues in TaskHistory and other components

Flow before: Controller → GenerateReportJob → shell rake → HostInventoryReportJob
Flow after: Controller → HostInventoryReportJob (direct!)

This completes the UI redesign by removing all terminal/log-based UI components
and replacing them with modern Patternfly 4 task progress displays with direct
Ruby-based report generation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 7, 2025

Reviewer's Guide

This PR overhauls the Inventory Upload workflow by replacing the old tabbed Dashboard and Redux polling with new React TaskProgress and TaskHistory components (using hooks and PatternFly 4), consolidating report generation and upload into a single Dynflow HostInventoryReportJob, introducing a direct Ruby HTTP uploader (UploadReportDirectJob) with certificate handling, updating controllers and routes to serve task-centric JSON via new API endpoints, and cleaning up obsolete scripts and Redux code.

Sequence diagram for unified report generation and upload workflow

sequenceDiagram
  actor User
  participant UI as "React UI (TaskProgress)"
  participant API as "Rails API Controller"
  participant Job as "HostInventoryReportJob (Dynflow)"
  participant Upload as "UploadReportDirectJob"
  participant DB as "Database"
  User->>UI: Clicks "Generate and upload report"
  UI->>API: POST /foreman_inventory_upload/:org_id/reports
  API->>Job: Start HostInventoryReportJob (with upload=true)
  Job->>DB: Link task to Organization
  Job->>Job: GenerateHostReport
  Job->>Upload: UploadReportDirectJob (HTTP upload)
  Upload->>DB: Store report file path
  Job->>API: Return task status
  API->>UI: Return task JSON
  UI->>User: Show TaskProgress (real-time updates)
  User->>UI: Clicks "Download report"
  UI->>API: GET /foreman_inventory_upload/api/reports/:org_id
  API->>User: Download report file
Loading

ER diagram for updated task-to-organization linking

erDiagram
  ORGANIZATION ||--o{ TASK : "has"
  TASK {
    id string
    label string
    action string
    state string
    result string
    progress number
    started_at datetime
    ended_at datetime
    duration float
    report_file_path string
  }
  ORGANIZATION {
    id integer
    label string
  }
Loading

Class diagram for new and refactored Dynflow jobs

classDiagram
  class HostInventoryReportJob {
    +plan(base_folder, organization_id, hosts_filter, upload)
    +organization_id
    +report_file_path
    +rescue_strategy_for_self
    -organization
  }
  class UploadReportDirectJob {
    +plan(filename, organization_id)
    +try_execute
    +upload_file(cer_path)
    +move_to_done_folder
    +certificate
    +filename
    +organization
    +content_disconnected?
    +logger
    +rescue_strategy_for_self
  }
  class FileUpload {
    +file
    +content_type
    +read()
    +path
  }
  HostInventoryReportJob --> UploadReportDirectJob : uses
  UploadReportDirectJob *-- FileUpload
Loading

File-Level Changes

Change Details Files
Refactor Dashboard UI to use TaskProgress component and remove legacy tab navigation
  • Replace NavContainer-based tabs with TaskProgress in Dashboard
  • Remove polling logic, Redux mappings, and Terminal components
  • Simplify Dashboard export to a plain component
webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js
webpack/ForemanInventoryUpload/Components/Dashboard/index.js
webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js
webpack/ForemanInventoryUpload/ForemanInventoryUploadReducers.js
Convert AccountList and ListItem to functional components with hooks
  • Use useEffect for status polling and cleanup
  • Expand the first organization accordion by default
  • Pass onTaskStart callback to trigger immediate UI updates
webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js
webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js
Rename task status props and update ListItemStatus component
  • Replace generate_report_status/upload_report_status with generated_status/uploaded_status
  • Update status labels and icons to 'Generated'/'Uploaded'
  • Adjust fixtures and propTypes to match renamed props
webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.js
webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js
webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js
webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.fixtures.js
Introduce TaskProgress and TaskHistory components for real-time task monitoring
  • Implement TaskProgress with optimistic UI updates, progress bar, and report download
  • Add TaskHistory for listing past inventory tasks
  • Wire in API calls, subscription settings, and IoP config hooks
webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js
webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js
Refactor backend tasks: unify generate+upload and replace shell uploader
  • Modify HostInventoryReportJob to plan a single Dynflow sequence and expose report_file_path
  • Remove shell-based QueueForUploadJob and introduce UploadReportDirectJob using HTTP and certificates
  • Update Rake task to call HostInventoryReportJob with upload flag
lib/foreman_inventory_upload/async/host_inventory_report_job.rb
lib/foreman_inventory_upload/async/queue_for_upload_job.rb
lib/foreman_inventory_upload/async/upload_report_direct_job.rb
lib/tasks/rh_cloud_inventory.rake
Update Rails controllers and routes to serve task-centric JSON and new endpoints
  • accounts#index now fetches latest Task records with sub-action statuses and JSON payload
  • reports#generate returns the new task id and humanized action
  • Add /api/tasks/current and /api/tasks/history endpoints in routes and TasksController
app/controllers/foreman_inventory_upload/accounts_controller.rb
app/controllers/foreman_inventory_upload/reports_controller.rb
app/controllers/foreman_inventory_upload/api/tasks_controller.rb
config/routes.rb
Revise and expand test suite for new UI and job logic
  • Adapt TabHeader tests to use ConfigHooks and verify enable/disable scenarios
  • Remove obsolete Dashboard and shell script tests
  • Add comprehensive UploadReportDirectJob tests covering certificates, errors, and cleanup
webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js
test/jobs/upload_report_direct_job_test.rb
webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.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

Simplifies the Dashboard by removing tab navigation and combining
generate/upload into a single unified workflow. Improves TaskProgress
component with optimistic updates, better progress indicators, and
clearer status messages. Adds report file path display with download
capability and fixes task-to-organization linking via action_subject.

Changes:
- Remove separate Upload tab (upload now part of generate task)
- Update accordion status text (Generated/Uploaded vs Generating/Uploading)
- Add report file path display and download button
- Expand first organization accordion by default
- Fix duration display to show actual task seconds
- Improve UI text with sentence case and clearer messaging
- Add optimistic UI updates for better responsiveness

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@jeremylenz jeremylenz force-pushed the redesign/inventory-upload-ui branch from d747765 to 04948bb Compare November 7, 2025 22:05
jeremylenz and others added 3 commits November 10, 2025 14:25
This change ensures that inventory uploads work correctly in all
combinations of IoP mode and subscription connection settings:

Backend changes:
- Modified upload_report_direct_job.rb to allow uploads when in IoP mode,
  regardless of subscription_connection_enabled setting
- Added test to verify uploads proceed in IoP mode even when setting is off

Frontend changes:
- Updated TaskProgress component to disable "Generate and upload report"
  button when IoP is OFF and subscription_connection_enabled is OFF
- Added helpful tooltip explaining why upload is disabled and how to enable
- Maintained TabHeader component compatibility (legacy, may be removed)

Expected behavior:
- IoP OFF + Setting ON: Upload to hosted (unchanged)
- IoP ON + Setting ON: Upload to local HBI (unchanged)
- IoP OFF + Setting OFF: Button disabled with tooltip (fixed)
- IoP ON + Setting OFF: Upload to local HBI (fixed)

Single-host reports continue to upload to local HBI in IoP mode as before.

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…emoval

The rh_cloud_inventory:report:generate_upload task was still calling the
deleted GenerateReportJob. Updated to use HostInventoryReportJob directly,
matching the pattern used in the controller and the generate task.

Parameters:
- base_folder: ForemanInventoryUpload.generated_reports_folder
- organization_id: from organization
- hosts_filter: empty string (all hosts)
- upload: true (upload to cloud/HBI)

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit enhances the inventory upload task progress UI with several
improvements for better user experience:

- Reduce polling interval from 5s to 2s for faster updates
- Trigger immediate poll after starting a task for instant feedback
- Always show Duration and Report saved to fields (even when blank) for
  consistent layout
- Keep action buttons visible at all times, using disabled state instead
  of hiding them to reduce visual jarring
- Fix flashing states during task transitions with optimistic UI that
  persists until new task data arrives
- Convert AccountList from class component to modern function component
  with hooks
- Pass fetchAccountsStatus callback through component tree to enable
  immediate polling

Technical changes:
- Added useEffect to clear lastTaskId when task data updates
- Created areButtonsDisabled logic to prevent button state flashing
- Added RelativeDateTime mock for tests
- Updated snapshots to reflect new component structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@jeremylenz jeremylenz marked this pull request as ready for review November 10, 2025 20:18
@jeremylenz
Copy link
Collaborator Author

@sourcery-ai review

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!

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

## Individual Comments

### Comment 1
<location> `lib/foreman_inventory_upload/async/upload_report_direct_job.rb:105-110` </location>
<code_context>
+      def move_to_done_folder
+        FileUtils.mkdir_p(ForemanInventoryUpload.done_folder)
+        done_file = ForemanInventoryUpload.done_file_path(File.basename(filename))
+        FileUtils.mv(filename, done_file)
+        logger.debug("Moved #{filename} to #{done_file}")
+      end
</code_context>

<issue_to_address>
**suggestion (bug_risk):** FileUtils.mv may overwrite existing files without warning.

Consider checking if the destination file exists before moving, or set the :force option to control overwriting behavior.

```suggestion
      def move_to_done_folder
        FileUtils.mkdir_p(ForemanInventoryUpload.done_folder)
        done_file = ForemanInventoryUpload.done_file_path(File.basename(filename))
        if File.exist?(done_file)
          logger.warn("Destination file #{done_file} already exists. Skipping move to avoid overwrite.")
        else
          FileUtils.mv(filename, done_file)
          logger.debug("Moved #{filename} to #{done_file}")
        end
      end
```
</issue_to_address>

### Comment 2
<location> `webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js:51-59` </location>
<code_context>
+    return null;
+  };
+
+  const formatDuration = seconds => {
+    if (!seconds) return __('N/A');
+    const mins = Math.floor(seconds / 60);
</code_context>

<issue_to_address>
**suggestion:** Duration formatting does not handle negative or non-numeric values.

Validate that 'seconds' is a non-negative number before formatting; otherwise, return 'N/A' or a suitable fallback.

```suggestion
  const formatDuration = seconds => {
    if (
      typeof seconds !== 'number' ||
      isNaN(seconds) ||
      seconds < 0
    ) {
      return __('N/A');
    }
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    if (mins > 0) {
      return `${mins}m ${secs}s`;
    }
    return `${secs}s`;
  };
```
</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.

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 new TaskProgress component is very large and handles multiple concerns (empty states, controls, progress display) — consider splitting it into smaller subcomponents (e.g. header, progress bar, actions) to improve readability and maintainability.
  • AccountsController’s sub_action_status, task_status_string, and task_json methods embed substantial business logic in the controller — extracting that into a dedicated service or decorator would keep the controller focused on orchestration and make testing easier.
  • Both the accounts#index and the new API endpoints join foreman_tasks and links for each organization/task — consider eager loading or caching task data to avoid potential N+1 queries when listing many organizations.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new TaskProgress component is very large and handles multiple concerns (empty states, controls, progress display) — consider splitting it into smaller subcomponents (e.g. header, progress bar, actions) to improve readability and maintainability.
- AccountsController’s sub_action_status, task_status_string, and task_json methods embed substantial business logic in the controller — extracting that into a dedicated service or decorator would keep the controller focused on orchestration and make testing easier.
- Both the accounts#index and the new API endpoints join foreman_tasks and links for each organization/task — consider eager loading or caching task data to avoid potential N+1 queries when listing many organizations.

## Individual Comments

### Comment 1
<location> `webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js:42` </location>
<code_context>
+    return <EmptyState />;
+  }

-    const items = Object.keys(filteredAccount).map((label, index) => {
-      const account = accounts[label];
-      return <ListItem key={index} label={label} account={account} />;
</code_context>

<issue_to_address>
**suggestion:** Using array index as key in list rendering may cause issues if list order changes.

Consider using a unique identifier like account label or id as the React key instead of the array index to avoid rendering issues when the list order changes.

Suggested implementation:

```javascript
  const items = Object.keys(filteredAccount).map((label) => {
    const account = accounts[label];
    return (

```

```javascript
      return <ListItem key={label} label={label} account={account} />;

```
</issue_to_address>

### Comment 2
<location> `webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js:93` </location>
<code_context>
+        ? __('Report generation started')
+        : __('Report generation and upload started');
+
+      window.tfm.toastNotifications.notify({
+        message,
+        type: 'info',
</code_context>

<issue_to_address>
**issue (bug_risk):** Assumes window.tfm.toastNotifications is always available; may cause runtime errors if not.

Consider adding a check to ensure window.tfm.toastNotifications exists before calling notify, to avoid potential runtime errors.
</issue_to_address>

### Comment 3
<location> `webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js:190-198` </location>
<code_context>
+    return null;
+  };
+
+  const formatDuration = seconds => {
+    if (!seconds) return __('N/A');
+    const mins = Math.floor(seconds / 60);
</code_context>

<issue_to_address>
**suggestion:** Duration formatting does not handle null or undefined values robustly.

Explicitly check for null or undefined values instead of using a general falsy check, to avoid treating zero as invalid. Return 'N/A' only for null or undefined.

```suggestion
  const formatDuration = seconds => {
    if (seconds === null || seconds === undefined) return __('N/A');
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    if (mins > 0) {
      return `${mins}m ${secs}s`;
    }
    return `${secs}s`;
  };
```
</issue_to_address>

### Comment 4
<location> `lib/foreman_inventory_upload/async/upload_report_direct_job.rb:80` </location>
<code_context>
+      def upload_file(cer_path)
+        cert_content = File.read(cer_path)
+
+        File.open(filename, 'rb') do |file|
+          # Wrap file with FileUpload class for RestClient multipart handling
+          # RestClient requires objects with :read, :path, and :content_type methods
</code_context>

<issue_to_address>
**issue (bug_risk):** File is moved to done folder after upload; consider error handling for failed uploads.

Currently, files are moved to the done folder even if the upload fails, which could misclassify failed uploads. Please ensure files are only moved after a successful upload, or implement error handling to address this.
</issue_to_address>

### Comment 5
<location> `lib/foreman_inventory_upload/async/upload_report_direct_job.rb:124` </location>
<code_context>
+        }
+      end
+
+      def foreman_certificate
+        {
+          cert: File.read(Setting[:ssl_certificate]),
</code_context>

<issue_to_address>
**issue:** No error handling for missing SSL certificate or key files.

Add error handling to manage cases where the certificate or key files are missing or unreadable, so users receive a clear message or fallback behavior.
</issue_to_address>

### Comment 6
<location> `test/jobs/upload_report_direct_job_test.rb:96-35` </location>
<code_context>
+    assert_equal 'FOREMAN KEY', cert[:key]
+  end
+
+  test 'certificate method returns manifest cert in regular mode' do
+    ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
+
+    action = create_and_plan_action(
+      ForemanInventoryUpload::Async::UploadReportDirectJob,
+      @filename,
+      @organization.id
+    )
+
+    cert = action.send(:certificate)
+    assert_equal 'FAKE CERTIFICATE', cert[:cert]
+    assert_equal 'FAKE KEY', cert[:key]
+  end
+
+  test 'certificate method returns foreman cert in IoP mode' do
</code_context>

<issue_to_address>
**suggestion (testing):** Test for certificate selection logic.

Add a test case for when no certificates are available to ensure the job handles this scenario correctly.
</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.

jeremylenz and others added 10 commits November 10, 2025 15:36
Address individual code review comments:

1. **AccountList.js**: Use unique label as React key instead of array index
   to prevent rendering issues when list order changes

2. **TaskProgress.js & TaskHistory.js**: Improve formatDuration validation
   to explicitly check for null/undefined instead of falsy values,
   preventing edge cases with zero values

3. **upload_report_direct_job.rb**: Add error handling for missing SSL
   certificate/key files with clear error messages

4. **upload_report_direct_job_test.rb**: Add test cases for missing
   certificate scenarios and add File.readable stubs to existing IoP test

Note: File move behavior (Comment 4) is already correct - files are only
moved to done folder after successful upload since upload_file raises
exceptions on failure. The move_to_done_folder call on line 73 only
executes if upload_file (line 70) completes successfully.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Replace global isNaN with Number.isNaN, remove invalid spellcheck
eslint-disable comments, and add missing ouiaId properties to
PatternFly components for accessibility and testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove obsolete test files for deleted GenerateReportJob and
uploads controller :last action. Update tests to match new
controller APIs and fix host ID ordering in playbook test.

Changes:
- Delete test/controllers/uploads_controller_test.rb (obsolete :last action)
- Delete test/jobs/generate_report_job_test.rb (GenerateReportJob deleted)
- Fix accounts_controller_test.rb to use new response structure
- Fix cloud_request_controller_test.rb host ID sorting
- Add route exceptions to rh_cloud_permissions_test.rb

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Convert UploadReportDirectJob and HostInventoryReportJob tests to use proper pattern
- Use create_action + expects(:action_subject) + plan_action instead of create_and_plan_action
- Add resource_locks method returning :link to both jobs to avoid exclusive lock in tests
- Remove obsolete tests for removed features (instance_label, ProgressOutput integration, TaskOutput clearing)
- Update remaining tests to remove references to ProgressOutput which was removed from UploadReportDirectJob

This follows the Katello testing pattern for Dynflow actions and ensures action_subject
is properly mocked without stubbing deep into Dynflow internals.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The :last action and GenerateReportJob were removed in the redesign,
making this test obsolete.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…rectJob

- Convert SingleHostReportJob tests to use proper Katello pattern
- Add FileUtils.mkdir_p to ensure uploads folder exists before touching test files
- SingleHostReportJob inherits resource_locks from HostInventoryReportJob

All tests now pass: 353 tests, 1224 assertions, 0 failures, 0 errors, 0 skips

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add technical terms (auditd, hostid, krb5, rebootable, etc.) to eslint skipWords and wrap code comments containing hex UUIDs with targeted spellcheck disablers to resolve CI linting failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Update Dashboard.test.js: Remove outdated stopPolling test for class component lifecycle, replace with simple functional component test
- Remove obsolete DashboardIntegration.test.js and snapshot: Integration test was incomplete and no longer relevant after Dashboard simplification
- Update AccountList snapshot: Fix key prop values to use label instead of array index (Account1/2/3 vs 0/1/2)

All three previously failing tests now pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Updates GenerateAllReportsJob to use HostInventoryReportJob instead of
the removed GenerateReportJob class. Also updates the Actions history
URL in the UI to search for the correct task class.

This fixes the automatic daily report generation scheduled task and
ensures the task history link shows current tasks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@jeremylenz jeremylenz marked this pull request as draft November 14, 2025 14:00
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.

1 participant