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
14 changes: 14 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
release:
name: Release
runs-on: ubuntu-latest
environment: NPM_TOKEN
permissions:
contents: write # Needed for GitHub release
issues: write # Needed for release notes
Expand All @@ -29,6 +30,11 @@ jobs:
node-version: 'lts/*'
registry-url: 'https://registry.npmjs.org'

- name: Upgrade npm for trusted publishing
run: |
npm install -g npm@latest
npm --version

- name: Clean dependencies and npm config
run: |
rm -rf node_modules
Expand Down Expand Up @@ -60,3 +66,11 @@ jobs:
if: steps.semantic-release.outputs.new_release == 'true'
run: npm publish --access public --provenance

- name: Upload npm logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: npm-debug-logs
path: /home/runner/.npm/_logs/
retention-days: 7

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
import { MaidrApp } from './ui/App';
import { Constant } from './util/constant';

// Export public API for React integration
export { Controller } from './controller';
export { MaidrApp } from './ui/App';
export { store } from './state/store';

Check failure on line 11 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint Code

Expected "./state/store" to come before "./ui/App"
export type { Maidr, MaidrLayer, MaidrSubplot, BarPoint, TraceType } from './type/grammar';

Check failure on line 12 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint Code

Expected "BarPoint" to come before "MaidrSubplot"
export { TraceType as TraceTypeEnum } from './type/grammar';

declare global {
interface Window {
maidr?: Maidr;
Expand Down Expand Up @@ -81,7 +88,15 @@
initMaidr(maidr, plot);
}

function initMaidr(maidr: Maidr, plot: HTMLElement): void {
/**
* Initializes MAIDR accessibility features on a plot element.
* This function wraps the plot in figure/article elements, sets up event handlers,
* and renders the MAIDR React UI components.
*
* @param maidr - The MAIDR configuration object containing plot data
* @param plot - The HTML element containing the plot (e.g., SVG)
*/
export function initMaidr(maidr: Maidr, plot: HTMLElement): void {
let maidrContainer: HTMLElement | null = null;
let controller: Controller | null = null;
let hasAnnounced = false;
Expand Down
3 changes: 3 additions & 0 deletions xability-maidr-panel/.config/.cprc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "6.6.0"
}
16 changes: 16 additions & 0 deletions xability-maidr-panel/.config/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in .config/README.md
*/

module.exports = {
endOfLine: 'auto',
printWidth: 120,
trailingComma: 'es5',
semi: true,
jsxSingleQuote: false,
singleQuote: true,
useTabs: false,
tabWidth: 2,
};
83 changes: 83 additions & 0 deletions xability-maidr-panel/.config/AGENTS/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: panel-plugin-agent-fundamentals
description: Develops Grafana panel plugins
---

You are an expert Grafana panel plugin developer for this project.

## Your role

- You are fluent in TypeScript and React
- You know how to use Grafana dashboards

## Project knowledge

This repository contains a **Grafana panel plugin**, providing a custom visualization for Grafana dashboards.
Panel plugins are used to:

- Display data from Grafana data sources in custom ways
- Add interactive behavior (drill-downs, navigation, etc.)
- Visualize or control external systems (IoT, integrations, custom controls)

### Plugin anatomy

A typical panel plugin includes:

**plugin.json**

- Declares plugin ID, type (`panel`), name, version
- Loaded by Grafana at startup

**Main module (`src/module.ts`)**

- Exports: `new PanelPlugin(PanelComponent)`
- Registers panel options, migrations, defaults, ui extensions

**Panel component (`src/components/Panel.tsx`)**

- React component receiving: `data`, `timeRange`, `width`, `height`, `options`
- Renders visualization using Grafana data frames and field configs

### Repository layout

- `plugin.json` — Panel plugin manifest
- `src/module.ts` — Main plugin entry
- `src/components/` — Panel React components
- `src/types.ts` — Option and model types
- `tests/` — E2E tests (if present)
- `provisioning/` — Local development provisioning
- `README.md` — Human documentation

## Coding guidelines

- Use **TypeScript** (in strict mode), functional React components, and idiomatic patterns
- Use **@grafana/ui**, **@grafana/data**, **@grafana/runtime**
- Use **`useTheme2()`** for all colors, spacing, typography
- **Never hardcode** colors, spacing, padding, or font sizes
- Use **Emotion** + `useStyles2()` + theme tokens for styling
- Keep layouts responsive (use `width`/`height`)
- Avoid new dependencies unless necessary + Grafana-compatible
- Maintain consistent file structure and predictable types
- Use **`@grafana/plugin-e2e`** for E2E tests and **always use versioned selectors** to interact with the Grafana UI.

## Boundaries

You must **NOT**:

- Change plugin ID or plugin type in `plugin.json`
- Modify anything inside `.config/*`
- Add a backend (panel plugins = frontend only)
- Remove/change existing options without a migration handler
- Break public APIs (options, field configs, panel props)
- Store, read, or handle credentials

You **SHOULD**:

- Maintain backward compatibility
- Preserve option schema unless migration handler is added
- Follow official Grafana panel plugin patterns
- Use idiomatic React + TypeScript

## Instructions for specific tasks

- [Add panel options](./tasks/add-panel-options.md)
132 changes: 132 additions & 0 deletions xability-maidr-panel/.config/AGENTS/tasks/add-panel-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Adding a New Panel Option

Panel options are settings the user configures to change panel behavior.

Always complete **all three steps**:

### **1. Extend the options type**

File: `src/types.ts`

```ts
export interface SimpleOptions {
// existing...
myOptionName: MyOptionType;
}
```

- Property name must match the builder `path`.
- Type must match expected editor output.

---

### **2. Register the option in `setPanelOptions`**

File: `src/module.ts` inside `setPanelOptions((builder) => { ... })`

Use the correct builder method:

| Type | Builder |
| ------------------ | -------------------- |
| boolean | `addBooleanSwitch` |
| number | `addNumberInput` |
| string | `addTextInput` |
| select | `addSelect` |
| radio | `addRadio` |
| radio group | `addRadio` |
| slider | `addSliderInput` |
| color picker | `addColorPicker` |
| unit picker | `addUnitPicker` |
| field namer picker | `addFieldNamePicker` |

Template:

```ts
builder.addXxx({
path: 'myOptionName', // must match type property
name: 'My option',
defaultValue: <default>,
description: '',
settings: { /* optional */ },
// Optional visibility rule:
// showIf: (opts) => opts.someOtherOption,
});
```

Rules:

- Every option **must** have a `defaultValue`.
- Put numeric constraints in `settings` (`min`, `max`, `step`).
- Use `showIf` for conditional display.

---

### **3. Use the option in the panel component**

File: `src/Panel.tsx` (or equivalent)

```tsx
export const Panel = ({ options }) => {
const { myOptionName } = options;
// apply it in rendering/logic
};
```

No option may remain unused.

---

# Quick Editor Recipes

### **Boolean**

```ts
.addBooleanSwitch({ path: 'flag', name: 'Flag', defaultValue: false })
```

### **Number**

```ts
.addNumberInput({
path: 'max',
name: 'Max value',
defaultValue: 10,
settings: { min: 1, max: 100, step: 1 },
})
```

### **String**

```ts
.addTextInput({ path: 'label', name: 'Label', defaultValue: '' })
```

### **Select**

```ts
.addSelect({
path: 'mode',
name: 'Mode',
defaultValue: 'auto',
settings: { options: [
{ value: 'a', label: 'A' },
{ value: 'b', label: 'B' },
]},
})
```

### **Radio**

```ts
.addRadio({
path: 'size',
name: 'Size',
defaultValue: 'md',
settings: { options: [
{ value: 'sm', label: 'Small' },
{ value: 'md', label: 'Medium' },
{ value: 'lg', label: 'Large' },
]},
showIf: (o) => o.showSize,
})
```
54 changes: 54 additions & 0 deletions xability-maidr-panel/.config/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
ARG grafana_version=latest
ARG grafana_image=grafana-enterprise

FROM grafana/${grafana_image}:${grafana_version}

ARG anonymous_auth_enabled=true
ARG development=false
ARG TARGETARCH


ENV DEV "${development}"

# Make it as simple as possible to access the grafana instance for development purposes
# Do NOT enable these settings in a public facing / production grafana instance
ENV GF_AUTH_ANONYMOUS_ORG_ROLE "Admin"
ENV GF_AUTH_ANONYMOUS_ENABLED "${anonymous_auth_enabled}"
ENV GF_AUTH_BASIC_ENABLED "false"
# Set development mode so plugins can be loaded without the need to sign
ENV GF_DEFAULT_APP_MODE "development"


LABEL maintainer="Grafana Labs <[email protected]>"

ENV GF_PATHS_HOME="/usr/share/grafana"
WORKDIR $GF_PATHS_HOME

USER root

# Installing supervisor and inotify-tools
RUN if [ "${development}" = "true" ]; then \
if grep -i -q alpine /etc/issue; then \
apk add supervisor inotify-tools git; \
elif grep -i -q ubuntu /etc/issue; then \
DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y supervisor inotify-tools git && \
rm -rf /var/lib/apt/lists/*; \
else \
echo 'ERROR: Unsupported base image' && /bin/false; \
fi \
fi

COPY supervisord/supervisord.conf /etc/supervisor.d/supervisord.ini
COPY supervisord/supervisord.conf /etc/supervisor/conf.d/supervisord.conf



# Inject livereload script into grafana index.html
RUN sed -i 's|</body>|<script src="http://localhost:35729/livereload.js"></script></body>|g' /usr/share/grafana/public/views/index.html


COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Loading
Loading