@marble-sh/backstage-plugin-grafana-common 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # @marble-sh/backstage-plugin-grafana-common
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a31b91d: Added: the dashboards tab now renders real graphs and the alerts tab a live
8
+ detail table. The backend gained read-only panel routes
9
+ (`GET …/dashboards/:uid/panels` and `GET …/panels/:panelId/data?from&to`)
10
+ that read a dashboard's model, resolve its template variables' current
11
+ values, query the panel targets through Grafana's `/api/ds/query`, and
12
+ return normalized time series — gated by the new `grafana.allowPanelQueries`
13
+ flag and cached per `grafana.panelDataCacheTtl` (default 30s). The frontend
14
+ draws `timeseries`/`graph` panels as charts and `stat`/`gauge`/`singlestat`
15
+ panels as value tiles, per-dashboard and lazily, with a time-range picker
16
+ and refresh. Alerts are enriched with rule uid (deep links), health,
17
+ active-since, active instance count, dashboard/panel links, and the
18
+ `summary` annotation. `GrafanaClient`/`GrafanaService` gained _optional_
19
+ `getPanels`/`getPanelData` members, so existing custom implementations
20
+ remain compatible.
21
+
22
+ ## 1.1.0
23
+
24
+ ### Minor Changes
25
+
26
+ - 476051b: Catalog discovery no longer leaves the `defaultOwner` relation dangling, and
27
+ every discovered dashboard `Resource` now shows exactly its own dashboard.
28
+
29
+ - The entity provider creates a placeholder `Group` (`spec.type: virtual`) for
30
+ `defaultOwner` while no other catalog source defines that ref; a definition
31
+ from any other source (e.g. a hand-written catalog-info.yaml) always takes
32
+ precedence. Disable with `grafana.catalog.emitOwnerGroup: false` — also the
33
+ handover switch for replacing an existing placeholder with your own
34
+ definition (see the module README).
35
+ - New `grafana/dashboard-uid` annotation (exact, case-sensitive uid match),
36
+ supported end-to-end: `getDashboardUid` in `-common`, `DashboardFilter.uid`
37
+ in `-node`, a `uid` query parameter on the backend dashboard routes,
38
+ `ListDashboardsRequest.uid` + `DashboardsCard` support in the frontend, and
39
+ emitted by catalog discovery on every dashboard `Resource`.
40
+
3
41
  ## 1.0.2
4
42
 
5
43
  ### Patch Changes
package/README.md CHANGED
@@ -16,6 +16,7 @@ It provides:
16
16
  | ------------------------------ | ------------------------ | ------------------------------------------------------------------ |
17
17
  | `grafana/instance` | `getGrafanaInstanceName` | Which configured Grafana instance the entity belongs to. |
18
18
  | `grafana/dashboard-selector` | `getDashboardSelector` | Comma-separated title substrings; any match selects the dashboard. |
19
+ | `grafana/dashboard-uid` | `getDashboardUid` | A single dashboard uid (exact, case-sensitive match). |
19
20
  | `grafana/tag-selector` | `getTagSelector` | A comma-separated list of dashboard tags. |
20
21
  | `grafana/alert-label-selector` | `getAlertLabelSelector` | A `key=value,...` list of alert label matchers. |
21
22
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const GRAFANA_ANNOTATION_INSTANCE = "grafana/instance";
4
4
  const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR = "grafana/dashboard-selector";
5
+ const GRAFANA_ANNOTATION_DASHBOARD_UID = "grafana/dashboard-uid";
5
6
  const GRAFANA_ANNOTATION_TAG_SELECTOR = "grafana/tag-selector";
6
7
  const GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR = "grafana/alert-label-selector";
7
8
  const read = (entity, key) => {
@@ -10,20 +11,23 @@ const read = (entity, key) => {
10
11
  };
11
12
  const getGrafanaInstanceName = (entity) => read(entity, GRAFANA_ANNOTATION_INSTANCE);
12
13
  const getDashboardSelector = (entity) => read(entity, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR);
14
+ const getDashboardUid = (entity) => read(entity, GRAFANA_ANNOTATION_DASHBOARD_UID);
13
15
  const getTagSelector = (entity) => read(entity, GRAFANA_ANNOTATION_TAG_SELECTOR);
14
16
  const getAlertLabelSelector = (entity) => read(entity, GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR);
15
17
  const isDashboardsAvailable = (entity) => Boolean(
16
- getGrafanaInstanceName(entity) || getDashboardSelector(entity) || getTagSelector(entity)
18
+ getGrafanaInstanceName(entity) || getDashboardSelector(entity) || getDashboardUid(entity) || getTagSelector(entity)
17
19
  );
18
20
  const isAlertsAvailable = (entity) => Boolean(getGrafanaInstanceName(entity) || getAlertLabelSelector(entity));
19
21
  const isGrafanaAvailable = (entity) => isDashboardsAvailable(entity) || isAlertsAvailable(entity);
20
22
 
21
23
  exports.GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR = GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR;
22
24
  exports.GRAFANA_ANNOTATION_DASHBOARD_SELECTOR = GRAFANA_ANNOTATION_DASHBOARD_SELECTOR;
25
+ exports.GRAFANA_ANNOTATION_DASHBOARD_UID = GRAFANA_ANNOTATION_DASHBOARD_UID;
23
26
  exports.GRAFANA_ANNOTATION_INSTANCE = GRAFANA_ANNOTATION_INSTANCE;
24
27
  exports.GRAFANA_ANNOTATION_TAG_SELECTOR = GRAFANA_ANNOTATION_TAG_SELECTOR;
25
28
  exports.getAlertLabelSelector = getAlertLabelSelector;
26
29
  exports.getDashboardSelector = getDashboardSelector;
30
+ exports.getDashboardUid = getDashboardUid;
27
31
  exports.getGrafanaInstanceName = getGrafanaInstanceName;
28
32
  exports.getTagSelector = getTagSelector;
29
33
  exports.isAlertsAvailable = isAlertsAvailable;
@@ -1 +1 @@
1
- {"version":3,"file":"annotations.cjs.js","sources":["../src/annotations.ts"],"sourcesContent":["/*\n * Copyright 2026 Cassidy Marble\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\n\n/**\n * Selects which configured Grafana instance an entity belongs to. The value\n * must match an instance `name` in the backend configuration.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_INSTANCE = 'grafana/instance';\n\n/**\n * Selects the dashboards shown for an entity: a comma-separated list of\n * case-insensitive title substrings, matching any dashboard whose title\n * contains at least one of the values.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR =\n 'grafana/dashboard-selector';\n\n/**\n * A comma-separated list of dashboard tags used to select the dashboards shown\n * for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_TAG_SELECTOR = 'grafana/tag-selector';\n\n/**\n * A comma-separated list of `key=value` label matchers used to select the\n * alerts shown for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR =\n 'grafana/alert-label-selector';\n\nconst read = (entity: Entity, key: string): string | undefined => {\n const value = entity.metadata.annotations?.[key];\n return value ? value : undefined;\n};\n\n/**\n * Returns the configured Grafana instance name for an entity, if any.\n *\n * @public\n */\nexport const getGrafanaInstanceName = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_INSTANCE);\n\n/**\n * Returns the dashboard selector expression for an entity, if any.\n *\n * @public\n */\nexport const getDashboardSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR);\n\n/**\n * Returns the dashboard tag selector for an entity, if any.\n *\n * @public\n */\nexport const getTagSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_TAG_SELECTOR);\n\n/**\n * Returns the alert label selector for an entity, if any.\n *\n * @public\n */\nexport const getAlertLabelSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR);\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * dashboards, and therefore has dashboard content to display.\n *\n * @public\n */\nexport const isDashboardsAvailable = (entity: Entity): boolean =>\n Boolean(\n getGrafanaInstanceName(entity) ||\n getDashboardSelector(entity) ||\n getTagSelector(entity),\n );\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * alerts, and therefore has alert content to display.\n *\n * @public\n */\nexport const isAlertsAvailable = (entity: Entity): boolean =>\n Boolean(getGrafanaInstanceName(entity) || getAlertLabelSelector(entity));\n\n/**\n * Returns `true` when an entity carries any Grafana annotation, and therefore\n * has Grafana content to display.\n *\n * @public\n */\nexport const isGrafanaAvailable = (entity: Entity): boolean =>\n isDashboardsAvailable(entity) || isAlertsAvailable(entity);\n"],"names":[],"mappings":";;AAwBO,MAAM,2BAAA,GAA8B;AASpC,MAAM,qCAAA,GACX;AAQK,MAAM,+BAAA,GAAkC;AAQxC,MAAM,uCAAA,GACX;AAEF,MAAM,IAAA,GAAO,CAAC,MAAA,EAAgB,GAAA,KAAoC;AAChE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,GAAG,CAAA;AAC/C,EAAA,OAAO,QAAQ,KAAA,GAAQ,MAAA;AACzB,CAAA;AAOO,MAAM,sBAAA,GAAyB,CAAC,MAAA,KACrC,IAAA,CAAK,QAAQ,2BAA2B;AAOnC,MAAM,oBAAA,GAAuB,CAAC,MAAA,KACnC,IAAA,CAAK,QAAQ,qCAAqC;AAO7C,MAAM,cAAA,GAAiB,CAAC,MAAA,KAC7B,IAAA,CAAK,QAAQ,+BAA+B;AAOvC,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,IAAA,CAAK,QAAQ,uCAAuC;AAQ/C,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,OAAA;AAAA,EACE,uBAAuB,MAAM,CAAA,IAC3B,qBAAqB,MAAM,CAAA,IAC3B,eAAe,MAAM;AACzB;AAQK,MAAM,iBAAA,GAAoB,CAAC,MAAA,KAChC,OAAA,CAAQ,uBAAuB,MAAM,CAAA,IAAK,qBAAA,CAAsB,MAAM,CAAC;AAQlE,MAAM,qBAAqB,CAAC,MAAA,KACjC,sBAAsB,MAAM,CAAA,IAAK,kBAAkB,MAAM;;;;;;;;;;;;"}
1
+ {"version":3,"file":"annotations.cjs.js","sources":["../src/annotations.ts"],"sourcesContent":["/*\n * Copyright 2026 Cassidy Marble\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\n\n/**\n * Selects which configured Grafana instance an entity belongs to. The value\n * must match an instance `name` in the backend configuration.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_INSTANCE = 'grafana/instance';\n\n/**\n * Selects the dashboards shown for an entity: a comma-separated list of\n * case-insensitive title substrings, matching any dashboard whose title\n * contains at least one of the values.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR =\n 'grafana/dashboard-selector';\n\n/**\n * Selects a single dashboard by its Grafana uid (exact, case-sensitive match).\n * Written by catalog discovery onto the `Resource` it emits per dashboard, so\n * that the entity shows exactly its own dashboard; it can also be set by hand.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_DASHBOARD_UID = 'grafana/dashboard-uid';\n\n/**\n * A comma-separated list of dashboard tags used to select the dashboards shown\n * for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_TAG_SELECTOR = 'grafana/tag-selector';\n\n/**\n * A comma-separated list of `key=value` label matchers used to select the\n * alerts shown for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR =\n 'grafana/alert-label-selector';\n\nconst read = (entity: Entity, key: string): string | undefined => {\n const value = entity.metadata.annotations?.[key];\n return value ? value : undefined;\n};\n\n/**\n * Returns the configured Grafana instance name for an entity, if any.\n *\n * @public\n */\nexport const getGrafanaInstanceName = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_INSTANCE);\n\n/**\n * Returns the dashboard selector expression for an entity, if any.\n *\n * @public\n */\nexport const getDashboardSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR);\n\n/**\n * Returns the dashboard uid selector for an entity, if any.\n *\n * @public\n */\nexport const getDashboardUid = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_DASHBOARD_UID);\n\n/**\n * Returns the dashboard tag selector for an entity, if any.\n *\n * @public\n */\nexport const getTagSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_TAG_SELECTOR);\n\n/**\n * Returns the alert label selector for an entity, if any.\n *\n * @public\n */\nexport const getAlertLabelSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR);\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * dashboards, and therefore has dashboard content to display.\n *\n * @public\n */\nexport const isDashboardsAvailable = (entity: Entity): boolean =>\n Boolean(\n getGrafanaInstanceName(entity) ||\n getDashboardSelector(entity) ||\n getDashboardUid(entity) ||\n getTagSelector(entity),\n );\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * alerts, and therefore has alert content to display.\n *\n * @public\n */\nexport const isAlertsAvailable = (entity: Entity): boolean =>\n Boolean(getGrafanaInstanceName(entity) || getAlertLabelSelector(entity));\n\n/**\n * Returns `true` when an entity carries any Grafana annotation, and therefore\n * has Grafana content to display.\n *\n * @public\n */\nexport const isGrafanaAvailable = (entity: Entity): boolean =>\n isDashboardsAvailable(entity) || isAlertsAvailable(entity);\n"],"names":[],"mappings":";;AAwBO,MAAM,2BAAA,GAA8B;AASpC,MAAM,qCAAA,GACX;AASK,MAAM,gCAAA,GAAmC;AAQzC,MAAM,+BAAA,GAAkC;AAQxC,MAAM,uCAAA,GACX;AAEF,MAAM,IAAA,GAAO,CAAC,MAAA,EAAgB,GAAA,KAAoC;AAChE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,GAAG,CAAA;AAC/C,EAAA,OAAO,QAAQ,KAAA,GAAQ,MAAA;AACzB,CAAA;AAOO,MAAM,sBAAA,GAAyB,CAAC,MAAA,KACrC,IAAA,CAAK,QAAQ,2BAA2B;AAOnC,MAAM,oBAAA,GAAuB,CAAC,MAAA,KACnC,IAAA,CAAK,QAAQ,qCAAqC;AAO7C,MAAM,eAAA,GAAkB,CAAC,MAAA,KAC9B,IAAA,CAAK,QAAQ,gCAAgC;AAOxC,MAAM,cAAA,GAAiB,CAAC,MAAA,KAC7B,IAAA,CAAK,QAAQ,+BAA+B;AAOvC,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,IAAA,CAAK,QAAQ,uCAAuC;AAQ/C,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,OAAA;AAAA,EACE,sBAAA,CAAuB,MAAM,CAAA,IAC3B,oBAAA,CAAqB,MAAM,KAC3B,eAAA,CAAgB,MAAM,CAAA,IACtB,cAAA,CAAe,MAAM;AACzB;AAQK,MAAM,iBAAA,GAAoB,CAAC,MAAA,KAChC,OAAA,CAAQ,uBAAuB,MAAM,CAAA,IAAK,qBAAA,CAAsB,MAAM,CAAC;AAQlE,MAAM,qBAAqB,CAAC,MAAA,KACjC,sBAAsB,MAAM,CAAA,IAAK,kBAAkB,MAAM;;;;;;;;;;;;;;"}
@@ -1,5 +1,6 @@
1
1
  const GRAFANA_ANNOTATION_INSTANCE = "grafana/instance";
2
2
  const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR = "grafana/dashboard-selector";
3
+ const GRAFANA_ANNOTATION_DASHBOARD_UID = "grafana/dashboard-uid";
3
4
  const GRAFANA_ANNOTATION_TAG_SELECTOR = "grafana/tag-selector";
4
5
  const GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR = "grafana/alert-label-selector";
5
6
  const read = (entity, key) => {
@@ -8,13 +9,14 @@ const read = (entity, key) => {
8
9
  };
9
10
  const getGrafanaInstanceName = (entity) => read(entity, GRAFANA_ANNOTATION_INSTANCE);
10
11
  const getDashboardSelector = (entity) => read(entity, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR);
12
+ const getDashboardUid = (entity) => read(entity, GRAFANA_ANNOTATION_DASHBOARD_UID);
11
13
  const getTagSelector = (entity) => read(entity, GRAFANA_ANNOTATION_TAG_SELECTOR);
12
14
  const getAlertLabelSelector = (entity) => read(entity, GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR);
13
15
  const isDashboardsAvailable = (entity) => Boolean(
14
- getGrafanaInstanceName(entity) || getDashboardSelector(entity) || getTagSelector(entity)
16
+ getGrafanaInstanceName(entity) || getDashboardSelector(entity) || getDashboardUid(entity) || getTagSelector(entity)
15
17
  );
16
18
  const isAlertsAvailable = (entity) => Boolean(getGrafanaInstanceName(entity) || getAlertLabelSelector(entity));
17
19
  const isGrafanaAvailable = (entity) => isDashboardsAvailable(entity) || isAlertsAvailable(entity);
18
20
 
19
- export { GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR, GRAFANA_ANNOTATION_INSTANCE, GRAFANA_ANNOTATION_TAG_SELECTOR, getAlertLabelSelector, getDashboardSelector, getGrafanaInstanceName, getTagSelector, isAlertsAvailable, isDashboardsAvailable, isGrafanaAvailable };
21
+ export { GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_UID, GRAFANA_ANNOTATION_INSTANCE, GRAFANA_ANNOTATION_TAG_SELECTOR, getAlertLabelSelector, getDashboardSelector, getDashboardUid, getGrafanaInstanceName, getTagSelector, isAlertsAvailable, isDashboardsAvailable, isGrafanaAvailable };
20
22
  //# sourceMappingURL=annotations.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"annotations.esm.js","sources":["../src/annotations.ts"],"sourcesContent":["/*\n * Copyright 2026 Cassidy Marble\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\n\n/**\n * Selects which configured Grafana instance an entity belongs to. The value\n * must match an instance `name` in the backend configuration.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_INSTANCE = 'grafana/instance';\n\n/**\n * Selects the dashboards shown for an entity: a comma-separated list of\n * case-insensitive title substrings, matching any dashboard whose title\n * contains at least one of the values.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR =\n 'grafana/dashboard-selector';\n\n/**\n * A comma-separated list of dashboard tags used to select the dashboards shown\n * for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_TAG_SELECTOR = 'grafana/tag-selector';\n\n/**\n * A comma-separated list of `key=value` label matchers used to select the\n * alerts shown for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR =\n 'grafana/alert-label-selector';\n\nconst read = (entity: Entity, key: string): string | undefined => {\n const value = entity.metadata.annotations?.[key];\n return value ? value : undefined;\n};\n\n/**\n * Returns the configured Grafana instance name for an entity, if any.\n *\n * @public\n */\nexport const getGrafanaInstanceName = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_INSTANCE);\n\n/**\n * Returns the dashboard selector expression for an entity, if any.\n *\n * @public\n */\nexport const getDashboardSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR);\n\n/**\n * Returns the dashboard tag selector for an entity, if any.\n *\n * @public\n */\nexport const getTagSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_TAG_SELECTOR);\n\n/**\n * Returns the alert label selector for an entity, if any.\n *\n * @public\n */\nexport const getAlertLabelSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR);\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * dashboards, and therefore has dashboard content to display.\n *\n * @public\n */\nexport const isDashboardsAvailable = (entity: Entity): boolean =>\n Boolean(\n getGrafanaInstanceName(entity) ||\n getDashboardSelector(entity) ||\n getTagSelector(entity),\n );\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * alerts, and therefore has alert content to display.\n *\n * @public\n */\nexport const isAlertsAvailable = (entity: Entity): boolean =>\n Boolean(getGrafanaInstanceName(entity) || getAlertLabelSelector(entity));\n\n/**\n * Returns `true` when an entity carries any Grafana annotation, and therefore\n * has Grafana content to display.\n *\n * @public\n */\nexport const isGrafanaAvailable = (entity: Entity): boolean =>\n isDashboardsAvailable(entity) || isAlertsAvailable(entity);\n"],"names":[],"mappings":"AAwBO,MAAM,2BAAA,GAA8B;AASpC,MAAM,qCAAA,GACX;AAQK,MAAM,+BAAA,GAAkC;AAQxC,MAAM,uCAAA,GACX;AAEF,MAAM,IAAA,GAAO,CAAC,MAAA,EAAgB,GAAA,KAAoC;AAChE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,GAAG,CAAA;AAC/C,EAAA,OAAO,QAAQ,KAAA,GAAQ,MAAA;AACzB,CAAA;AAOO,MAAM,sBAAA,GAAyB,CAAC,MAAA,KACrC,IAAA,CAAK,QAAQ,2BAA2B;AAOnC,MAAM,oBAAA,GAAuB,CAAC,MAAA,KACnC,IAAA,CAAK,QAAQ,qCAAqC;AAO7C,MAAM,cAAA,GAAiB,CAAC,MAAA,KAC7B,IAAA,CAAK,QAAQ,+BAA+B;AAOvC,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,IAAA,CAAK,QAAQ,uCAAuC;AAQ/C,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,OAAA;AAAA,EACE,uBAAuB,MAAM,CAAA,IAC3B,qBAAqB,MAAM,CAAA,IAC3B,eAAe,MAAM;AACzB;AAQK,MAAM,iBAAA,GAAoB,CAAC,MAAA,KAChC,OAAA,CAAQ,uBAAuB,MAAM,CAAA,IAAK,qBAAA,CAAsB,MAAM,CAAC;AAQlE,MAAM,qBAAqB,CAAC,MAAA,KACjC,sBAAsB,MAAM,CAAA,IAAK,kBAAkB,MAAM;;"}
1
+ {"version":3,"file":"annotations.esm.js","sources":["../src/annotations.ts"],"sourcesContent":["/*\n * Copyright 2026 Cassidy Marble\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\n\n/**\n * Selects which configured Grafana instance an entity belongs to. The value\n * must match an instance `name` in the backend configuration.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_INSTANCE = 'grafana/instance';\n\n/**\n * Selects the dashboards shown for an entity: a comma-separated list of\n * case-insensitive title substrings, matching any dashboard whose title\n * contains at least one of the values.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR =\n 'grafana/dashboard-selector';\n\n/**\n * Selects a single dashboard by its Grafana uid (exact, case-sensitive match).\n * Written by catalog discovery onto the `Resource` it emits per dashboard, so\n * that the entity shows exactly its own dashboard; it can also be set by hand.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_DASHBOARD_UID = 'grafana/dashboard-uid';\n\n/**\n * A comma-separated list of dashboard tags used to select the dashboards shown\n * for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_TAG_SELECTOR = 'grafana/tag-selector';\n\n/**\n * A comma-separated list of `key=value` label matchers used to select the\n * alerts shown for an entity.\n *\n * @public\n */\nexport const GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR =\n 'grafana/alert-label-selector';\n\nconst read = (entity: Entity, key: string): string | undefined => {\n const value = entity.metadata.annotations?.[key];\n return value ? value : undefined;\n};\n\n/**\n * Returns the configured Grafana instance name for an entity, if any.\n *\n * @public\n */\nexport const getGrafanaInstanceName = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_INSTANCE);\n\n/**\n * Returns the dashboard selector expression for an entity, if any.\n *\n * @public\n */\nexport const getDashboardSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR);\n\n/**\n * Returns the dashboard uid selector for an entity, if any.\n *\n * @public\n */\nexport const getDashboardUid = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_DASHBOARD_UID);\n\n/**\n * Returns the dashboard tag selector for an entity, if any.\n *\n * @public\n */\nexport const getTagSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_TAG_SELECTOR);\n\n/**\n * Returns the alert label selector for an entity, if any.\n *\n * @public\n */\nexport const getAlertLabelSelector = (entity: Entity): string | undefined =>\n read(entity, GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR);\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * dashboards, and therefore has dashboard content to display.\n *\n * @public\n */\nexport const isDashboardsAvailable = (entity: Entity): boolean =>\n Boolean(\n getGrafanaInstanceName(entity) ||\n getDashboardSelector(entity) ||\n getDashboardUid(entity) ||\n getTagSelector(entity),\n );\n\n/**\n * Returns `true` when an entity carries an annotation that selects Grafana\n * alerts, and therefore has alert content to display.\n *\n * @public\n */\nexport const isAlertsAvailable = (entity: Entity): boolean =>\n Boolean(getGrafanaInstanceName(entity) || getAlertLabelSelector(entity));\n\n/**\n * Returns `true` when an entity carries any Grafana annotation, and therefore\n * has Grafana content to display.\n *\n * @public\n */\nexport const isGrafanaAvailable = (entity: Entity): boolean =>\n isDashboardsAvailable(entity) || isAlertsAvailable(entity);\n"],"names":[],"mappings":"AAwBO,MAAM,2BAAA,GAA8B;AASpC,MAAM,qCAAA,GACX;AASK,MAAM,gCAAA,GAAmC;AAQzC,MAAM,+BAAA,GAAkC;AAQxC,MAAM,uCAAA,GACX;AAEF,MAAM,IAAA,GAAO,CAAC,MAAA,EAAgB,GAAA,KAAoC;AAChE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,GAAG,CAAA;AAC/C,EAAA,OAAO,QAAQ,KAAA,GAAQ,MAAA;AACzB,CAAA;AAOO,MAAM,sBAAA,GAAyB,CAAC,MAAA,KACrC,IAAA,CAAK,QAAQ,2BAA2B;AAOnC,MAAM,oBAAA,GAAuB,CAAC,MAAA,KACnC,IAAA,CAAK,QAAQ,qCAAqC;AAO7C,MAAM,eAAA,GAAkB,CAAC,MAAA,KAC9B,IAAA,CAAK,QAAQ,gCAAgC;AAOxC,MAAM,cAAA,GAAiB,CAAC,MAAA,KAC7B,IAAA,CAAK,QAAQ,+BAA+B;AAOvC,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,IAAA,CAAK,QAAQ,uCAAuC;AAQ/C,MAAM,qBAAA,GAAwB,CAAC,MAAA,KACpC,OAAA;AAAA,EACE,sBAAA,CAAuB,MAAM,CAAA,IAC3B,oBAAA,CAAqB,MAAM,KAC3B,eAAA,CAAgB,MAAM,CAAA,IACtB,cAAA,CAAe,MAAM;AACzB;AAQK,MAAM,iBAAA,GAAoB,CAAC,MAAA,KAChC,OAAA,CAAQ,uBAAuB,MAAM,CAAA,IAAK,qBAAA,CAAsB,MAAM,CAAC;AAQlE,MAAM,qBAAqB,CAAC,MAAA,KACjC,sBAAsB,MAAM,CAAA,IAAK,kBAAkB,MAAM;;"}
package/dist/index.cjs.js CHANGED
@@ -7,10 +7,12 @@ var selectors = require('./selectors.cjs.js');
7
7
 
8
8
  exports.GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR = annotations.GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR;
9
9
  exports.GRAFANA_ANNOTATION_DASHBOARD_SELECTOR = annotations.GRAFANA_ANNOTATION_DASHBOARD_SELECTOR;
10
+ exports.GRAFANA_ANNOTATION_DASHBOARD_UID = annotations.GRAFANA_ANNOTATION_DASHBOARD_UID;
10
11
  exports.GRAFANA_ANNOTATION_INSTANCE = annotations.GRAFANA_ANNOTATION_INSTANCE;
11
12
  exports.GRAFANA_ANNOTATION_TAG_SELECTOR = annotations.GRAFANA_ANNOTATION_TAG_SELECTOR;
12
13
  exports.getAlertLabelSelector = annotations.getAlertLabelSelector;
13
14
  exports.getDashboardSelector = annotations.getDashboardSelector;
15
+ exports.getDashboardUid = annotations.getDashboardUid;
14
16
  exports.getGrafanaInstanceName = annotations.getGrafanaInstanceName;
15
17
  exports.getTagSelector = annotations.getTagSelector;
16
18
  exports.isAlertsAvailable = annotations.isAlertsAvailable;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -15,6 +15,14 @@ declare const GRAFANA_ANNOTATION_INSTANCE = "grafana/instance";
15
15
  * @public
16
16
  */
17
17
  declare const GRAFANA_ANNOTATION_DASHBOARD_SELECTOR = "grafana/dashboard-selector";
18
+ /**
19
+ * Selects a single dashboard by its Grafana uid (exact, case-sensitive match).
20
+ * Written by catalog discovery onto the `Resource` it emits per dashboard, so
21
+ * that the entity shows exactly its own dashboard; it can also be set by hand.
22
+ *
23
+ * @public
24
+ */
25
+ declare const GRAFANA_ANNOTATION_DASHBOARD_UID = "grafana/dashboard-uid";
18
26
  /**
19
27
  * A comma-separated list of dashboard tags used to select the dashboards shown
20
28
  * for an entity.
@@ -41,6 +49,12 @@ declare const getGrafanaInstanceName: (entity: Entity) => string | undefined;
41
49
  * @public
42
50
  */
43
51
  declare const getDashboardSelector: (entity: Entity) => string | undefined;
52
+ /**
53
+ * Returns the dashboard uid selector for an entity, if any.
54
+ *
55
+ * @public
56
+ */
57
+ declare const getDashboardUid: (entity: Entity) => string | undefined;
44
58
  /**
45
59
  * Returns the dashboard tag selector for an entity, if any.
46
60
  *
@@ -132,6 +146,12 @@ type GrafanaDashboard = {
132
146
  * @public
133
147
  */
134
148
  type GrafanaAlertState = 'firing' | 'pending' | 'inactive' | 'normal' | 'no_data' | 'error' | 'unknown';
149
+ /**
150
+ * The evaluation health of a Grafana alert rule.
151
+ *
152
+ * @public
153
+ */
154
+ type GrafanaAlertHealth = 'ok' | 'error' | 'nodata' | 'unknown';
135
155
  /**
136
156
  * A Grafana alert rule together with its current state.
137
157
  *
@@ -150,6 +170,90 @@ type GrafanaAlert = {
150
170
  folderTitle?: string;
151
171
  /** The name of the instance this alert was read from. */
152
172
  instanceName: string;
173
+ /** The alert rule uid, when the source API provides one. */
174
+ uid?: string;
175
+ /** The evaluation health of the rule. */
176
+ health?: GrafanaAlertHealth;
177
+ /** The rule's `summary` annotation, if any. */
178
+ summary?: string;
179
+ /** ISO-8601 timestamp of when the rule became active, when it is. */
180
+ activeAt?: string;
181
+ /** The number of currently active (pending or firing) alert instances. */
182
+ activeCount?: number;
183
+ /** The uid of the dashboard the rule is linked to, if any. */
184
+ dashboardUid?: string;
185
+ /** The id of the panel the rule is linked to, if any. */
186
+ panelId?: number;
187
+ };
188
+ /**
189
+ * How a Grafana panel is rendered by the frontend.
190
+ *
191
+ * - `timeseries`: rendered as a chart (Grafana `timeseries` and legacy `graph`
192
+ * panels).
193
+ * - `stat`: rendered as a single-value tile (Grafana `stat`, `gauge`, and
194
+ * legacy `singlestat` panels).
195
+ * - `unsupported`: not rendered; shown as a link into Grafana.
196
+ *
197
+ * @public
198
+ */
199
+ type GrafanaPanelKind = 'timeseries' | 'stat' | 'unsupported';
200
+ /**
201
+ * A single panel of a Grafana dashboard, as listed by the backend.
202
+ *
203
+ * @public
204
+ */
205
+ type GrafanaPanel = {
206
+ /** The panel id, unique within its dashboard. */
207
+ id: number;
208
+ /** The panel title. */
209
+ title: string;
210
+ /** The raw Grafana panel type (`timeseries`, `stat`, `table`, ...). */
211
+ type: string;
212
+ /** How the frontend renders this panel. */
213
+ kind: GrafanaPanelKind;
214
+ /** The panel description, if any. */
215
+ description?: string;
216
+ /** The uid of the dashboard containing the panel. */
217
+ dashboardUid: string;
218
+ /** The name of the instance the panel was read from. */
219
+ instanceName: string;
220
+ };
221
+ /**
222
+ * A single point of a time series: a timestamp and a value.
223
+ *
224
+ * @public
225
+ */
226
+ type GrafanaPanelPoint = {
227
+ /** The point's timestamp, in epoch milliseconds. */
228
+ timeMs: number;
229
+ /** The point's value; `null` marks a gap in the series. */
230
+ value: number | null;
231
+ };
232
+ /**
233
+ * A single named series of a panel's query results.
234
+ *
235
+ * @public
236
+ */
237
+ type GrafanaPanelSeries = {
238
+ /** The display name of the series. */
239
+ name: string;
240
+ /** The labels attached to the series, if any. */
241
+ labels?: Record<string, string>;
242
+ /** The data points, ordered by time. */
243
+ points: GrafanaPanelPoint[];
244
+ };
245
+ /**
246
+ * The queried data of a single panel, normalized from Grafana data frames.
247
+ *
248
+ * @public
249
+ */
250
+ type GrafanaPanelData = {
251
+ /** The id of the panel the data belongs to. */
252
+ panelId: number;
253
+ /** The normalized series, across all of the panel's queries. */
254
+ series: GrafanaPanelSeries[];
255
+ /** Human-readable notes about queries that failed or were skipped. */
256
+ warnings?: string[];
153
257
  };
154
258
  /**
155
259
  * Response body for `GET /instances`.
@@ -175,6 +279,20 @@ type ListDashboardsResponse = {
175
279
  type ListAlertsResponse = {
176
280
  items: GrafanaAlert[];
177
281
  };
282
+ /**
283
+ * Response body for the panel listing endpoint.
284
+ *
285
+ * @public
286
+ */
287
+ type ListPanelsResponse = {
288
+ items: GrafanaPanel[];
289
+ };
290
+ /**
291
+ * Response body for the panel data endpoint.
292
+ *
293
+ * @public
294
+ */
295
+ type GetPanelDataResponse = GrafanaPanelData;
178
296
 
179
- export { GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR, GRAFANA_ANNOTATION_INSTANCE, GRAFANA_ANNOTATION_TAG_SELECTOR, getAlertLabelSelector, getDashboardSelector, getGrafanaInstanceName, getTagSelector, isAlertsAvailable, isDashboardsAvailable, isGrafanaAvailable, parseLabelSelector, parseTagSelector };
180
- export type { GrafanaAlert, GrafanaAlertState, GrafanaDashboard, GrafanaInstanceInfo, ListAlertsResponse, ListDashboardsResponse, ListInstancesResponse };
297
+ export { GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_UID, GRAFANA_ANNOTATION_INSTANCE, GRAFANA_ANNOTATION_TAG_SELECTOR, getAlertLabelSelector, getDashboardSelector, getDashboardUid, getGrafanaInstanceName, getTagSelector, isAlertsAvailable, isDashboardsAvailable, isGrafanaAvailable, parseLabelSelector, parseTagSelector };
298
+ export type { GetPanelDataResponse, GrafanaAlert, GrafanaAlertHealth, GrafanaAlertState, GrafanaDashboard, GrafanaInstanceInfo, GrafanaPanel, GrafanaPanelData, GrafanaPanelKind, GrafanaPanelPoint, GrafanaPanelSeries, ListAlertsResponse, ListDashboardsResponse, ListInstancesResponse, ListPanelsResponse };
package/dist/index.esm.js CHANGED
@@ -1,3 +1,3 @@
1
- export { GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR, GRAFANA_ANNOTATION_INSTANCE, GRAFANA_ANNOTATION_TAG_SELECTOR, getAlertLabelSelector, getDashboardSelector, getGrafanaInstanceName, getTagSelector, isAlertsAvailable, isDashboardsAvailable, isGrafanaAvailable } from './annotations.esm.js';
1
+ export { GRAFANA_ANNOTATION_ALERT_LABEL_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_SELECTOR, GRAFANA_ANNOTATION_DASHBOARD_UID, GRAFANA_ANNOTATION_INSTANCE, GRAFANA_ANNOTATION_TAG_SELECTOR, getAlertLabelSelector, getDashboardSelector, getDashboardUid, getGrafanaInstanceName, getTagSelector, isAlertsAvailable, isDashboardsAvailable, isGrafanaAvailable } from './annotations.esm.js';
2
2
  export { parseLabelSelector, parseTagSelector } from './selectors.esm.js';
3
3
  //# sourceMappingURL=index.esm.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marble-sh/backstage-plugin-grafana-common",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Common functionality for the Grafana Backstage plugins (types, annotations, and API contracts)",
5
5
  "main": "./dist/index.cjs.js",
6
6
  "types": "./dist/index.d.ts",