Skip to content

fix(ups): convert estimatedRuntime from minutes to seconds#1822

Merged
elibosley merged 3 commits into
unraid:mainfrom
jandrop:fix/ups-estimated-runtime-unit
Dec 15, 2025
Merged

fix(ups): convert estimatedRuntime from minutes to seconds#1822
elibosley merged 3 commits into
unraid:mainfrom
jandrop:fix/ups-estimated-runtime-unit

Conversation

@jandrop

@jandrop jandrop commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

Summary

Fixes the estimatedRuntime field in the UPS GraphQL query to return values in seconds as documented, instead of minutes.

Problem

The TIMELEFT value from apcupsd is returned in minutes (e.g., 6.0 for 6 minutes), but the GraphQL schema documentation states:

Estimated runtime remaining on battery power. Unit: seconds. Example: 3600 means 1 hour of runtime remaining

Currently, the API returns 6 (minutes) instead of 360 (seconds).

Solution

Convert the TIMELEFT value from minutes to seconds by multiplying by 60:

// Before
estimatedRuntime: parseInt(upsData.TIMELEFT || '3600', 10),

// After
estimatedRuntime: Math.round(parseFloat(upsData.TIMELEFT || '60') * 60),

Testing

  1. Query upsDevices before the fix → estimatedRuntime: 6 (incorrect - minutes)
  2. Query upsDevices after the fix → estimatedRuntime: 360 (correct - seconds)

Tested on Unraid server with APC UPS connected via apcupsd.

Related Issues

Fixes #1821

Summary by CodeRabbit

  • Bug Fixes
    • Corrected UPS battery runtime calculation to interpret provider TIMELEFT as minutes, convert to seconds, and use a sensible default when missing—improves displayed battery runtime accuracy.
  • Tests
    • Updated UPS test fixtures to match the minute-based TIMELEFT format used by the UPS provider.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

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

UPS runtime calculation changed to parse TIMELEFT as minutes (float), multiply by 60 and round to produce seconds; the fallback default was adjusted from '3600' (seconds) to '60' (minutes).

Changes

Cohort / File(s) Summary
UPS Runtime Calculation Fix
api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts
Replaced `parseInt(upsData.TIMELEFT
Test fixture update
api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts
Updated mock UPS TIMELEFT value from '3600' to '60' to reflect apcupsd minutes format used by the resolver change.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Check parsing/rounding for fractional minutes and non-numeric TIMELEFT.
  • Confirm tests reflect expected seconds in GraphQL responses and schema documentation.

Poem

🐰 I hopped into the code tonight, TIMELEFT in hand,
Minutes turned to seconds at my gentle command.
I multiplied by sixty, rounded with care,
Now the UPS speaks seconds—accurate and fair.
Nibbles of change, and the logs all stand.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main fix: converting estimatedRuntime from minutes to seconds in the UPS resolver.
Linked Issues check ✅ Passed The PR implements all coding objectives from issue #1821: converts TIMELEFT from minutes to seconds, changes the default value appropriately, and updates tests to reflect the fix.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving the TIMELEFT conversion issue; the resolver logic change and test fixture update are both necessary and related to the stated objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 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 e87a93a and b605030.

📒 Files selected for processing (2)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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

24-27: LGTM! Conversion logic correctly fixes the unit mismatch.

The conversion from minutes to seconds is mathematically correct, and the default value of '60' minutes appropriately maintains the 3600-second (1-hour) fallback. The comment provides valuable context about the external apcupsd format.

Optional: Consider defensive handling for invalid input.

If upsData.TIMELEFT is an empty string or non-numeric, parseFloat returns NaN, resulting in estimatedRuntime: NaN. The || operator only catches null/undefined, not invalid strings. While the old code had the same limitation and apcupsd should provide valid data in normal operation, you could add a fallback:

 // Convert TIMELEFT from minutes (apcupsd format) to seconds
-estimatedRuntime: Math.round(
-    parseFloat(upsData.TIMELEFT || '60') * 60
-),
+estimatedRuntime: Math.round(
+    (parseFloat(upsData.TIMELEFT || '60') || 60) * 60
+),

This ensures that any NaN from parseFloat falls back to 60 minutes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23a7120 and 25766ff.

📒 Files selected for processing (1)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts
api/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 expects TTL values in milliseconds, not seconds (e.g., 600000 for 10 minutes, not 600)

Files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:51:37.915Z
Learnt from: CR
Repo: unraid/api PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T17:51:37.915Z
Learning: Applies to api/**/*.{ts,tsx} : cache-manager v7 expects TTL values in milliseconds, not seconds (e.g., 600000 for 10 minutes, not 600)

Applied to files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts

@elibosley

Copy link
Copy Markdown
Member

@jandrop Thanks so much for your contribution! For consistency, and to avoid breaking changes with consumers, why don't we just change the comment on this to tell users that the value is returned in minutes (matching apcupsd)?

We'd love to get this in if you change to that approach, your contribution is much appreciated!

@jandrop

jandrop commented Dec 8, 2025

Copy link
Copy Markdown
Contributor Author

@jandrop Thanks so much for your contribution! For consistency, and to avoid breaking changes with consumers, why don't we just change the comment on this to tell users that the value is returned in minutes (matching apcupsd)?

We'd love to get this in if you change to that approach, your contribution is much appreciated!

Hi
My PR was simply trying to keep things consistent with NUT itself, since NUT reports the remaining runtime in seconds.

If the project prefers to keep returning minutes for compatibility reasons, that's totally fine.

Feel free to reject/close this PR — I honestly have no idea where the documentation for this part is located, so it's definitely easier if someone from the team updates it directly to avoid future confusion.

Thanks anyway!

@pujitm

pujitm commented Dec 9, 2025

Copy link
Copy Markdown
Member

Just in case this ends up in someone's google search, for future reference, the graphql schema documentation lives within the code, inside *.model.ts files which contain the graphql type definitions & validators.

In this case, the incorrect documentation is coming from ups.model.ts 🔨

@pujitm

pujitm commented Dec 9, 2025

Copy link
Copy Markdown
Member

Good news! We decided to go proceed based on NUT standards as you've suggested. If the workflows pass, we can merge it in.

@jandrop

jandrop commented Dec 9, 2025

Copy link
Copy Markdown
Contributor Author

@jandrop Thanks so much for your contribution! For consistency, and to avoid breaking changes with consumers, why don't we just change the comment on this to tell users that the value is returned in minutes (matching apcupsd)?

We'd love to get this in if you change to that approach, your contribution is much appreciated!

I'm glad to hear that.

Copilot AI review requested due to automatic review settings December 10, 2025 20:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes the estimatedRuntime field in the UPS battery data to correctly return values in seconds instead of minutes, aligning with the GraphQL schema documentation that specifies the unit should be seconds.

Key Changes:

  • Converted TIMELEFT value from apcupsd (minutes) to seconds by multiplying by 60
  • Changed parsing from parseInt to parseFloat with Math.round to handle decimal minute values
  • Updated the default fallback value from '3600' to '60' (maintaining 3600 seconds as the default)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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

21-29: Fixture TIMELEFT now matches apcupsd minutes; consider adding an assertion on estimatedRuntime

The change to TIMELEFT: '60' with the minutes-format comment looks correct and consistent with the resolver’s new “minutes → seconds” conversion.

However, this spec still doesn’t assert anything about estimatedRuntime, so a regression like the original bug (returning minutes instead of seconds) would not be caught here. Consider extending the upsDevices test to also verify the computed runtime, for example by checking that result[0].estimatedRuntime equals the expected seconds value derived from this TIMELEFT input.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25766ff and e87a93a.

📒 Files selected for processing (2)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Always use TypeScript imports with .js extensions for ESM compatibility
Never add comments unless they are needed for clarity of function
Never add comments for obvious things, and avoid commenting when starting and ending code blocks

Files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts
api/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never use the any type. Always prefer proper typing
Avoid using casting whenever possible, prefer proper typing from the start

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 expects TTL values in milliseconds, not seconds (e.g., 600000 for 10 minutes, not 600)

Files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/default.mdc)

Never add comments unless they are needed for clarity of function

Files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-01-27T14:57:46.617Z
Learnt from: elibosley
Repo: unraid/api PR: 1063
File: web/_data/serverState.ts:137-147
Timestamp: 2025-01-27T14:57:46.617Z
Learning: The values in `web/_data/serverState.ts` are used for testing purposes and should remain as hardcoded mock data to facilitate testing different scenarios.

Applied to files:

  • api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts

The TIMELEFT value from apcupsd is returned in minutes, but the GraphQL
schema documentation states it should be in seconds.

This fix converts the value by multiplying by 60 to match the documented
API contract.

Fixes unraid#1821
@elibosley elibosley force-pushed the fix/ups-estimated-runtime-unit branch from e87a93a to b605030 Compare December 13, 2025 16:30
@codecov

codecov Bot commented Dec 13, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.04%. Comparing base (317e0fa) to head (b605030).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1822   +/-   ##
=======================================
  Coverage   52.04%   52.04%           
=======================================
  Files         876      876           
  Lines       50509    50510    +1     
  Branches     5023     5023           
=======================================
+ Hits        26285    26286    +1     
  Misses      24149    24149           
  Partials       75       75           

☔ 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.

@pujitm

pujitm commented Dec 15, 2025

Copy link
Copy Markdown
Member

@elibosley could a cloudflare perms/secrets access issue (CF_ENDPOINT) be blocking the upload in the "Build and Deploy Plugin" workflow?

@elibosley elibosley merged commit 024ae69 into unraid:main Dec 15, 2025
12 of 16 checks passed
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.

Bug: estimatedRuntime returns minutes instead of seconds in UpsDevices query

4 participants