Skip to content

Conversation

@pujitm
Copy link
Member

@pujitm pujitm commented Oct 8, 2025

Summary by CodeRabbit

  • New Features

    • Per-package CPU power and temperature shown in hardware info (total and per-package values).
    • CPU package topology (cores/threads per package) included in CPU info.
    • Real-time per-package CPU telemetry available via a new system metrics subscription.
  • Chores

    • Added an automated deployment script and npm deploy script for the shared package.

@claude
Copy link

claude bot commented Oct 8, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new CpuPackages GraphQL type and exposes per-package topology and telemetry via InfoCpu.topology, InfoCpu.packages, and a systemMetricsCpuTelemetry subscription; introduces CpuTopologyService to read /sys for topology/power/temps, wires services into a new CpuModule, updates MetricsResolver subscriptions and tests, and adds a deploy script for unraid-shared.

Changes

Cohort / File(s) Summary
GraphQL schema
api/generated-schema.graphql
Added CpuPackages type (totalPower: Float!, power: [Float!]!, temp: [Float!]!); extended InfoCpu with topology and packages; added systemMetricsCpuTelemetry subscription.
CPU model & types
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
Added exported CpuPackages class and added topology: number[][][] and packages: CpuPackages to InfoCpu.
CPU topology & telemetry service
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
New CpuTopologyService with generateTopology() and generateTelemetry() that parse /sys (cpu dirs, hwmon, powercap), expand thread lists, aggregate per-package power/temp, and return telemetry objects with defensive IO error handling and logging.
CPU module
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
New CpuModule that provides/exports CpuService and CpuTopologyService.
CPU service & tests
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts, api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
CpuService now injects CpuTopologyService; generateCpu() returns processors, topology, and packages (CpuPackages); tests updated to mock topology service and telemetry.
Module wiring
api/src/unraid-api/graph/resolvers/info/info.module.ts, api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
CpuModule imported into InfoModule and MetricsModule; removed direct CpuService provider where the module provides it.
Metrics resolver & tests
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts, .../metrics.resolver.spec.ts, .../metrics.resolver.integration.spec.ts
MetricsResolver injects CpuTopologyService; onModuleInit registers CPU_TELEMETRY handler that calls generateTelemetry, builds CpuPackages (computes totalPower), publishes via pubsub; added systemMetricsCpuTelemetry subscription; tests/providers updated.
PubSub config
packages/unraid-shared/src/pubsub/graphql.pubsub.ts
Added CPU_TELEMETRY to GRAPHQL_PUBSUB_CHANNEL enum.
Deploy script & package
packages/unraid-shared/deploy.sh, packages/unraid-shared/package.json
New deploy.sh for build + rsync deploy, chown, and remote service restart; added unraid:deploy npm script entry.

Sequence Diagram(s)

sequenceDiagram
    participant Sub as Subscriber
    participant MR as MetricsResolver
    participant CTS as CpuTopologyService
    participant Sysfs as /sys
    participant Pub as PubSub

    Note over MR,CTS: onModuleInit registers CPU_TELEMETRY handler

    Sub->>MR: subscribe systemMetricsCpuTelemetry
    MR->>Pub: subscribe to CPU_TELEMETRY

    loop periodic telemetry
        MR->>CTS: generateTelemetry()
        CTS->>Sysfs: read cpu topology, hwmon temps, powercap energy_uj
        Sysfs-->>CTS: topology, temps, energy samples
        CTS->>CTS: compute per-package power, temps, totalPower
        CTS-->>MR: return CpuPackages payload
        MR->>Pub: publish CPU_TELEMETRY payload
        Pub-->>Sub: deliver systemMetricsCpuTelemetry payload
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • cpu-topology.service.ts: sysfs parsing, thread-range expansion, deduplication, ordering.
    • RAPL sampling and power computation (timing, units conversion).
    • MetricsResolver subscription publish flow and topic registration.
    • Module/provider refactor (CpuModule import/export) and DI changes in tests.

Possibly related PRs

  • feat: add cpu power query & subscription #1745 — Implements the same CPU power/telemetry additions: CpuPackages/topology in the schema, CpuTopologyService with generateTopology/generateTelemetry, updated services/resolvers and CPU_TELEMETRY subscription (strongly related).

Poem

🐰
I hop through sysfs with curious paws,
I count each package, map every cause;
Watts and warmth in tidy rows,
Packaged whispers softly flow—
Subscribe and watch the rabbit's laws.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add cpu power query & subscription' accurately reflects the main changes: introducing CPU power telemetry via new GraphQL queries and subscriptions, supported by CpuTopologyService and related infrastructure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba4d632 and 7ec4f68.

📒 Files selected for processing (2)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Oct 8, 2025

Codecov Report

❌ Patch coverage is 49.64029% with 140 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.25%. Comparing base (34075e4) to head (7ec4f68).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
...i/graph/resolvers/info/cpu/cpu-topology.service.ts 45.02% 105 Missing ⚠️
...id-api/graph/resolvers/metrics/metrics.resolver.ts 26.31% 28 Missing ⚠️
...c/unraid-api/graph/resolvers/info/cpu/cpu.model.ts 50.00% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1745      +/-   ##
==========================================
- Coverage   52.69%   52.25%   -0.44%     
==========================================
  Files         865      874       +9     
  Lines       49343    50536    +1193     
  Branches     4952     5053     +101     
==========================================
+ Hits        26000    26407     +407     
- Misses      23270    24054     +784     
- Partials       73       75       +2     

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)

23-33: Guard against NaN for stepping; consider latency impact of power fetch.

  • Number(stepping) can yield NaN (GraphQL Int cannot represent NaN). Guard it.
  • generateCpuPower() performs a ~200ms sampling; this adds latency to the InfoCpu query. Consider caching, lazy resolution via a separate field resolver, or background sampling.
-            power: await this.cpuPowerService.generateCpuPower(),
-            stepping: Number(stepping),
+            power: await this.cpuPowerService.generateCpuPower(),
+            stepping: stepping != null && `${stepping}` !== '' ? Number(stepping) : undefined,
♻️ Duplicate comments (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)

2-4: Remove unused imports (CodeQL findings).

fsConstants, access, readdir, readFile, basename, join are unused in this file.

-import { constants as fsConstants } from 'node:fs';
-import { access, readdir, readFile } from 'node:fs/promises';
-import { basename, join } from 'node:path';
🧹 Nitpick comments (7)
api/src/unraid-api/graph/resolvers/info/info.module.ts (1)

5-5: Remove unused CpuService import.

CpuService is no longer used directly in InfoModule since it's now provided by CpuModule (line 18).

Apply this diff to remove the unused import:

-import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

3-5: Remove unused imports.

CpuPowerService and CpuService are imported but not directly used in MetricsModule. Both services are now provided by CpuModule (line 11), making these direct imports unnecessary.

Apply this diff to remove the unused imports:

-import { CpuPowerService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu-power.service.js';
 import { CpuModule } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.module.js';
-import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (1)

51-61: Clarify naming vs. semantics (packages/sockets vs. cores).

The field name coresPower suggests per-core values, but RAPL reads package/socket power. Either rename the field or update the description to avoid confusion.

Apply this minimal-description fix:

-        description: 'CPU power in watts for each physical processor',
+        description: 'CPU power in watts for each CPU package/socket (physical processor)',
api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts (4)

4-4: Use node:path for ESM consistency.

Prefer Node built-in ESM specifier.

-import { basename, join } from 'path';
+import { basename, join } from 'node:path';

46-48: Make sampling window a constant/configurable.

Hardcoded 200ms can be a knob for accuracy vs. latency; extract to a const.

-            // ~200ms delay to measure energy delta
-            await new Promise((resolve) => setTimeout(resolve, 200));
+            // Sampling window to measure energy delta
+            const measureWindowMs = 200;
+            await new Promise((resolve) => setTimeout(resolve, measureWindowMs));

71-78: Handle RAPL energy counter wrap-around.

energy_uj can wrap; adjust diff using max_energy_range_uj when negative.

-                const diffEnergy = now - prevE; // microjoules
+                let diffEnergy = now - prevE; // microjoules
+                if (diffEnergy < 0) {
+                    try {
+                        const maxFile = join(domainPath, 'max_energy_range_uj');
+                        await access(maxFile, fsConstants.R_OK);
+                        const maxStr = await readFile(maxFile, 'utf8');
+                        const maxRange = Number.parseInt(maxStr.trim(), 10);
+                        if (Number.isFinite(maxRange) && maxRange > 0) {
+                            diffEnergy = (now + maxRange) - prevE;
+                        }
+                    } catch {
+                        // if wrap-around can't be determined, skip this sample
+                        continue;
+                    }
+                }
                 const diffTimeNs = Number(tNow - prevT); // nanoseconds
                 if (!(diffTimeNs > 0)) continue;

132-134: Log unexpected errors for observability.

Capture and log the error; return safe default.

-        } catch {
-            return { id };
-        }
+        } catch (e) {
+            this.logger.debug(`generateCpuPower failed: ${String(e)}`);
+            return { id };
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 754966d and 14c0f90.

📒 Files selected for processing (13)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/info.module.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts
  • api/src/unraid-api/graph/resolvers/info/info.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts
  • api/src/unraid-api/graph/resolvers/info/info.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
api/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.{test,spec}.{ts,tsx}: API test suite is Vitest; do not use Jest
Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts
  • api/src/unraid-api/graph/resolvers/info/info.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
🧠 Learnings (2)
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
PR: unraid/api#1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to api/**/*.{test,spec}.{ts,tsx} : API test suite is Vitest; do not use Jest

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
🧬 Code graph analysis (6)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)
  • Module (6-10)
api/src/unraid-api/graph/resolvers/resolvers.module.ts (1)
  • Module (37-76)
api/src/unraid-api/graph/services/services.module.ts (1)
  • Module (8-13)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)
api/src/unraid-api/graph/resolvers/info/info.model.ts (1)
  • ObjectType (13-44)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Node (1371-1373)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (2)
api/src/unraid-api/graph/resolvers/info/info.module.ts (1)
  • Module (17-37)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)
  • Module (10-15)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
  • Injectable (11-54)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts (1)
  • Injectable (8-136)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (2)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
🪛 GitHub Check: CodeQL
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts

[notice] 3-3: Unused variable, import, function or class
Unused import CpuPowerService.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts

[notice] 2-2: Unused variable, import, function or class
Unused import fsConstants.


[notice] 3-3: Unused variable, import, function or class
Unused imports access, readFile, readdir.


[notice] 4-4: Unused variable, import, function or class
Unused imports basename, join.

🔇 Additional comments (7)
packages/unraid-shared/src/pubsub/graphql.pubsub.ts (1)

8-8: LGTM!

The CPU_POWER channel addition is consistent with existing patterns and properly supports the new CPU power metrics feature.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (1)

3-3: LGTM!

The test properly mocks the CpuPowerService dependency and updates expectations to include the new power field. The mock implementation is appropriately simple for unit testing.

Also applies to: 92-99, 130-130

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts (1)

8-8: LGTM!

The integration test correctly includes CpuPowerService in the DI container, enabling proper integration testing of CPU power metrics.

Also applies to: 26-26

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (1)

6-6: LGTM!

The test correctly accommodates the new CpuPowerService dependency and updates the topic registration expectation from 2 to 3 to account for the new CPU_POWER topic.

Also applies to: 22-22, 168-168, 176-176

api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)

1-10: LGTM!

The CpuModule is well-structured and follows NestJS conventions. The module properly encapsulates CPU-related services and exports them for use by other modules.

api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts (1)

9-9: LGTM!

The integration test correctly includes CpuPowerService in the DI container, ensuring the new CPU power functionality is properly tested.

Also applies to: 32-32

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

91-101: Approve CPU power subscription wiring

PUBSUB_CHANNEL.CPU_POWER is defined in the shared GRAPHQL_PUBSUB_CHANNEL enum.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (4)
api/generated-schema.graphql (1)

2669-2671: Regenerate GraphQL client typings for new subscriptions.

Clients likely miss systemMetricsCpuPower and systemMetricsCpuTelemetry. Regenerate the codegen outputs across CLI/plugin/web.

#!/bin/bash
# Locate client graphql typings
fd 'graphql.ts' | sed -n '1,200p'
# Check presence of new fields
rg -n 'systemMetricsCpuPower|systemMetricsCpuTelemetry' -t ts -C2 || true
# Run project codegen
pnpm run generate || npm run generate || yarn generate
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

3-5: Remove unused imports (CodeQL warning).

CpuTopologyService and CpuService are not referenced in this module.

 import { Module } from '@nestjs/common';

-import { CpuTopologyService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.js';
-import { CpuModule } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.module.js';
+import { CpuModule } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.module.js';
-import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
 import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
 import { MetricsResolver } from '@app/unraid-api/graph/resolvers/metrics/metrics.resolver.js';
 import { ServicesModule } from '@app/unraid-api/graph/services/services.module.js';
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)

2-4: Drop unused Node fs/path imports.

These are not used and trigger CodeQL “unused import” notices.

-import { constants as fsConstants } from 'node:fs';
-import { access, readdir, readFile } from 'node:fs/promises';
-import { basename, join } from 'node:path';
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

66-74: Comment interval mismatch (should be 5s).

The code uses 5000ms; comment says 1 second. Please update.

-        // Register CPU power polling with 1 second interval
+        // Register CPU power polling with 5 second interval
🧹 Nitpick comments (6)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)

20-23: Consider removing unused injection until needed.

CpuPowerService is injected but unused here; keep only if a field resolver consumes it elsewhere.

If unused across the service, remove it from the constructor to avoid dead deps. Otherwise, add a TODO indicating which resolver uses it.

api/generated-schema.graphql (1)

1404-1412: Field naming clarity: coresPower seems per-package.

If values are per physical package, consider renaming to packagesPower or perPackagePower for clarity before GA.

api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (2)

175-177: Harden package-id inference for non-package-X labels.

Fallback to parsing the domain path (e.g., intel-rapl:1) like CpuPowerService does.

-            const pkgMatch = label.match(/package-(\d+)/i);
-            const pkgId = pkgMatch ? Number(pkgMatch[1]) : 0;
+            const pkgMatch = label.match(/package-(\d+)/i);
+            let pkgId = pkgMatch ? Number(pkgMatch[1]) : 0;
+            if (!pkgMatch) {
+                const base = p.split('/').pop() ?? '';
+                const m2 = base.match(/:(\d+)/);
+                if (m2) pkgId = Number(m2[1]);
+            }

12-13: Remove unused cache or implement usage.

topologyCache is defined but never used; either wire it up or drop it.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)

63-74: Name doesn’t match description; clarify per-core vs per-package.

coresPower suggests per-core, but description says “each physical processor.” Either:

  • rename to packagePower (or processorsPower) if per-package; or
  • fix the description if it’s truly per-core.
 export class CpuPower extends Node {
     @Field(() => Float, { nullable: true, description: 'CPU power in watts' })
     totalPower?: number;

-    @Field(() => [Float], {
-        nullable: true,
-        description: 'CPU power in watts for each physical processor',
-    })
-    coresPower?: number[];
+    @Field(() => [Float], {
+        nullable: true,
+        description: 'CPU power in watts for each physical processor (package)',
+    })
+    packagePower?: number[];
 }

131-135: Type alias for readability of triple-nested arrays.

Define a named alias (e.g., type CpuTopology = number[][][];) and use it here to improve readability and future reuse.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14c0f90 and b530490.

📒 Files selected for processing (8)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (4 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
🧰 Additional context used
📓 Path-based instructions (3)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
🧬 Code graph analysis (6)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
  • Injectable (16-78)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)
  • Module (7-11)
api/src/unraid-api/graph/resolvers/resolvers.module.ts (1)
  • Module (37-76)
api/src/unraid-api/graph/services/services.module.ts (1)
  • Module (8-13)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (2)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
  • Injectable (8-208)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts (1)
  • Injectable (8-136)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (2)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)
  • Module (10-15)
api/src/unraid-api/graph/resolvers/info/info.module.ts (1)
  • Module (17-37)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)
api/src/unraid-api/graph/resolvers/info/info.model.ts (1)
  • ObjectType (13-44)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Node (1371-1373)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)
api/src/core/pubsub.ts (1)
  • pubsub (12-12)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
🪛 GitHub Actions: CI - Main (API)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts

[error] 199-199: TS2345: Argument of type '{ id: number; power: number; temp: number; }' is not assignable to parameter of type 'never'.

api/generated-schema.graphql

[error] 1-1: Build failed during code generation. GraphQL Code Generator could not load schema from './generated-schema.graphql': Unknown type: 'number'. Command failed: 'tsx ./scripts/build.ts'.

🪛 GitHub Check: CodeQL
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts

[notice] 182-182: Unused variable, import, function or class
Unused variable pkgId.

api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts

[notice] 3-3: Unused variable, import, function or class
Unused import CpuTopologyService.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts

[notice] 2-2: Unused variable, import, function or class
Unused import fsConstants.


[notice] 3-3: Unused variable, import, function or class
Unused imports access, readFile, readdir.


[notice] 4-4: Unused variable, import, function or class
Unused imports basename, join.

🔇 Additional comments (4)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

11-13: Module wiring looks good.

Importing CpuModule and trimming providers is correct.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)

7-11: LGTM — module composition is correct.

Providers and exports align with consumers; ESM .js paths follow our guideline.

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (2)

24-31: Module wiring is correctly configured; no action required.

The verification confirms that MetricsModule imports CpuModule (line 11 of metrics.module.ts), which provides both CpuPowerService and CpuTopologyService. Dependency injection will resolve these services correctly in the constructor.


43-64: The review suggestion to change totalpower to totalPower is incorrect.

The original code is already type-safe. The CpuPackages model in api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts defines the property as totalpower (lowercase 'p') at line 45, so the code in metrics.resolver.ts is correct. Changing it to totalPower would break the type contract and cause TypeScript errors.

The only valid suggestion is a minor comment clarity: line 43 could say "Register CPU telemetry polling" instead of "Register CPU power polling" to align with the channel name CPU_TELEMETRY, but this is a cosmetic improvement, not a code fix.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (4)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)

42-52: Align GraphQL nullability and casing; make fields required.

The TypeScript properties are optional (?) while GraphQL fields are non-null (no nullable: true), which can cause "Cannot return null for non-nullable field" errors. Additionally, totalpower uses inconsistent casing compared to CpuPower.totalPower.

Apply this diff:

 @ObjectType()
 export class CpuPackages {
-    @Field(() => Float, { description: 'Total CPU package power draw (W)' })
-    totalpower?: number;
+    @Field(() => Float, { description: 'Total CPU package power draw (W)' })
+    totalPower!: number;

-    @Field(() => [Float], { description: 'Power draw per package (W)' })
-    power?: number[];
+    @Field(() => [Float], { description: 'Power draw per package (W)' })
+    power!: number[];

-    @Field(() => [Float], { description: 'Temperature per package (°C)' })
-    temp?: number[];
+    @Field(() => [Float], { description: 'Temperature per package (°C)' })
+    temp!: number[];
 }

Based on past review comments.


136-137: Non-null GraphQL field should have definite assignment.

The packages field is non-null in GraphQL but lacks definite assignment in TypeScript. Use ! to satisfy strict property initialization.

Apply this diff:

-    @Field(() => CpuPackages)
-    packages: CpuPackages;
+    @Field(() => CpuPackages)
+    packages!: CpuPackages;

Based on past review comments.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (2)

36-44: Fix total power calculation; filter sentinel values and align field naming.

The calculation pkg.power ?? 0 includes -1 sentinel values in the sum, producing incorrect totals. Additionally, the field name totalpower doesn't match the GraphQL model's camelCase convention totalPower.

Apply this diff:

         // Compute total power (2 decimals)
         const totalpower =
-            Math.round(packageList.reduce((sum, pkg) => sum + (pkg.power ?? 0), 0) * 100) / 100;
+            Math.round(
+                packageList.reduce(
+                    (sum, pkg) => sum + (typeof pkg.power === 'number' && pkg.power >= 0 ? pkg.power : 0),
+                    0
+                ) * 100
+            ) / 100;

         // Build CpuPackages object
         const packages: CpuPackages = {
-            totalpower,
+            totalPower: totalpower,
             power: packageList.map((pkg) => pkg.power ?? -1),
             temp: packageList.map((pkg) => pkg.temp ?? -1),
         };

Based on past review comments.


2-4: Remove unused imports.

The imports fsConstants, access, readFile, readdir, basename, and join are not used in this file. They appear to be leftover from code that was moved to CpuPowerService and CpuTopologyService.

Apply this diff:

 import { Injectable, Logger } from '@nestjs/common';
-import { constants as fsConstants } from 'node:fs';
-import { access, readdir, readFile } from 'node:fs/promises';
-import { basename, join } from 'node:path';

 import { cpu, cpuFlags, currentLoad } from 'systeminformation';
🧹 Nitpick comments (1)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

45-68: Consolidate duplicate CPU telemetry logic.

The telemetry calculation logic (lines 51-59) duplicates similar code in cpu.service.ts (lines 36-44). Both compute totalpower from packageList and build a CpuPackages object. Consider extracting this into a shared method in CpuTopologyService.

Additionally, this uses Number().toFixed(2) while cpu.service.ts uses Math.round() * 100 / 100 for the same purpose—standardize the rounding approach.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b530490 and 1fb3511.

📒 Files selected for processing (5)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (4 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
api/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.{test,spec}.{ts,tsx}: API test suite is Vitest; do not use Jest
Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to api/**/*.{test,spec}.{ts,tsx} : API test suite is Vitest; do not use Jest

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
🧬 Code graph analysis (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)
api/src/unraid-api/graph/resolvers/info/info.model.ts (1)
  • ObjectType (13-44)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Node (1371-1373)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (2)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-power.service.ts (1)
  • Injectable (8-136)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
  • Injectable (8-208)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)
api/src/core/pubsub.ts (1)
  • pubsub (12-12)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
🪛 GitHub Check: CodeQL
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts

[notice] 2-2: Unused variable, import, function or class
Unused import fsConstants.


[notice] 3-3: Unused variable, import, function or class
Unused imports access, readFile, readdir.


[notice] 4-4: Unused variable, import, function or class
Unused imports basename, join.

🔇 Additional comments (1)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

135-157: LGTM: New subscriptions correctly implemented.

The systemMetricsCpuPower and systemMetricsCpuTelemetry subscriptions follow the established pattern with proper permissions, resolve functions, and channel bindings.

Comment on lines 23 to 24
CpuPowerService,
CpuTopologyService,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Mock CpuPowerService and CpuTopologyService for unit tests.

These services read from the filesystem (/sys/class/powercap, /sys/devices/system/cpu) and are not simple dependencies. In unit tests, they should be mocked to avoid filesystem dependencies and ensure tests run in isolation.

Apply this diff:

-                CpuPowerService,
-                CpuTopologyService,
+                {
+                    provide: CpuPowerService,
+                    useValue: {
+                        generateCpuPower: vi.fn().mockResolvedValue({
+                            id: 'info/cpu-power',
+                            totalPower: 65.5,
+                            coresPower: [32.5, 33.0],
+                        }),
+                    },
+                },
+                {
+                    provide: CpuTopologyService,
+                    useValue: {
+                        generateTelemetry: vi.fn().mockResolvedValue([
+                            { id: 0, power: 32.5, temp: 45.0 },
+                            { id: 1, power: 33.0, temp: 46.0 },
+                        ]),
+                        generateTopology: vi.fn().mockResolvedValue([
+                            [[0, 1], [2, 3]],
+                            [[4, 5], [6, 7]],
+                        ]),
+                    },
+                },
📝 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
CpuPowerService,
CpuTopologyService,
{
provide: CpuPowerService,
useValue: {
generateCpuPower: vi.fn().mockResolvedValue({
id: 'info/cpu-power',
totalPower: 65.5,
coresPower: [32.5, 33.0],
}),
},
},
{
provide: CpuTopologyService,
useValue: {
generateTelemetry: vi.fn().mockResolvedValue([
{ id: 0, power: 32.5, temp: 45.0 },
{ id: 1, power: 33.0, temp: 46.0 },
]),
generateTopology: vi.fn().mockResolvedValue([
[[0, 1], [2, 3]],
[[4, 5], [6, 7]],
]),
},
},
🤖 Prompt for AI Agents
In api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts around
lines 23 to 24, CpuPowerService and CpuTopologyService are real
filesystem-reading services and must be replaced with test doubles; update the
test module setup to provide mock implementations (e.g., use providers with
useValue or jest mocks) for CpuPowerService and CpuTopologyService that return
deterministic, minimal data used by the resolver tests, and remove any reliance
on the real filesystem so tests run in isolation.

this.subscriptionTracker.registerTopic(
PUBSUB_CHANNEL.CPU_POWER,
async () => {
const payload = new CpuPower();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Initialize CpuPower with required id field.

CpuPower extends Node, which requires an id field. Creating an instance without setting id will cause GraphQL validation errors.

Apply this diff:

-                const payload = new CpuPower();
+                const payload = { id: 'info/cpu-power' } as CpuPower;
📝 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 payload = new CpuPower();
const payload = new CpuPower();
payload.id = 'info/cpu-power';
Suggested change
const payload = new CpuPower();
const payload = new CpuPower({ id: 'info/cpu-power' });
🤖 Prompt for AI Agents
In api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts around line
74, the code creates a new CpuPower without setting the required Node.id field;
update the instantiation to provide a valid id (for example a generated/derived
string or UUID) when constructing CpuPower so the object includes id before
being returned or used, ensuring GraphQL validation passes.

ARRAY = "ARRAY",
CPU_UTILIZATION = "CPU_UTILIZATION",
CPU_POWER = "CPU_POWER",
CPY_TELEMETRY = "CPU_TELEMETRY",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix typo in enum key name.

The enum key is misspelled as CPY_TELEMETRY (missing 'U') while the value correctly uses "CPU_TELEMETRY". This inconsistency can cause confusion and errors when referencing the enum.

Apply this diff:

-  CPY_TELEMETRY = "CPU_TELEMETRY",
+  CPU_TELEMETRY = "CPU_TELEMETRY",
📝 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
CPY_TELEMETRY = "CPU_TELEMETRY",
CPU_TELEMETRY = "CPU_TELEMETRY",
🤖 Prompt for AI Agents
packages/unraid-shared/src/pubsub/graphql.pubsub.ts around line 9: the enum key
is misspelled as CPY_TELEMETRY while the value is "CPU_TELEMETRY"; rename the
enum key to CPU_TELEMETRY to match the value, and update all references/usages
in the codebase to use the corrected enum key to avoid runtime/compile errors.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/unraid-shared/deploy.sh (2)

40-40: Replace eval with array-based command execution.

Using eval with constructed commands is error-prone and a security anti-pattern, even with quoted variables. Construct the rsync command as a bash array and invoke directly to avoid shell injection risks and improve reliability.

Apply this diff to remove eval:

-# Replace the value inside the rsync command with the user's input
-rsync_command="rsync -avz --delete --progress --stats -e ssh \"$source_directory/\" \"root@${server_name}:$destination_directory/\""
-
-echo "Executing the following command:"
-echo "$rsync_command"
-
-# Execute the rsync command and capture the exit code
-eval "$rsync_command"
-exit_code=$?
+# Execute rsync using array to avoid eval
+rsync_args=(
+  -avz
+  --delete
+  --progress
+  --stats
+  -e ssh
+  "$source_directory/"
+  "root@${server_name}:$destination_directory/"
+)
+
+echo "Executing: rsync ${rsync_args[*]}"
+rsync "${rsync_args[@]}"
+exit_code=$?

Also applies to: 46-46


56-65: Consider making sound notification optional for automated/CI environments.

The sound notifications (lines 56–65) are a nice developer ergonomic feature but may not be appropriate in CI/automated environments. Consider adding an environment variable to suppress them:

if [[ -z "$QUIET_DEPLOY" ]]; then
  if [[ "$OSTYPE" == "darwin"* ]]; then
    afplay /System/Library/Sounds/Glass.aiff
  elif [[ "$OSTYPE" == "linux-gnu" ]]; then
    paplay /usr/share/sounds/freedesktop/stereo/complete.oga
  elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
    powershell.exe -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Default.wav').PlaySync()"
  fi
fi
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 186bea3 and 26211cb.

📒 Files selected for processing (2)
  • packages/unraid-shared/deploy.sh (1 hunks)
  • packages/unraid-shared/package.json (1 hunks)
🔇 Additional comments (1)
packages/unraid-shared/package.json (1)

24-25: No issues found—deploy.sh has proper executable permissions.

The verification confirms that packages/unraid-shared/deploy.sh is tracked in git with mode 100755 (executable). The script reference in the npm script is correctly configured and ready for use.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (8)
api/generated-schema.graphql (3)

2659-2659: Regenerate GraphQL client typings for new subscription.

systemMetricsCpuTelemetry was added. Ensure CLI/web/generated clients are updated so Subscription types include it; otherwise type/runtime mismatches will occur.


1404-1413: Schema blocker: invalid scalar and missing id in CpuPackages.

  • totalPower: number is not valid GraphQL; use Float.
  • CpuPackages implements Node but lacks id: PrefixedID!.
  • Docstring malformed for temp.

Apply in the code-first model (api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts), then regenerate this file:

 type CpuPackages implements Node {
-  """Total CPU package power draw (W)"""
-  totalPower: number
+  id: PrefixedID!
+  """Total CPU package power draw (W)"""
+  totalPower: Float
   """Power draw per package (W)"""
   power: [Float!]
-  """description: 'Temperature per package (°C)"""
+  """Temperature per package (°C)"""
   temp: [Float!]
 }

Also ensure the TS class either implements Node or declares id with @Field(() => PrefixedID). Regenerate schema and client types afterward.


1460-1462: Expose topology on InfoCpu to match service output.

CpuService returns topology: number[][][] but the schema lacks this field. Add it to the model and regenerate.

 type InfoCpu implements Node {
   ...
   """CPU packages information"""
   packages: CpuPackages
+  """Per-package core/thread groupings (package -> cores -> thread IDs)"""
+  topology: [[[Int!]!]!]
   ...
 }
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (3)

2-4: Remove unused imports.

fsConstants, access/readdir/readFile, and basename/join are unused here. Clean them up.

-import { constants as fsConstants } from 'node:fs';
-import { access, readdir, readFile } from 'node:fs/promises';
-import { basename, join } from 'node:path';

32-33: Exclude sentinel values from total power.

Summing pkg.power ?? 0 includes -1 (unknown), underreporting totals. Ignore negatives.

-        const totalPower =
-            Math.round(packageList.reduce((sum, pkg) => sum + (pkg.power ?? 0), 0) * 100) / 100;
+        const totalPower =
+            Math.round(
+                packageList.reduce(
+                    (sum, pkg) => sum + (typeof pkg.power === 'number' && pkg.power >= 0 ? pkg.power : 0),
+                    0
+                ) * 100
+            ) / 100;

36-40: If CpuPackages implements Node, include id.

Once the model is corrected, return an id (e.g., 'info/cpu-packages') to satisfy the Node contract.

-        const packages: CpuPackages = {
-            totalPower,
-            power: packageList.map((pkg) => pkg.power ?? -1),
-            temp: packageList.map((pkg) => pkg.temp ?? -1),
-        };
+        const packages: CpuPackages = {
+            // id required if CpuPackages implements Node
+            // id: 'info/cpu-packages',
+            totalPower,
+            power: packageList.map((pkg) => pkg.power ?? -1),
+            temp: packageList.map((pkg) => pkg.temp ?? -1),
+        };
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (1)

166-172: Replace {} casts with typed stubs.

Construct MetricsResolver with a real stub for CpuTopologyService instead of {} as any.

-            const testModule = new MetricsResolver(
-                cpuService,
-                {} as any,
-                memoryService,
-                subscriptionTracker as any,
-                {} as any
-            );
+            const cpuTopologyStub = {
+                generateTelemetry: vi.fn().mockResolvedValue([{ id: 0, power: 10, temp: 40 }]),
+                generateTopology: vi.fn().mockResolvedValue([[[0, 1]]]),
+            } as unknown as CpuTopologyService;
+            const helperStub = { createTrackedSubscription: vi.fn() } as SubscriptionHelperService;
+            const testModule = new MetricsResolver(
+                cpuService,
+                cpuTopologyStub,
+                memoryService,
+                subscriptionTracker as any,
+                helperStub as any
+            );
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (1)

42-52: Make CpuPackages fields required to match non-nullable GraphQL schema.

The TypeScript properties are optional (?) but the @Field decorators don't specify nullable: true, meaning GraphQL expects these fields to always be present. This mismatch can cause runtime errors if any field is omitted.

Apply this diff:

 @ObjectType()
 export class CpuPackages {
     @Field(() => Float, { description: 'Total CPU package power draw (W)' })
-    totalPower?: number;
+    totalPower!: number;

     @Field(() => [Float], { description: 'Power draw per package (W)' })
-    power?: number[];
+    power!: number[];

     @Field(() => [Float], { description: 'Temperature per package (°C)' })
-    temp?: number[];
+    temp!: number[];
 }
🧹 Nitpick comments (4)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts (1)

8-8: Mock CpuTopologyService in integration tests to avoid FS flakiness.

Provide a lightweight stub via useValue (return fixed topology/telemetry) instead of the real service. This keeps tests hermetic and faster, especially on CI.

-                CpuTopologyService,
+                {
+                    provide: CpuTopologyService,
+                    useValue: {
+                        generateTelemetry: vi.fn().mockResolvedValue([
+                            { id: 0, power: 32.5, temp: 45.0 },
+                            { id: 1, power: 33.0, temp: 46.0 },
+                        ]),
+                        generateTopology: vi.fn().mockResolvedValue([
+                            [[0, 1], [2, 3]],
+                            [[4, 5], [6, 7]],
+                        ]),
+                    },
+                },

Also applies to: 26-26

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (2)

6-6: Provide a stubbed CpuTopologyService in unit tests.

Avoid the real FS-backed service; inject a simple stub via useValue.

-                CpuTopologyService,
+                {
+                    provide: CpuTopologyService,
+                    useValue: {
+                        generateTelemetry: vi.fn().mockResolvedValue([
+                            { id: 0, power: 32.5, temp: 45.0 },
+                            { id: 1, power: 33.0, temp: 46.0 },
+                        ]),
+                        generateTopology: vi.fn().mockResolvedValue([
+                            [[0, 1], [2, 3]],
+                            [[4, 5], [6, 7]],
+                        ]),
+                    },
+                },

Also applies to: 22-22


176-186: Also assert the CPU telemetry topic name.

You expect three registrations; verify the third is the new CPU telemetry channel to prevent regressions.

             testModule.onModuleInit();
 
             expect(subscriptionTracker.registerTopic).toHaveBeenCalledTimes(3);
             expect(subscriptionTracker.registerTopic).toHaveBeenCalledWith(
                 'CPU_UTILIZATION',
                 expect.any(Function),
                 1000
             );
+            expect(subscriptionTracker.registerTopic).toHaveBeenCalledWith(
+                'CPU_TELEMETRY',
+                expect.any(Function),
+                expect.any(Number)
+            );
             expect(subscriptionTracker.registerTopic).toHaveBeenCalledWith(
                 'MEMORY_UTILIZATION',
                 expect.any(Function),
                 2000
             );
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

61-61: Remove duplicate debug log.

This debug statement is redundant—the same payload was already logged on line 54.

Apply this diff:

                 pubsub.publish(PUBSUB_CHANNEL.CPU_TELEMETRY, {
                     systemMetricsCpuTelemetry: packages,
                 });
-
-                this.logger.debug(`CPU_TELEMETRY payload2: ${JSON.stringify(packages)}`);
             },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26211cb and 14a1665.

📒 Files selected for processing (10)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts
🧰 Additional context used
📓 Path-based instructions (6)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
api/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.{test,spec}.{ts,tsx}: API test suite is Vitest; do not use Jest
Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
PR: unraid/api#1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
🧬 Code graph analysis (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)
api/src/unraid-api/graph/resolvers/info/info.model.ts (1)
  • ObjectType (13-44)
api/src/unraid-api/graph/resolvers/metrics/metrics.model.ts (1)
  • ObjectType (8-21)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)
api/src/core/pubsub.ts (1)
  • pubsub (12-12)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (2)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
  • Injectable (8-208)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • InfoCpu (855-892)
🔇 Additional comments (3)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (2)

39-64: LGTM!

The CPU telemetry subscription registration is well-structured: it aggregates package power with appropriate precision, handles missing data with -1 fallbacks, and publishes the payload correctly.


110-120: LGTM!

The GraphQL subscription is properly configured with the correct return type, name, resolve function, and permissions.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (1)

116-122: LGTM!

The new topology and packages fields are correctly declared with definite assignment (!) to match their non-nullable GraphQL schema declarations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (1)

95-99: Fix mock: still has typo and missing required methods.

Despite being marked as addressed in a previous commit, the mock still defines generateCpuTotopology (typo) and lacks the methods CpuService actually calls: generateTelemetry() and generateTopology(). This will cause runtime failures.

Apply this diff to fix the mock:

-        cpuTopologyService = {
-            generateCpuTotopology: vi.fn().mockResolvedValue({ id: 'info/cpu-topology' }),
-        } as any;
+        cpuTopologyService = {
+            generateTelemetry: vi.fn().mockResolvedValue([
+                { id: 0, power: 32.5, temp: 45.0 },
+                { id: 1, power: 33.0, temp: 46.0 },
+            ]),
+            generateTopology: vi.fn().mockResolvedValue([
+                [[0, 1], [2, 3]],
+                [[4, 5], [6, 7]],
+            ]),
+        } as unknown as CpuTopologyService;

Based on learnings

api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)

195-195: Fix type inference: explicitly type result array.

TypeScript infers result as never[], causing the push on line 199 to fail type checking. This is blocking the pipeline.

-        const result = [];
+        const result: { id: number; power: number; temp: number }[] = [];
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)

27-28: Fix totalPower: exclude sentinel -1 values from sum.

pkg.power ?? 0 only handles null/undefined. When pkg.power is -1 (the sentinel for unavailable data), it gets added to the sum, producing incorrect totals.

         const totalPower =
-            Math.round(packageList.reduce((sum, pkg) => sum + (pkg.power ?? 0), 0) * 100) / 100;
+            Math.round(
+                packageList.reduce(
+                    (sum, pkg) => sum + (typeof pkg.power === 'number' && pkg.power >= 0 ? pkg.power : 0),
+                    0
+                ) * 100
+            ) / 100;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14a1665 and 894dab5.

📒 Files selected for processing (3)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
api/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.{test,spec}.{ts,tsx}: API test suite is Vitest; do not use Jest
Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
🧠 Learnings (1)
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
PR: unraid/api#1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
🧬 Code graph analysis (2)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
  • Injectable (8-208)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • InfoCpu (855-892)
web/composables/gql/graphql.ts (1)
  • InfoCpu (842-879)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
  • Injectable (12-70)
🪛 GitHub Actions: CI - Main (API)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts

[error] 199-199: TypeScript error TS2345: Argument of type '{ id: number; power: number; temp: number; }' is not assignable to parameter of type 'never'.


[error] 199-199: Command failed with exit code 1 while running 'pnpm run type-check' (tsc --noEmit).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)

53-53: Use consistent logging: prefer this.logger.warn.

This line still uses console.warn instead of the injected logger. The rest of the file consistently uses this.logger.warn.

-                console.warn('Topology read error for', dir, err);
+                this.logger.warn('Topology read error for', dir, err);

Based on coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 894dab5 and e4e48a1.

📒 Files selected for processing (2)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/generated-schema.graphql
🧰 Additional context used
📓 Path-based instructions (3)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
🧬 Code graph analysis (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
  • Injectable (12-70)
🪛 GitHub Check: Test API
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts

[failure] 5-5: src/unraid-api/app/test/app.module.integration.spec.ts
Error: [vitest] No "promises" export is defined on the "node:fs" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("node:fs"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts:5:39
❯ src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts:3:1

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (1)

94-113: Consider adding explicit mock clearing for consistency.

While the service mock is recreated in beforeEach, consider adding explicit mock clearing for the module mocks to follow the guideline about clearing mocks between tests.

As per coding guidelines.

     beforeEach(() => {
+        vi.clearAllMocks();
+
         cpuTopologyService = {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 322cfba and 4533741.

📒 Files selected for processing (1)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
api/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.{test,spec}.{ts,tsx}: API test suite is Vitest; do not use Jest
Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
PR: unraid/api#1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
🔇 Additional comments (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (1)

143-157: LGTM!

The test expectations correctly verify the new packages and topology structure, with totalPower properly computed as the sum of individual package powers and arrays matching the mock telemetry data.

pujitm and others added 15 commits October 27, 2025 11:17
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Signed-off-by: Simon Fairweather <simon.n.fairweather@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/generated-schema.graphql (2)

1415-1471: Regenerate schema to add missing topology field to InfoCpu.

The model declares topology!: number[][][]; but this field is missing from the InfoCpu type in the generated schema. Regenerate to sync.


2650-2662: Regenerate clients to include systemMetricsCpuTelemetry subscription.

Verification confirms that systemMetricsCpuTelemetry is defined in api/generated-schema.graphql but is absent from all generated client type files:

  • web/src/composables/gql/graphql.ts
  • api/src/unraid-api/cli/generated/graphql.ts
  • packages/unraid-api-plugin-connect/src/graphql/generated/client/graphql.ts

Other subscriptions are present in these files, confirming that codegen has run but is now out of sync with the schema. Run pnpm codegen to regenerate all clients and avoid type/runtime mismatches.

♻️ Duplicate comments (5)
packages/unraid-shared/deploy.sh (3)

37-37: Add error handling for SSH commands.

Lines 37 and 50 execute SSH operations without checking for failure. If these commands fail (connection issue, permission denied, or command execution failure), the script silently continues and may deploy a broken state. The issue was flagged in a previous review and remains unresolved.

Apply this diff to add error checking:

  # Create destination directory on remote server
  ssh root@"${server_name}" "mkdir -p $destination_directory"
+ if [ $? -ne 0 ]; then
+   echo "Error: Failed to create remote directory on $server_name!"
+   exit 1
+ fi
  
  # (... rsync execution ...)
  
  # Chown the directory
  ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api/node_modules/@unraid/"
+ if [ $? -ne 0 ]; then
+   echo "Error: Failed to change ownership on remote $server_name!"
+   exit 1
+ fi

Also applies to: 53-53

Also applies to: 50-50


50-50: Document SSH key setup and narrow ownership scope.

The script uses root@${server_name} for SSH and recursively changes permissions on the entire @unraid directory. This was also flagged previously and remains unaddressed. Ensure the following are documented:

  1. SSH key-based authentication must be configured (passwordless root access).
  2. Whether the full chown -R on the entire @unraid/ directory is necessary—consider narrowing to just the shared package to reduce blast radius.
  3. Root SSH access requirement and security implications.

Consider narrowing the chown scope:

- ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api/node_modules/@unraid/"
+ ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api/node_modules/@unraid/shared"

37-53: Fix exit code logic: SSH commands execute after rsync exit code is captured.

The script captures the rsync exit code at line 47, but then executes SSH commands on lines 50 and 53. If these SSH commands fail, the script exits with the rsync code (line 68), not the actual failure reason. This masks deployment failures and should be restructured so all critical operations check for success before proceeding.

Consider restructuring to check and exit on each critical operation:

  # Create destination directory on remote server
  ssh root@"${server_name}" "mkdir -p $destination_directory" || {
+   echo "Error: Failed to create remote directory!"
+   exit 1
+ }
  
  # ... rsync and other operations ...
  
  # Chown the directory
- ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api/node_modules/@unraid/"
+ ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api/node_modules/@unraid/" || {
+   echo "Error: Failed to change ownership!"
+   exit 1
+ }
  
  # Run unraid-api restart on remote host
- ssh root@"${server_name}" 'INTROSPECTION=true LOG_LEVEL=trace unraid-api restart'
+ ssh root@"${server_name}" 'INTROSPECTION=true LOG_LEVEL=trace unraid-api restart' || {
+   echo "Error: Failed to restart unraid-api!"
+   exit 1
+ }
  
- # Exit with the rsync command's exit code
- exit $exit_code
+ exit 0
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)

26-35: Exclude sentinel values when summing totalPower.

pkg.power ?? 0 will add -1, skewing totals. Only include non-negative numbers.

-        const totalPower =
-            Math.round(packageList.reduce((sum, pkg) => sum + (pkg.power ?? 0), 0) * 100) / 100;
+        const totalPower =
+            Math.round(
+                packageList.reduce(
+                    (sum, pkg) => sum + (typeof pkg.power === 'number' && pkg.power >= 0 ? pkg.power : 0),
+                    0,
+                ) * 100,
+            ) / 100;
api/generated-schema.graphql (1)

1404-1413: Schema invalid: CpuPackages implements Node but lacks id.

Either add id: PrefixedID! here or (preferred) update the code-first model to extend/implement Node and regenerate this file.

Suggested model fix (then regenerate):

-@ObjectType()
-export class CpuPackages {
+@ObjectType({ implements: () => Node })
+export class CpuPackages extends Node {
   @Field(() => Float, { description: 'Total CPU package power draw (W)' })
-  totalPower?: number;
+  totalPower!: number;
   @Field(() => [Float], { description: 'Power draw per package (W)' })
-  power?: number[];
+  power!: number[];
   @Field(() => [Float], { description: 'Temperature per package (°C)' })
-  temp?: number[];
+  temp!: number[];
 }
🧹 Nitpick comments (8)
packages/unraid-shared/deploy.sh (2)

40-40: Quote the destination path in rsync command.

Line 40 expands $destination_directory within a double-quoted eval string. Although the current path is safe, adding quotes prevents potential issues if the path ever contains spaces or special characters.

- rsync_command="rsync -avz --delete --progress --stats -e ssh \"$source_directory/\" \"root@${server_name}:$destination_directory/\""
+ rsync_command="rsync -avz --delete --progress --stats -e ssh \"$source_directory/\" \"root@${server_name}:${destination_directory}/\""

55-65: Handle missing sound files gracefully.

The sound notification uses hard-coded paths that may not exist on all systems (e.g., /usr/share/sounds/freedesktop/stereo/complete.oga may not be present on minimal Linux installations). Consider adding existence checks or using stderr redirection to suppress errors.

  if [[ "$OSTYPE" == "darwin"* ]]; then
    # macOS
-   afplay /System/Library/Sounds/Glass.aiff
+   afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || true
  elif [[ "$OSTYPE" == "linux-gnu" ]]; then
    # Linux
-   paplay /usr/share/sounds/freedesktop/stereo/complete.oga
+   paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || true
  elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
    # Windows
-   powershell.exe -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Default.wav').PlaySync()"
+   powershell.exe -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Default.wav').PlaySync()" 2>/dev/null || true
  fi
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (2)

95-110: Avoid as any on the service mock; use a typed Vitest mock.

Replace the unsafe cast with a precise mocked type to satisfy our “no any” guideline. Based on learnings.

-        cpuTopologyService = {
+        cpuTopologyService = {
             generateTopology: vi.fn().mockResolvedValue([
@@
             generateTelemetry: vi.fn().mockResolvedValue([
                 { power: 32.5, temp: 45.0 },
                 { power: 33.0, temp: 46.0 },
             ]),
-        } as any;
+        } as unknown as vi.Mocked<Pick<CpuTopologyService, 'generateTopology' | 'generateTelemetry'>>;

161-179: Prefer ts-expect-error over as any for partial systeminformation mock.

Document the intentional type bypass and drop as any. Based on learnings.

-            vi.mocked(cpu).mockResolvedValueOnce({
+            // @ts-expect-error test-only partial shape of systeminformation.cpu()
+            vi.mocked(cpu).mockResolvedValueOnce({
                 manufacturer: 'Intel',
@@
-            } as any);
+            });
api/src/unraid-api/graph/resolvers/info/info.module.ts (1)

4-6: Remove unused CpuService import.

CpuService is provided via CpuModule; this import is unused.

 import { CpuModule } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.module.js';
-import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
api/generated-schema.graphql (1)

1411-1412: Docstring typo.

Drop the accidental “description:” prefix.

-  """description: 'Temperature per package (°C)"""
+  """Temperature per package (°C)"""
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

3-6: Remove unused imports (CpuTopologyService, CpuService).

They aren’t referenced in this module file.

-import { CpuTopologyService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.js';
 import { CpuModule } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.module.js';
-import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

54-62: Drop duplicate debug log.

Two identical logs add noise.

-                this.logger.debug(`CPU_TELEMETRY payload: ${JSON.stringify(packages)}`);
@@
-                this.logger.debug(`CPU_TELEMETRY payload2: ${JSON.stringify(packages)}`);
+                this.logger.debug(`CPU_TELEMETRY payload: ${JSON.stringify(packages)}`);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dd49d1 and 1aa6525.

📒 Files selected for processing (15)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/info.module.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
  • packages/unraid-shared/deploy.sh (1 hunks)
  • packages/unraid-shared/package.json (1 hunks)
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.integration.spec.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
  • packages/unraid-shared/src/pubsub/graphql.pubsub.ts
  • api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts
🧰 Additional context used
📓 Path-based instructions (6)
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/info.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/info.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
api/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.{test,spec}.{ts,tsx}: API test suite is Vitest; do not use Jest
Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/info.module.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
🧠 Learnings (2)
📚 Learning: 2025-03-07T19:29:12.540Z
Learnt from: pujitm
PR: unraid/api#1219
File: api/src/unraid-api/main.ts:18-25
Timestamp: 2025-03-07T19:29:12.540Z
Learning: When handling type mismatches between dependencies in TypeScript (such as between NestJS and Fastify), prefer using `ts-expect-error` with a detailed explanation comment instead of `as any` type assertions. This better documents the reason for the type bypass and makes the issue more visible for future maintenance.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
PR: unraid/api#1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
🧬 Code graph analysis (4)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)
api/src/core/pubsub.ts (1)
  • pubsub (12-12)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)
api/src/unraid-api/graph/resolvers/info/info.model.ts (1)
  • ObjectType (13-44)
api/src/unraid-api/graph/resolvers/metrics/metrics.model.ts (1)
  • ObjectType (8-21)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
  • Injectable (6-211)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • InfoCpu (855-892)
web/composables/gql/graphql.ts (1)
  • InfoCpu (842-879)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (2)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)
  • Module (6-10)
api/src/unraid-api/graph/services/services.module.ts (1)
  • Module (8-13)
🪛 GitHub Check: CodeQL
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts

[notice] 3-3: Unused variable, import, function or class
Unused import CpuTopologyService.

🔇 Additional comments (5)
packages/unraid-shared/package.json (1)

20-26: Script configuration looks good.

The JSON is well-formed, and the new unraid:deploy script is properly registered. No concerns here.

api/src/unraid-api/graph/resolvers/info/info.module.ts (1)

18-18: LGTM on CpuModule wiring.

Importing CpuModule here is the right DI boundary.

api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

11-13: LGTM on DI: consume CpuService/CpuTopologyService via CpuModule.

Providers list is correct.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (1)

116-123: Definite assignment for topology and packages is correct.

This matches strict initialization and schema.

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

110-120: Subscription wiring looks correct.

Field name and payload key match; permissions applied.

@pujitm pujitm marked this pull request as ready for review November 14, 2025 17:56
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

59-67: Remove duplicate debug log statement.

Line 66 logs the same payload that was already logged on line 59. This is redundant.

Apply this diff:

                 this.logger.debug(`CPU_TELEMETRY payload: ${JSON.stringify(packages)}`);

                 // Publish the payload
                 pubsub.publish(PUBSUB_CHANNEL.CPU_TELEMETRY, {
                     systemMetricsCpuTelemetry: packages,
                 });
-
-                this.logger.debug(`CPU_TELEMETRY payload2: ${JSON.stringify(packages)}`);
             },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aa6525 and ba4d632.

📒 Files selected for processing (8)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/generated-schema.graphql
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-03-07T19:29:12.540Z
Learnt from: pujitm
Repo: unraid/api PR: 1219
File: api/src/unraid-api/main.ts:18-25
Timestamp: 2025-03-07T19:29:12.540Z
Learning: When handling type mismatches between dependencies in TypeScript (such as between NestJS and Fastify), prefer using `ts-expect-error` with a detailed explanation comment instead of `as any` type assertions. This better documents the reason for the type bypass and makes the issue more visible for future maintenance.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-09-09T14:34:07.189Z
Learnt from: pujitm
Repo: unraid/api PR: 1582
File: packages/unraid-shared/src/util/processing.ts:69-71
Timestamp: 2025-09-09T14:34:07.189Z
Learning: In TypeScript code, pujitm considers it acceptable to use `any` type for private/internal fields when the type is not exposed through the public API and is masked by properly typed method declarations. The focus is on ensuring the public interface maintains type safety rather than enforcing strict typing on all internal implementation details.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
Repo: unraid/api PR: 1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
📚 Learning: 2025-03-12T13:48:14.850Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
📚 Learning: 2025-04-21T18:44:39.643Z
Learnt from: elibosley
Repo: unraid/api PR: 1352
File: packages/unraid-api-plugin-connect/src/config.persistence.ts:0-0
Timestamp: 2025-04-21T18:44:39.643Z
Learning: When working with file operations in Node.js, prefer using the native Promise-based APIs from the fs/promises module instead of callback-based APIs from fs or manually wrapping callbacks in Promises.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-05-23T21:59:29.632Z
Learnt from: elibosley
Repo: unraid/api PR: 1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-03-13T16:17:55.820Z
Learnt from: elibosley
Repo: unraid/api PR: 1211
File: api/src/unraid-api/graph/connect/connect-settings.service.ts:48-53
Timestamp: 2025-03-13T16:17:55.820Z
Learning: In NestJS services, dynamic imports should be wrapped in try/catch blocks with proper error logging and fallback handling to prevent unhandled rejections from crashing the application.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-03-04T14:55:00.903Z
Learnt from: elibosley
Repo: unraid/api PR: 1151
File: plugin/builder/utils/consts.ts:6-6
Timestamp: 2025-03-04T14:55:00.903Z
Learning: The startingDir constant in plugin/builder/utils/consts.ts is defined using process.cwd(), which can cause issues if directory changes occur after importing this constant. Using __dirname.split('/builder')[0] would make it more reliable by making it relative to the file location rather than the current working directory.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-07-22T13:29:32.175Z
Learnt from: elibosley
Repo: unraid/api PR: 1526
File: api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts:82-85
Timestamp: 2025-07-22T13:29:32.175Z
Learning: In GraphQL subscriptions using the graphql-subscriptions PubSub, the correct method is `asyncIterableIterator()`, not `asyncIterator()`. The `asyncIterableIterator` method returns an `AsyncIterableIterator` that conforms to both `AsyncIterable` and `AsyncIterator` interfaces, which is required for GraphQL subscription resolvers. Using `asyncIterator()` would cause TypeScript type errors as it only returns an `AsyncIterator` that doesn't satisfy the `AsyncIterable` interface requirement.

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
📚 Learning: 2024-11-06T20:59:25.809Z
Learnt from: mdatelle
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
📚 Learning: 2025-03-10T18:43:13.016Z
Learnt from: mdatelle
Repo: unraid/api PR: 1219
File: api/src/unraid-api/types/fastify.ts:25-30
Timestamp: 2025-03-10T18:43:13.016Z
Learning: In the Unraid API, cookies should be marked as optional (with `?`) in the FastifyRequest type definition since the cookies may not always be present, which is consistent with the authentication flow throughout the codebase.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
🧬 Code graph analysis (5)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (2)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
  • Injectable (6-216)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • InfoCpu (855-892)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
  • Injectable (12-76)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)
api/src/core/pubsub.ts (1)
  • pubsub (12-12)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (3)
api/src/unraid-api/graph/resolvers/info/info.model.ts (1)
  • ObjectType (13-44)
api/src/unraid-api/graph/resolvers/metrics/metrics.model.ts (1)
  • ObjectType (8-21)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Node (1371-1373)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)
  • Module (6-10)
🪛 GitHub Actions: CI - Main (API)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts

[error] 119-119: AssertionError: expected { id: 'info/cpu', …(19) } to deeply equal { id: 'info/cpu', …(19) }

🔇 Additional comments (15)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)

42-52: LGTM! CpuPackages implementation is correct.

The type correctly implements Node, uses definite assignment assertions, and has consistent camelCase naming. All concerns from previous reviews have been addressed.


116-122: LGTM! New fields properly integrated.

The topology and packages fields are correctly typed with definite assignments and appropriate descriptions.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (1)

95-112: LGTM! Mock implementation is correct.

The cpuTopologyService mock properly implements both required methods (generateTopology and generateTelemetry) with appropriate return values. The as unknown as CpuTopologyService cast is acceptable for test mocks.

api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

3-11: LGTM! Proper module-based dependency injection.

The refactoring correctly imports CpuModule instead of directly providing CpuService. This follows NestJS best practices for module organization.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (3)

14-14: LGTM! Dependency injection properly configured.

CpuTopologyService is correctly injected via constructor.


22-41: LGTM! Telemetry gathering and totalPower calculation are correct.

The implementation properly:

  • Gathers telemetry and topology data
  • Filters out negative sentinel values (≥ 0) before summing power
  • Rounds to 2 decimal places
  • Includes required id field for the Node type
  • Maps arrays with -1 sentinel fallback

43-55: LGTM! Return structure correctly includes new fields.

The InfoCpu object now properly includes processors, packages, and topology fields as expected by the GraphQL schema.

api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (5)

1-8: LGTM! Proper service setup with logging.

Injectable decorator and Logger initialization follow NestJS best practices.


13-67: LGTM! Topology generation with proper error handling.

The implementation correctly:

  • Reads CPU topology from /sys/devices/system/cpu
  • Parses package IDs and thread siblings
  • Expands ranges (e.g., "0-3" → [0,1,2,3])
  • Deduplicates and sorts results
  • Returns empty array on failure with appropriate warning

72-109: LGTM! Temperature reading with defensive I/O.

The method properly reads hwmon sensors, filters for CPU temperature labels (coretemp, k10temp, zenpower), and converts millidegrees to Celsius. Error handling is defensive and uses the logger consistently.


111-191: LGTM! RAPL power measurement implementation is sound.

The implementation:

  • Correctly samples energy counters over a 100ms interval
  • Computes power in watts from energy delta and time delta
  • Aggregates domains per package with proper rounding
  • Handles file access failures gracefully

193-215: LGTM! Telemetry aggregation correctly combines power and temperature.

The method properly combines temperature and power data, uses -1 as sentinel for missing values, and returns a well-typed array.

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)

1-26: LGTM! Dependencies properly injected.

Logger and CpuTopologyService are correctly added to the resolver with proper typing.


39-58: LGTM! CPU telemetry publisher correctly implemented.

The topic registration properly:

  • Calls generateTelemetry to fetch per-package data
  • Filters negative values before computing totalPower
  • Rounds to 2 decimals
  • Builds CpuPackages with required id field
  • Uses 5-second polling interval

115-125: LGTM! Subscription handler properly configured.

The systemMetricsCpuTelemetry subscription is correctly set up with:

  • Proper return type (CpuPackages)
  • Correct resolve function
  • Required permissions decorator
  • Tracked subscription via helper service

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Investigate pipeline failure on line 119.

The test expectations look correct (32.5 + 33.0 = 65.5 for totalPower), but the pipeline reports an assertion failure. This could indicate a mismatch between the actual implementation output and the expected structure.

Run the test locally to see the full diff:


🏁 Script executed:

#!/bin/bash
# Run the specific test to see detailed output
cd api
npm test -- cpu.service.spec.ts --reporter=verbose

Length of output: 118


I'll examine the test file directly to understand the assertion failure at line 119.


---

🏁 Script executed:

```shell
#!/bin/bash
# Read the test file to see the actual test and what's at line 119
fd -e "cpu.service.spec.ts" -x cat {} | head -200 | tail -100

Length of output: 0


Let me search for the test file more broadly:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the test file
find . -name "cpu.service.spec.ts" -type f 2>/dev/null

Length of output: 65


Now let me read the test file to examine line 119 and the surrounding context:


---

🏁 Script executed:

```shell
#!/bin/bash
# Read the test file with line numbers, focusing on the relevant area
cat -n ./api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts | sed -n '100,170p'

Length of output: 2747


Let me examine the CpuService implementation to see what it actually returns:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the CpuService implementation file
find . -name "cpu.service.ts" -type f ! -name "*.spec.ts" 2>/dev/null

Length of output: 60



---

🏁 Script executed:

```shell
#!/bin/bash
# Read the CpuService implementation
cat -n ./api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts

Length of output: 2975


Let me check the CpuPackages type definition:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and read the cpu model file
find . -name "cpu.model.ts" -type f 2>/dev/null

Length of output: 58



---

🏁 Script executed:

```shell
#!/bin/bash
# Read the cpu model file to see CpuPackages definition
cat -n ./api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts

Length of output: 4916


Update test expectation to include id field in packages object.

The assertion fails because CpuPackages extends the Node base class (line 43 of cpu.model.ts), which provides an id field. The service correctly sets id: 'info/cpu/packages' on line 37 of cpu.service.ts, but the test expectation at lines 143-157 doesn't include this field. Add id: 'info/cpu/packages' to the packages object in the expected result to match the actual implementation output.

🤖 Prompt for AI Agents
In api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts around lines
143 to 157, the test expectation for the packages object is missing the id
field; update the expected packages object to include id: 'info/cpu/packages' so
it matches CpuPackages (which extends Node) and the service output that sets id
on line 37 of cpu.service.ts.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (1)

39-69: CPU telemetry topic logic is sound; consider trimming duplicate debug log

The CPU_TELEMETRY registration correctly:

  • uses generateTelemetry() as source of per-package data,
  • filters out negative values when summing totalPower,
  • builds a CpuPackages object with a stable Node id, and
  • publishes on the new CPU_TELEMETRY pubsub channel with systemMetricsCpuTelemetry payload key matching the subscription.

You can optionally drop one of the two this.logger.debug calls logging the same payload to reduce log noise.

api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)

193-215: Optional: parallelize temperature and power reads for telemetry

generateTelemetry currently does:

const temps = await this.getPackageTemps();
const powerData = await this.getPackagePower();

These are independent IO-bound operations; running them in parallel will slightly reduce the interval work:

-        const temps = await this.getPackageTemps();
-        const powerData = await this.getPackagePower();
+        const [temps, powerData] = await Promise.all([
+            this.getPackageTemps(),
+            this.getPackagePower(),
+        ]);

Not required for correctness, but a small win given this runs on a timer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aa6525 and ba4d632.

📒 Files selected for processing (8)
  • api/generated-schema.graphql (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.spec.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts
  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-03-12T13:48:14.850Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
  • api/generated-schema.graphql
📚 Learning: 2025-03-10T18:43:13.016Z
Learnt from: mdatelle
Repo: unraid/api PR: 1219
File: api/src/unraid-api/types/fastify.ts:25-30
Timestamp: 2025-03-10T18:43:13.016Z
Learning: In the Unraid API, cookies should be marked as optional (with `?`) in the FastifyRequest type definition since the cookies may not always be present, which is consistent with the authentication flow throughout the codebase.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts
📚 Learning: 2025-01-30T19:38:02.478Z
Learnt from: pujitm
Repo: unraid/api PR: 1075
File: unraid-ui/src/register.ts:15-34
Timestamp: 2025-01-30T19:38:02.478Z
Learning: In the web components registration process for unraid-ui, use a soft-fail approach (logging warnings/errors) instead of throwing errors, to ensure other components can still register successfully even if one component fails.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-04-21T18:44:39.643Z
Learnt from: elibosley
Repo: unraid/api PR: 1352
File: packages/unraid-api-plugin-connect/src/config.persistence.ts:0-0
Timestamp: 2025-04-21T18:44:39.643Z
Learning: When working with file operations in Node.js, prefer using the native Promise-based APIs from the fs/promises module instead of callback-based APIs from fs or manually wrapping callbacks in Promises.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-03-07T19:29:12.540Z
Learnt from: pujitm
Repo: unraid/api PR: 1219
File: api/src/unraid-api/main.ts:18-25
Timestamp: 2025-03-07T19:29:12.540Z
Learning: When handling type mismatches between dependencies in TypeScript (such as between NestJS and Fastify), prefer using `ts-expect-error` with a detailed explanation comment instead of `as any` type assertions. This better documents the reason for the type bypass and makes the issue more visible for future maintenance.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-05-23T21:59:29.632Z
Learnt from: elibosley
Repo: unraid/api PR: 1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
  • api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts
📚 Learning: 2025-03-13T16:17:55.820Z
Learnt from: elibosley
Repo: unraid/api PR: 1211
File: api/src/unraid-api/graph/connect/connect-settings.service.ts:48-53
Timestamp: 2025-03-13T16:17:55.820Z
Learning: In NestJS services, dynamic imports should be wrapped in try/catch blocks with proper error logging and fallback handling to prevent unhandled rejections from crashing the application.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-03-04T14:55:00.903Z
Learnt from: elibosley
Repo: unraid/api PR: 1151
File: plugin/builder/utils/consts.ts:6-6
Timestamp: 2025-03-04T14:55:00.903Z
Learning: The startingDir constant in plugin/builder/utils/consts.ts is defined using process.cwd(), which can cause issues if directory changes occur after importing this constant. Using __dirname.split('/builder')[0] would make it more reliable by making it relative to the file location rather than the current working directory.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-09-09T14:34:07.189Z
Learnt from: pujitm
Repo: unraid/api PR: 1582
File: packages/unraid-shared/src/util/processing.ts:69-71
Timestamp: 2025-09-09T14:34:07.189Z
Learning: In TypeScript code, pujitm considers it acceptable to use `any` type for private/internal fields when the type is not exposed through the public API and is masked by properly typed method declarations. The focus is on ensuring the public interface maintains type safety rather than enforcing strict typing on all internal implementation details.

Applied to files:

  • api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts
📚 Learning: 2025-07-22T13:29:32.175Z
Learnt from: elibosley
Repo: unraid/api PR: 1526
File: api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts:82-85
Timestamp: 2025-07-22T13:29:32.175Z
Learning: In GraphQL subscriptions using the graphql-subscriptions PubSub, the correct method is `asyncIterableIterator()`, not `asyncIterator()`. The `asyncIterableIterator` method returns an `AsyncIterableIterator` that conforms to both `AsyncIterable` and `AsyncIterator` interfaces, which is required for GraphQL subscription resolvers. Using `asyncIterator()` would cause TypeScript type errors as it only returns an `AsyncIterator` that doesn't satisfy the `AsyncIterable` interface requirement.

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
📚 Learning: 2024-11-06T20:59:25.809Z
Learnt from: mdatelle
Repo: unraid/api PR: 942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.

Applied to files:

  • api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts
🧬 Code graph analysis (4)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (1)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Node (1371-1373)
api/src/unraid-api/graph/resolvers/info/cpu/cpu-topology.service.ts (1)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.ts (1)
  • Injectable (12-76)
api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (3)
api/src/core/pubsub.ts (1)
  • pubsub (12-12)
api/src/unraid-api/cli/generated/graphql.ts (1)
  • Subscription (2046-2058)
web/composables/gql/graphql.ts (1)
  • Subscription (2032-2044)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (3)
api/src/unraid-api/graph/resolvers/info/cpu/cpu.module.ts (1)
  • Module (6-10)
api/src/unraid-api/graph/resolvers/info/info.module.ts (1)
  • Module (17-37)
api/src/unraid-api/graph/services/services.module.ts (1)
  • Module (8-13)
🔇 Additional comments (7)
api/src/unraid-api/graph/resolvers/metrics/metrics.module.ts (1)

3-10: MetricsModule wiring with CpuModule looks correct

Importing CpuModule and dropping CpuService from providers cleanly delegates CPU-related providers (including CpuTopologyService) to CpuModule, matching how InfoModule is wired and avoiding duplicate provider declarations. No issues here.

api/src/unraid-api/graph/resolvers/info/cpu/cpu.model.ts (2)

42-52: CpuPackages Node type aligns with resolvers and schema

CpuPackages correctly implements Node, uses non-null Float/[Float!]! fields, and matches how CpuService/MetricsResolver construct the payloads (including the required id). This keeps the schema and TS model in sync.


116-122: Non-null topology and packages on InfoCpu are consistent with CpuService

Marking topology and packages as non-null matches the CpuService implementation, which always returns an array (possibly empty) and a fully-populated CpuPackages (with sentinel -1 for missing readings). This avoids non-null GraphQL field failures while giving clients predictable shapes.

api/src/unraid-api/graph/resolvers/metrics/metrics.resolver.ts (2)

1-26: DI updates (logger + CpuTopologyService) are wired correctly

Implementing OnModuleInit, injecting CpuTopologyService, and adding a scoped Logger follow existing patterns in this project. Combined with CpuModule import in MetricsModule, CpuService and CpuTopologyService will resolve correctly.


115-125: New systemMetricsCpuTelemetry subscription matches pubsub topic and payload

The subscription decorator, payload key (systemMetricsCpuTelemetry), permissions, and tracked subscription on PUBSUB_CHANNEL.CPU_TELEMETRY are consistent with the registration in onModuleInit and with the CpuPackages GraphQL type.

api/generated-schema.graphql (2)

1394-1405: CpuPackages schema matches code-first model and Node contract

CpuPackages implements Node with id: PrefixedID! and non-null Float/[Float!]! fields, which aligns with the CpuPackages class in cpu.model.ts and how CpuService/MetricsResolver populate these values.


1471-1475: Client typings must be regenerated before merge

The new systemMetricsCpuTelemetry subscription field is missing from all generated GraphQL client typings (checked in api/src/unraid-api/cli/generated/graphql.ts, packages/unraid-api-plugin-connect/src/graphql/generated/client/graphql.ts, web/src/composables/gql/graphql.ts, and related files). Regenerate client typings to include the new CpuPackages type and systemMetricsCpuTelemetry subscription field before merging.

@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1745/dynamix.unraid.net.plg

@pujitm pujitm merged commit d7aca81 into main Nov 14, 2025
10 of 12 checks passed
@pujitm pujitm deleted the feat/cpu-power branch November 14, 2025 19:28
elibosley pushed a commit that referenced this pull request Nov 17, 2025
🤖 I have created a release *beep* *boop*
---


## [4.26.0](v4.25.3...v4.26.0)
(2025-11-17)


### Features

* add cpu power query & subscription
([#1745](#1745))
([d7aca81](d7aca81))
* add schema publishing to apollo studio
([#1772](#1772))
([7e13202](7e13202))
* add workflow_dispatch trigger to schema publishing workflow
([818e7ce](818e7ce))
* apollo studio readme link
([c4cd0c6](c4cd0c6))
* **cli:** make `unraid-api plugins remove` scriptable
([#1774](#1774))
([64eb9ce](64eb9ce))
* use persisted theme css to fix flashes on header
([#1784](#1784))
([854b403](854b403))


### Bug Fixes

* **api:** decode html entities before parsing notifications
([#1768](#1768))
([42406e7](42406e7))
* **connect:** disable api plugin if unraid plugin is absent
([#1773](#1773))
([c264a18](c264a18))
* detection of flash backup activation state
([#1769](#1769))
([d18eaf2](d18eaf2))
* re-add missing header gradient styles
([#1787](#1787))
([f8a6785](f8a6785))
* respect OS safe mode in plugin loader
([#1775](#1775))
([92af3b6](92af3b6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

3 participants