Upgrading from v0.54.0 to v0.55.0#
This document provides instructions for upgrading Perses specially when dealing with breaking changes. As Perses is a rapidly evolving project, it's important to keep your installation up to date to benefit from the latest features and improvements.
Then, upgrading Perses is also depending on your usage as Perses can be used in different ways (e.g. as a standalone application, as a library, etc.). Therefore, the upgrade process may vary based on your specific use case.
Perses application#
Resource names are validated more strictly#
The names of the resources (metadata.name and metadata.project), the panel keys and the variable names can no longer
contain .., nor start or end with . (e.g. .foo, foo. or foo..bar). This validation is applied when a resource
is saved, but also when it is read from the database and on every parameter of the API URLs.
A resource stored with such a name can no longer be read, updated or deleted once Perses is upgraded. Rename these
resources before upgrading, while still running v0.54: re-create them with a valid name (e.g. with percli apply),
then delete the old ones. Don't forget to update the references to the renamed panels ($ref in the layouts) and
variables ($var).
You can find them with a command like this one, for each kind of resource (projects, dashboards, datasources,
variables, secrets, roles, globaldatasources, ...):
curl -s "$PERSES_URL/api/v1/dashboards" | jq -r '.[] | select(.metadata.name | test("\\.\\.|^\\.|\\.$")) | "\(.metadata.project)/\(.metadata.name)"'
Important dashboards are now grouped#
frontend.important_dashboards is now a list of groups, each with an optional title and description, and a list
of dashboard selectors. A selector without dashboard marks every dashboard of the project as important.
The previous flat list of selectors is deprecated and will be removed in v0.57.0. It is still accepted (and converted into a single untitled group), but a deprecation warning is logged at startup. Mixing both formats is rejected.
# Before
frontend:
important_dashboards:
- project: "perses"
dashboard: "Demo"
# After
frontend:
important_dashboards:
- title: "Quick links" # Optional
dashboards:
- project: "perses"
dashboard: "Demo"
If you set important dashboards through environment variables, the legacy shape is not supported there: Perses will fail to start until the variables are updated accordingly:
PERSES_FRONTEND_IMPORTANT_DASHBOARDS_0_PROJECT -> PERSES_FRONTEND_IMPORTANT_DASHBOARDS_0_DASHBOARDS_0_PROJECT
PERSES_FRONTEND_IMPORTANT_DASHBOARDS_0_DASHBOARD -> PERSES_FRONTEND_IMPORTANT_DASHBOARDS_0_DASHBOARDS_0_DASHBOARD
Perses also refuses to start when a group has no dashboards, or when a selector has no project.
Note that the endpoint GET /api/config always returns frontend.important_dashboards with the new grouped shape, even
when the legacy format is used in the configuration. Update the tools reading this endpoint accordingly.
The datasource proxy restricts the destinations it can reach#
To prevent the datasource proxy from being used to reach services that are not datasources (Server-Side Request Forgery), the destinations of the proxy are now verified. By default, the proxy refuses to reach:
- the loopback interface (
127.0.0.0/8,::1,localhost) - the link-local addresses (
169.254.0.0/16,fe80::/10) and the known cloud metadata / credentials endpoints - the multicast, reserved and unspecified addresses
- the Kubernetes API service when Perses is running in a Kubernetes cluster
- any URL scheme other than
httpandhttps, the URLs containing credentials (http://user:password@host), and the Unix sockets for the SQL datasources - the IPv4 addresses not written in the dotted-decimal notation (e.g.
2130706433,0x7f000001or127.1)
The private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, ...) remain allowed by default.
If one of your datasources is running on the same host as Perses (e.g. http://localhost:9090), the requests to
this datasource are now refused with a 403 Forbidden error, and saving such a datasource is refused with a
400 Bad Request error. The same applies to the datasources embedded in a dashboard or an ephemeral dashboard
(spec.datasources), and to the validation endpoints (POST /api/v1/validate/datasources and
POST /api/v1/validate/globaldatasources). Provisioning a resource using such a datasource fails as well: the resource
is skipped, and only an error is logged. You have to explicitly allow the loopback interface:
The same applies to the OAuth token URL of the secrets used by the datasources. Saving a secret whose OAuth token URL
is not allowed is refused with a 400 Bad Request error, and the requests to a datasource using such a secret are
refused with a 403 Forbidden error. If you restrict the destinations with allowed_hosts, this list must also contain
the host of the OAuth token URL.
When an IP address is part of both an allowed and a denied network, the most specific network wins (on equal prefix lengths, the allowed network wins). Allowing a large network therefore doesn't allow a more specific denied network, which has to be allowed explicitly. For example:
- allowing
127.0.0.0/8allows the loopback interface; - allowing
10.0.0.0/8(e.g. withdeny_private_networks: true) doesn't allow the Kubernetes API service IP; - allowing
169.254.0.0/16doesn't allow the cloud metadata endpoint169.254.169.254.
If Perses is using an HTTP proxy configured through the environment (HTTP_PROXY, HTTPS_PROXY), a datasource can no
longer target the address of this proxy, neither directly (for example when the proxy is bypassed with NO_PROXY) nor
through the proxy itself.
You can also further restrict the destinations, for example to deny the private networks or to only allow a list of hosts. See the DatasourceProxy config for more details.
Note for the users of v0.55.0-beta.4: the configuration of the HTTP proxy introduced in this beta (datasource.http_proxy)
has moved to datasource.proxy.http, so the whole configuration of the datasource proxy lives in a single section.
# Before (v0.55.0-beta.4)
datasource:
http_proxy:
max_timeout: 30s
# After
datasource:
proxy:
http:
max_timeout: 30s
The datasource proxy no longer forwards the credentials used to authenticate against Perses#
Before forwarding a request to a datasource, the proxy now removes the credentials that the client used to authenticate against Perses:
- The
Cookieheader sent by the client is always removed, because it contains the Perses session (and possibly the tokens of the OIDC/OAuth provider). You can't forward it withallowHeaders. ACookieheader explicitly defined in the datasource configuration is still sent. - When the Perses native authorization is enabled, the
Authorizationheader sent by the client (which contains the Perses token) is always removed. You can't forward it withallowHeaders. Use a Secret or theoauthPassthroughoption to authenticate against the datasource. - When the authorization is delegated (i.e. Kubernetes), the
Authorizationheader sent by the client is still forwarded to the datasource. To prevent this, addAuthorizationtodropHeaders. - An
Authorizationheader defined in theheadersof the datasource configuration is now always ignored, whatever its case (e.g.authorization). If you were using it to authenticate against the datasource, move the credentials to a Secret (authorization,basicAuthoroauth) and reference it in the datasource configuration:
# Before
kind: Datasource
spec:
plugin:
kind: PrometheusDatasource
spec:
proxy:
kind: HTTPProxy
spec:
url: https://prometheus.example.com
headers:
Authorization: "Bearer <token>"
# After
kind: Secret
metadata:
name: prometheus-token
project: my-project
spec:
authorization:
type: Bearer
credentials: "<token>"
---
kind: Datasource
spec:
plugin:
kind: PrometheusDatasource
spec:
proxy:
kind: HTTPProxy
spec:
url: https://prometheus.example.com
secret: prometheus-token
The responses returned by the datasources through the proxy are also hardened, as they are served under the Perses origin:
- they get the headers
Content-Security-Policy: sandbox; default-src 'none'; frame-ancestors 'none'andX-Content-Type-Options: nosniff; - the headers that would apply to the Perses origin are removed:
Set-Cookie,Set-Cookie2,Clear-Site-Data,Refresh,Strict-Transport-Security,Alt-Svc,Service-Worker-Allowedand the CORS headers (the CORS policy is defined bysecurity.cors); - only the relative
Location/Content-Locationheaders are kept.
As a consequence, the web UI of a datasource (e.g. the Prometheus UI) can no longer be browsed through the proxy. Access it directly instead.
See the proxy documentation for more details.
Saving a dashboard embedding a datasource with a secret requires to read the secrets#
A datasource embedded in a dashboard or an ephemeral dashboard (spec.datasources) can reference a secret of the
project. Creating or updating such a dashboard now requires the read permission on the Secret kind in the project.
Otherwise, the request is refused with a 403 Forbidden error. This doesn't apply to the provisioning.
If your users need to save such dashboards, grant them the permission to read the secrets:
kind: Role
metadata:
name: dashboard-editor
project: my-project
spec:
permissions:
- actions: ["create", "update"]
scopes: ["Dashboard"]
- actions: ["read"]
scopes: ["Secret"]
A secret can only define one authentication method#
basicAuth, authorization and oauth are now mutually exclusive in a Secret or a GlobalSecret. Previously, a secret
was only refused when the three were set at the same time. A secret defining two of them is now refused when it is
saved, and it can no longer be read once Perses is upgraded (the datasources using it then fail).
Before upgrading, keep only the authentication method actually used by the datasource:
# Before
kind: Secret
spec:
basicAuth:
username: "foo"
password: "bar"
authorization:
type: Bearer
credentials: "<token>"
# After
kind: Secret
spec:
authorization:
type: Bearer
credentials: "<token>"
The SQL proxy only accepts read-only queries#
To prevent a datasource from being used to modify the data or the schema of a database, the SQL proxy is now stricter:
- A query must start with one of these keywords:
SELECT,WITH,SHOW,DESCRIBE,DESC,EXPLAIN,VALUESorTABLE. Previously, only the queries starting with a known write keyword (INSERT,UPDATE,DELETE,DROP, ...) were refused. The queries starting with another keyword (e.g.CALL,SET,DO,PRAGMA) are now refused. - Every query is executed in a read-only transaction. The database must support read-only transactions.
- The queries using
INTO OUTFILEorINTO DUMPFILE(MySQL / MariaDB) are refused. - For MySQL / MariaDB, the driver options
multiStatements,allowAllFiles,allowCleartextPasswordsandallowOldPasswordsare always disabled, whatever the value set in the datasource. If a database user authenticates with a cleartext password (e.g.mysql_clear_passwordwith PAM or LDAP), switch it tocaching_sha2_password. - For PostgreSQL, the option
prepareThresholdis ignored.
Rewrite the queries that don't start with one of the allowed keywords, and split the queries containing several statements.
Variables referenced with ${var} are now validated#
The variables referenced in other variables are now detected with both syntaxes $var and ${var}. Previously, a
variable referencing an undefined variable, or a circular dependency between variables, was silently accepted when using
the syntax ${var}. Saving such a dashboard (or ephemeral dashboard) is now refused with an error like
variable "x" is used in the variable "y" but not defined.
Define the missing variables or remove the references. You can check your dashboards before applying them with
percli lint -f <file>.
Pyroscope datasource: the profile queries use new endpoints#
The Pyroscope plugin (v0.7.0) now queries the profiles through the Connect API of Pyroscope instead of
GET /pyroscope/render. If your Pyroscope datasources restrict the endpoints reachable through the proxy
(allowedEndpoints), the profile queries are refused with a 403 Forbidden error until the new endpoints are allowed:
kind: Datasource
spec:
plugin:
kind: PyroscopeDatasource
spec:
proxy:
kind: HTTPProxy
spec:
url: http://pyroscope:4040
allowedEndpoints:
# Before
# - endpointPattern: /pyroscope/render
# method: GET
# After
- endpointPattern: /querier.v1.QuerierService/SelectMergeStacktraces
method: POST
- endpointPattern: /querier.v1.QuerierService/SelectSeries
method: POST
- endpointPattern: /querier.v1.QuerierService/ProfileTypes
method: POST
- endpointPattern: /querier.v1.QuerierService/LabelNames
method: POST
- endpointPattern: /querier.v1.QuerierService/LabelValues
method: POST
Files referenced in secrets must be in an allowed directory#
The Secrets and GlobalSecrets can reference files from the Perses server (basicAuth.passwordFile,
authorization.credentialsFile, oauth.clientSecretFile, tlsConfig.caFile, tlsConfig.certFile,
tlsConfig.keyFile). This could be used by anyone able to create a secret to read arbitrary files through the
datasource proxy.
These file references are now rejected by default. If you rely on them, you must list the directories where the
files are located with the new setting security.secret_file_allowed_directories:
Each directory must be an absolute path, and the root directory (/) is not accepted. The file paths used in the
secrets must also be absolute. Symlinks are resolved, and the resolved file must remain inside one of the allowed
directories. The verification is done when saving a secret and before the proxy uses it. Saving a secret referencing a
file that is not allowed is refused, and the requests to a datasource using such a secret fail.
encryption_key is mandatory when the authentication is enabled#
When the authentication is enabled (security.enable_auth: true), the encryption key is also used to encrypt the access
and refresh tokens. Perses was previously falling back on a default (and publicly known) key when none was provided.
Now, if the authentication is enabled and neither security.encryption_key nor security.encryption_key_file is set,
Perses refuses to start. The key must be exactly 32 bytes long. You can generate one with one of these commands:
security:
enable_auth: true
encryption_key: "<32 bytes long secret>"
# or
encryption_key_file: /etc/perses/encryption_key
Note that if you were already running Perses with the auth enabled and without any encryption key, setting a new key means the data previously encrypted with the default key (e.g. the secrets stored in the database) can no longer be decrypted. You will have to re-create these secrets after the upgrade, and users will need to log in again.
Plugin developer#
Go and CUE toolchains#
- The Go modules (
github.com/perses/persesincluding the Go SDK,github.com/perses/specand the official plugins) now require Go 1.27.2 or later. Upgrade Go, or let Go download the right toolchain (GOTOOLCHAIN=auto), and bump thegodirective of yourgo.modif needed. - The CUE modules of Perses and of the official plugins now declare the CUE language version
v0.17.1. ThecueCLI must be at leastv0.17.1, otherwise it fails with an error likelanguage version "v0.17.1" declared in module.cue is too new.perclimust also be at leastv0.55.0to build Dashboard-as-Code depending on these modules.
Go SDK: datasource.Selector is now a union type#
datasource.Selector has been restructured as a union type to make the distinction between a
concrete datasource reference and a variable reference explicit. The flat struct (with public Kind
and Name fields) no longer exists.
Before:
// Concrete datasource — struct literal
sel := &datasource.Selector{Kind: "PrometheusDatasource", Name: "myPrometheus"}
// Reading fields
fmt.Println(sel.Kind, sel.Name)
After:
// Concrete datasource — use the constructor
sel := datasource.NewStaticSelector("PrometheusDatasource", "myPrometheus")
// Variable reference — new in this release; name may be "foo", "$foo", or "${foo}"
sel, err := datasource.NewVariableSelector("myDatasource")
// Reading fields — check which variant is set first
if sel.Static != nil {
fmt.Println(sel.Static.Kind, sel.Static.Name)
}
if sel.Variable != nil {
fmt.Println(sel.Variable.Name) // bare name without "$"
}
If you maintain a datasource plugin with a Selector helper, update it to call the constructors. This is what the
official plugins now do: their Selector helper returns an error as well, and returns a variable selector when the
name starts with $ (e.g. prometheus.Selector("$ds")):
// Before
func Selector(datasourceName string) *datasource.Selector {
return &datasource.Selector{
Kind: PluginKind,
Name: datasourceName,
}
}
// After
func Selector(datasourceName string) (*datasource.Selector, error) {
return datasource.NewSelector(PluginKind, datasourceName)
}
If you use the Selector helper of an official plugin (Prometheus, Tempo, Loki, ...), handle the returned error:
// Before
sel := prometheus.Selector("myPrometheus")
// After
sel, err := prometheus.Selector("myPrometheus")
if err != nil {
return err
}
A static selector continues to serialize as {"kind":"...","name":"..."} (unchanged from previous
versions). A variable selector serializes as the plain JSON/YAML string "$myVar". This form was already accepted by
the CUE schemas and the UI, but it can now also be produced with the Go SDK. As a consequence, a datasource name starting
with $ passed to a Selector helper is now serialized as "$myVar" instead of {"kind":"...","name":"$myVar"}.
Note that marshaling a zero-value Selector (neither Static nor Variable set) or one with both fields set returns
an error.
If you don't know in advance whether the name is a variable reference or not, use datasource.NewSelector(kind, name):
it returns a variable selector when name starts with $, and a static selector otherwise.
Go: the old dashboard and datasource specification is removed#
Since v0.54.0, the dashboard and datasource specification lives in the repository perses/spec. The deprecated copies
kept in perses/perses are now removed. If you still import them, replace the import paths as follows:
Removed package (github.com/perses/perses/...) |
Replacement (github.com/perses/spec/...) |
|---|---|
pkg/model/api/v1/common |
go/common (Plugin is now in go/plugin) |
pkg/model/api/v1/dashboard |
go/dashboard |
pkg/model/api/v1/variable |
go/dashboard/variable |
pkg/model/api/v1/plugin |
go/plugin |
pkg/model/api/v1/datasource/http |
go/datasource/proxy/http |
pkg/model/api/v1/datasource/sql |
go/datasource/proxy/sql |
The deprecated types in github.com/perses/perses/pkg/model/api/v1 (DashboardSpec, Panel, PanelSpec,
PanelDisplay, Query, QuerySpec, Link, DatasourceSpec, ...) are also removed. Use their equivalent from
github.com/perses/spec/go/dashboard and github.com/perses/spec/go/datasource (e.g. dashboard.Spec,
dashboard.Panel, datasource.Spec).
Go: other API changes#
K8sAuth.GetToken()(packagegithub.com/perses/perses/pkg/client/config) is removed, as the token is now provided by the Kubernetes client (including the exec credential plugins of the kubeconfig). UseWrapRoundTripperinstead:
// Before
token, err := k8sAuth.GetToken()
// After
roundTripper, err := k8sAuth.WrapRoundTripper(http.DefaultTransport)
- In
github.com/perses/perses/pkg/model/api/config,Frontend.ImportantDashboardsis now of typeconfig.ImportantDashboards(a list of groups), see Important dashboards are now grouped. - The field
hiddenof the variable display (github.com/perses/spec/go/dashboard/variable.Display) is now omitted when it'sfalse. If you compare the output of your Dashboard-as-Code with golden files, regenerate them.
CUE: the old schemas are removed#
For the same reason, the following CUE packages are removed:
| Removed package | Replacement |
|---|---|
github.com/perses/perses/cue/model/api/v1/common |
github.com/perses/spec/cue/common (#Plugin is in github.com/perses/spec/cue/plugin) |
github.com/perses/perses/cue/model/api/v1/dashboard |
github.com/perses/spec/cue/dashboard |
github.com/perses/perses/cue/model/api/v1/variable |
github.com/perses/spec/cue/dashboard/variable |
github.com/perses/perses/cue/model/api/v1/plugin |
github.com/perses/spec/cue/plugin |
github.com/perses/perses/cue/model/api/v1/datasource/* |
github.com/perses/spec/cue/datasource/proxy/http and .../proxy/sql |
github.com/perses/shared/cue/common/proxy |
github.com/perses/spec/cue/datasource/proxy/http and .../proxy/sql |
v1.#DashboardSpec, v1.#Panel, v1.#PanelSpec, v1.#PanelDisplay, v1.#Query, v1.#QuerySpec and v1.#Link are
replaced by dashboard.#Spec, dashboard.#Panel, dashboard.#PanelSpec, dashboard.#PanelDisplay, dashboard.#Query,
dashboard.#QuerySpec and dashboard.#Link (from github.com/perses/spec/cue/dashboard). v1.#DatasourceSpec is
replaced by datasource.#Spec (from github.com/perses/spec/cue/datasource).
If your plugin schemas were using the deprecated github.com/perses/shared/cue/common/proxy package, replace its
definitions as follows:
| Removed definition | Replacement | Package |
|---|---|---|
proxy.#HTTPProxy |
http.#Proxy |
github.com/perses/spec/cue/datasource/proxy/http |
proxy.#HTTPAllowedEndpoint |
http.#AllowedEndpoint |
github.com/perses/spec/cue/datasource/proxy/http |
proxy.#baseHTTPDatasourceSpec |
datasource.#HTTPDatasourceSpec |
github.com/perses/spec/cue/datasource |
proxy.#SQLProxy |
sql.#Proxy |
github.com/perses/spec/cue/datasource/proxy/sql |
proxy.#MySQL |
sql.#MySQLConfig |
github.com/perses/spec/cue/datasource/proxy/sql |
proxy.#Postgres |
sql.#PostgresConfig |
github.com/perses/spec/cue/datasource/proxy/sql |
proxy.#baseSQLDatasourceSpec |
datasource.#SQLDatasourceSpec |
github.com/perses/spec/cue/datasource |
// Before
import "github.com/perses/shared/cue/common/proxy"
spec: proxy.#baseHTTPDatasourceSpec
// After
import "github.com/perses/spec/cue/datasource"
spec: datasource.#HTTPDatasourceSpec
Don't forget to bump the dependencies in your cue.mod/module.cue (github.com/perses/perses/cue@v0 and
github.com/perses/shared/cue@v0 to v0.55.x, github.com/perses/spec/cue@v0 to v0.3.x). Note that
github.com/perses/shared/cue no longer depends on github.com/perses/spec/cue: if your module imports the spec, it
must declare this dependency itself. Running cue mod tidy adds the missing dependencies.
As mentioned in Go and CUE toolchains, the cue CLI must be at least v0.17.1.
@perses-dev/core is removed#
As announced in v0.54.0, the package @perses-dev/core is no longer published, and the other packages don't depend on
it anymore. If you are still importing it, follow the migration described in
Core package deprecated: its content is now available in @perses-dev/spec,
@perses-dev/client, @perses-dev/components, @perses-dev/plugin-system and @perses-dev/dashboards.
Toolchain and dependencies upgrade#
The Perses npm packages (@perses-dev/spec, @perses-dev/client, @perses-dev/components,
@perses-dev/plugin-system, @perses-dev/dashboards, @perses-dev/explore), as well as the packages of the official
plugins (@perses-dev/*-plugin), changed their requirements:
- Node.js 24 (LTS) and npm 11 are now required to build the Perses projects (Perses itself, perses/shared and the
plugins), including a plugin generated with
percli plugin generate("engines": { "node": ">=24", "npm": ">=11" }). - ESM only: the CommonJS build (
dist/cjsandlib/cjsfor the plugins) is no longer published. The packages are declared with"type": "module", andmainandmoduleboth point to the ES module output. Projects consuming these packages must support ES modules (e.g. norequire('@perses-dev/...')). The TypeScript projects consuming them must use"moduleResolution": "bundler"(node16andnodenextcan't resolve the published type declarations). - ES2023: the published code targets ES2023 (e.g. it uses
Array.prototype.toSorted), so it requires a recent browser (Chrome / Edge 110+, Firefox 115+, Safari 16+) and Node.js 20+ for your tests. - React 17 is no longer supported: the peer dependencies are now
reactandreact-dom^18.3.0.@perses-dev/clientnow also hasreact(^18.3.0) as peer dependency. - Zod 4:
zodis upgraded from^3.25to^4(including in@perses-dev/spec). The Zod schemas exported by the packages are typed with both their input and output types:z.ZodType<T>becomesz.ZodType<T, T>(e.g.datasourcesSchema,roleSpecSchema,userSchema,panelSpecSchema,datasourceSpecSchema...). They can't be composed with Zod 3 schemas. Follow the Zod 4 migration guide for your own schemas. react-hook-formis upgraded to^7.87.0and@hookform/resolversto^5.9.1.@perses-dev/specmust be upgraded to^0.3.0.- The code is compiled with TypeScript 7.
The plugins built for v0.54 must be rebuilt against the v0.55 packages: Perses no longer shares @perses-dev/core with
the plugins at runtime, and the shared packages no longer provide the APIs removed in this release.
If you are maintaining a plugin, the easiest way to align your project is to compare it with a project freshly generated
with percli plugin generate. The main changes are:
- in
package.json:- add
"type": "module"and theenginesfield; - remove the
build:cjsscript and the file.cjs.swcrc; - set
"main": "lib/index.js"; - upgrade the peer dependencies (
@perses-dev/*to^0.55.0,@perses-dev/specto^0.3.0,react/react-domto^18.3.0,react-hook-formto^7.87.0,@hookform/resolversto^5.9.1);
- add
- in
.swcrc, targetes2023; - in
tsconfig.json, targetes2023and use"moduleResolution": "bundler"; - in
rsbuild.config.ts, add@perses-dev/clientto the shared singletons of the Module Federation configuration:
pluginModuleFederation({
// ...
shared: {
// ...
'@perses-dev/client': { singleton: true },
'@perses-dev/components': { singleton: true },
'@perses-dev/plugin-system': { singleton: true },
'@perses-dev/explore': { singleton: true },
'@perses-dev/dashboards': { singleton: true },
},
});
Embedding Perses: the host must register its shared modules#
The plugin runtime (Module Federation) is now ESM-native: the Perses packages are no longer loaded with require when a
remote plugin asks for them. Instead, the application hosting the plugins must provide its own instance of the Perses
packages before any plugin is loaded, using registerHostSharedModules. Otherwise, loading a remote plugin fails
with the error Shared module "..." was not registered before a plugin tried to consume it.
// After
import * as PersesClient from '@perses-dev/client';
import * as PersesComponents from '@perses-dev/components';
import * as PersesDashboards from '@perses-dev/dashboards';
import * as PersesExplore from '@perses-dev/explore';
import * as PersesPluginSystem from '@perses-dev/plugin-system';
import { registerHostSharedModules } from '@perses-dev/plugin-system';
import * as PersesSpec from '@perses-dev/spec';
// Provide the host's already-loaded Perses packages to the plugin runtime as Module Federation singletons.
registerHostSharedModules({
'@perses-dev/spec': PersesSpec,
'@perses-dev/client': PersesClient,
'@perses-dev/components': PersesComponents,
'@perses-dev/plugin-system': PersesPluginSystem,
'@perses-dev/explore': PersesExplore,
'@perses-dev/dashboards': PersesDashboards,
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
@perses-dev/components: removed hooks and stricter types#
To comply with the React Compiler rules, the following helpers are removed from @perses-dev/components:
useMemoizedanduseDeepMemo: use React'suseMemoinstead.useId(prefix): use React'suseIdinstead.
// Before
import { useId } from '@perses-dev/components';
const id = useId('MyComponent');
// After
import { useId } from 'react';
const id = `MyComponent-${useId()}`;
Some types are also stricter (any is replaced by unknown):
@perses-dev/client: thespecof aResourceis nowunknown. Narrow it (or cast it to the expected type) before using it.@perses-dev/components: the cell values of aTableColumnConfigare nowunknown, andcellDescriptionnow takes aCellContext<T, unknown>. In the cell callbacks, usegetValue<ValueType>()when the value type is known, or narrow the value before using it.
@perses-dev/dashboards: annotation store refactor#
The annotation data is now fetched on demand through the TanStack Query cache (like the panel queries) instead of being loaded when the dashboard is opened and copied into the annotation store. As a consequence:
AnnotationHydrationWrapperis removed.- The
annotationStatefield and thesetAnnotationStateaction of the annotation store are removed. useAnnotationsWithData,useAnnotationStatesanduseAnnotationSpecAndStatenow fetch the data themselves. They must be used under theQueryClientProvider, the plugin registry, the time range, the variable and the datasource store providers.- The TanStack Query key of the annotations changed from
['Annotation', ...]to['annotation', ...]. If you invalidate the annotation queries yourself, usequeryClient.invalidateQueries({ queryKey: ['annotation'] }).
@perses-dev/dashboards: panel repeat and grid layout#
To support the repetition of a single panel, the repeat variable identifying a repeated panel (repeatVariable in
PanelGroupItemId and VirtualPanelRef) is no longer a [name, value] tuple. It's now an object distinguishing the
variable repeating the panel group from the one repeating the panel:
// Before
setViewPanel({ panelGroupId, panelGroupItemLayoutId, repeatVariable: ['env', 'prod'] });
const [name, value] = panelGroupItemId.repeatVariable ?? [];
// After
setViewPanel({ panelGroupId, panelGroupItemLayoutId, repeatVariable: { group: ['env', 'prod'] } });
const [name, value] = panelGroupItemId.repeatVariable?.group ?? [];
// A repeated panel in a repeated panel group: { group: ['env', 'prod'], panel: ['host', 'a'] }
The viewPanelRef query parameter (the panel displayed in full screen) uses the same format. The links bookmarked or
shared with v0.54 pointing to a panel in full screen inside a repeated panel group no longer work: they display an
empty dashboard. Re-create them, or replace "repeatVariable":["env","prod"] by
"repeatVariable":{"group":["env","prod"]} in the URL.
The grid layout is now based on Snapgrid (@snapgridjs/react) instead of react-grid-layout, to allow
moving panels between panel groups:
Row(and the repeated grid layouts) no longer accept the propsgridColWidthandonWidthChange, andonLayoutChangenow receives the new layout of the panel group:(layout: PanelGroupItemLayout[]) => void.- The CSS classes of
react-grid-layout(.react-grid-layout,.react-grid-item,.react-grid-placeholder,.react-resizable-handle) no longer exist. Use.snapgrid-item,.snapgrid-placeholder,.snapgrid-resize-handle--seand the attribute[data-grid-id]in your custom styles and E2E tests.
The dashboard store also has new required fields (e.g. repeatVariableMaxValues and setViewPanelFromRef): update the
mocks of the store accordingly.
@perses-dev/plugin-system and @perses-dev/client: API changes#
PanelSpecEditorhas two new required props,variableDefinitionGroupsandonAnnotationsChange(also required byPanelQueriesSharedControlsin@perses-dev/dashboards), and an optionalpanelGroups. Its new "Layout" tab relies on the react-hook-form context, so it must be rendered under aFormProvider:
// Before
<PanelSpecEditor control={form.control} panelDefinition={panelDefinition} ... />
// After
<FormProvider {...form}>
<PanelSpecEditor
control={form.control}
panelDefinition={panelDefinition}
variableDefinitionGroups={variableDefinitionGroups} // e.g. useAllVariableDefinitions() from @perses-dev/dashboards
onAnnotationsChange={(annotations) => setAnnotations(annotations)}
...
/>
</FormProvider>
executeActionrequires a new parameterfetchFn, used to call the webhooks:
import { useFetch } from '@perses-dev/client';
const { fetch } = useFetch();
await executeAction({ action, selectionMap, variableState, setActionStatus, fetchFn: fetch });
- If you implement your own plugin registry,
listPluginMetadatacan now be called without any argument, in which case it must return the plugins of every type:listPluginMetadata(pluginTypes?: PluginType[]). - The value of
TimeRangeSettingsContexthas two new required fields,disableAutoRefreshandautoRefreshIntervalOptions. UseTimeRangeSettingsProviderrather than providing the context yourself. - When a remote plugin is loaded with
baseURL: '', it's now resolved from the root of the site instead of/plugins. Don't setbaseURL(or set it to'/plugins') to keep the previous behavior. @perses-dev/client: thenameofBuildDatasourceProxyUrlParamsis now optional (it's omitted for a datasource that is not saved yet). A customBuildDatasourceProxyUrlFuncmust handle anamebeingundefined, or reusebuildProxyUrlfrom@perses-dev/client.- Some union types have new members, which breaks the exhaustive
switchstatements andRecordtypes:PluginTypeincludes'JsonQuery', andTransformincludes thePivotByLabeltransform.
@perses-dev/explore: the default explorer must be provided#
ExploreManager no longer selects the first explorer when none is selected. If you compose the explore components
yourself, pass the default explorer to the provider (ViewExplore already does it):
// Before
<ExplorerManagerProviderWithQueryParams>
<ExploreManager />
</ExplorerManagerProviderWithQueryParams>
// After
import { getExplorerKey, useSortedExplorerPlugins } from '@perses-dev/explore';
const plugins = useSortedExplorerPlugins();
const defaultExplorer = plugins?.[0] ? getExplorerKey(plugins[0]) : undefined;
<ExplorerManagerProviderWithQueryParams defaultExplorer={defaultExplorer}>
<ExploreManager />
</ExplorerManagerProviderWithQueryParams>
Official plugin packages: API changes#
If you import the npm packages of the official plugins:
- Pyroscope:
searchProfiles(and the typesSearchProfilesParameters,SearchProfilesResponse,Flamebearer,Metadata,Timeline) andtransformProfileResponseare removed. UseselectMergeStacktracesandselectSeriesofPyroscopeClient, andtransformFlameGraph/transformTimeline. - Pyroscope and Jaeger:
executeRequestnow takes the fetch function to use as first argument:executeRequest(url, init)becomesexecuteRequest(undefined, url, init). - Prometheus: the
PrometheusClientinterface has a new required methodqueryExemplars. Implement it in your custom clients and mocks. - TimeSeriesChart:
TimeSeriesChartPaletteOptionsis now a union type ({ mode: 'auto' } | { mode: 'categorical'; name?: string }), so an interface can no longer extend it.