Skip to content

Comments

[Bug] (SC-204212) Improve site name fallback handling in Filters component#66

Open
AFOJ wants to merge 1 commit intomainfrom
bug/sc-204212/lansweeper-replace-site-id-with-site-name
Open

[Bug] (SC-204212) Improve site name fallback handling in Filters component#66
AFOJ wants to merge 1 commit intomainfrom
bug/sc-204212/lansweeper-replace-site-id-with-site-name

Conversation

@AFOJ
Copy link
Contributor

@AFOJ AFOJ commented Jan 2, 2026

Description

This PR replaces site ID with site name in Filters component and add tests for site name fallbacks.

Evidence

Site select in Deskpro Site select in Lansweeper

Summary by Sourcery

Update the LinkDevice Filters component to use human-friendly site name fallbacks and add coverage for the new behavior.

Bug Fixes:

  • Ensure the Filters site selector displays a meaningful label using branding name, then name, then an 'Unnamed Site (id)' fallback instead of relying on site ID alone.

Tests:

  • Add unit tests verifying the Filters component’s site label fallback order for branding name, name, and unnamed sites.

@AFOJ AFOJ self-assigned this Jan 2, 2026
@AFOJ AFOJ added the unit-tests Adds or modifies unit tests label Jan 2, 2026
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 2, 2026

Reviewer's Guide

Replaces the generic getOptions-based site selector in the Filters component with explicit site option construction that prefers brandingName, then name, then a predictable "Unnamed Site (id)" fallback, and adds tests to verify the new fallback behavior.

Class diagram for updated Filters component props and option construction

classDiagram
  class Filters {
    +Filters(props Props)
  }

  class Props {
    +Maybe~Array~Site~~ sites
    +Maybe~string~ siteId
    +boolean isFetching
    +function onChangeSearchQuery
    +function onChangeSite
  }

  class Site {
    +string id
    +string brandingName
    +string name
  }

  class Option {
    +string value
    +string label
    +string key
    +string type
  }

  Props --> "*" Site : uses
  Filters --> Props : receives
  Filters --> "*" Option : creates siteOptions
Loading

File-Level Changes

Change Details Files
Inline site option construction in Filters with explicit name/branding fallbacks instead of using getOptions.
  • Remove useMemo and getOptions helper from Filters and compute siteOptions directly from sites prop.
  • Define siteOptions to map each site to an Option with value/key set to site.id and label resolved by brandingName
Add unit tests covering Filters site name fallback behavior.
  • Create a Filters.test.tsx file that renders Filters within a Deskpro ThemeProvider.
  • Introduce a renderComponent helper to render Filters with a single-site sites array and stubbed callbacks.
  • Use jest.it.each to validate three cases: missing names uses "Unnamed Site (id)", name-only uses name, and brandingName overrides name.
  • Assert that the expected label text is present in the rendered output for each test case.
src/components/LinkDevice/blocks/__tests__/Filters.test.tsx

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The new siteOptions array is rebuilt on every render whereas the previous implementation used useMemo; consider wrapping the sites.map(...) in useMemo again if sites can be large or the component re-renders frequently.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `siteOptions` array is rebuilt on every render whereas the previous implementation used `useMemo`; consider wrapping the `sites.map(...)` in `useMemo` again if `sites` can be large or the component re-renders frequently.

## Individual Comments

### Comment 1
<location> `src/components/LinkDevice/blocks/Filters.tsx:16-20` </location>
<code_context>

 const Filters: FC<Props> = ({ sites, siteId, isFetching, onChangeSearchQuery, onChangeSite }) => {
-  const options = useMemo(() => getOptions(sites, "brandingName"), [sites]) as Array<Option>;
+  const siteOptions: Option[] = sites.map((site) => ({
+    value: site.id,
+    label: site.brandingName || site.name || `Unnamed Site (${site.id})`,
+    key: site.id,
+    type: "value",
+  }))

</code_context>

<issue_to_address>
**issue (bug_risk):** Handle `sites` being null/undefined before calling `.map`.

Since `sites` is `Maybe<Site[]>`, it can be `null`/`undefined`, so `sites.map` risks a runtime error. Normalize it first, e.g. `const siteOptions: Option[] = (sites ?? []).map(...)`, so the component still renders safely when no sites are provided.
</issue_to_address>

### Comment 2
<location> `src/components/LinkDevice/blocks/__tests__/Filters.test.tsx:23-32` </location>
<code_context>
+
+
+describe("Filters", () => {
+  it.each([
+    [{ id: "dp-97" }, "Unnamed Site (dp-97)"],
+    [{ id: "dp-97", name: "Deskpro Site" }, "Deskpro Site"],
+    [
+      { id: "dp-97", name: "Deskpro Site", brandingName: "Deskpro Site (Branding)" },
+      "Deskpro Site (Branding)",
+    ],
+  ])("correctly handles site name fallbacks", (site, target) => {
+    const { getByText } = renderComponent(site)
+
</code_context>

<issue_to_address>
**suggestion (testing):** Add more edge cases for site name/branding fallbacks (e.g. empty strings and undefined values).

The current cases cover only the basic fallbacks. Please add a few rows for more realistic inputs, such as `brandingName: ""` with a non-empty `name`, and scenarios where `name` and/or `brandingName` are empty strings/whitespace or `null`/`undefined`, so the fallback behavior is clearly defined and guarded by tests.

```suggestion
describe("Filters", () => {
  it.each([
    // No name or brandingName -> fallback to "Unnamed Site (id)"
    [{ id: "dp-97" }, "Unnamed Site (dp-97)"],

    // Name only -> use name
    [{ id: "dp-97", name: "Deskpro Site" }, "Deskpro Site"],

    // Branding name only -> use brandingName
    [{ id: "dp-97", brandingName: "Deskpro Site (Branding)" }, "Deskpro Site (Branding)"],

    // Both name and brandingName non-empty -> prefer brandingName
    [
      { id: "dp-97", name: "Deskpro Site", brandingName: "Deskpro Site (Branding)" },
      "Deskpro Site (Branding)",
    ],

    // Empty brandingName, non-empty name -> fall back to name
    [
      { id: "dp-97", name: "Deskpro Site", brandingName: "" },
      "Deskpro Site",
    ],

    // Whitespace brandingName, non-empty name -> fall back to name
    [
      { id: "dp-97", name: "Deskpro Site", brandingName: "   " },
      "Deskpro Site",
    ],

    // Empty name, non-empty brandingName -> use brandingName
    [
      { id: "dp-97", name: "", brandingName: "Deskpro Site (Branding)" },
      "Deskpro Site (Branding)",
    ],

    // Whitespace name, non-empty brandingName -> use brandingName
    [
      { id: "dp-97", name: "   ", brandingName: "Deskpro Site (Branding)" },
      "Deskpro Site (Branding)",
    ],

    // Empty name and brandingName -> fall back to "Unnamed Site (id)"
    [
      { id: "dp-97", name: "", brandingName: "" },
      "Unnamed Site (dp-97)",
    ],

    // Null brandingName, non-empty name -> fall back to name
    [
      {
        id: "dp-97",
        name: "Deskpro Site",
        brandingName: null as unknown as string,
      },
      "Deskpro Site",
    ],

    // Null name and brandingName -> fall back to "Unnamed Site (id)"
    [
      {
        id: "dp-97",
        name: null as unknown as string,
        brandingName: null as unknown as string,
      },
      "Unnamed Site (dp-97)",
    ],
  ])("correctly handles site name fallbacks", (site, target) => {
    const { getByText } = renderComponent(site)
```
</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.

Comment on lines +23 to +32
describe("Filters", () => {
it.each([
[{ id: "dp-97" }, "Unnamed Site (dp-97)"],
[{ id: "dp-97", name: "Deskpro Site" }, "Deskpro Site"],
[
{ id: "dp-97", name: "Deskpro Site", brandingName: "Deskpro Site (Branding)" },
"Deskpro Site (Branding)",
],
])("correctly handles site name fallbacks", (site, target) => {
const { getByText } = renderComponent(site)
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Add more edge cases for site name/branding fallbacks (e.g. empty strings and undefined values).

The current cases cover only the basic fallbacks. Please add a few rows for more realistic inputs, such as brandingName: "" with a non-empty name, and scenarios where name and/or brandingName are empty strings/whitespace or null/undefined, so the fallback behavior is clearly defined and guarded by tests.

Suggested change
describe("Filters", () => {
it.each([
[{ id: "dp-97" }, "Unnamed Site (dp-97)"],
[{ id: "dp-97", name: "Deskpro Site" }, "Deskpro Site"],
[
{ id: "dp-97", name: "Deskpro Site", brandingName: "Deskpro Site (Branding)" },
"Deskpro Site (Branding)",
],
])("correctly handles site name fallbacks", (site, target) => {
const { getByText } = renderComponent(site)
describe("Filters", () => {
it.each([
// No name or brandingName -> fallback to "Unnamed Site (id)"
[{ id: "dp-97" }, "Unnamed Site (dp-97)"],
// Name only -> use name
[{ id: "dp-97", name: "Deskpro Site" }, "Deskpro Site"],
// Branding name only -> use brandingName
[{ id: "dp-97", brandingName: "Deskpro Site (Branding)" }, "Deskpro Site (Branding)"],
// Both name and brandingName non-empty -> prefer brandingName
[
{ id: "dp-97", name: "Deskpro Site", brandingName: "Deskpro Site (Branding)" },
"Deskpro Site (Branding)",
],
// Empty brandingName, non-empty name -> fall back to name
[
{ id: "dp-97", name: "Deskpro Site", brandingName: "" },
"Deskpro Site",
],
// Whitespace brandingName, non-empty name -> fall back to name
[
{ id: "dp-97", name: "Deskpro Site", brandingName: " " },
"Deskpro Site",
],
// Empty name, non-empty brandingName -> use brandingName
[
{ id: "dp-97", name: "", brandingName: "Deskpro Site (Branding)" },
"Deskpro Site (Branding)",
],
// Whitespace name, non-empty brandingName -> use brandingName
[
{ id: "dp-97", name: " ", brandingName: "Deskpro Site (Branding)" },
"Deskpro Site (Branding)",
],
// Empty name and brandingName -> fall back to "Unnamed Site (id)"
[
{ id: "dp-97", name: "", brandingName: "" },
"Unnamed Site (dp-97)",
],
// Null brandingName, non-empty name -> fall back to name
[
{
id: "dp-97",
name: "Deskpro Site",
brandingName: null as unknown as string,
},
"Deskpro Site",
],
// Null name and brandingName -> fall back to "Unnamed Site (id)"
[
{
id: "dp-97",
name: null as unknown as string,
brandingName: null as unknown as string,
},
"Unnamed Site (dp-97)",
],
])("correctly handles site name fallbacks", (site, target) => {
const { getByText } = renderComponent(site)

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

unit-tests Adds or modifies unit tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant