Imported from grafana/business-calendar (
AGENTS.md). Install upstream withnpx skills add grafana/business-calendar. Copyright stays with the author.
AGENTS.md — Business Calendar (Grafana Plugin)
Grafana panel plugin providing a calendar view for time-series data. Plugin ID:
marcusolsson-calendar-panel| Owner: Grafana Labs
Project Overview
Grafana frontend panel plugin (marcusolsson-calendar-panel)
built with TypeScript, React, and react-big-calendar.
No backend component.
Uses webpack (via .config/) for bundling and SWC for transpilation.
Node version: >=24 (see .nvmrc / mise.toml). Package manager: npm.
Build / Dev Commands
npm run build # Production build (webpack)
npm run dev # Watch mode (webpack, development)
npm run typecheck # tsc --noEmit
npm run lint # ESLint (flat config)
npm run lint:fix # ESLint with auto-fix
npm run markdownlint # markdownlint-cli2 on AGENTS.md, CHANGELOG.md, README.md
npm run spellcheck # cspell on all source files
npm start # Docker Compose: pull + start Grafana
npm stop # Docker Compose: stop
Test Commands
npm test # Jest watch mode
npm run test:ci # Jest all tests, coverage
npx jest path/to/file.test.ts # Run single test file
npx jest --testPathPattern="migration" # Pattern match
npx jest -t "Should render" # Single test by name
npm run test:e2e # Playwright E2E tests
npm run test:e2e:dev # Playwright interactive UI
npm run test:e2e:docker # Full Docker Compose (Grafana + tests)
Jest sets TZ=UTC globally.
Project Structure
src/
module.ts # Plugin entry point
migration.ts # Panel option migration handler
plugin.json # Grafana plugin manifest
components/ # React components (PascalCase dirs)
CalendarPanel/ # Main panel wrapper
BigCalendar/ # react-big-calendar integration
BigToolbar/ # Toolbar (nav, view switcher)
EventDetails/ # Event detail drawer
YearView/ # Yearly calendar view
hooks/ # Custom React hooks (useXxx.ts)
types/ # TypeScript types and enums
constants/ # Default values, option defs, test IDs
utils/ # Pure utility functions
i18n/ # Internationalization (6 languages)
@types/ # Module augmentations (i18next.d.ts)
.config/ # Grafana scaffolded build config
Every directory has a barrel index.ts.
Critical Rules
- Do not use
volkovlabs.ioURLs anywhere in the codebase. This project was forked from Volkov Labs and all references should point to Grafana equivalents (e.g.,grafana.com). - Never modify anything inside
.config/— managed by Grafana plugin tooling. - Never change
idortypeinsrc/plugin.json. - Changes to
plugin.jsonrequire a Grafana server restart. - Use webpack from
.config/for builds; do not add a custom bundler. - Use
@grafana/plugin-e2efor E2E tests. - Grafana API docs: https://grafana.com/developers/plugin-tools/llms.txt
- Always run
npm run markdownlinton any.mdfile you create or modify (includingAGENTS.md,README.md,CHANGELOG.md) and fix all reported issues before committing. When wrapping long lines, fill each line as close to 120 characters as possible rather than wrapping early. - Always run
npm run typecheckwhensrc/files are changed and fix any type errors before committing. - Always run
npm run lintbefore committing changes tosrc/. Fix errors withnpm run lint:fixand verify no errors remain. - Always run
npm run spellcheckbefore committing. Fix any issues and add new words tocspell.config.jsonif they are legitimate. - Always update
CHANGELOG.mdbefore committing. Every commit must include the corresponding changelog entry. Do not commit code changes without first updating the changelog in the same commit. - NEVER commit unless the user explicitly asks. Do not commit as part of completing a task.
- NEVER push unless the user explicitly asks.
Do not push as part of completing a task.
Never chain
git commit && git pushin one command. Always wait for the user to explicitly ask to push. - Do not add a
Co-Authored-Byline to commit messages. - After pushing, always update the PR summary if a
PR exists for the current branch. Treat push and PR
update as an atomic pair — never stop between them.
Use
gh pr editto update the title and body with well-formatted text that reflects all changes across the entire branch. Wrap PR summary lines at 120 characters — use the full width, do not wrap shorter than necessary. - Prefer subagents for research, code exploration,
and multi-step work. Use the Task tool with
exploreorgeneralagents rather than running many search/read commands directly. Launch multiple agents in parallel when tasks are independent. - Always confirm before editing files. Before applying any file change
(
edit,write,ast_edit,notebook), describe the change and wait for explicit user approval.
Code Style
Formatting (Prettier)
- Print width: 120, tab width: 2, no tabs
- Single quotes, trailing commas (
es5), semicolons - JSX uses double quotes
- End of line: auto
Imports
Three groups separated by blank lines, alphabetical within each:
// 1. External packages
import { Field, FieldType } from '@grafana/data';
import { useCallback, useMemo } from 'react';
// 2. Internal bare aliases or relative parent paths
import { CalendarOptions } from 'types';
import { getVariableValue } from 'utils';
// 3. Relative sibling/child imports
import { useLocalizer } from './useLocalizer';
Destructured members sorted alphabetically within braces.
Exports
- Named exports only — no default exports anywhere.
- Barrel files (
index.ts) re-export viaexport * from './Module'.
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Components | PascalCase | CalendarPanel |
| Component files | PascalCase.tsx |
CalendarPanel.tsx |
| Hooks | useCamelCase |
useCalendarEvents |
| Hook files | useCamelCase.ts |
useCalendarEvents.ts |
| Utilities | camelCase | getVariableValue |
| Util files | camelCase.ts |
calendarEvents.ts |
| Constants | UPPER_SNAKE_CASE | DEFAULT_OPTIONS |
| Enums | PascalCase + UPPER_SNAKE | View.WORK_WEEK |
| Interfaces | PascalCase | CalendarEvent |
| Style files | Component.styles.ts |
BigCalendar.styles.ts |
| Test files | *.test.ts(x) co-located |
migration.test.ts |
Types
- Interfaces for data models (see JSDoc Comments section).
- Component props use
type Props = ...orinterface Props extends Pick<...>. const enumfor internal-only enums; regularenumwhen values are iterated at runtime.- Avoid
as anyin production code; acceptable in test mocks and partial objects. - Use
as neverfor Grafana API type escapes (e.g.,disableStandardOptions).
Components
- Functional components:
const Name: React.FC<Props> = ({ ... }) => { ... }. - Props destructured in the function signature, not inside the body.
- Styles via
useStyles2(getComponentStyles)withGrafanaTheme2parameter.
i18n
- All user-facing strings use
t('dotted.key.path')from i18next. - Option arrays are functions accepting
TFunction:const OPTIONS = (t: TFunction) => [...]. - Translations live in
src/i18n/translations/per language.
JSDoc Comments
This codebase uses pervasive JSDoc comments. Add /** ... */ blocks above:
- Every interface and each of its properties (include
@typetags on properties) - Every function and constant declaration
- Logical sections within function bodies (state, theme, callbacks, return)
/**
* Properties
*/
interface Props {
/**
* Events
*
* @type {CalendarEvent[]}
*/
events: CalendarEvent[];
}
/**
* Calendar Panel
*/
export const CalendarPanel: React.FC<Props> = ({ events }) => {
/**
* Styles
*/
const styles = useStyles2(getStyles);
/**
* Return
*/
return <div className={styles.wrapper}>...</div>;
};
Error Handling
- Defensive guard patterns with early returns — no try/catch blocks.
- Optional chaining and nullish coalescing for safe property access.
- Filter falsy values:
.filter((x) => x)or.filter(Boolean).
ESLint
Flat config (ESLint 9) extending @grafana/eslint-config/flat.js, @volkovlabs/eslint-config, and
eslint-config-prettier. Custom rule: @typescript-eslint/no-empty-object-type: off. Test files, mocks,
config files, and server dirs are excluded.
Additional Rules
no-consoleandno-debuggerare errors@typescript-eslint/no-deprecatedis a warning — avoid using deprecated APIs- Unused variables are errors (except rest siblings)
Key Dependencies
| Package | Purpose |
|---|---|
react-big-calendar |
Calendar rendering engine |
dayjs |
Date/time manipulation |
i18next + react-i18next |
Internationalization |
@grafana/data, @grafana/ui, @grafana/runtime |
Grafana plugin SDK |
@emotion/css |
CSS-in-JS styling |
CI/CD
- CI (
.github/workflows/push.yml): Runs on push tomainand all PRs. Usesgrafana/plugin-ci-workflows. - CD (
.github/workflows/publish.yml): Manual dispatch to dev/ops/prod environments. - Coverage (
.github/workflows/coverage.yml): Runs on PRs; posts a Jest coverage comparison comment. - PR File Changes (
.github/workflows/pr-files.yml): Runs on PRs; posts a grouped file-changes summary comment. - Do NOT pin
grafana/plugin-ci-workflowsto a commit SHA. Grafana's CI enforces tagged releases only (e.g.,@ci-cd-workflows/v7). SHA pinning will fail the "Check for release channel" job. All other GitHub Actions should be pinned to SHAs.
Changelog Policy
Add entries under the current [Unreleased] section in CHANGELOG.md.
Categorize under ### Added, ### Changed, ### Removed, ### Fixed,
or ### Project Updates as appropriate.
Branching Policy
- Never commit directly to
main. Always create a new branch for changes. - Use descriptive branch names (e.g.,
feat/add-feature,fix/bug-description). - After pushing, always update the PR summary if a
PR exists for the current branch. Treat push and PR
update as an atomic pair — never stop between them.
Use
gh pr editto update the title and body with well-formatted text that reflects all changes across the entire branch. - Always create pull requests as drafts
(
gh pr create --draft). - Use categories in PR summaries — group changes
under headings like
### CI/CD,### Dependencies,### Bug Fixes,### AGENTS.md,### Tooling, etc. so reviewers can quickly scan the scope of the PR.
Testing Conventions
Structure
- Use
describe/it(nottest). Nestdescribeblocks for related functionality. it('Should ...')with capital S is the dominant naming pattern.
Mocking
-
Use
jest.mock('module', () => ({ ...jest.requireActual('module'), ... }))to preserve original exports. -
Every mock block gets a
/** Mock @grafana/data */comment header. -
Component props captured via mock pattern:
let calendarProps = {} as any; jest.mocked(Calendar).mockImplementation((props: any): any => { calendarProps = props; return null; });
Component Tests
render+screenfrom@testing-library/react.getJestSelectors(TEST_IDS.component)from@volkovlabs/jest-selectors.- Selector with
truearg forqueryBy(no-throw):selectors.element(true). - Factory function pattern:
const getComponent = (props: Partial<Props>) => .... - Async interactions wrap in
await act(async () => ...).
Hook Tests
- Use
renderHookfrom@testing-library/react. - Pass partial mocks with
as anyfor unused fields.
Test Data
- Deterministic dates:
const getSafeDate = () => new Date('2023-02-02'). - Test IDs in
src/constants/tests.tsas nestedTEST_IDSobject. - Parameterized IDs use functions:
description: (index: number) => ....
Migration Pattern
When removing or renaming panel options,
update src/migration.ts:
- Add deprecated fields to
OutdatedPanelOptionswith JSDoc noting removal version. - Use
hasOwnPropertychecks +deletefor removed options. - Use
Array.isArray/ type checks for format changes. - Add corresponding tests in
migration.test.ts.
Important
Always create a branch before making any changes. Never commit directly to main.
Do not add a Co-Authored-By line to commit messages.
When checking out a branch or main, always git fetch and git pull to ensure you have the latest changes.