Skip to content

Initialize filter on Run job envs page based on what tokens user holds#516

Open
andreip136 wants to merge 1 commit into
mainfrom
feat/filter-envs-user-tokens
Open

Initialize filter on Run job envs page based on what tokens user holds#516
andreip136 wants to merge 1 commit into
mainfrom
feat/filter-envs-user-tokens

Conversation

@andreip136

@andreip136 andreip136 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Fixes # .

Changes proposed in this PR:

Summary by CodeRabbit

  • Bug Fixes

    • Form environment fields now properly reinitialize when input values change.
  • New Features

    • Automatic token balance detection with fee token auto-selection when exactly one token is available in your account.

@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nodes-dashboard Ready Ready Preview, Comment Jun 14, 2026 1:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

RunJobEnvsProvider gains a useEffect that fetches token balances and escrow funds for all supported tokens using useOceanAccount, then auto-sets filters.feeToken when exactly one token is held. SelectEnvironment's Formik config adds enableReinitialize: true so the form reinitializes when those filters change.

Changes

Auto fee token detection and form sync

Layer / File(s) Summary
Token balance fetch and feeToken auto-selection
src/context/run-job-envs-context.tsx
Imports useOceanAccount and adds a useEffect that fetches balances and escrow for all supported tokens in parallel via Promise.all, then sets filters.feeToken to the single held token address if exactly one token is held; a cancellation flag prevents stale updates after unmount.
Formik reinitialization for updated feeToken
src/components/run-job/select-environment.tsx
Adds enableReinitialize: true to the useFormik configuration so the form resets its initial values when the auto-selected feeToken from the context propagates through.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Hop hop, the tokens align,
One coin in the wallet — that's the sign!
The form now wakes when values shift,
Reinitialize — what a gift!
No stale state left behind today,
The rabbit checked the balance. Hooray! 🪙

🚥 Pre-merge checks | ✅ 5
✅ 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 accurately describes the main objective of the PR, which is initializing filters on the Run job environments page based on user-held tokens.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/filter-envs-user-tokens

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
src/components/run-job/select-environment.tsx (1)

68-73: ⚠️ Potential issue | 🟠 Major

free must be derived from the filters context like other field values.

With enableReinitialize: true, hard-coding initialValues.free to false causes the form to reset the toggle to unchecked whenever filters change, even if filters.free is true. This desyncs the UI from the active filter state in the context.

Suggested fix
     initialValues: {
       feeToken: Array.isArray(filters.feeToken) ? '' : (filters.feeToken ?? ''),
-      free: false,
+      free: filters.free ?? false,
       gpuName: filters.gpuName ?? [],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/run-job/select-environment.tsx` around lines 68 - 73, The
`free` field in the formik initialValues is hardcoded to `false`, which causes
the form to reset whenever filters change due to `enableReinitialize: true`,
even when `filters.free` is `true`. This desynchronizes the UI from the active
filter state. In the useFormik hook's initialValues object within the
select-environment.tsx component, change the `free` field from `free: false` to
derive it from the `filters` context like the other field values (e.g., use
`free: filters.free ?? false`), ensuring the form state reflects the actual
filter state when it reinitializes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/context/run-job-envs-context.tsx`:
- Around line 73-75: The setFilters call in the effect is overwriting the entire
filter state with DEFAULT_FILTERS and only the computed feeToken, which can wipe
out any active user filters set in between when the async effect resolves.
Instead of using setFilters({ ...DEFAULT_FILTERS, feeToken }), use a functional
state update approach with setFilters that preserves the previous filter state
and only updates the feeToken property. This ensures existing user filters
remain intact and are not lost when this effect completes asynchronously.
- Around line 60-75: The Promise.all() chain in the tokenEntries mapping lacks
error handling for failures in ocean.getBalance() or ocean.getUserFunds() calls.
Add a .catch() handler after the existing .then() block to gracefully handle any
promise rejections. The catch handler should log the error appropriately and
either set the filters with default values or skip the filter update to prevent
unhandled promise rejections and ensure the component remains functional even if
token balance fetching fails.

---

Outside diff comments:
In `@src/components/run-job/select-environment.tsx`:
- Around line 68-73: The `free` field in the formik initialValues is hardcoded
to `false`, which causes the form to reset whenever filters change due to
`enableReinitialize: true`, even when `filters.free` is `true`. This
desynchronizes the UI from the active filter state. In the useFormik hook's
initialValues object within the select-environment.tsx component, change the
`free` field from `free: false` to derive it from the `filters` context like the
other field values (e.g., use `free: filters.free ?? false`), ensuring the form
state reflects the actual filter state when it reinitializes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba521a2d-990d-4825-8320-1242b008b08b

📥 Commits

Reviewing files that changed from the base of the PR and between 46e6245 and 5d62ec2.

📒 Files selected for processing (2)
  • src/components/run-job/select-environment.tsx
  • src/context/run-job-envs-context.tsx

Comment on lines +60 to +75
Promise.all(
tokenEntries.map(async (token) => {
const [walletBalance, escrowBalance] = await Promise.all([
ocean.getBalance(token.address, account.address!),
ocean.getUserFunds(token.address, account.address!),
]);
return { address: token.address, hasBalance: Number(walletBalance) > 0 || Number(escrowBalance) > 0 };
})
).then((results) => {
if (cancelled) {
return;
}
const held = results.filter((r) => r.hasBalance);
const feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
setFilters({ ...DEFAULT_FILTERS, feeToken });
});

Copy link
Copy Markdown

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

🏁 Script executed:

cat -n src/context/run-job-envs-context.tsx | head -100

Repository: oceanprotocol/nodes-dashboard

Length of output: 4360


🏁 Script executed:

cat -n src/context/run-job-envs-context.tsx | sed -n '53,85p'

Repository: oceanprotocol/nodes-dashboard

Length of output: 1593


🏁 Script executed:

# Check if there's a .catch() handler after the .then() that might continue beyond line 75
cat -n src/context/run-job-envs-context.tsx | sed -n '60,80p'

Repository: oceanprotocol/nodes-dashboard

Length of output: 961


Handle token-balance fetch failures explicitly.

The Promise.all() chain on lines 60–75 lacks error handling. If either ocean.getBalance() or ocean.getUserFunds() fails, the entire promise rejects without a .catch() handler, causing an unhandled promise rejection and preventing the fee token filter from auto-populating.

Suggested fix
-    Promise.all(
-      tokenEntries.map(async (token) => {
+    const loadHeldTokens = async () => {
+      const results = await Promise.allSettled(
+        tokenEntries.map(async (token) => {
           const [walletBalance, escrowBalance] = await Promise.all([
             ocean.getBalance(token.address, account.address!),
             ocean.getUserFunds(token.address, account.address!),
           ]);
           return { address: token.address, hasBalance: Number(walletBalance) > 0 || Number(escrowBalance) > 0 };
-      })
-    ).then((results) => {
+        })
+      );
+
       if (cancelled) {
         return;
       }
-      const held = results.filter((r) => r.hasBalance);
+      const held = results
+        .filter((r): r is PromiseFulfilledResult<{ address: string; hasBalance: boolean }> => r.status === 'fulfilled')
+        .map((r) => r.value)
+        .filter((r) => r.hasBalance);
       const feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
       setFilters({ ...DEFAULT_FILTERS, feeToken });
-    });
+    };
+
+    loadHeldTokens().catch((error) => {
+      console.error('Failed to auto-detect fee token:', error);
+    });
📝 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
Promise.all(
tokenEntries.map(async (token) => {
const [walletBalance, escrowBalance] = await Promise.all([
ocean.getBalance(token.address, account.address!),
ocean.getUserFunds(token.address, account.address!),
]);
return { address: token.address, hasBalance: Number(walletBalance) > 0 || Number(escrowBalance) > 0 };
})
).then((results) => {
if (cancelled) {
return;
}
const held = results.filter((r) => r.hasBalance);
const feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
setFilters({ ...DEFAULT_FILTERS, feeToken });
});
const loadHeldTokens = async () => {
const results = await Promise.allSettled(
tokenEntries.map(async (token) => {
const [walletBalance, escrowBalance] = await Promise.all([
ocean.getBalance(token.address, account.address!),
ocean.getUserFunds(token.address, account.address!),
]);
return { address: token.address, hasBalance: Number(walletBalance) > 0 || Number(escrowBalance) > 0 };
})
);
if (cancelled) {
return;
}
const held = results
.filter((r): r is PromiseFulfilledResult<{ address: string; hasBalance: boolean }> => r.status === 'fulfilled')
.map((r) => r.value)
.filter((r) => r.hasBalance);
const feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
setFilters({ ...DEFAULT_FILTERS, feeToken });
};
loadHeldTokens().catch((error) => {
console.error('Failed to auto-detect fee token:', error);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/context/run-job-envs-context.tsx` around lines 60 - 75, The Promise.all()
chain in the tokenEntries mapping lacks error handling for failures in
ocean.getBalance() or ocean.getUserFunds() calls. Add a .catch() handler after
the existing .then() block to gracefully handle any promise rejections. The
catch handler should log the error appropriately and either set the filters with
default values or skip the filter update to prevent unhandled promise rejections
and ensure the component remains functional even if token balance fetching
fails.

Comment on lines +73 to +75
const feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
setFilters({ ...DEFAULT_FILTERS, feeToken });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid overwriting the full filter state during auto-selection.

Setting setFilters({ ...DEFAULT_FILTERS, feeToken }) can wipe active user filters when this async effect resolves later, which then propagates into form reset/reload behavior.

Suggested fix
-      const feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
-      setFilters({ ...DEFAULT_FILTERS, feeToken });
+      const feeToken = held.length === 1 ? held[0].address : undefined;
+      setFilters((prev) => {
+        const nextFeeToken = feeToken ?? prev.feeToken ?? DEFAULT_FILTERS.feeToken;
+        if (prev.feeToken === nextFeeToken) return prev;
+        return { ...prev, feeToken: nextFeeToken };
+      });
📝 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 feeToken = held.length === 1 ? held[0].address : DEFAULT_FILTERS.feeToken;
setFilters({ ...DEFAULT_FILTERS, feeToken });
});
const feeToken = held.length === 1 ? held[0].address : undefined;
setFilters((prev) => {
const nextFeeToken = feeToken ?? prev.feeToken ?? DEFAULT_FILTERS.feeToken;
if (prev.feeToken === nextFeeToken) return prev;
return { ...prev, feeToken: nextFeeToken };
});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/context/run-job-envs-context.tsx` around lines 73 - 75, The setFilters
call in the effect is overwriting the entire filter state with DEFAULT_FILTERS
and only the computed feeToken, which can wipe out any active user filters set
in between when the async effect resolves. Instead of using setFilters({
...DEFAULT_FILTERS, feeToken }), use a functional state update approach with
setFilters that preserves the previous filter state and only updates the
feeToken property. This ensures existing user filters remain intact and are not
lost when this effect completes asynchronously.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant