Skip to content

Create frontend plugin router-companion-android#445

Closed
rm3l wants to merge 1 commit intomainfrom
test-rhdh-backend-plugin-scaffolder
Closed

Create frontend plugin router-companion-android#445
rm3l wants to merge 1 commit intomainfrom
test-rhdh-backend-plugin-scaffolder

Conversation

@rm3l
Copy link
Copy Markdown
Member

@rm3l rm3l commented Sep 9, 2025

This pull request creates the skeleton for your frontend plugin

Summary by Sourcery

Scaffold a new Frontend plugin “router-companion-android” including plugin registration, routing, sample UI components, example data fetching, docs, and development setup

New Features:

  • Create plugin entrypoints with createPlugin, routable extension for PluginPage and component extension for PluginIcon
  • Implement ExampleComponent layout and ExampleFetchComponent that retrieves GitHub users and displays them in a Material-UI table with chips
  • Add Router, routes, and utility getChipStyle for consistent theming
  • Provide a dev-only index for local plugin iteration with TestApiProvider and discoveryApi mock

Build:

  • Add package.json with Backstage CLI scripts, dependencies, peerDependencies, and plugin metadata

Documentation:

  • Add README with installation, development, integration, and proxy configuration instructions
  • Add catalog-info.yaml for Backstage component registration

Tests:

  • Include placeholder test files for components and plugin entrypoint

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Sep 9, 2025

Reviewer's Guide

This PR scaffolds a new Backstage frontend plugin named 'router-companion-android', providing boilerplate setup, routing, dev tooling, core components for data fetching and display, utility styling, and documentation.

Sequence diagram for fetching and displaying GitHub users in ExampleFetchComponent

sequenceDiagram
  participant User
  participant ExampleFetchComponent
  participant fetchApi
  participant discoveryApi
  participant Table
  User->>ExampleFetchComponent: Visit plugin page
  ExampleFetchComponent->>discoveryApi: getBaseUrl('proxy')
  discoveryApi-->>ExampleFetchComponent: Return proxy URL
  ExampleFetchComponent->>fetchApi: fetch(proxyURL + '/github/users')
  fetchApi-->>ExampleFetchComponent: Return response
  ExampleFetchComponent->>ExampleFetchComponent: Parse response as JSON
  ExampleFetchComponent->>Table: Render user data
  Table-->>User: Display GitHub users
Loading

Class diagram for new components in router-companion-android plugin

classDiagram
  class ExampleComponent {
    +render()
  }
  class ExampleFetchComponent {
    +render()
  }
  class DenseTable {
    +render(users)
  }
  class ExampleComponentIcon {
    +render()
  }
  class Router {
    +render()
  }
  ExampleComponent --> ExampleFetchComponent
  ExampleFetchComponent --> DenseTable
  Router --> ExampleComponent
  ExampleComponentIcon <|-- Router
  ExampleComponentIcon <|-- ExampleComponent
Loading

Class diagram for plugin setup and routing

classDiagram
  class plugin {
    +id: string
    +routes: object
  }
  class PluginPage {
    +component: Router
    +mountPoint: rootRouteRef
  }
  class PluginIcon {
    +component: ExampleComponentIcon
  }
  plugin --> PluginPage
  plugin --> PluginIcon
  PluginPage --> Router
  PluginIcon --> ExampleComponentIcon
Loading

Class diagram for utility function getChipStyle

classDiagram
  class getChipStyle {
    +getChipStyle(theme: Theme): object
  }
Loading

File-Level Changes

Change Details Files
Scaffold plugin boilerplate
  • Define plugin metadata, dependencies, and scripts in package.json
  • Add README.md with setup and usage instructions
  • Provide catalog-info.yaml for Backstage catalog integration
  • Configure ESLint via .eslintrc.js
  • Create entrypoint re-exports in src/index.ts
  • Establish plugin and route definitions in src/plugin.ts and src/routes.ts
plugins/router-companion-android/package.json
plugins/router-companion-android/README.md
plugins/router-companion-android/catalog-info.yaml
plugins/router-companion-android/.eslintrc.js
plugins/router-companion-android/src/index.ts
plugins/router-companion-android/src/plugin.ts
plugins/router-companion-android/src/routes.ts
Configure development environment
  • Set up a DevApp with TestApiProvider for isolated plugin rendering
plugins/router-companion-android/dev/index.tsx
Implement routing layer
  • Create Router component mapping all paths to ExampleComponent
  • Export Router in component index
plugins/router-companion-android/src/components/Router.tsx
plugins/router-companion-android/src/components/index.ts
Add ExampleFetchComponent for data fetching
  • Use useAsync with discoveryApi and fetchApi to fetch Github users
  • Display results in a DenseTable with styled chips
  • Handle loading and error states
plugins/router-companion-android/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx
plugins/router-companion-android/src/components/ExampleFetchComponent/index.ts
plugins/router-companion-android/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx
Add primary UI component and icon
  • Compose ExampleComponent with Page, Header, InfoCard, and embed fetch component
  • Define ExampleComponentIcon for sidebar navigation
  • Provide component index and tests
plugins/router-companion-android/src/components/ExampleComponent/ExampleComponent.tsx
plugins/router-companion-android/src/components/ExampleComponent/index.ts
plugins/router-companion-android/src/components/ExampleComponent/ExampleComponent.test.tsx
plugins/router-companion-android/src/components/ExampleComponentIcon.tsx
Introduce styling utility
  • Implement getChipStyle to adjust chip background based on theme
plugins/router-companion-android/src/utils/getChipStyle.ts

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

Copy link
Copy Markdown

@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:

  • There’s a mix of MUI v4 (@material-ui/core/icons) and MUI v5 (@mui/icons‐material) imports—please consolidate to a single major version to avoid styling/runtime conflicts.
  • The repository.url in your package.json is set to "[object Object]"—replace it with the actual Git URL and update catalog-info.yaml if necessary.
  • Avoid using any in ExampleFetchComponent and define proper interfaces for the fetched GitHub user shape, or remap fields with a clearly typed DTO to improve type safety.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- There’s a mix of MUI v4 (@material-ui/core/icons) and MUI v5 (@mui/icons‐material) imports—please consolidate to a single major version to avoid styling/runtime conflicts.
- The repository.url in your package.json is set to "[object Object]"—replace it with the actual Git URL and update catalog-info.yaml if necessary.
- Avoid using `any` in ExampleFetchComponent and define proper interfaces for the fetched GitHub user shape, or remap fields with a clearly typed DTO to improve type safety.

## Individual Comments

### Comment 1
<location> `plugins/router-companion-android/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx:20` </location>
<code_context>
+  type: string;
+};
+
+export const DenseTable = ({ users }: any) => {
+  const theme = useTheme();
+  const chipStyle = getChipStyle(theme);
</code_context>

<issue_to_address>
Consider using a more specific type for the 'users' prop.

Defining 'users' as 'User[]' instead of 'any' will improve type safety and maintainability.

Suggested implementation:

```typescript
export const DenseTable = ({ users }: { users: User[] }) => {

```

```typescript
  const data = users.map((user: User) => {

```
</issue_to_address>

### Comment 2
<location> `plugins/router-companion-android/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx:31` </location>
<code_context>
+
+  const data = users.map((user: any) => {
+    return {
+      id: user.login,
+      githubId: (
+        <Chip
+          style={chipStyle}
+          avatar={<Avatar alt={user.login} src={user.avatar_url} />}
+          label={user.login}
+          variant="outlined"
+        />
+      ),
+      repoUrl: user.html_url,
+      type: user.type,
+    };
+  });
</code_context>

<issue_to_address>
The 'id' field is set but not used in the Table columns.

If 'id' is not needed, remove it from the mapped data to simplify the structure.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
  const data = users.map((user: any) => {
    return {
      id: user.login,
      githubId: (
        <Chip
          style={chipStyle}
          avatar={<Avatar alt={user.login} src={user.avatar_url} />}
          label={user.login}
          variant="outlined"
        />
      ),
      repoUrl: user.html_url,
      type: user.type,
    };
  });
=======
  const data = users.map((user: any) => {
    return {
      githubId: (
        <Chip
          style={chipStyle}
          avatar={<Avatar alt={user.login} src={user.avatar_url} />}
          label={user.login}
          variant="outlined"
        />
      ),
      repoUrl: user.html_url,
      type: user.type,
    };
  });
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `plugins/router-companion-android/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx:62` </location>
<code_context>
+
+  const { value, loading, error } = useAsync(async (): Promise<User[]> => {
+    const res = await fetchApi.fetch(`${await proxyURL}/github/users`);
+    if (!res.ok) throw new Error(res.statusText || "Error occured");
+    const users = await res.json();
+    return users;
</code_context>

<issue_to_address>
Typo in error message: 'Error occured' should be 'Error occurred'.

Fixing this typo enhances clarity and maintains a professional user experience.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    if (!res.ok) throw new Error(res.statusText || "Error occured");
=======
    if (!res.ok) throw new Error(res.statusText || "Error occurred");
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `plugins/router-companion-android/README.md:59` </location>
<code_context>
+   );
+  ```
+
+4. Set-up the Backstage Proxy. Follow https://backstage.io/docs/integrations/github/locations#token-scopes for creating personal access token
+
+  ```yaml title="app-config.yaml"
</code_context>

<issue_to_address>
Change 'Set-up' to 'Set up' for correct usage.

Use 'Set up' instead of 'Set-up' for grammatical accuracy.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
4. Set-up the Backstage Proxy. Follow https://backstage.io/docs/integrations/github/locations#token-scopes for creating personal access token
=======
4. Set up the Backstage Proxy. Follow https://backstage.io/docs/integrations/github/locations#token-scopes for creating personal access token
>>>>>>> REPLACE

</suggested_fix>

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.

type: string;
};

export const DenseTable = ({ users }: any) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Consider using a more specific type for the 'users' prop.

Defining 'users' as 'User[]' instead of 'any' will improve type safety and maintainability.

Suggested implementation:

export const DenseTable = ({ users }: { users: User[] }) => {
  const data = users.map((user: User) => {

Comment on lines +29 to +43
const data = users.map((user: any) => {
return {
id: user.login,
githubId: (
<Chip
style={chipStyle}
avatar={<Avatar alt={user.login} src={user.avatar_url} />}
label={user.login}
variant="outlined"
/>
),
repoUrl: user.html_url,
type: user.type,
};
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: The 'id' field is set but not used in the Table columns.

If 'id' is not needed, remove it from the mapped data to simplify the structure.

Suggested change
const data = users.map((user: any) => {
return {
id: user.login,
githubId: (
<Chip
style={chipStyle}
avatar={<Avatar alt={user.login} src={user.avatar_url} />}
label={user.login}
variant="outlined"
/>
),
repoUrl: user.html_url,
type: user.type,
};
});
const data = users.map((user: any) => {
return {
githubId: (
<Chip
style={chipStyle}
avatar={<Avatar alt={user.login} src={user.avatar_url} />}
label={user.login}
variant="outlined"
/>
),
repoUrl: user.html_url,
type: user.type,
};
});


const { value, loading, error } = useAsync(async (): Promise<User[]> => {
const res = await fetchApi.fetch(`${await proxyURL}/github/users`);
if (!res.ok) throw new Error(res.statusText || "Error occured");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick (typo): Typo in error message: 'Error occured' should be 'Error occurred'.

Fixing this typo enhances clarity and maintains a professional user experience.

Suggested change
if (!res.ok) throw new Error(res.statusText || "Error occured");
if (!res.ok) throw new Error(res.statusText || "Error occurred");

);
```

4. Set-up the Backstage Proxy. Follow https://backstage.io/docs/integrations/github/locations#token-scopes for creating personal access token
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (typo): Change 'Set-up' to 'Set up' for correct usage.

Use 'Set up' instead of 'Set-up' for grammatical accuracy.

Suggested change
4. Set-up the Backstage Proxy. Follow https://backstage.io/docs/integrations/github/locations#token-scopes for creating personal access token
4. Set up the Backstage Proxy. Follow https://backstage.io/docs/integrations/github/locations#token-scopes for creating personal access token

Comment on lines +63 to +64
const users = await res.json();
return users;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const users = await res.json();
return users;
return await res.json();


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

@rm3l
Copy link
Copy Markdown
Member Author

rm3l commented Sep 9, 2025

Just for testing Backstage/RHDH

@rm3l rm3l closed this Sep 9, 2025
@rm3l rm3l deleted the test-rhdh-backend-plugin-scaffolder branch September 9, 2025 08:11
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