Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .changeset/cost-insights-tabs-and-chart-ux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
'@openchoreo/backstage-plugin-openchoreo-observability': minor
'@openchoreo/backstage-plugin-react': minor
'@openchoreo/backstage-portal-app': minor
---

Reorganise the Cost Insights view into tabs and improve the graph/tooltip UX.

- **Tabs**: the Cost Insights page now hosts two tabs — **Insights** (the
existing table/graph views) and **Cost Analysis** (the FinOps report list,
moved here from the project catalog entity page). The Cost Analysis tab
reuses the existing `CostAnalysisPage` via a synthesised entity context and
only enables its reports once a project scope is selected. The route is now
`/cost-insights/*`, and the Incidents "View Cost Analysis" deep link points
to the new location. The Cost Analysis tab was removed from the catalog
system page (both the legacy `EntityPage` and the new-frontend-system
`alpha` registration).
- **Consistent header**: extracted the catalog entity header's gradient bar
into a reusable `GradientPageHeader` (exported from
`@openchoreo/backstage-plugin-react`), and used it for the Cost Insights
header so its purple bar, title sizing and tab seam match the catalog.
`CompactEntityHeader` now consumes the same shell. Breadcrumb level labels
are pluralised (`namespaces` / `projects` / `components`) to match the
catalog.
- **Overview summary card**: the catalog Overview tab now shows a Cost
Insights summary card at both the project and component levels,
displaying the last-24-hour total cost (reusing the Total Cost card and,
for a component, summed across its environments) with a "Go to Cost
Insights" button that deep-links into the full view.
- **Chart tooltips**: the stacked bar chart and the line chart tooltips now
show the **Total** of the visible series and **highlight the row** for the
segment/line under the pointer.
- **Forecast clarity**: the "Forecast this month" summary card and the spend
forecast chart gained an info tooltip explaining that the forecast projects
the selected time window's rate across the month, so it can change with the
chosen range and the amount of data available.
Comment thread
nilushancosta marked this conversation as resolved.
23 changes: 17 additions & 6 deletions packages/portal-app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ import {
ObservabilityAlerts,
ObservabilityWirelogs,
ObservabilityProjectIncidents,
ObservabilityCostAnalysis,
ObservabilityCostInsightsSummaryCard,
useComponentHasAnyCiliumEnabledEnvironment,
type RenderLogRowAction,
} from '@openchoreo/backstage-plugin-openchoreo-observability';
Expand Down Expand Up @@ -327,6 +327,15 @@ function OverviewContent() {
<Grid item md={6} xs={12}>
<EntityCatalogGraphCard height={400} renderNode={CustomGraphNode} />
</Grid>
<EntitySwitch>
<EntitySwitch.Case if={isKind('component')}>
<FeatureGate feature="observability">
<Grid item md={3} sm={6} xs={12}>
<ObservabilityCostInsightsSummaryCard />
</Grid>
</FeatureGate>
</EntitySwitch.Case>
</EntitySwitch>
</Grid>
);
}
Expand Down Expand Up @@ -735,6 +744,13 @@ const systemPage = (
<Grid item xs={12}>
<EntityCatalogGraphCard height={400} renderNode={CustomGraphNode} />
</Grid>

{/* Row 4: Cost Insights summary */}
<FeatureGate feature="observability">
<Grid item md={3} sm={6} xs={12}>
<ObservabilityCostInsightsSummaryCard />
</Grid>
</FeatureGate>
</Grid>
</EntityLayout.Route>
<EntityLayout.Route path="/definition" title="Definition">
Expand Down Expand Up @@ -787,11 +803,6 @@ const systemPage = (
<ObservabilityRCA />
</FeatureGatedContent>
</EntityLayout.Route>
<EntityLayout.Route path="/cost-analysis" title="Cost Analysis">
<FeatureGatedContent feature="observability">
<ObservabilityCostAnalysis />
</FeatureGatedContent>
</EntityLayout.Route>

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.

Did we check whether we have not referred to this path from any other pages ? AFAIR there was a link in Alerts Page

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.

This had been missed. Only the path from the incidents page had been updated. I'll fix it

</EntityLayoutWithDelete>
);

Expand Down
2 changes: 1 addition & 1 deletion packages/portal-app/src/createPortalApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ const routes = (
element={<CatalogGraphPage renderNode={CustomGraphNode} />}
/>
<Route path="/platform-overview" element={<PlatformOverviewPage />} />
<Route path="/cost-insights" element={<CostInsightsPage />} />
<Route path="/cost-insights/*" element={<CostInsightsPage />} />
{/*
Standalone full-window exec terminal, opened in a new browser tab from the
resource drawer. The page renders a fixed viewport overlay over the app
Expand Down
3 changes: 2 additions & 1 deletion plugins/openchoreo-observability/src/alpha.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ describe('openchoreo-observability alpha plugin', () => {
`entity-content:${plugin}/traces`,
`entity-content:${plugin}/project-incidents`,
`entity-content:${plugin}/rca-reports`,
`entity-content:${plugin}/cost-analysis`,
// overview cards
`entity-card:${plugin}/cost-insights-summary`,
]) {
expect(ids).toContain(expected);
}
Expand Down
40 changes: 27 additions & 13 deletions plugins/openchoreo-observability/src/alpha.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ import {
fetchApiRef,
PluginWrapperBlueprint,
} from '@backstage/frontend-plugin-api';
import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';
import { FeatureGatedContent } from '@openchoreo/backstage-plugin-react';
import {
EntityCardBlueprint,
EntityContentBlueprint,
} from '@backstage/plugin-catalog-react/alpha';
import {
FeatureGate,
FeatureGatedContent,
} from '@openchoreo/backstage-plugin-react';
import { CHOREO_ANNOTATIONS } from '@openchoreo/backstage-plugin-common';

import { rootRouteRef } from './routes';
import {
Expand Down Expand Up @@ -263,17 +270,23 @@ const rcaReportsEntityContent = EntityContentBlueprint.make({
},
});

const costAnalysisEntityContent = EntityContentBlueprint.make({
name: 'cost-analysis',
/**
* Cost Insights summary card, shown on the Component and Project (System)
* overview pages. Filtered to entities carrying the openchoreo namespace
* annotation (the scope the card resolves cost by) and gated on the
* observability feature so it vanishes when the host has it disabled.
*/
const costInsightsSummaryCard = EntityCardBlueprint.make({
name: 'cost-insights-summary',
params: {
path: '/cost-analysis',
title: 'Cost Analysis',
filter: 'kind:system',
filter: entity =>
['component', 'system'].includes(entity.kind.toLowerCase()) &&
Boolean(entity.metadata.annotations?.[CHOREO_ANNOTATIONS.NAMESPACE]),
loader: () =>
import('./components/CostAnalysis').then(m => (
<FeatureGatedContent feature="observability">
<m.CostAnalysisPage />
</FeatureGatedContent>
import('./components/CostInsights/CostInsightsSummaryCard').then(m => (
<FeatureGate feature="observability">
<m.CostInsightsSummaryCard />
</FeatureGate>
)),
},
});
Expand All @@ -284,7 +297,8 @@ const costAnalysisEntityContent = EntityContentBlueprint.make({
* Registers the three observability backend clients, the log-row-action
* registry API, the component-page entity tabs (Logs, Events, Metrics,
* Alerts, Wirelogs) and the system-page entity tabs (Logs, Traces,
* Incidents, RCA Reports, Cost Analysis).
* Incidents, RCA Reports), plus the Cost Insights summary card shown on the
* Component and Project overview pages.
*/
export default createFrontendPlugin({
pluginId: 'openchoreo-observability',
Expand All @@ -304,6 +318,6 @@ export default createFrontendPlugin({
tracesEntityContent,
projectIncidentsEntityContent,
rcaReportsEntityContent,
costAnalysisEntityContent,
costInsightsSummaryCard,
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -169,28 +169,28 @@ const ObservabilityAlertsContent = () => {
[entity, project, filters.environment],
);

// Open the parent project's Cost Analysis tab in a new browser tab,
// pre-filtered by alertId and with a time range that covers the alert's age.
// Open the Cost Analysis tab of the Cost Insights page in a new browser tab,
// scoped to this project and pre-filtered by alertId, environment and a time
// range covering the alert's age.
const handleViewCostAnalysis = useCallback(
(alert: AlertSummary) => {
const parentProject =
(entity.spec?.system as string | undefined) || project || '';
const catalogNs = entity.metadata.namespace || 'default';
if (!parentProject) return;
if (!project || !namespace) return;

const timeRange = alert.timestamp
? pickRangeForAge(Date.now() - new Date(alert.timestamp).getTime())
: '1h';

const params = new URLSearchParams({
namespace,
project,
q: alert.alertId,
timeRange,
...(filters.environment ? { env: filters.environment } : {}),
});
const url = `/catalog/${catalogNs}/system/${parentProject}/cost-analysis?${params.toString()}`;
const url = `/cost-insights/cost-analysis?${params.toString()}`;
window.open(url, '_blank', 'noopener,noreferrer');
},
[entity, project, filters.environment],
[namespace, project, filters.environment],
);

const renderError = (error: string) => {
Expand Down

This file was deleted.

Loading
Loading