Skip to content

EDM-1433: Add YAML viewer for fleet, device and repository detail pages#278

Merged
celdrake merged 2 commits into
flightctl:mainfrom
celdrake:EDM-1433-read-only-yaml-viewer
May 22, 2025
Merged

EDM-1433: Add YAML viewer for fleet, device and repository detail pages#278
celdrake merged 2 commits into
flightctl:mainfrom
celdrake:EDM-1433-read-only-yaml-viewer

Conversation

@celdrake

@celdrake celdrake commented Apr 15, 2025

Copy link
Copy Markdown
Collaborator

In Standalone mode:
https://github.com/user-attachments/assets/956a616f-b249-4170-b786-ff9960b63778

In OCP console:
https://github.com/user-attachments/assets/b02ab13f-c219-4bb3-8d65-44026ec4628a

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced YAML editor views for Fleet, Device, and Repository details, enabling resource management with reload and download capabilities.
    • Added keyboard and mouse shortcut displays along with a popover for editor shortcuts.
    • Implemented custom console-themed color schemes for the code editor.
  • Improvements

    • Added tabbed navigation to Fleet, Device, and Repository detail pages for seamless view switching.
    • Updated translations with additional UI and editor shortcut strings.
  • Removals

    • Removed device debug information modal and associated UI elements.
  • Style

    • Added and refined styles for shortcut keys, YAML editor interface, and action groups.
  • Chores

    • Added new dependencies supporting code editing and file saving features.

@coderabbitai

coderabbitai Bot commented Apr 15, 2025

Copy link
Copy Markdown
## Walkthrough

This change introduces YAML editor functionality for Fleet, Device, and Repository detail pages by integrating the Monaco code editor and PatternFly React code editor components. New dependencies and type definitions are added to relevant `package.json` files. Several UI components are updated to provide tabbed navigation between details and YAML views, and new components for YAML editing, shortcut display, and custom Monaco themes are implemented. Translation strings are expanded to cover new UI actions and shortcuts. The device status debug modal and associated styles are removed, and routing is adjusted to support nested routes for the new YAML tabs.

## Changes

| File(s)                                                                                         | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|-------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `apps/ocp-plugin/package.json`, `apps/standalone/package.json`, `libs/ui-components/package.json`| Added dependencies for Monaco editor and PatternFly React code editor; added `file-saver` and its types; updated peer dependencies.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts`                        | Added function to define custom Monaco editor themes for light and dark modes.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx`, `Shortcut.css`              | Added new React component and CSS for displaying keyboard/mouse shortcuts in the UI.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx`, `YamlEditorBase.tsx`, `YamlEditor.css` | Added new YAML editor component with version change detection, reload, and download; base editor component for rendering Monaco editor; associated CSS for layout and appearance.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx`                    | Added hook to provide a popover listing editor shortcuts.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx`                     | Renamed and enhanced Fleet details component to support tabbed navigation and YAML editing; updated wrapper component.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx`          | Enhanced repository details component to support tabs and YAML editing via nested routing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx`                  | Removed device status debug modal; added "YAML" tab and route with YAML editor for device details.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.tsx`, `.css`     | Removed device status debug modal component and its CSS.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `libs/ansible/src/hooks/useFctlNavigation.tsx`, `apps/ocp-plugin/src/components/Fleets/FleetDetailsPage.tsx`, `apps/standalone/src/app/routes.tsx` | Updated imports and route paths to use the renamed and enhanced Fleet details page and support nested routing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `libs/i18n/locales/en/translation.json`                                                         | Added and reorganized translation keys for new editor actions, shortcuts, and UI elements; removed obsolete debug info keys.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx`                          | Simplified type annotation of `children` prop in `DetailsPageActions` component.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx`        | Simplified type alias for `CreateRepositoryFormContentProps` by removing unnecessary intersection.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `libs/ui-components/src/components/form/FlightCtlActionGroup.tsx`                              | Simplified type annotation of `children` prop in `FlightCtlActionGroup` component.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant UIComponent
    participant YamlEditor
    participant Backend

    User->>UIComponent: Navigate to Details Page
    UIComponent->>UIComponent: Display tabs (Details, YAML)
    User->>UIComponent: Click "YAML" tab
    UIComponent->>YamlEditor: Render YAML editor with resource
    YamlEditor->>Backend: (optional) Refetch resource on reload
    YamlEditor->>User: Display YAML, allow download/copy/reload

Suggested reviewers

  • rawagner

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> npm warn config production Use `--omit=dev` instead.
> npm error Exit handler never called!
> npm error This is an error with npm itself. Please report this error at:
> npm error   <https://github.com/npm/cli/issues>
> npm error A complete log of this run can be found in: /.npm/_logs/2025-05-22T13_04_49_345Z-debug-0.log
> 
> 
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->

<!-- announcements_start -->

> [!NOTE]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!NOTE]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b55eae2822245403098014bf2316d2b57df28b5c and 5d4e37bb7b44f75586f86c67640d7acf40c72014.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `package-lock.json` is excluded by `!**/package-lock.json`

</details>

<details>
<summary>📒 Files selected for processing (23)</summary>

* `apps/ocp-plugin/package.json` (2 hunks)
* `apps/ocp-plugin/src/components/Fleets/FleetDetailsPage.tsx` (1 hunks)
* `apps/standalone/package.json` (2 hunks)
* `apps/standalone/src/app/routes.tsx` (2 hunks)
* `libs/ansible/src/hooks/useFctlNavigation.tsx` (1 hunks)
* `libs/i18n/locales/en/translation.json` (3 hunks)
* `libs/ui-components/package.json` (2 hunks)
* `libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx` (1 hunks)
* `libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx` (3 hunks)
* `libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.css` (0 hunks)
* `libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.tsx` (0 hunks)
* `libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx` (4 hunks)
* `libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx` (1 hunks)
* `libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx` (3 hunks)
* `libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/Shortcut.css` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/YamlEditor.css` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.css` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx` (1 hunks)
* `libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx` (1 hunks)
* `libs/ui-components/src/components/form/FlightCtlActionGroup.tsx` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (2)</summary>

* libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.css
* libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.tsx

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* libs/ui-components/src/components/form/FlightCtlActionGroup.tsx
* libs/ui-components/src/components/common/CodeEditor/Shortcut.css

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (19)</summary>

* apps/ocp-plugin/src/components/Fleets/FleetDetailsPage.tsx
* libs/ui-components/src/components/common/CodeEditor/YamlEditor.css
* libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.css
* apps/standalone/package.json
* libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx
* libs/ansible/src/hooks/useFctlNavigation.tsx
* apps/ocp-plugin/package.json
* apps/standalone/src/app/routes.tsx
* libs/ui-components/package.json
* libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts
* libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx
* libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx
* libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx
* libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx
* libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx
* libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx
* libs/i18n/locales/en/translation.json
* libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx
* libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: integration-tests

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=flightctl/flightctl-ui&utm_content=278):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@celdrake celdrake force-pushed the EDM-1433-read-only-yaml-viewer branch 2 times, most recently from d5745ac to 3f1d74c Compare April 16, 2025 07:27
@celdrake celdrake marked this pull request as ready for review April 16, 2025 11:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (10)
libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx (1)

7-36: Good implementation of reusable hook for editor shortcuts

The hook is well-structured and provides proper typing with PopoverProps. It effectively encapsulates the popover configuration for keyboard shortcuts that can be reused across the application.

One optimization to consider is memoizing the return value since it creates a new object on every render:

export const useShortcutPopover = (onHideShortcuts?: VoidFunction): PopoverProps => {
  const { t } = useTranslation();

-  return {
+  return React.useMemo(() => ({
    'aria-label': t('View shortcuts'),
    bodyContent: (
      <table>
        <tbody>
          <Shortcut keyName="F1">{t('View all editor shortcuts')}</Shortcut>
          <Shortcut ctrl keyName="space">
            {t('Activate auto complete')}
          </Shortcut>
          <Shortcut ctrl shift keyName="m">
            {t('Toggle Tab action between insert Tab character and move focus out of editor')}
          </Shortcut>
          <Shortcut ctrlCmd shift keyName="o">
            {t('View document outline')}
          </Shortcut>
          <Shortcut hover>{t('View property descriptions')}</Shortcut>
          <Shortcut ctrlCmd keyName="s">
            {t('Save')}
          </Shortcut>
        </tbody>
      </table>
    ),
    maxWidth: '25rem',
    distance: 18,
    onHide: onHideShortcuts,
-  };
+  }), [t, onHideShortcuts]);
};
libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts (1)

5-19: Consider using a more structured approach for color constants

The current color constants use a flat naming convention. Consider organizing them into a color object or using PatternFly's existing token system once available (as noted in the TODO).

// TODO import from PF6 react-tokens. These are not available in PF5
-const t_color_green_70 = '#204d00';
-const t_color_white = '#ffffff';
-const t_color_black = '#000000';
-const t_color_gray_60 = '#4d4d4d';
-const t_color_yellow_70 = '#73480b';
-const t_color_yellow_30 = '#ffcc17';
-const t_color_green_30 = '#afdc8f';
-const t_color_blue_30 = '#92c5f9';
-const t_color_blue_70 = '#003366';
-const t_color_purple_70 = '#21134d';
-const t_color_purple_30 = '#b6a6e9';
-const t_color_gray_90 = '#1f1f1f';
-const t_color_gray_20 = '#e0e0e0';
+const colors = {
+  green: {
+    30: '#afdc8f',
+    70: '#204d00'
+  },
+  white: '#ffffff',
+  black: '#000000',
+  gray: {
+    20: '#e0e0e0',
+    60: '#4d4d4d',
+    90: '#1f1f1f'
+  },
+  yellow: {
+    30: '#ffcc17',
+    70: '#73480b'
+  },
+  blue: {
+    30: '#92c5f9',
+    70: '#003366'
+  },
+  purple: {
+    30: '#b6a6e9',
+    70: '#21134d'
+  }
+};

This would make the theme definition more readable and maintainable going forward.

libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx (2)

2-2: Imports need to be reorganized for better structure

The imports on line 2 mix both UI components and routing components. Consider reorganizing the imports for better readability.

-import { DropdownItem, DropdownList, Grid, GridItem, Nav, NavList } from '@patternfly/react-core';
+import { 
+  DropdownItem, 
+  DropdownList, 
+  Grid, 
+  GridItem, 
+  Nav, 
+  NavList 
+} from '@patternfly/react-core';

78-99: Well-structured routing setup for the tabs

The routing configuration is well-implemented with a default redirect to the details tab for backward compatibility. The route structure follows React Router best practices.

One consideration: you might want to add error handling for the YAML editor in case the API object is malformed or doesn't meet expected format:

<Route
  path="yaml"
-  element={<YamlEditor filename={repositoryId} apiObj={repoDetails} refetch={refetch} />}
+  element={
+    <YamlEditor 
+      filename={repositoryId} 
+      apiObj={repoDetails} 
+      refetch={refetch}
+      onError={(error) => console.error('YAML editor error:', error)} 
+    />
+  }
/>
libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx (2)

23-27: Add TypeScript typing for ShortcutCommand component

The ShortcutCommand component is missing proper TypeScript typing for its props.

-export const ShortcutCommand = ({ children }) => (
+export const ShortcutCommand: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <span className="fctl-shortcut__command">
    <kbd className="fctl-shortcut__command-key">{children}</kbd>
  </span>
);

46-85: Table structure doesn't include accessibility elements

The table structure is missing proper accessibility elements for screen readers.

Consider adding proper table structure elements:

  return (
-    <tr>
-      <td className="fctl-shortcut__cell">
+    <tr role="row">
+      <td className="fctl-shortcut__cell" role="cell">
       {/* shortcut content */}
      </td>
-      <td className="fctl-shortcut__cell">{children}</td>
+      <td className="fctl-shortcut__cell" role="cell">{children}</td>
    </tr>
  );
libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx (2)

36-42: Add error handling for download failures

The download YAML function doesn't include error handling for potential failures.

const downloadYaml = () => {
  const resource = editorRef.current?.getValue();
  if (resource) {
+   try {
      const blob = new Blob([resource], { type: 'text/yaml;charset=utf-8' });
      saveAs(blob, `${filename}.yaml`);
+   } catch (error) {
+     console.error('Failed to download YAML:', error);
+     // Consider adding user notification for download failure
+   }
  }
};

86-86: Avoid inline styles with CSS variables

Using inline styles with CSS variables can be fragile. Consider moving this to a CSS class.

-<ActionGroup style={{ '--pf-v5-c-form__group--m-action--MarginTop': 0 } as React.CSSProperties}>
+<ActionGroup className="fctl-yaml-editor-action-group">

And add to your CSS file:

.fctl-yaml-editor-action-group {
  --pf-v5-c-form__group--m-action--MarginTop: 0;
}
libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx (2)

51-56: Add type assertion for resource version comparison

The comparison of resourceVersion might lead to unexpected behavior if values are undefined.

-  hasChanged = resourceVersion !== apiObj.metadata.resourceVersion;
+  hasChanged = resourceVersion !== (apiObj.metadata.resourceVersion || '0');

87-89: Avoid hardcoded navigation path

The navigation path ../ is hardcoded, which may be brittle if routing structure changes.

Consider using a more explicit navigation approach:

onCancel={() => {
-  navigate('../.');
+  // Navigate to parent route more explicitly, e.g.:
+  const parentPath = window.location.pathname.split('/').slice(0, -1).join('/');
+  navigate(parentPath || '.');
}}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 972a77a and 3f1d74c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • apps/ocp-plugin/package.json (2 hunks)
  • apps/ocp-plugin/src/components/Fleets/FleetDetailsPage.tsx (1 hunks)
  • apps/standalone/package.json (2 hunks)
  • apps/standalone/src/app/routes.tsx (2 hunks)
  • libs/ansible/src/hooks/useFctlNavigation.tsx (1 hunks)
  • libs/i18n/locales/en/translation.json (3 hunks)
  • libs/ui-components/package.json (2 hunks)
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx (3 hunks)
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.css (0 hunks)
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.tsx (0 hunks)
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx (4 hunks)
  • libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx (3 hunks)
  • libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/Shortcut.css (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/YamlEditor.css (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.css
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceStatusDebugModal.tsx
🧰 Additional context used
🧬 Code Graph Analysis (4)
libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx (2)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts (1)
  • defineConsoleThemes (21-53)
libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx (4)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useAppContext.tsx (1)
  • useAppContext (119-119)
libs/ui-components/src/hooks/useFetchPeriodically.ts (1)
  • useFetchPeriodically (9-86)
libs/types/index.ts (1)
  • RepoSpecType (104-104)
libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
🔇 Additional comments (30)
apps/ocp-plugin/package.json (1)

51-51: Monaco and PatternFly code editor dependencies added correctly.

The new dependencies for Monaco and PatternFly code editor are appropriate and necessary for YAML editing features. No issues found.

Also applies to: 77-78

libs/ui-components/src/components/common/CodeEditor/Shortcut.css (1)

1-22: Shortcut key styling is clear and consistent.

The CSS for shortcut key display is well-structured and uses PatternFly variables for consistency. No issues found.

apps/ocp-plugin/src/components/Fleets/FleetDetailsPage.tsx (1)

2-2: Import path updated for refactored FleetDetailsPage.

The import now correctly points to the new FleetDetailsPage component with tabbed navigation. No issues found.

libs/ui-components/src/components/common/CodeEditor/YamlEditor.css (1)

1-8: YAML editor styling is appropriate and theme-consistent.

The CSS for the YAML editor is minimal, targeted, and uses theme variables. No issues found.

libs/ansible/src/hooks/useFctlNavigation.tsx (1)

40-40: Import path updated for refactored FleetDetailsPage.

The import now correctly points to the new FleetDetailsPage component with tabbed navigation. No issues found.

apps/standalone/src/app/routes.tsx (2)

61-61: Import path updated correctly for FleetDetailsPage component.

The import path now correctly points to the FleetDetailsPage component, aligning with the component renaming in the ui-components library.


204-204: Route path correctly updated to support nested routes.

The path change from :fleetId to :fleetId/* properly enables nested routing, which is essential for the new tabbed interface with YAML viewer functionality. This change maintains consistency with similar device and repository route patterns.

apps/standalone/package.json (2)

29-29: Monaco editor webpack plugin correctly added as a dev dependency.

The monaco-editor-webpack-plugin is necessary for properly bundling Monaco editor in a webpack-based build process. Version 7.1.0 is appropriate for the Monaco editor version being used.


54-55: Required dependencies added for code editing functionality.

The added dependencies (monaco-editor and @patternfly/react-code-editor) are essential for implementing the YAML viewer feature mentioned in the PR objectives. These versions are compatible with the other PatternFly components used in the project.

libs/ui-components/package.json (2)

28-28: File-saving dependencies added for YAML download functionality.

The file-saver library and its TypeScript type definitions will allow users to download YAML content, enhancing the editor functionality.

Also applies to: 35-35


53-55: Required peer dependencies correctly specified.

The updated peer dependencies ensure that consuming applications provide compatible versions of React Router DOM, Monaco Editor, and PatternFly React Code Editor components, which are needed for the new YAML editing functionality.

libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx (4)

20-20: YamlEditor component imported correctly.

The import statement correctly references the YamlEditor component from the common CodeEditor directory.


95-95: YAML tab added to navigation.

The YAML tab has been appropriately added to the tertiary navigation alongside existing tabs, providing users with access to the new YAML editing functionality.


135-138: YAML route and editor implemented correctly.

The YAML route is properly configured to render the YamlEditor component with the necessary props:

  • filename: Uses device alias or ID for identification
  • apiObj: Passes the device object for rendering as YAML
  • refetch: Provides callback to refresh data after edits

This implementation ensures the YAML editor has all required context for proper operation.


1-158:

Details

✅ Verification successful

Verify the absence of debug functionality is intended.

The removal of the DeviceStatusDebugModal and related functionality appears to be replacing debug information viewing with the more comprehensive YAML editor. Please confirm this is the intended behavior and that no debug capabilities are lost with this change.


🏁 Script executed:

#!/bin/bash
# Search for references to DeviceStatusDebugModal in the entire codebase
rg "DeviceStatusDebugModal" 

Length of output: 27


Debug functionality removal verified – YAML editor now serves as the debug interface.

  • A repository-wide search for DeviceStatusDebugModal returned no results, confirming its removal.
  • The current implementation leverages the YAML editor to display device configuration details, which can serve as a consolidated debugging view.

Please confirm that replacing the debug modal with the YAML editor is the desired design change and that no additional debug features are required.

libs/ui-components/src/components/common/CodeEditor/CodeEditorTheme.ts (1)

21-53: LGTM! Well-structured theme definitions for Monaco editor

The console light and dark themes are well-defined and follow Monaco's theming API correctly. The color assignments are appropriately different between light and dark themes to maintain proper contrast and readability.

libs/i18n/locales/en/translation.json (3)

40-63: Good addition of translation strings for the new YAML editor features

The new translation strings are comprehensive and follow the existing pattern, covering keyboard shortcuts, editor actions, and YAML-specific terminology.


126-126: Good addition of YAML translation string for the new tab

This translation string correctly supports the new YAML tab added to the UI.


407-408: LGTM! Added translation strings for resource sync behavior

These strings provide clear messaging about fleets that are managed by resource syncs.

libs/ui-components/src/components/Repository/RepositoryDetails/RepositoryDetails.tsx (4)

18-19: LGTM! Good addition of required components for the tabbed interface

The NavItem and YamlEditor imports are correctly added to support the new tabbed interface with YAML editing capability.


24-24: LGTM! Updated to use React Router hooks

Good use of modern React Router hooks from the application context.


27-27: Correct extraction of the refetch function for the YAML editor

You've properly extracted the refetch function from useFetchPeriodically to pass to the YamlEditor component, allowing the YAML view to refresh data after changes.


67-74: Good implementation of tabbed navigation

The tertiary navigation implementation follows PatternFly's navigation patterns correctly, providing a clean way to switch between details and YAML views.

libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx (3)

47-54: LGTM - Consistent tabbed navigation implementation

The tabbed navigation pattern with tertiary nav matches the PatternFly design guidelines and is consistent with other detail pages in the application.


131-131: Component reference updated correctly

The wrapper component has been updated to reference the renamed FleetDetailPage component.


101-108:

Details

❓ Verification inconclusive

Verify resource reload propagation

The YAML editor is properly configured with the fleet object and refetch callback. When changes are made in the YAML editor, ensure that the main view is also updated to reflect these changes.


🏁 Script executed:

#!/bin/bash
# Check if FleetDetailsContent is also updated when refetch is called
echo "Checking how FleetDetailsContent handles updates:"
rg -A 10 -B 10 "FleetDetailsContent" --type tsx

Length of output: 184


Action: Manual Verification of FleetDetailsPage Refetch Flow

The routing in FleetDetailsPage.tsx correctly passes the refetch callback to the YAML editor, and the fleet object is provided as expected. However, our automated search did not yield clear evidence that the FleetDetailsContent component updates when refetch is called. Please verify manually that:

  • FleetDetailsContent Updates: When changes are made in the YAML editor (triggering refetch), the main view (i.e., FleetDetailsContent) properly reloads its data.
  • Data Propagation: The updated fleet object is correctly reflected in the main view after YAML modifications.

If necessary, consider adding temporary logging or state checks within FleetDetailsContent to confirm that it reacts appropriately to updates.

libs/ui-components/src/components/common/CodeEditor/Shortcut.tsx (2)

7-7:

Details

✅ Verification successful

Verify CSS file existence

Ensure that the referenced CSS file exists and contains the necessary styles for the Shortcut component.


🏁 Script executed:

#!/bin/bash
# Check if the CSS file exists
if [ -f "libs/ui-components/src/components/common/CodeEditor/Shortcut.css" ]; then
  echo "CSS file exists"
  cat "libs/ui-components/src/components/common/CodeEditor/Shortcut.css"
else
  echo "CSS file doesn't exist!"
fi

Length of output: 747


Shortcut CSS file verified

The CSS file at libs/ui-components/src/components/common/CodeEditor/Shortcut.css exists and contains the necessary styling for the Shortcut component. No further changes are needed.


29-29:

Details

❓ Verification inconclusive

Replace deprecated platform detection method

The window.navigator.platform property is deprecated. Consider using a more modern approach for platform detection.

-export const isMac = window.navigator.platform.includes('Mac');
+export const isMac = navigator.userAgent.includes('Mac') || navigator.userAgent.includes('Macintosh');

Alternatively, consider using a more robust platform detection library:

-export const isMac = window.navigator.platform.includes('Mac');
+// You could add 'platform' npm package as a dependency
+import platform from 'platform';
+export const isMac = platform.os?.family?.includes('Mac') || platform.os?.family?.includes('OS X');

🌐 Web query:

Is window.navigator.platform deprecated and what should be used instead?

💡 Result:

Yes, window.navigator.platform (also accessible as navigator.platform) is deprecated and should generally be avoided for modern web development[1][4][5][7]. This API once provided a string identifying the user's operating system (e.g., "Win32", "MacIntel", "Linux x86_64"), but has been removed from the latest web standards and may cease to work at any time in the future[1][4][5][7].

Why Is It Deprecated?

  • Browsers are deprecating navigator.platform due to privacy concerns and its unreliability—modern browsers may provide misleading or spoofed values, and some may always return an empty string[1][7].

Alternatives and Best Practices

1. Feature Detection

  • The widely recommended alternative is to use feature detection instead of trying to infer the user's environment. This means testing directly for the capabilities your code needs (e.g., testing for touch support, specific APIs, etc.), rather than guessing based on the platform string[1][9].

2. User-Agent Client Hints

  • The emerging standard in browsers like Chromium is User-Agent Client Hints (navigator.userAgentData), which provide structured and permission-controlled access to user agent information[8][9].
  • However, these are not universally supported yet—Firefox and Safari are still missing implementation—so you cannot rely on them for cross-browser compatibility as of 2025[8][9].

3. User-Agent String

  • The traditional user agent string (navigator.userAgent) is also being deprecated and reduced in detail by modern browsers for privacy reasons, so it is not a futureproof solution[3][8][9].

Summary Table

API Status Recommended Use
navigator.platform Deprecated Avoid, unreliable
navigator.userAgent Deprecated Avoid, unreliable
navigator.appVersion Deprecated Avoid, unreliable
navigator.userAgentData Emerging Use (Chromium only, limited cross-browser support)
Feature Detection Supported Preferred

Guidance

Avoid using navigator.platform and similar OS-detection properties. Where practical, use feature detection to adapt your web application to different environments. For cases where platform identification is absolutely necessary, consider using navigator.userAgentData if you are targeting browsers that support it, but be aware of its limited compatibility at this time[1][8][9].

In short: window.navigator.platform is deprecated. Use feature detection instead, and explore navigator.userAgentData for platforms that support it, but be aware of current browser compatibility limitations[1][8][9].

Citations:


Action: Update OS detection logic due to deprecation of navigator.platform

The current usage of window.navigator.platform is deprecated because it’s unreliable and may be removed in the future. Instead of relying solely on this property or even falling back to navigator.userAgent—which is also subject to deprecation and privacy limitations—we recommend a twofold approach:

  • Primary Option (if browser support permits): Use the emerging navigator.userAgentData API for detecting the platform in a more structured and privacy-conscious manner.
  • Fallback Option: Fall back to checking navigator.userAgent for Mac-related substrings only if userAgentData isn’t available.

Below are revised diff snippets that illustrate these updated approaches:

-export const isMac = window.navigator.platform.includes('Mac');
+// Primary: Use navigator.userAgentData if available; fallback to userAgent
+export const isMac = (navigator.userAgentData?.platform === 'macOS')
+  || navigator.userAgent.includes('Mac')
+  || navigator.userAgent.includes('Macintosh');

Alternatively, if you prefer a more robust, library-driven solution, consider using the platform package:

-export const isMac = window.navigator.platform.includes('Mac');
+import platform from 'platform';
+export const isMac = platform.os?.family?.includes('Mac') 
+  || platform.os?.family?.includes('OS X');

Notes:

  • Browser Compatibility: The navigator.userAgentData API is currently supported in Chromium-based browsers. For Firefox, Safari, and others, the fallback to navigator.userAgent is essential.
  • Feature Detection Consideration: If the only goal is to adapt UI behavior (e.g., for shortcut keys), consider whether directly detecting the OS is required or if feature detection might serve as a more future-proof alternative.

Please verify these changes in your targeted browsers to ensure consistent behavior across environments.

libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx (1)

57-85: LGTM - Well-structured editor implementation

The CodeEditor component is well-configured with appropriate translations, code manipulation, and theming. The editor mounting logic properly initializes Monaco with custom themes.

libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx (1)

68-80: Well-implemented window resize handling

The resize handling for Monaco editor is properly implemented with React's useEffect and useCallback hooks to ensure the editor maintains proper dimensions when the window size changes.

import YamlEditor from '../../common/CodeEditor/YamlEditor';

const FleetDetails = () => {
const FleetDetailPage = () => {

@coderabbitai coderabbitai Bot Apr 16, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Component rename detected

The component name changed from FleetDetails to FleetDetailPage. This improves consistency with the naming pattern seen in other detail pages, but make sure to update all imports and references throughout the codebase.


🏁 Script executed:

#!/bin/bash
# Check if there are any references to the old component name that might need updating
echo "Searching for potential missed references to the old component name:"
rg "FleetDetails(?!Page|Content|WithPermissions)" --type ts --type tsx

Length of output: 479


Below is a revised verification script that uses PCRE2 to properly handle the look-ahead syntax. Please run this updated script to re-check for any lingering references to "FleetDetails" (excluding those already updated to Page, Content, or WithPermissions):

Once you run this script, verify that no results are returned. If the output remains empty, it confirms that all references have been updated accordingly.

Next steps:

  • If any results appear, please update the imports/references accordingly.
  • If no results are found, you can safely consider the component rename change complete.

Note: The original review comment regarding the need to update all references still applies until this verification is confirmed.


🏁 Script executed:

#!/bin/bash
echo "Searching for potential references to the old component name using PCRE2 regex:"
rg --pcre2 "FleetDetails(?!Page|Content|WithPermissions)" --type ts --type tsx .

Length of output: 401


Below is an updated verification script that searches TS and TSX files by explicitly using glob patterns to avoid the “unrecognized file type” error. Please run the following script to verify that there are no lingering references to "FleetDetails" (except those meant to match the exceptions):

If the script returns no output, it indicates that all references to the old name have been updated. Otherwise, please update any output results accordingly.

Component Rename Verification:

  • The component has been correctly renamed from FleetDetails to FleetDetailPage.
  • All imports and references should be updated consistently across the codebase.
  • Run the above script to ensure that no obsolete references remain.

🏁 Script executed:

#!/bin/bash
echo "Searching for potential references to the old component name in TS and TSX files using PCRE2 regex:"
rg --pcre2 "FleetDetails(?!Page|Content|WithPermissions)" -g "*.ts" -g "*.tsx" .

Length of output: 1667


Action: Update All Imports of the Renamed Component

The file libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx now defines the component as FleetDetailPage, which aligns with our naming conventions. However, the following files still reference the old name (FleetDetails):

  • libs/ansible/src/hooks/useFctlNavigation.tsx:

    • Lazy-loaded import and JSX usage still reference <FleetDetails />.
  • apps/standalone/src/app/routes.tsx:

    • The component is imported and used as <FleetDetails />.
  • apps/ocp-plugin/src/components/Fleets/FleetDetailsPage.tsx:

    • Similarly, the import and usage are still using the old name.

Please update these references to use FleetDetailPage to maintain consistency across the codebase.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The current name aligns to how we're importing and using the Devices equivalent components.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's look at the Device details component files to see their naming structure
echo "===== Device Component Structure ====="
find libs/ui-components/src/components/Device -name "*Detail*" -type f | sort

# Check the content of DeviceDetailsPage.tsx to see the component name
echo -e "\n===== DeviceDetailsPage.tsx Component Name ====="
grep -n "^const DeviceDetails" libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx

# Check how DeviceDetailsPage is imported in other files
echo -e "\n===== Import Patterns for Device Components ====="
rg "import.*from.*Device\/DeviceDetails" --type ts --type tsx

# Look for import paths that include DeviceDetailsPage
echo -e "\n===== Import for DeviceDetailsPage ====="
rg "import.*DeviceDetailsPage" --type ts --type tsx

Length of output: 1267


🏁 Script executed:

#!/bin/bash
# Let's check the actual component names more precisely
echo "===== DeviceDetailsPage Component Name ====="
grep -A 3 "^const DeviceDetails" libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx

echo -e "\n===== FleetDetailPage Component Name ====="
grep -A 3 "^const FleetDetail" libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx

# Let's check how these components are imported
echo -e "\n===== DeviceDetailsPage Imports ====="
rg "import.*DeviceDetails.*from" -g "*.ts" -g "*.tsx"

echo -e "\n===== FleetDetails/FleetDetailPage Imports ====="
rg "import.*(?:FleetDetails|FleetDetailPage).*from" -g "*.ts" -g "*.tsx" --pcre2

# Let's check file exports
echo -e "\n===== DeviceDetailsPage Export ====="
grep -A 2 "export default" libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx

echo -e "\n===== FleetDetailsPage Export ====="
grep -A 2 "export default" libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx

Length of output: 2613


@celdrake I see what you mean now. You're absolutely right. Looking at the codebase patterns:

  • DeviceDetailsPage.tsx contains DeviceDetailsPage component but is imported as DeviceDetails in other files
  • FleetDetailsPage.tsx contains FleetDetailPage component but is imported as FleetDetails in other files

This follows a consistent pattern where components are imported without the "Page" suffix regardless of their internal name. Thanks for clarifying this pattern - the internal component rename doesn't require changing all imports to maintain consistency with your existing conventions.


🧬 Code Graph Analysis Results

Relevant snippets for libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx


From libs/ui-components/src/hooks/useAppContext.tsx (line 119)

export const useAppContext = () => React.useContext(AppContext);
  • Provides access to the global app context, including router, fetch, i18n, and other shared resources used in FleetDetailsPage.tsx.

From libs/ui-components/src/hooks/useTranslation.ts (lines 5-8)

export const useTranslation = () => {
  const { i18n } = useAppContext();
  return useI18nTranslation(i18n.transNamespace);
};
  • Hook used for internationalization (i18n) in the component, returning a translation function t used for UI strings.

From libs/ui-components/src/hooks/useNavigate.tsx (lines 43-66)

export const useNavigate = () => {
  const {
    router: { useNavigate: useRouterNavigate, appRoutes },
  } = useAppContext();

  const navigate = useRouterNavigate();

  return React.useCallback<NavigateFunction>(
    (to: Route | number | ToObj) => {
      if (typeof to === 'number') {
        navigate(to);
      } else {
        if (toParamIsToObj(to)) {
          const route = appRoutes[to.route];
          navigate(`${route}/${to.postfix}`);
        } else {
          const route = appRoutes[to];
          navigate(route);
        }
      }
    },
    [navigate, appRoutes],
  );
};
  • Custom navigation hook wrapping router navigation, used in the component for programmatic route changes, e.g., navigating to edit or fleet list pages.

From libs/ui-components/src/hooks/useAccessReview.ts (lines 8-33)

export const useAccessReview = (kind: RESOURCE, verb: VERB): AccessReviewResult => {
  const [isLoading, setIsLoading] = React.useState(true);
  const [error, setError] = React.useState<string>();
  const [isAllowed, setIsAllowed] = React.useState(false);

  const {
    fetch: { checkPermissions },
  } = useAppContext();
  React.useEffect(() => {
    const doItAsync = async () => {
      setIsLoading(true);
      try {
        const allowed = await checkPermissions(kind, verb);
        setIsAllowed(allowed);
      } catch (err) {
        setError(getErrorMessage(err));
        setIsAllowed(false);
      } finally {
        setIsLoading(false);
      }
    };
    doItAsync();
  }, [kind, verb, checkPermissions]);

  return [isAllowed, isLoading, error];
};
  • Hook to check user permissions for a given resource and verb, used in the component to conditionally render edit/delete actions.

From libs/ui-components/src/hooks/useFetchPeriodically.ts (lines 9-86)

export const useFetchPeriodically = <R>(
  query: FlightControlQuery,
  onFetchComplete?: (data: R) => void,
): [R | undefined, boolean, unknown, VoidFunction, boolean] => {
  const [isLoading, setIsLoading] = React.useState(true);
  const [isUpdating, setIsUpdating] = React.useState(false);
  const [data, setData] = React.useState<R>();
  const [error, setError] = React.useState<unknown>();
  const [forceUpdate, setForceUpdate] = React.useState(0);
  const ref = React.useRef(0);
  const prevResolvedQueryHash = React.useRef<string>();

  const { get } = useFetch();

  // When the query parameters change, the hash will too. We must perform a refetch outside the timeout loop
  const queryStringHash = getQueryStringHash(query);

  // Callback that generates the updated query for refreshes. It may provide updated values for parameters such as from/to for metric queries
  const getRequestQuery = React.useCallback(() => getRequestQueryString(query), [query]);

  React.useEffect(() => {
    let abortController: AbortController;

    const fetchPeriodically = async (id: number) => {
      while (ref.current === id) {
        const requestQuery = getRequestQuery();
        if (requestQuery) {
          try {
            abortController = new AbortController();
            if (id > 0 && prevResolvedQueryHash.current !== queryStringHash) {
              setIsUpdating(true);
            }

            const data = (await get<R>(requestQuery, abortController.signal)) as R;
            if (isLoading) {
              setIsLoading(false);
            }
            setIsUpdating(false);

            if (onFetchComplete) {
              onFetchComplete(data); // Data might be mutated at this point
              setData(data);
            } else {
              setData(data);
            }
            setError(undefined);
          } catch (err) {
            // aborting fetch trows 'AbortError', we can ignore it
            if (abortController.signal.aborted) {
              return;
            }
            setError(err);
            setIsLoading(false);
            setIsUpdating(false);
          }
        } else {
          setIsLoading(false);
          setError(undefined);
          setData(undefined);
        }
        prevResolvedQueryHash.current = queryStringHash;
        await new Promise((resolve) => setTimeout(resolve, TIMEOUT));
      }
    };

    fetchPeriodically(ref.current);
    return () => {
      // eslint-disable-next-line
      ref.current++;
      abortController?.abort();
    };
    // eslint-disable-next-line
  }, [get, forceUpdate, queryStringHash]);

  const refetch = React.useCallback(() => setForceUpdate((val) => val + 1), []);

  return [data, isLoading, error, refetch, isUpdating];
};
  • Hook used to fetch fleet data periodically from the API endpoint fleets/${fleetId}?addDevicesSummary=true.
  • Returns data, loading state, error, a refetch function, and updating state.
  • Used in the component to keep fleet data fresh and to trigger refetch after deletion.

These snippets provide context on hooks and utilities used in FleetDetailsPage.tsx for routing, permissions, data fetching, and translation, which are relevant to understanding the component's structure and behavior.

Comment on lines +13 to +14
// TODO add useShortcutPopover when adding saving capabilities to the YAML editor
// import { useShortcutPopover } from './useShortcutPopover';

@coderabbitai coderabbitai Bot Apr 16, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Address TODO comment before merging

There's a TODO comment about adding useShortcutPopover when adding saving capabilities to the YAML editor. Consider addressing this now or creating a follow-up task.


🏁 Script executed:

#!/bin/bash
# Check if useShortcutPopover hook is implemented
if [ -f "libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx" ]; then
  echo "useShortcutPopover hook exists:"
  head -n 20 "libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx"
  echo "..."
else
  echo "useShortcutPopover hook doesn't exist yet!"
fi

Length of output: 988


Action Required: Update the TODO in YamlEditorBase.tsx

The useShortcutPopover hook now exists in the codebase (see libs/ui-components/src/components/common/CodeEditor/useShortcutPopover.tsx). Please update the TODO in YamlEditorBase.tsx accordingly. If saving capabilities aren’t ready for integration, either remove the commented-out import or create a follow-up task to integrate the hook once the feature is enabled.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll remove the commented import.
The idea is that we'll add it back when the editor allows to make modifications, as the shortcuts refer to the editing actions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@celdrake That approach makes perfect sense. Since the YAML editor is currently in read-only mode, the shortcuts from useShortcutPopover wouldn't be applicable yet. Removing the commented import and TODO now, then adding it back when implementing editing capabilities is a clean solution.


✏️ Learnings added
Learnt from: celdrake
PR: flightctl/flightctl-ui#278
File: libs/ui-components/src/components/common/CodeEditor/YamlEditorBase.tsx:13-14
Timestamp: 2025-04-16T12:05:30.695Z
Learning: The YAML editor component is intentionally configured as read-only in the current implementation. The useShortcutPopover hook should only be added when editing capabilities are implemented, as the shortcuts are specifically for editing actions.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +25 to +34
const convertObjToYAMLString = (obj: FlightCtlYamlResource) => {
let yaml = '';
if (obj) {
try {
yaml = dump(obj, { lineWidth: -1 });
} catch (e) {
yaml = 'Error';
}
}
return yaml;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling for YAML conversion

The current implementation simply returns "Error" when YAML conversion fails. Consider improving error handling to provide more context.

const convertObjToYAMLString = (obj: FlightCtlYamlResource) => {
  let yaml = '';
  if (obj) {
    try {
      yaml = dump(obj, { lineWidth: -1 });
    } catch (e) {
-     yaml = 'Error';
+     console.error('Failed to convert object to YAML:', e);
+     yaml = `# Error converting object to YAML\n# ${e instanceof Error ? e.message : String(e)}`;
    }
  }
  return yaml;
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const convertObjToYAMLString = (obj: FlightCtlYamlResource) => {
let yaml = '';
if (obj) {
try {
yaml = dump(obj, { lineWidth: -1 });
} catch (e) {
yaml = 'Error';
}
}
return yaml;
};
const convertObjToYAMLString = (obj: FlightCtlYamlResource) => {
let yaml = '';
if (obj) {
try {
yaml = dump(obj, { lineWidth: -1 });
} catch (e) {
console.error('Failed to convert object to YAML:', e);
yaml = `# Error converting object to YAML\n# ${e instanceof Error ? e.message : String(e)}`;
}
}
return yaml;
};

@celdrake celdrake force-pushed the EDM-1433-read-only-yaml-viewer branch from 3f1d74c to 35b0e69 Compare April 16, 2025 12:06
@celdrake celdrake marked this pull request as draft April 30, 2025 06:42
@celdrake celdrake marked this pull request as ready for review May 7, 2025 13:44
Comment thread libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx Outdated
Comment thread libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx Outdated
@rawagner

Copy link
Copy Markdown
Collaborator

We should also check permissions if user can edit the resource. Otherwise show read-only editor

@celdrake

Copy link
Copy Markdown
Collaborator Author

@rawagner The YAML is read-only ATM, because for Devices the updates (of internal "resourceVersion") happen so fast that it's literally impossible to modify a device. It would be frustrating for the user to attempt to edit/reload etc and we need to address this first.

@celdrake celdrake force-pushed the EDM-1433-read-only-yaml-viewer branch from 56b67d4 to b55eae2 Compare May 20, 2025 07:28
@celdrake celdrake force-pushed the EDM-1433-read-only-yaml-viewer branch from b55eae2 to 5d4e37b Compare May 22, 2025 13:03
@celdrake celdrake requested a review from rawagner May 22, 2025 13:05
@celdrake celdrake merged commit b435541 into flightctl:main May 22, 2025
6 checks passed
@celdrake celdrake deleted the EDM-1433-read-only-yaml-viewer branch May 22, 2025 13:18
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