Skip to content

Share GlobalHealthBar via Module Federation#4875

Merged
bert-e merged 7 commits intodevelopment/133.0from
improvement/MK8S-219-share-health-bar
Apr 15, 2026
Merged

Share GlobalHealthBar via Module Federation#4875
bert-e merged 7 commits intodevelopment/133.0from
improvement/MK8S-219-share-health-bar

Conversation

@JeanMarcMilletScality
Copy link
Copy Markdown
Contributor

@JeanMarcMilletScality JeanMarcMilletScality commented Apr 10, 2026

Context

The new overview page in artesca has a Platform status section showing the global health bar

Summary

Extract the global health bar from the dashboard and move it in its own component
Use the new component in dashboard
Wrap the new component with its providers before sharing it via module federation

…t in a self-contained PlatformGlobalHealthBarFederated component that initialises the Prometheus client and provides the required context providers (MetricsTimeSpan, StartTime, IntlProvider). Expose the component in rspack
@JeanMarcMilletScality JeanMarcMilletScality requested a review from a team as a code owner April 10, 2026 17:01
@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 10, 2026

Hello jeanmarcmilletscality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 10, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

Peer approvals must include at least 1 approval from the following list:

duration: durationSeconds,
interval: frequencySeconds,
frequency: frequencySeconds, // StartTimeProvider reads this deprecated field
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The timeSpan object is recreated on every render, which gives MetricsTimeSpanContext.Provider a new reference each time and forces all context consumers (including the charts inside PlatformGlobalHealthBar) to re-render unnecessarily.

Memoize it:

```suggestion
const timeSpan = useMemo(() => ({
query: '',
label: '',
duration: durationSeconds,
interval: frequencySeconds,
frequency: frequencySeconds, // StartTimeProvider reads this deprecated field
}), [durationSeconds, frequencySeconds]);

<br>And add `useMemo` to the React import on line 3.<br><br>— Claude Code

@claude
Copy link
Copy Markdown

claude bot commented Apr 10, 2026

  • PlatformGlobalHealthBarFederated.tsx:39-45: the timeSpan context value is a new object on every render, causing unnecessary re-renders of all MetricsTimeSpanContext consumers. Wrap it in useMemo.

    Review by Claude Code

duration: durationSeconds,
interval: frequencySeconds,
frequency: frequencySeconds, // StartTimeProvider reads this deprecated field
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

timeSpan is recreated on every render, producing a new object reference each time. Since it is passed as the value to MetricsTimeSpanContext.Provider, every parent re-render will push a new context value and trigger re-renders in all consumers (StartTimeProvider, PlatformGlobalHealthBar, useMetricsTimeSpan).

Wrap it in useMemo:

suggestion const timeSpan = useMemo(() => ({ query: '', label: '', duration: durationSeconds, interval: frequencySeconds, frequency: frequencySeconds, // StartTimeProvider reads this deprecated field }), [durationSeconds, frequencySeconds]);
and add useMemo to the import on line 3.

— Claude Code

@claude
Copy link
Copy Markdown

claude bot commented Apr 10, 2026

  • timeSpan object in PlatformGlobalHealthBarFederated.tsx is not memoized — new reference on every render causes unnecessary re-renders of all MetricsTimeSpanContext consumers. Wrap in useMemo.

    Review by Claude Code

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 13, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

Peer approvals must include at least 1 approval from the following list:

initializePrometheus(prometheusUrl);
setHeaders({ Authorization: `Bearer ${token}` });
}
}, [prometheusUrl, token]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Race condition: initializePrometheus and setHeaders run in useEffect (after paint), but PlatformGlobalHealthBar fires its useQuery during the same render — before the effect executes. Since prometheusApiClient is still null, queryPrometheusRange returns undefined, React Query treats it as a successful response with no data, and the health bar stays empty until refetchInterval fires (60 s).

Fix by tracking initialization state so the child only renders once Prometheus is ready, and gate the render: if (!token || !isInitialized) return null;

— Claude Code

label: '',
duration: durationSeconds,
interval: frequencySeconds,
frequency: frequencySeconds, // StartTimeProvider reads this deprecated field
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment: StartTimeProvider was changed in this PR to read interval instead of frequency (see StartTimeProvider.tsx line 25). This field is no longer read by StartTimeProvider. Consider updating the comment to reflect the actual reason this field is kept (e.g., backward compatibility with the MetricsTimeSpanContext type).

— Claude Code

@claude
Copy link
Copy Markdown

claude bot commented Apr 13, 2026

  • PlatformGlobalHealthBarFederated.tsx:30-35 — Prometheus initialization race condition: useEffect runs after paint, but useQuery fires during the render, so the first query hits an uninitialized client and silently returns empty data for up to 60 seconds.
    • Track initialization state and gate child rendering on it: if (!token || !isInitialized) return null;
  • PlatformGlobalHealthBarFederated.tsx:43 — Stale comment claims StartTimeProvider reads the frequency field, but this PR changed it to read interval instead.
    • Update the comment to reflect the actual reason (e.g., backward compat with MetricsTimeSpanContext type).

Review by Claude Code

use iterval in StartTimeProvider instead of deprecated frequency
@JeanMarcMilletScality JeanMarcMilletScality force-pushed the improvement/MK8S-219-share-health-bar branch from 0a9a831 to 3fc94ab Compare April 13, 2026 14:27
Comment thread ui/src/components/PlatformGlobalHealthBarFederated.tsx
@claude
Copy link
Copy Markdown

claude bot commented Apr 13, 2026

  • PlatformGlobalHealthBarFederated is missing AlertProvider (and its AppConfigProvider dependency) — the inner component uses useAlertLibrary() and useHighestSeverityAlerts() which require the shell's alert system to be initialized. The existing federated component AlertNavbarUpdaterComponent wraps with AppConfigProvider + AlertProvider for this reason.
    • Follow the same wrapping pattern, or accept alertManagerUrl as a prop and initialize alerts directly.

Review by Claude Code

@claude
Copy link
Copy Markdown

claude bot commented Apr 13, 2026

LGTM

Clean extraction of the global health bar into a self-contained component. The federated wrapper correctly provides the MetalK8s-specific contexts (IntlProvider, MetricsTimeSpan, StartTime) while relying on the shell for QueryClientProvider and ShellHooksProvider — standard module federation practice. Provider ordering is correct (MetricsTimeSpanContext → StartTimeProvider → FederatedIntlProvider → PlatformGlobalHealthBar). The frequency→interval migration in StartTimeProvider aligns with the upstream core-ui deprecation.

Review by Claude Code

Comment on lines +30 to +35
useLayoutEffect(() => {
if (token) {
initializePrometheus(prometheusUrl);
setHeaders({ Authorization: `Bearer ${token}` });
}
}, [prometheusUrl, token]);
Copy link
Copy Markdown

@damiengillesscality damiengillesscality Apr 14, 2026

Choose a reason for hiding this comment

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

  • Since this component displays null when no token is available we should probably make token a property of the component and do conditional rendering, that would allow us to dodge some ifs.
  • Why is it a useLayoutEffect and not a useEffect ?
  • I don't see initializePrometheus defined anywhere in the code, there is function that looks like it with a different name tho.
  • From where will prometheusUrl come from ? Does it need to initialise the api so deep into the code ? This part can probably be bubled up or we will forget it and reinitialise it at different places causing more redraws.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

initializePrometheus is an alias for initialize from prometheus api:
import { initialize as initializePrometheus, setHeaders } from '../services/prometheus/api';

The component will be used in artesca, if the prometheus is not initialized the query will throw an error

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 14, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

Peer approvals must include at least 1 approval from the following list:

The following reviewers are expecting changes from the author, or must review again:

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 15, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

Peer approvals must include at least 1 approval from the following list:

@@ -0,0 +1,66 @@
import { MetricsTimeSpanContext } from '@scality/core-ui/dist/components/charts/MetricsTimeSpanProvider';
import { useShellHooks } from '@scality/module-federation';
import { useEffect, useLayoutEffect, useMemo } from 'react';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused import. useLayoutEffect is imported but never used.

```suggestion
import { useEffect, useMemo } from 'react';

[durationSeconds, frequencySeconds],
);

if (!token) return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Race condition: useEffect runs after paint, so on the first render where token is available, PlatformGlobalHealthBar mounts and useQuery fires before initializePrometheus executes. queryPrometheusRange finds prometheusApiClient still null and silently returns undefined.

Consider adding an initialization guard — e.g. an isInitialized state set to true inside the effect, and gate rendering on !token || !isInitialized.

(The unused useLayoutEffect import on line 3 suggests this was partially considered.)

— Claude Code

@JeanMarcMilletScality
Copy link
Copy Markdown
Contributor Author

/approve

@claude
Copy link
Copy Markdown

claude bot commented Apr 15, 2026

  • PlatformGlobalHealthBarFederated.tsx:3: Unused useLayoutEffect import
    • Remove it from the import statement
  • PlatformGlobalHealthBarFederated.tsx:36-55: Race condition — Prometheus client is initialized in useEffect (runs after paint) but PlatformGlobalHealthBar fires useQuery on mount, before the effect runs. The first query hits a null prometheusApiClient and silently returns undefined.
    • Add an isInitialized state flag set inside the effect, and gate rendering on !token || !isInitialized

Review by Claude Code

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 15, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

Peer approvals must include at least 1 approval from the following list:

The following options are set: approve

<MetricsTimeSpanContext.Provider value={timeSpan}>
<StartTimeProvider>
<FederatedIntlProvider>
<PlatformGlobalHealthBar title={title} />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PlatformGlobalHealthBar calls useHighestSeverityAlerts() and useAlertLibrary(), which both rely on useShellAlerts(). The other federated component (AlertNavbarUpdaterComponent) explicitly wraps its children with AlertProvider to ensure alert hooks work. This component omits it — is the host app guaranteed to have AlertsProvider in its tree above the mount point for this federated component? If not, these hooks may fail at runtime.

— Claude Code


/**
* Initialize the Prometheus client and set the authorization header if the token is available
* The initialization of Prometheus client is neeeded here as it is shared with Module Federation to another ui
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Typo: "neeeded" should be "needed".

— Claude Code

const HealthBarContainer = styled.div`
display: flex;
flex-direction: column;
`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The original HealthBarContainer had width: 90%; margin: 0 auto; which is now removed. This changes the dashboard layout — the health bar will render at full parent width instead of centered at 90%. Intentional?

— Claude Code

@claude
Copy link
Copy Markdown

claude bot commented Apr 15, 2026

  • PlatformGlobalHealthBarFederated does not wrap children with AlertProvider, unlike AlertNavbarUpdaterComponent. If the host app does not provide AlertsProvider above the mount point, useHighestSeverityAlerts() may fail at runtime.
    • Verify the host app guarantees AlertsProvider in the tree, or add an explicit wrapper.
  • HealthBarContainer lost width: 90% and margin: 0 auto from the original, changing the dashboard layout.
    • Confirm the visual change to the dashboard health bar is intentional.
  • Typo in comment: "neeeded" → "needed".

Review by Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread ui/src/components/PlatformGlobalHealthBarFederated.tsx
@claude
Copy link
Copy Markdown

claude bot commented Apr 15, 2026

  • PlatformGlobalHealthBarFederated is missing an AlertsProvider wrapping. The inner PlatformGlobalHealthBar calls useAlertLibrary() and useHighestSeverityAlerts() which depend on AlertsProvider (set up in App.tsx via AlertProvider). If the host app does not already provide it, the component will crash at runtime. Consider adding alertManagerUrl as a prop and wrapping with AlertsProvider, or documenting the host requirement.

    Review by Claude Code

@JeanMarcMilletScality
Copy link
Copy Markdown
Contributor Author

/status

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 15, 2026

Status

Status report is not available.

The following options are set: approve

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 15, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • 2 peers

Peer approvals must include at least 1 approval from the following list:

The following options are set: approve

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 15, 2026

In the queue

The changeset has received all authorizations and has been added to the
relevant queue(s). The queue(s) will be merged in the target development
branch(es) as soon as builds have passed.

The changeset will be merged in:

  • ✔️ development/133.0

The following branches will NOT be impacted:

  • development/123.0
  • development/124.0
  • development/124.1
  • development/125.0
  • development/126.0
  • development/127.0
  • development/128.0
  • development/129.0
  • development/130.0
  • development/131.0
  • development/132.0
  • development/2.0
  • development/2.1
  • development/2.10
  • development/2.11
  • development/2.2
  • development/2.3
  • development/2.4
  • development/2.5
  • development/2.6
  • development/2.7
  • development/2.8
  • development/2.9

There is no action required on your side. You will be notified here once
the changeset has been merged. In the unlikely event that the changeset
fails permanently on the queue, a member of the admin team will
contact you to help resolve the matter.

IMPORTANT

Please do not attempt to modify this pull request.

  • Any commit you add on the source branch will trigger a new cycle after the
    current queue is merged.
  • Any commit you add on one of the integration branches will be lost.

If you need this pull request to be removed from the queue, please contact a
member of the admin team now.

The following options are set: approve

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Apr 15, 2026

I have successfully merged the changeset of this pull request
into targetted development branches:

  • ✔️ development/133.0

The following branches have NOT changed:

  • development/123.0
  • development/124.0
  • development/124.1
  • development/125.0
  • development/126.0
  • development/127.0
  • development/128.0
  • development/129.0
  • development/130.0
  • development/131.0
  • development/132.0
  • development/2.0
  • development/2.1
  • development/2.10
  • development/2.11
  • development/2.2
  • development/2.3
  • development/2.4
  • development/2.5
  • development/2.6
  • development/2.7
  • development/2.8
  • development/2.9

Please check the status of the associated issue MK8S-219.

Goodbye jeanmarcmilletscality.

@bert-e bert-e merged commit 297c882 into development/133.0 Apr 15, 2026
31 checks passed
@bert-e bert-e deleted the improvement/MK8S-219-share-health-bar branch April 15, 2026 20:02
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.

4 participants