Skip to content
Open
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PLATFORMS ?= linux/arm64,linux/amd64
ORG ?= openshift-observability-ui
PLUGIN_NAME ?=monitoring-plugin
IMAGE ?= quay.io/${ORG}/${PLUGIN_NAME}:${VERSION}
MONITORING_FEATURES ?=alerting,targets,legacy-dashboards,metrics
MONITORING_FEATURES ?=alerting,services,targets,legacy-dashboards,metrics
ALL_FEATURES ?=$(MONITORING_FEATURES),cluster-health-analyzer,perses-dashboards
MCP_DEVSPACE_FEATURES ?=cluster-health-analyzer,perses-dashboards,acm-alerting

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Feature flags should be added to the Feature enum [here](pkg/server/server.go) a
| legacy-dashboards | 5.0+ |
| metrics | 5.0+ |
| targets | 5.0+ |
| services | 5.0+ |

## monitoring-plugin

Expand Down Expand Up @@ -195,7 +196,7 @@ $ make start-console
$ make start-coo-backend
```

`make start-coo-backend` will inject the `alerting,targets,legacy-dashboards,metrics,incidents,perses-dashboards` features.
`make start-coo-backend` will inject the `alerting,targets,services,legacy-dashboards,metrics,incidents,perses-dashboards` features.

#### Local Development with Perses Proxy

Expand Down
2 changes: 1 addition & 1 deletion cmd/plugin-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var (
portArg = flag.Int("port", 9443, "server port to listen on\nports 9444 and 9445 reserved for other use")
certArg = flag.String("cert", "", "cert file path to enable TLS (disabled by default)")
keyArg = flag.String("key", "", "private key file path to enable TLS (disabled by default)")
featuresArg = flag.String("features", "", "enabled features, comma separated.\noptions: ['acm-alerting', 'alerting', 'incidents', 'legacy-dashboards', 'metrics', 'targets', 'perses-dashboards', 'cluster-health-analyzer']")
featuresArg = flag.String("features", "", "enabled features, comma separated.\noptions: ['acm-alerting', 'alerting', 'incidents', 'legacy-dashboards', 'metrics', 'targets', 'services', 'perses-dashboards', 'cluster-health-analyzer']")
staticPathArg = flag.String("static-path", "/opt/app-root/web/dist", "static files path to serve frontend")
configPathArg = flag.String("config-path", "/opt/app-root/config", "config files path")
pluginConfigArg = flag.String("plugin-config-path", "/etc/plugin/config.yaml", "plugin yaml configuration")
Expand Down
30 changes: 30 additions & 0 deletions config/services.patch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"op": "add",
"path": "/extensions/0",
"value": {
"type": "console.navigation/href",
"properties": {
"id": "services",
"name": "%plugin__monitoring-plugin~Observability services%",
"href": "/monitoring/services",
"perspective": "admin",
"section": "observe"
}
}
},
{
"op": "add",
"path": "/extensions/0",
"value": {
"type": "console.page/route",
"properties": {
"exact": false,
"path": "/monitoring/services",
"component": {
"$codeRef": "ServicesPage.MpCmoServicesPage"
}
}
}
}
]
1 change: 1 addition & 0 deletions pkg/server/plugin_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func patchManifest(baseManifestData []byte, cfg *Config) []byte {
{"metrics.patch.json", features[Metrics]},
{"legacy-dashboards.patch.json", features[LegacyDashboards]},
{"targets.patch.json", features[Targets]},
{"services.patch.json", features[Services]},
{"monitoring-console-plugin.patch.json", features[Incidents] || features[ClusterHealthAnalyzer] || features[PersesDashboards] || features[AcmAlerting]},
{"acm-alerting.patch.json", features[AcmAlerting]},
{"cluster-health-analyzer.patch.json", features[Incidents] || features[ClusterHealthAnalyzer]},
Expand Down
64 changes: 64 additions & 0 deletions pkg/server/plugin_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package server

import (
"encoding/json"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestPatchManifestServices(t *testing.T) {
baseManifest := []byte(`{
"name": "monitoring-plugin",
"extensions": []
}`)
configPath := filepath.Join("..", "..", "config")

t.Run("enabled adds services navigation and route", func(t *testing.T) {
patched := patchManifest(baseManifest, &Config{
ConfigPath: configPath,
Features: map[Feature]bool{
Services: true,
},
})

var manifest struct {
Extensions []struct {
Type string `json:"type"`
Properties struct {
ID string `json:"id"`
Path string `json:"path"`
Href string `json:"href"`
} `json:"properties"`
} `json:"extensions"`
}
require.NoError(t, json.Unmarshal(patched, &manifest))

var hasNav, hasRoute bool
for _, ext := range manifest.Extensions {
if ext.Type == "console.navigation/href" && ext.Properties.ID == "services" {
hasNav = true
require.Equal(t, "/monitoring/services", ext.Properties.Href)
}
if ext.Type == "console.page/route" && ext.Properties.Path == "/monitoring/services" {
hasRoute = true
}
}
require.True(t, hasNav, "expected services navigation item in patched manifest")
require.True(t, hasRoute, "expected services route in patched manifest")
})

t.Run("disabled leaves services patch absent", func(t *testing.T) {
patched := patchManifest(baseManifest, &Config{
ConfigPath: configPath,
Features: map[Feature]bool{
Services: false,
},
})

require.JSONEq(t, string(baseManifest), string(patched))
require.NotContains(t, string(patched), `"id": "services"`)
require.NotContains(t, string(patched), "/monitoring/services")
})
}
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
LegacyDashboards Feature = "legacy-dashboards"
Metrics Feature = "metrics"
Targets Feature = "targets"
Services Feature = "services"
PersesDashboards Feature = "perses-dashboards"
ClusterHealthAnalyzer Feature = "cluster-health-analyzer"
)
Expand Down
76 changes: 76 additions & 0 deletions web/cypress/component/SummaryCard.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { MemoryRouter } from 'react-router';

import SummaryCard from '@/features/services/components/summary/SummaryCard';
import { DataTestIDs } from '@/shared/constants/data-test';

const mountCard = (props: {
count: number;
title: string;
url: string;
cardId: string;
loading?: boolean;
error?: string;
}) => {
cy.mount(
<MemoryRouter>
<SummaryCard {...props} />
</MemoryRouter>,
);
};

describe('SummaryCard', () => {
it('renders title and count, and navigates on click', () => {
mountCard({
cardId: 'metrics',
count: 42,
title: 'Metrics',
url: '/monitoring/query-browser',
});

cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCard}-metrics"]`).should('be.visible');
cy.contains('h3', 'Metrics').should('be.visible');
cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCardCount}-metrics"]`)
.should('be.visible')
.should('contain.text', '42')
.click();

cy.location('pathname').should('eq', '/monitoring/query-browser');
});

it('renders loading state', () => {
mountCard({
cardId: 'targets',
count: 0,
title: 'Targets',
url: '/monitoring/targets',
loading: true,
});

cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCardLoading}-targets"]`).should(
'be.visible',
);
cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCardCount}-targets"]`).should(
'not.exist',
);
cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCardError}-targets"]`).should(
'not.exist',
);
});

it('renders error state', () => {
mountCard({
cardId: 'dashboards',
count: 0,
title: 'Perses Dashboards',
url: '/monitoring/v2/dashboards',
error: 'Failed to fetch dashboards',
});

cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCardError}-dashboards"]`).should(
'be.visible',
);
cy.get(`[data-test="${DataTestIDs.ServicesPage.SummaryCardCount}-dashboards"]`).should(
'not.exist',
);
});
});
2 changes: 2 additions & 0 deletions web/cypress/e2e/monitoring/00.bvt_admin.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ describe('BVT: Monitoring', { tags: ['@smoke', '@monitoring'] }, () => {
cy.log(`Admin perspective - Observe Menu and verify all submenus`);
nav.sidenav.clickNavLink(['Administration', 'Cluster Settings']);
commonPages.detailsPage.administration_clusterSettings();
nav.sidenav.clickNavLink(['Observe', 'Observability services']);
commonPages.titleShouldHaveText('Observability services');
nav.sidenav.clickNavLink(['Observe', 'Alerting']);
commonPages.titleShouldHaveText('Alerting');
nav.tabs.switchTab('Silences');
Expand Down
30 changes: 30 additions & 0 deletions web/cypress/e2e/monitoring/regression/04.reg_services_admin.cy.ts

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.

not sure why this is needed? can you clarify?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is following the pattern for the other nav sections for running a suite of regression tests.

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.

cc @etmurasaki can you check?

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.

it is fine the structure he followed... I have more comments to make about beforeEach section and other scenario in the support file, but I will wait for the discussion on the thread to proceed with the review

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { runAllRegressionServicesTests } from '../../../support/monitoring/04.reg_services.cy';
import { commonPages } from '../../../views/common';
import { nav } from '../../../views/nav';
import { servicesPage } from '../../../views/services-page';

const MP = {
namespace: 'openshift-monitoring',
operatorName: 'Cluster Monitoring Operator',
};

describe(
'Regression: Monitoring - Observability services (Administrator)',
{ tags: ['@monitoring', '@services'] },
() => {
before(() => {
cy.beforeBlock(MP);
});

beforeEach(() => {
nav.sidenav.clickNavLink(['Observe', 'Metrics']);
commonPages.titleShouldHaveText('Metrics');
servicesPage.clearInfoAlertDismissed();
servicesPage.goTo();
});

runAllRegressionServicesTests({
name: 'Administrator',
});
},
);
87 changes: 87 additions & 0 deletions web/cypress/support/monitoring/04.reg_services.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { commonPages } from '../../views/common';
import { servicesPage, ServicesSummaryCardId } from '../../views/services-page';

export interface PerspectiveConfig {
name: string;
beforeEach?: () => void;
}

const ALL_CARDS: ServicesSummaryCardId[] = [
'dashboards',
'alerting-rules',
'firing-alerts',
'targets',
'metrics',
];

export function runAllRegressionServicesTests(perspective: PerspectiveConfig) {
testServicesRegression(perspective);
}

export function testServicesRegression(perspective: PerspectiveConfig) {
it(`${perspective.name} perspective - Observability services page`, () => {
cy.log('1.1 Navigate to Observability services and verify page chrome');
servicesPage.clearInfoAlertDismissed();
servicesPage.goTo();
servicesPage.shouldBeLoaded();
cy.contains(
'Manage and monitor your metrics, logs, and traces from a single, unified hub.',
).should('be.visible');

cy.log('1.2 Verify info alert is visible and dismissible with localStorage persistence');
servicesPage.elements.infoAlert().should('be.visible');
servicesPage.elements.infoAlert().should('contain.text', 'Cluster-wide observability scope');
servicesPage.dismissInfoAlert();
cy.window()
.its('localStorage')
.invoke('getItem', 'monitoring/services/info-alert-dismissed')
.should('eq', 'true');

cy.log('1.3 Reload and verify dismissed alert stays hidden');
servicesPage.goTo();
servicesPage.elements.infoAlert().should('not.exist');
servicesPage.elements.summarySection().should('be.visible');

cy.log('1.4 Verify all summary cards finish loading');
ALL_CARDS.forEach((cardId) => {
servicesPage.assertSummaryCardReady(cardId);
});
servicesPage.elements.summaryCard('alerting-rules').should('contain.text', 'Alerting rules');
servicesPage.elements.summaryCard('firing-alerts').should('contain.text', 'Firing alerts');
servicesPage.elements.summaryCard('targets').should('contain.text', 'Targets');
servicesPage.elements.summaryCard('metrics').should('contain.text', 'Metrics');
servicesPage.elements.summaryCard('dashboards').should('contain.text', 'Perses Dashboards');

cy.log('1.5 Click Firing alerts count and verify Alerting page');
servicesPage.goTo();
servicesPage.clickSummaryCardCount('firing-alerts');
commonPages.titleShouldHaveText('Alerting');

cy.log('1.6 Click Alerting rules count and verify Alerting page');
servicesPage.goTo();
servicesPage.clickSummaryCardCount('alerting-rules');
commonPages.titleShouldHaveText('Alerting');

cy.log('1.7 Click Targets count and verify Metrics targets page');
servicesPage.goTo();
servicesPage.clickSummaryCardCount('targets');
commonPages.titleShouldHaveText('Metrics targets');

cy.log('1.8 Click Metrics count and verify Metrics page');
servicesPage.goTo();
servicesPage.clickSummaryCardCount('metrics');
commonPages.titleShouldHaveText('Metrics');

cy.log(
'1.9 Click Perses Dashboards count when available (may be error-only without COO/Perses)',
);
servicesPage.goTo();
servicesPage.clickSummaryCardCountIfAvailable('dashboards').then((navigated) => {
if (navigated) {
cy.url().should('include', '/monitoring/v2/dashboards');
}
});

cy.log('Verified: Observability services page load, alert dismiss, and card navigation');
});
}
Loading