Skip to content

List categories device endpoint - #46938

Merged
georgekarrv merged 1 commit into
feat/39018-self-service-categoriesfrom
46829-list-categories-2
Jun 8, 2026
Merged

List categories device endpoint#46938
georgekarrv merged 1 commit into
feat/39018-self-service-categoriesfrom
46829-list-categories-2

Conversation

@jkatz01

@jkatz01 jkatz01 commented Jun 5, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #46829

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: aggregate-result

Failed stage: Check for failures [❌]

Failed test name: vuln-mysql8.0.44

Failure summary:

The action failed in the status aggregation step because one of the downloaded test job status files
contained fail.
- The script iterates over ./**/status files and exits with code 1 if any status
file includes fail (lines 146-160).
- ./vuln-mysql8.0.44-status/status had status content fail, so
the job reported ❌ One or more test jobs failed: vuln-mysql8.0.44 and exited 1 (lines 166-168,
184-185).

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

119:  Artifact download completed successfully.
120:  Extracting artifact entry: /home/runner/work/fleet/fleet/scripts-status/status
121:  Extracting artifact entry: /home/runner/work/fleet/fleet/mysql-mysql8.0.44-status/status
122:  Artifact download completed successfully.
123:  Extracting artifact entry: /home/runner/work/fleet/fleet/vuln-mysql8.0.44-status/status
124:  Artifact download completed successfully.
125:  Artifact download completed successfully.
126:  Extracting artifact entry: /home/runner/work/fleet/fleet/service-mysql8.0.44-status/status
127:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-enterprise-mysql8.0.44-status/status
128:  Artifact download completed successfully.
129:  Artifact download completed successfully.
130:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-core-mysql8.0.44-status/status
131:  Artifact download completed successfully.
132:  Total of 10 artifact(s) downloaded
133:  Download artifact has finished successfully
134:  ##[group]Run failed_tests=""
135:  �[36;1mfailed_tests=""�[0m
136:  �[36;1mstatus_count=0�[0m
137:  �[36;1m# Find all status files (they are in directories like 'fleetctl-mysql8.0.44-status/status')�[0m
138:  �[36;1mfor status_file in $(find ./ -type f -name 'status'); do�[0m
139:  �[36;1m  status_count=$((status_count + 1))�[0m
140:  �[36;1m  # Extract test name from parent directory (e.g., 'fleetctl-mysql8.0.44-status')�[0m
141:  �[36;1m  test_dir=$(basename $(dirname "$status_file"))�[0m
142:  �[36;1m  # Remove '-status' suffix to get the test name�[0m
143:  �[36;1m  test_name="${test_dir%-status}"�[0m
144:  �[36;1m  status_content=$(cat "$status_file")�[0m
145:  �[36;1m  echo "Processing: $status_file (Test: $test_name) with status content: $status_content"�[0m
146:  �[36;1m  if grep -q "fail" "$status_file"; then�[0m
147:  �[36;1m    echo "  ❌ Test failed: $test_name"�[0m
148:  �[36;1m    failed_tests="${failed_tests}${test_name}, "�[0m
149:  �[36;1m  else�[0m
150:  �[36;1m    echo "  ✅ Test passed: $test_name"�[0m
151:  �[36;1m  fi�[0m
152:  �[36;1mdone�[0m
153:  �[36;1mif [[ $status_count -eq 0 ]]; then�[0m
154:  �[36;1m  echo "❌ ERROR: No status files found! This indicates a workflow issue."�[0m
155:  �[36;1m  exit 1�[0m
156:  �[36;1mfi�[0m
157:  �[36;1mif [[ -n "$failed_tests" ]]; then�[0m
158:  �[36;1m  echo "❌ One or more test jobs failed: ${failed_tests%, }"�[0m
159:  �[36;1m  exit 1�[0m
160:  �[36;1mfi�[0m
161:  �[36;1mecho "✅ All test jobs succeeded."�[0m
162:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
163:  ##[endgroup]
164:  Processing: ./integration-core-mysql8.0.44-status/status (Test: integration-core-mysql8.0.44) with status content: success
165:  ✅ Test passed: integration-core-mysql8.0.44
166:  Processing: ./vuln-mysql8.0.44-status/status (Test: vuln-mysql8.0.44) with status content: fail
167:  ❌ Test failed: vuln-mysql8.0.44
168:  Processing: ./service-mysql8.0.44-status/status (Test: service-mysql8.0.44) with status content: success
169:  ✅ Test passed: service-mysql8.0.44
170:  Processing: ./main-mysql8.0.44-status/status (Test: main-mysql8.0.44) with status content: success
171:  ✅ Test passed: main-mysql8.0.44
172:  Processing: ./fleetctl-mysql8.0.44-status/status (Test: fleetctl-mysql8.0.44) with status content: success
173:  ✅ Test passed: fleetctl-mysql8.0.44
174:  Processing: ./integration-enterprise-mysql8.0.44-status/status (Test: integration-enterprise-mysql8.0.44) with status content: success
175:  ✅ Test passed: integration-enterprise-mysql8.0.44
176:  Processing: ./fast-status/status (Test: fast) with status content: success
177:  ✅ Test passed: fast
178:  Processing: ./mysql-mysql8.0.44-status/status (Test: mysql-mysql8.0.44) with status content: success
179:  ✅ Test passed: mysql-mysql8.0.44
180:  Processing: ./integration-mdm-mysql8.0.44-status/status (Test: integration-mdm-mysql8.0.44) with status content: success
181:  ✅ Test passed: integration-mdm-mysql8.0.44
182:  Processing: ./scripts-status/status (Test: scripts) with status content: success
183:  ✅ Test passed: scripts
184:  ❌ One or more test jobs failed: vuln-mysql8.0.44
185:  ##[error]Process completed with exit code 1.
186:  Post job cleanup.

@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.63636% with 8 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feat/39018-self-service-categories@d6b56d8). Learn more about missing BASE report.

Files with missing lines Patch % Lines
server/service/categories.go 46.66% 6 Missing and 2 partials ⚠️
Additional details and impacted files
@@                          Coverage Diff                          @@
##             feat/39018-self-service-categories   #46938   +/-   ##
=====================================================================
  Coverage                                      ?   66.83%           
=====================================================================
  Files                                         ?     2807           
  Lines                                         ?   224031           
  Branches                                      ?    11328           
=====================================================================
  Hits                                          ?   149736           
  Misses                                        ?    60697           
  Partials                                      ?    13598           
Flag Coverage Δ
backend 68.55% <63.63%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkatz01
jkatz01 marked this pull request as ready for review June 5, 2026 19:34
@jkatz01
jkatz01 requested a review from a team as a code owner June 5, 2026 19:34

@claude claude 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.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@jkatz01

jkatz01 commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@claude review once

Comment on lines +40 to +48
func (svc *Service) ListSelfServiceSoftwareCategoriesForHost(ctx context.Context, host *fleet.Host) ([]fleet.SoftwareCategory, error) {
teamID := ptr.ValOrZero(host.TeamID)

categories, err := svc.ds.ListSoftwareCategories(ctx, teamID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "list self-service software categories for host")
}
return categories, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit: this PR's new code imports server/ptr and calls ptr.ValOrZero(host.TeamID) in ListSelfServiceSoftwareCategoriesForHost, but the repo convention in .claude/CLAUDE.md says not to use the legacy server/ptr package in new code. The test file added in the same PR already follows the new convention (new(uint(7))), so the EE service is internally inconsistent. Consider inlining: var teamID uint; if host.TeamID != nil { teamID = *host.TeamID } and dropping the ptr import.

Extended reasoning...

What the issue is

ee/server/service/categories.go (added in this PR) imports github.com/fleetdm/fleet/v4/server/ptr and calls ptr.ValOrZero(host.TeamID) on line ~41 inside the brand-new ListSelfServiceSoftwareCategoriesForHost function. The repository's contributor convention at .claude/CLAUDE.md line 42 states: "Use Go 1.26 new(expression) for pointer values ... Do NOT use the legacy server/ptr package in new code — it exists throughout the codebase but is superseded by new(expr)." So this new import + new call sites is, by the letter of the rule, a convention violation.

Why this is only a nit, not a functional bug

The function works correctly: ptr.ValOrZero returns 0 when host.TeamID is nil, which is exactly what the downstream svc.ds.ListSoftwareCategories(ctx, teamID) expects for a no-team host (verified by the new test case "no-team host queries team 0"). There is no functional defect, no nil-deref risk, no incorrect behavior — the test cases all pass and the code is clearly correct.

Addressing the refutation

One verifier raised a fair objection: ptr.ValOrZero is a deref-or-zero helper, not a pointer-creation helper like ptr.String/ptr.Uint, and new(expr) doesn't replace it directly. They also noted ptr.ValOrZero has ~35-64 active uses across the codebase, suggesting the project isn't actually enforcing the rule for this helper. Both points are valid — the CLAUDE.md examples are pointer-creation helpers, and a strict reading of "superseded by new(expr)" only literally covers pointer creation.

That said, the CLAUDE.md rule says "Do NOT use the legacy server/ptr package in new code" as a blanket statement about the package, not just specific functions in it. And the same PR's test file (ee/server/service/categories_test.go lines 22/37/52) uses new(uint(7)) — so the PR author is aware of the convention and applying it elsewhere; the production file is inconsistent with the PR's own test file. That inconsistency is the strongest argument for the nit.

Step-by-step proof of the convention violation

  1. .claude/CLAUDE.md:42 declares: "Do NOT use the legacy server/ptr package in new code."
  2. The PR diff adds a brand-new import line: +\t"github.com/fleetdm/fleet/v4/server/ptr" in ee/server/service/categories.go:13.
  3. The PR diff adds a brand-new function ListSelfServiceSoftwareCategoriesForHost (lines 40-48) whose very first statement is teamID := ptr.ValOrZero(host.TeamID).
  4. The same PR adds ee/server/service/categories_test.go containing TeamID: new(uint(7)) on lines 22, 37, and 52 — applying the new-style convention.
  5. Result: identical PR, two files, two different conventions for pointer-to-uint handling.

How to fix

Trivial — drop the import and inline the dereference:

var teamID uint
if host.TeamID != nil {
    teamID = *host.TeamID
}

This is a 3-line replacement for 1 line plus removing one import. Pure style cleanup; no behavior change. Severity: nit, not blocking.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we should adjust the repo convention, because ptr.ValOrZero is very useful for team_id related stuff and new(type(value)) is the opposite afaik.

@georgekarrv
georgekarrv merged commit a471b6f into feat/39018-self-service-categories Jun 8, 2026
34 of 36 checks passed
@georgekarrv
georgekarrv deleted the 46829-list-categories-2 branch June 8, 2026 14:15
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