Thank you for your interest in contributing to the OpenShift Monitoring Plugin! This document provides guidelines for contributing code, submitting pull requests, and maintaining code quality.
- Getting Started
- Pull Request Process
- Code Conventions
- Testing Requirements
- Internationalization (i18n)
- Troubleshooting
- Getting Help
Before you start contributing, ensure you have the following tools installed:
- Node.js 22+ and npm
- Go 1.24+
- oc CLI
- podman 3.2.0+ or Docker
- An OpenShift cluster (for testing)
-
Clone the repository:
git clone https://github.com/openshift/monitoring-plugin.git cd monitoring-plugin -
Install dependencies:
make install
-
Verify setup:
make lint-frontend make lint-backend
For detailed setup instructions, see README.md.
This project uses Prow for CI/CD automation. Pull requests require specific labels to be merged, which are applied based on reviews from team members.
-
Title format:
JIRA_ISSUE: [release-x.y] Description- Example:
OU-1234: [release-4.19] Add support for custom dashboards - Example:
COO-456: Add support for custom datasources
- Example:
-
Before submitting, run the following checks locally and address any errors:
make lint-frontend # ESLint and Prettier checks make lint-backend # Go fmt and mod tidy make test-translations # Verify i18n keys make test-backend # Go unit tests make test-frontend # Jest unit tests
-
Required checks must pass in CI before merging.
The Prow bot manages labels based on reviews. The following labels are required for a PR to be merged:
| Label | Description | How to Obtain |
|---|---|---|
/lgtm |
Code review approval | Wait for a reviewer to comment /lgtm, reviewers are automatically assigned |
/qe-approved |
QE verification passed | Applied when QE team reviews (if applicable) |
- Contributor opens PR with proper title format
- CI runs automatically (lint, tests, build)
- Reviewer reviews code and comments
/lgtm - If significant feature: QE team tests and applies
/qe-approved - Prow bot merges the PR when all required labels are present
Use descriptive branch names that reference the JIRA issue:
ou-1234-feature-description
coo-1234-fix-bug-description
ou-1234-refactor-component
- Use the format from https://www.conventionalcommits.org/en/v1.0.0/
- Reference the JIRA issue if applicable
- Keep commits focused and atomic
- Prefer multiple focused commits over one large commit
Good commit message:
feat(alerts): add filter by severity
Add dropdown to filter alerts by severity level on the alerts page.
Users can now select critical, warning, or info severity filters.
Fixes OU-1234
Avoid:
fixed stuff
update
changes
| Type | Convention | Example |
|---|---|---|
| React Components | PascalCase | AlertsDetailsPage, SilenceForm, QueryBrowser, LoadingBox, EmptyBox, StatusBox |
| Page Components | PascalCase with Page suffix |
MetricsPage, TargetsPage |
| HOCs | camelCase with with prefix |
withFallback |
| Regular functions | camelCase | formatAlertLabel, buildPromQuery, handleNamespaceChange |
File naming conventions (TypeScript, Go, shell scripts, CSS, and more) are defined in the canonical Style Guide. All new and renamed files must conform to those rules.
The frontend source is split into two top-level directories under web/src/:
web/src/features/— one subdirectory per product feature. Each feature directory follows an internal structure ofcomponents/,pages/,hooks/,utils/,types/, andassets/as needed. New feature-specific code goes here.web/src/shared/— code that is used by more than one feature. Organized by kind:
| Directory | Contents | Example files |
|---|---|---|
shared/components/ |
Reusable UI components | labels.tsx, format.tsx, query-browser/query-browser.tsx |
shared/hooks/ |
Shared custom hooks | useBoolean.ts, useAlerts.ts, usePerspective.tsx |
shared/store/ |
Redux store, actions, reducers, thunks | store.ts, actions.ts, reducers.ts |
shared/contexts/ |
React contexts | MonitoringContext.tsx |
shared/constants/ |
Shared constants | data-test.ts, query-params.ts |
shared/types/ |
Shared TypeScript types | types.ts |
shared/utils/ |
Shared pure utility functions | utils.ts, react-router-7-adapter.ts |
shared/assets/ |
Static assets (fonts, images) | codicon.ttf |
shared/console/ |
Vendored/adapted OpenShift Console internals | utils/safe-fetch-hook.ts, graphs/ |
Decision rules:
- If a file is only ever imported by code inside a single feature directory, it belongs in that feature. If it is (or may be) imported by multiple features, it belongs in
shared/. - If a component or utility is only used by a single page, it should be co-located inside that page's own subdirectory rather than the feature-level
components/orutils/folder. For example,AggregateAlertTableRow.tsxlives insidefeatures/alerts/pages/alerts-page/because it is only used byAlertsPage.tsx. A page that has page-scoped files should be promoted to its own subdirectory (e.g.alerts-page/AlertsPage.tsx) rather than being a standalone file at thepages/level.
The current features are:
| Feature | Directory |
|---|---|
| Alerting (alerts, silences, alert rules) | features/alerts/ |
| Incidents | features/incidents/ |
| Legacy Dashboards | features/legacy-dashboards/ |
| Metrics / PromQL | features/metrics/ |
| Perses Dashboards | features/perses-dashboards/ |
| Targets | features/targets/ |
| Type | Convention | Example |
|---|---|---|
| Type aliases | PascalCase | MonitoringResource, TimeRange, AlertSource |
| Interface names | PascalCase with Props suffix for component props | SilenceFormProps, TypeaheadSelectProps |
| Enum values | PascalCase or SCREAMING_SNAKE_CASE | AlertSource.Platform, ActionType.AlertingSetLoading |
// ✅ Good: Type definitions
export type MonitoringResource = {
group: string;
resource: string;
abbr: string;
kind: string;
label: string;
url: string;
};
type SilenceFormProps = {
defaults: any;
Info?: ComponentType;
title: string;
isNamespaced: boolean;
};
export const enum AlertSource {
Platform = "platform",
User = "user",
}| Type | Convention | Example |
|---|---|---|
| Custom hooks | camelCase with use prefix |
useBoolean, usePerspective, useMonitoring |
| Hook files | camelCase with use prefix |
useBoolean.ts, usePerspective.tsx |
// ✅ Good: Hook definition
export const useBoolean = (
initialValue: boolean
): [boolean, () => void, () => void, () => void] => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue((v) => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return [value, toggle, setTrue, setFalse];
};| Type | Convention | Example |
|---|---|---|
| Constants | SCREAMING_SNAKE_CASE | PROMETHEUS_BASE_PATH, QUERY_CHUNK_SIZE |
| Resource definitions | PascalCase | AlertResource, RuleResource, SilenceResource |
// ✅ Good: Constants
export const QUERY_CHUNK_SIZE = 24 * 60 * 60 * 1000;
export const PROMETHEUS_BASE_PATH = window.SERVER_FLAGS.prometheusBaseURL;
export const AlertResource: MonitoringResource = {
group: "monitoring.coreos.com",
resource: "alertingrules",
kind: "Alert",
label: "Alert",
url: "/monitoring/alerts",
abbr: "AL",
};Use the centralized DataTestIDs object in web/src/shared/constants/data-test.ts:
// ✅ Good: Test ID definitions
export const DataTestIDs = {
AlertCluster: "alert-cluster",
AlertResourceIcon: "alert-resource-icon",
CancelButton: "cancel-button",
// Group related IDs using nested objects
SilencesPageFormTestIDs: {
AddLabel: "add-label",
Comment: "comment",
Creator: "creator",
},
};Use functional components with explicit type annotations:
// ✅ Good: Functional component with FC type and named export
import type { FC } from "react";
type ErrorAlertProps = {
error: Error;
};
export const ErrorAlert: FC<ErrorAlertProps> = ({ error }) => {
return (
<Alert isInline title={error.name} variant="danger">
{error.message}
</Alert>
);
};Always use the useTranslation hook for user-facing strings, this allows the translation
strings to be extracted and localized:
// ✅ Good: Using translations with named export
import { useTranslation } from "react-i18next";
export const MyComponent: FC = () => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
return (
<EmptyState>
<Title>{t("No data available")}</Title>
<EmptyStateBody>{t("Please try again later")}</EmptyStateBody>
</EmptyState>
);
};Use type imports for types that are only used for type checking:
// ✅ Good: Type-only imports
import type { FC, ReactNode, ComponentType } from "react";
import { useState, useCallback, useEffect } from "react";Use proper types instead of any when possible:
// ❌ Bad
const handleData = (data: any) => { ... };
// ✅ Good
type DataResponse = {
results: PrometheusResult[];
status: string;
};
const handleData = (data: DataResponse) => { ... };Leverage TypeScript utility types:
// ✅ Good: Using utility types
type AugmentedColumnStyle = ColumnStyle & {
className?: string;
};
type PartialMetric = Partial<Metric>;
type RequiredFields = Required<Pick<Config, "name" | "url">>;The backend that serves plugin assets and proxies APIs is written in Go (see /cmd and /pkg). The main packages under pkg/ are pkg/server/ (HTTP server and plugin handler) and pkg/monitoring/ (proxy and API sanitization).
Follow these Go-specific conventions in addition to the general naming rules:
- File names: lowercase with underscores only when required (e.g.,
plugin_handler.go,server_test.go). - Packages: short, all lowercase, no underscores or mixedCaps (e.g.,
monitoring,server). - Tests: co-locate
_test.gofiles next to the implementation and keep table-driven tests when feasible.
- Exported identifiers use
MixedCapsstarting with an uppercase letter (PluginServer,DefaultTimeout). - Unexported identifiers use
mixedCapsstarting lowercase (proxyClient,requestCtx). - Favor descriptive names over abbreviations; keep short-lived loop variables concise (
i,tc). - Group related constants using
const (...)blocks.
- Exported functions and methods must have doc comments beginning with the function name.
- Function names follow
MixedCaps(StartServer,newProxyHandler). - Keep parameter order consistent (ctx first, dependencies before data structs) and return
(value, error)pairs. - Ensure all files pass
go fmt ./cmd ./pkgandgo vet ./...before submitting.
-
Struct names are
MixedCaps(e.g.,ProxyConfig,TLSBundle). -
Exported struct fields must be capitalized if used by other packages or JSON marshaling. Always add JSON tags for serialized types:
type ProxyConfig struct { TargetURL string `json:"targetURL"` Timeout time.Duration `json:"timeout"` }
-
Interfaces describe behavior (
MetricsFetcher,ClientProvider) and live near their consumers. -
Keep files focused: one primary struct or related set of functions per file to ease reviews.
- Frontend: Co-locate test files with source files using
.spec.tssuffix - Backend: Use Go's testing package with
_test.gosuffix
# Run all tests
make test-backend
make test-frontend
# Run frontend tests with watch mode
cd web && npm run test:unit -- --watchFor significant UI changes, add or update Cypress tests:
- Test files:
web/cypress/e2e/ - Documentation:
web/cypress/CYPRESS_TESTING_GUIDE.md
# Run Cypress tests
cd web/cypress
npm run cypress:run --spec "cypress/e2e/**/regression/**"For testing individual React components in isolation (no cluster required):
- Test files:
web/cypress/component/(.cy.tsxextension) - Support file:
web/cypress/support/component.ts
cd web
# Interactive mode
npm run cypress:open:component
# Headless mode
npm run cypress:run:componentAll user-facing strings must be translatable:
- Use the
t()function fromuseTranslation - Add new keys to
web/locales/en/plugin__monitoring-plugin.json - Run
make test-translationsto verify
// ✅ Good
const { t } = useTranslation(process.env.I18N_NAMESPACE);
return <Title>{t("Alerting rules")}</Title>;
// ❌ Bad - hardcoded string
return <Title>Alerting rules</Title>;Run the linter and formatter before committing:
make lint-frontend
cd web && npm run prettier -- --write .Make sure you're running the correct version of Node.js:
node --version # Should be 22+Clear npm cache and reinstall:
cd web
npm cache clean --force
rm -rf node_modules package-lock.json
npm installIf you encounter issues after switching branches or pulling main:
cd web
npm cache clean --force
rm -rf node_modules package-lock.json dist
npm install
npm run lint
npm run buildConfirm Node and npm versions are correct:
node --version # Should be 22+
npm --version # Matches Node release
which npm # Ensure expected binary is usedWhen running local unit tests, clear caches first:
cd web
npm cache clean --force
rm -rf node_modules/.cache
npm run test:unit -- --clearCacheFor Cypress E2E runs:
cd web/cypress
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
npm run cypress:run -- --config cacheAcrossSpecs=falseAlways use the useTranslation hook for user-facing strings:
make test-translationsFix any errors by adding missing keys to web/locales/en/plugin__monitoring-plugin.json.
Ensure all dependencies are installed:
make installCheck the Makefile targets available:
grep "^\.PHONY" Makefile | sed 's/.PHONY: //'If ports 9443 or 3000 are in use:
# Find and kill processes
lsof -ti:9443 | xargs kill -9 # Backend
lsof -ti:3000 | xargs kill -9 # Frontend| Topic | Resource |
|---|---|
| Plugin Architecture | AGENTS.md |
| Development Setup | README.md |
| Cypress Testing | web/cypress/CYPRESS_TESTING_GUIDE.md |
| Console Plugin SDK | OpenShift Console SDK |
For questions, reach out via:
- Slack:
#forum-cluster-observability-operator(Red Hat internal) - GitHub Issues: openshift/monitoring-plugin