@marble-sh/backstage-plugin-grafana-common 1.0.1 → 1.1.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 ADDED
@@ -0,0 +1,73 @@
1
+ # @marble-sh/backstage-plugin-grafana-common
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 476051b: Catalog discovery no longer leaves the `defaultOwner` relation dangling, and
8
+ every discovered dashboard `Resource` now shows exactly its own dashboard.
9
+
10
+ - The entity provider creates a placeholder `Group` (`spec.type: virtual`) for
11
+ `defaultOwner` while no other catalog source defines that ref; a definition
12
+ from any other source (e.g. a hand-written catalog-info.yaml) always takes
13
+ precedence. Disable with `grafana.catalog.emitOwnerGroup: false` — also the
14
+ handover switch for replacing an existing placeholder with your own
15
+ definition (see the module README).
16
+ - New `grafana/dashboard-uid` annotation (exact, case-sensitive uid match),
17
+ supported end-to-end: `getDashboardUid` in `-common`, `DashboardFilter.uid`
18
+ in `-node`, a `uid` query parameter on the backend dashboard routes,
19
+ `ListDashboardsRequest.uid` + `DashboardsCard` support in the frontend, and
20
+ emitted by catalog discovery on every dashboard `Resource`.
21
+
22
+ ## 1.0.2
23
+
24
+ ### Patch Changes
25
+
26
+ - d5daa0c: Fixed: `release:publish` now actually invokes `scripts/release-publish.mjs`.
27
+ The previous fix added the script but left the release script running
28
+ `changeset publish`, so 1.0.1 was published with the same raw `workspace:^`
29
+ and `backstage:^` ranges as 1.0.0 and remains uninstallable outside this
30
+ monorepo. This release is the first one packed with Yarn (materialized
31
+ dependency ranges).
32
+
33
+ ## 1.0.1
34
+
35
+ ### Patch Changes
36
+
37
+ - 577eaca: Fixed: published manifests now carry real semver dependency ranges. Versions
38
+ 0.2.0 and 1.0.0 were published via `changeset publish` (plain `npm publish`),
39
+ which skips Yarn's pack hooks and leaked the raw `workspace:^` and
40
+ `backstage:^` protocols into the registry manifests, making the packages
41
+ uninstallable outside this monorepo. Releases now publish `yarn pack` tarballs
42
+ through the npm CLI.
43
+
44
+ ## 1.0.0
45
+
46
+ ### Major Changes
47
+
48
+ - b179a8f: Bumping to version 1.0.0, General Release!
49
+
50
+ ## 0.2.0
51
+
52
+ ### Minor Changes
53
+
54
+ - 72afd3d: Initial release of the Grafana plugin suite: a read-only, backend-centric
55
+ Grafana integration built on Backstage's new backend system, with a frontend
56
+ that supports both the legacy and the new frontend systems.
57
+
58
+ - `grafana-common` — shared entity annotations and data-transfer types.
59
+ - `grafana-node` — shared Grafana HTTP client (App Platform APIs, with folder
60
+ resolution), instance config reader + schema, and filters. Dashboard
61
+ selection supports comma-separated multi-value queries, and per-instance
62
+ flags can disable dashboards, alerts, or folder resolution.
63
+ - `grafana-backend` — read-only REST API with cache/database storage,
64
+ scheduled refresh, and `allowOnDemandRefresh` / `fetchOnDemand` flags to
65
+ make Grafana traffic fully deterministic. Discovery scoping and scaffolder
66
+ guard-rail options round out the configuration surface.
67
+ - `grafana` — entity dashboard/alert cards and content plus a standalone
68
+ instances page, for both frontend systems (new system via the `/alpha`
69
+ export).
70
+ - `catalog-backend-module-grafana` — discovers Grafana instances and dashboards
71
+ as catalog `Resource` entities with dependency relations.
72
+ - `scaffolder-backend-module-grafana` — a `grafana:dashboard:create` scaffolder
73
+ action.
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
  *
@@ -176,5 +190,5 @@ type ListAlertsResponse = {
176
190
  items: GrafanaAlert[];
177
191
  };
178
192
 
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 };
193
+ 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 };
180
194
  export type { GrafanaAlert, GrafanaAlertState, GrafanaDashboard, GrafanaInstanceInfo, ListAlertsResponse, ListDashboardsResponse, ListInstancesResponse };
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.1",
3
+ "version": "1.1.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",
@@ -28,10 +28,10 @@
28
28
  "test": "backstage-cli package test"
29
29
  },
30
30
  "dependencies": {
31
- "@backstage/catalog-model": "backstage:^"
31
+ "@backstage/catalog-model": "^1.10.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@backstage/cli": "backstage:^"
34
+ "@backstage/cli": "^0.36.5"
35
35
  },
36
36
  "files": [
37
37
  "dist"
@@ -66,4 +66,4 @@
66
66
  }
67
67
  },
68
68
  "module": "./dist/index.esm.js"
69
- }
69
+ }