Compare commits

..

1 Commits

Author SHA1 Message Date
beejeebus 7b87d528e9 [WIP] Not working yet
hacky conversion of k8s types to existing types started.

Hard-coded types for prometheus now successfully loads a UI.
2026-01-13 21:47:55 +00:00
246 changed files with 680 additions and 12504 deletions
-2
View File
@@ -543,7 +543,6 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-data/tsconfig.json @grafana/grafana-frontend-platform
/packages/grafana-data/test/ @grafana/grafana-frontend-platform
/packages/grafana-data/typings/ @grafana/grafana-frontend-platform
/packages/grafana-data/scripts/ @grafana/grafana-frontend-platform
/packages/grafana-data/src/**/*logs* @grafana/observability-logs
/packages/grafana-data/src/context/plugins/ @grafana/plugins-platform-frontend
@@ -659,7 +658,6 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-runtime/src/services/LocationService.tsx @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/services/LocationSrv.ts @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/services/live.ts @grafana/dashboards-squad
/packages/grafana-runtime/src/services/pluginMeta @grafana/plugins-platform-frontend
/packages/grafana-runtime/src/utils/chromeHeaderHeight.ts @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/utils/DataSourceWithBackend* @grafana/grafana-datasources-core-services
/packages/grafana-runtime/src/utils/licensing.ts @grafana/grafana-operator-experience-squad
-2
View File
@@ -121,8 +121,6 @@ linters:
- '**/pkg/tsdb/zipkin/**/*'
- '**/pkg/tsdb/jaeger/*'
- '**/pkg/tsdb/jaeger/**/*'
- '**/pkg/tsdb/elasticsearch/*'
- '**/pkg/tsdb/elasticsearch/**/*'
deny:
- pkg: github.com/grafana/grafana/pkg/api
desc: Core plugins are not allowed to depend on Grafana core packages
@@ -28,7 +28,7 @@ type check struct {
PluginStore pluginstore.Store
PluginContextProvider PluginContextProvider
PluginClient plugins.Client
PluginRepo checks.PluginInfoGetter
PluginRepo repo.Service
GrafanaVersion string
pluginCanBeInstalledCache map[string]bool
pluginExistsCacheMu sync.RWMutex
@@ -39,7 +39,7 @@ func New(
pluginStore pluginstore.Store,
pluginContextProvider PluginContextProvider,
pluginClient plugins.Client,
pluginRepo checks.PluginInfoGetter,
pluginRepo repo.Service,
grafanaVersion string,
) checks.Check {
return &check{
@@ -15,7 +15,7 @@ import (
type missingPluginStep struct {
PluginStore pluginstore.Store
PluginRepo checks.PluginInfoGetter
PluginRepo repo.Service
GrafanaVersion string
}
-8
View File
@@ -5,7 +5,6 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
"github.com/grafana/grafana/pkg/plugins/repo"
)
// Check returns metadata about the check being executed and the list of Steps
@@ -38,10 +37,3 @@ type Step interface {
// Run executes the step for an item and returns a report
Run(ctx context.Context, log logging.Logger, obj *advisorv0alpha1.CheckSpec, item any) ([]advisorv0alpha1.CheckReportFailure, error)
}
// PluginInfoGetter is a minimal interface for retrieving plugin information from a repository.
// It contains only the GetPluginsInfo method used by plugincheck and datasourcecheck.
type PluginInfoGetter interface {
// GetPluginsInfo will return a list of plugins from grafana.com/api/plugins.
GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error)
}
@@ -17,7 +17,7 @@ const (
func New(
pluginStore pluginstore.Store,
pluginRepo checks.PluginInfoGetter,
pluginRepo repo.Service,
updateChecker pluginchecker.PluginUpdateChecker,
pluginErrorResolver plugins.ErrorResolver,
grafanaVersion string,
@@ -33,7 +33,7 @@ func New(
type check struct {
PluginStore pluginstore.Store
PluginRepo checks.PluginInfoGetter
PluginRepo repo.Service
updateChecker pluginchecker.PluginUpdateChecker
pluginErrorResolver plugins.ErrorResolver
GrafanaVersion string
@@ -800,8 +800,6 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -804,8 +804,6 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -241,8 +241,6 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable
@@ -241,8 +241,6 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable
@@ -804,8 +804,6 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -1426,8 +1426,6 @@ type DashboardVariableOption struct {
Text DashboardStringOrArrayOfString `json:"text"`
// Value of the option
Value DashboardStringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewDashboardVariableOption creates a new DashboardVariableOption object.
@@ -5133,22 +5133,6 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardVariableOption(ref common.Refer
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardStringOrArrayOfString"),
},
},
"properties": {
SchemaProps: spec.SchemaProps{
Description: "Additional properties for multi-props variables",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"text", "value"},
},
@@ -808,8 +808,6 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -1429,8 +1429,6 @@ type DashboardVariableOption struct {
Text DashboardStringOrArrayOfString `json:"text"`
// Value of the option
Value DashboardStringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewDashboardVariableOption creates a new DashboardVariableOption object.
@@ -5196,22 +5196,6 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardVariableOption(ref common.Refere
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardStringOrArrayOfString"),
},
},
"properties": {
SchemaProps: spec.SchemaProps{
Description: "Additional properties for multi-props variables",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"text", "value"},
},
File diff suppressed because one or more lines are too long
+3 -10
View File
@@ -1,16 +1,9 @@
include ../sdk.mk
.PHONY: internal-generate # Run Grafana App SDK code generation
internal-generate: install-app-sdk update-app-sdk
.PHONY: generate # Run Grafana App SDK code generation
generate: install-app-sdk update-app-sdk
@$(APP_SDK_BIN) generate \
--source=./kinds/ \
--gogenpath=./pkg/apis \
--grouping=group \
--defencoding=none
.PHONY: generate
generate: internal-generate # copy files to packages/grafana-runtime/src/services/pluginMeta/types
rm -f ./packages/grafana-runtime/src/services/pluginMeta/types/*.ts
cp plugin/src/generated/meta/v0alpha1/meta_object_gen.ts ../../packages/grafana-runtime/src/services/pluginMeta/types/meta_object_gen.ts
cp plugin/src/generated/meta/v0alpha1/types.spec.gen.ts ../../packages/grafana-runtime/src/services/pluginMeta/types/types.spec.gen.ts
cp plugin/src/generated/meta/v0alpha1/types.status.gen.ts ../../packages/grafana-runtime/src/services/pluginMeta/types/types.status.gen.ts
--defencoding=none
+2 -1
View File
@@ -4,7 +4,8 @@ API documentation is available at http://localhost:3000/swagger?api=plugins.graf
## Codegen
- Go and TypeScript: `make generate`
- Go: `make generate`
- Frontend: Follow instructions in this [README](../..//packages/grafana-api-clients/README.md)
## Plugin sync
+1 -1
View File
@@ -11,7 +11,7 @@ manifest: {
v0alpha1Version: {
served: true
codegen: {
ts: {enabled: true}
ts: {enabled: false}
go: {enabled: true}
}
kinds: [
@@ -1,49 +0,0 @@
/*
* This file was generated by grafana-app-sdk. DO NOT EDIT.
*/
import { Spec } from './types.spec.gen';
import { Status } from './types.status.gen';
export interface Metadata {
name: string;
namespace: string;
generateName?: string;
selfLink?: string;
uid?: string;
resourceVersion?: string;
generation?: number;
creationTimestamp?: string;
deletionTimestamp?: string;
deletionGracePeriodSeconds?: number;
labels?: Record<string, string>;
annotations?: Record<string, string>;
ownerReferences?: OwnerReference[];
finalizers?: string[];
managedFields?: ManagedFieldsEntry[];
}
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export interface ManagedFieldsEntry {
manager?: string;
operation?: string;
apiVersion?: string;
time?: string;
fieldsType?: string;
subresource?: string;
}
export interface Meta {
kind: string;
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}
@@ -1,30 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
export interface Metadata {
updateTimestamp: string;
createdBy: string;
uid: string;
creationTimestamp: string;
deletionTimestamp?: string;
finalizers: string[];
resourceVersion: string;
generation: number;
updatedBy: string;
labels: Record<string, string>;
}
export const defaultMetadata = (): Metadata => ({
updateTimestamp: "",
createdBy: "",
uid: "",
creationTimestamp: "",
finalizers: [],
resourceVersion: "",
generation: 0,
updatedBy: "",
labels: {},
});
@@ -1,278 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// JSON configuration schema for Grafana plugins
// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json
export interface JSONData {
// Unique name of the plugin
id: string;
// Plugin type
type: "app" | "datasource" | "panel" | "renderer";
// Human-readable name of the plugin
name: string;
// Metadata for the plugin
info: Info;
// Dependency information
dependencies: Dependencies;
// Optional fields
alerting?: boolean;
annotations?: boolean;
autoEnabled?: boolean;
backend?: boolean;
buildMode?: string;
builtIn?: boolean;
category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other";
enterpriseFeatures?: EnterpriseFeatures;
executable?: string;
hideFromList?: boolean;
// +listType=atomic
includes?: Include[];
logs?: boolean;
metrics?: boolean;
multiValueFilterOperators?: boolean;
pascalName?: string;
preload?: boolean;
queryOptions?: QueryOptions;
// +listType=atomic
routes?: Route[];
skipDataQuery?: boolean;
state?: "alpha" | "beta";
streaming?: boolean;
suggestions?: boolean;
tracing?: boolean;
iam?: IAM;
// +listType=atomic
roles?: Role[];
extensions?: Extensions;
}
export const defaultJSONData = (): JSONData => ({
id: "",
type: "app",
name: "",
info: defaultInfo(),
dependencies: defaultDependencies(),
});
export interface Info {
// Required fields
// +listType=set
keywords: string[];
logos: {
small: string;
large: string;
};
updated: string;
version: string;
// Optional fields
author?: {
name?: string;
email?: string;
url?: string;
};
description?: string;
// +listType=atomic
links?: {
name?: string;
url?: string;
}[];
// +listType=atomic
screenshots?: {
name?: string;
path?: string;
}[];
}
export const defaultInfo = (): Info => ({
keywords: [],
logos: {
small: "",
large: "",
},
updated: "",
version: "",
});
export interface Dependencies {
// Required field
grafanaDependency: string;
// Optional fields
grafanaVersion?: string;
// +listType=set
// +listMapKey=id
plugins?: {
id: string;
type: "app" | "datasource" | "panel";
name: string;
}[];
extensions?: {
// +listType=set
exposedComponents?: string[];
};
}
export const defaultDependencies = (): Dependencies => ({
grafanaDependency: "",
});
export interface EnterpriseFeatures {
// Allow additional properties
healthDiagnosticsErrors?: boolean;
}
export const defaultEnterpriseFeatures = (): EnterpriseFeatures => ({
healthDiagnosticsErrors: false,
});
export interface Include {
uid?: string;
type?: "dashboard" | "page" | "panel" | "datasource";
name?: string;
component?: string;
role?: "Admin" | "Editor" | "Viewer" | "None";
action?: string;
path?: string;
addToNav?: boolean;
defaultNav?: boolean;
icon?: string;
}
export const defaultInclude = (): Include => ({
});
export interface QueryOptions {
maxDataPoints?: boolean;
minInterval?: boolean;
cacheTimeout?: boolean;
}
export const defaultQueryOptions = (): QueryOptions => ({
});
export interface Route {
path?: string;
method?: string;
url?: string;
reqSignedIn?: boolean;
reqRole?: string;
reqAction?: string;
// +listType=atomic
headers?: string[];
body?: Record<string, any>;
tokenAuth?: {
url?: string;
// +listType=set
scopes?: string[];
params?: Record<string, any>;
};
jwtTokenAuth?: {
url?: string;
// +listType=set
scopes?: string[];
params?: Record<string, any>;
};
// +listType=atomic
urlParams?: {
name?: string;
content?: string;
}[];
}
export const defaultRoute = (): Route => ({
});
export interface IAM {
// +listType=atomic
permissions?: {
action?: string;
scope?: string;
}[];
}
export const defaultIAM = (): IAM => ({
});
export interface Role {
role?: {
name?: string;
description?: string;
// +listType=atomic
permissions?: {
action?: string;
scope?: string;
}[];
};
// +listType=set
grants?: string[];
}
export const defaultRole = (): Role => ({
});
export interface Extensions {
// +listType=atomic
addedComponents?: {
// +listType=set
targets: string[];
title: string;
description?: string;
}[];
// +listType=atomic
addedLinks?: {
// +listType=set
targets: string[];
title: string;
description?: string;
}[];
// +listType=atomic
addedFunctions?: {
// +listType=set
targets: string[];
title: string;
description?: string;
}[];
// +listType=set
// +listMapKey=id
exposedComponents?: {
id: string;
title?: string;
description?: string;
}[];
// +listType=set
// +listMapKey=id
extensionPoints?: {
id: string;
title?: string;
description?: string;
}[];
}
export const defaultExtensions = (): Extensions => ({
});
export interface Spec {
pluginJson: JSONData;
class: "core" | "external";
module?: {
path: string;
hash?: string;
loadingStrategy?: "fetch" | "script";
};
baseURL?: string;
signature?: {
status: "internal" | "valid" | "invalid" | "modified" | "unsigned";
type?: "grafana" | "commercial" | "community" | "private" | "private-glob";
org?: string;
};
angular?: {
detected: boolean;
};
translations?: Record<string, string>;
// +listType=atomic
children?: string[];
}
export const defaultSpec = (): Spec => ({
pluginJson: defaultJSONData(),
class: "core",
});
@@ -1,30 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface OperatorState {
// lastEvaluation is the ResourceVersion last evaluated
lastEvaluation: string;
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
state: "success" | "in_progress" | "failed";
// descriptiveState is an optional more descriptive state field which has no requirements on format
descriptiveState?: string;
// details contains any extra information that is operator-specific
details?: Record<string, any>;
}
export const defaultOperatorState = (): OperatorState => ({
lastEvaluation: "",
state: "success",
});
export interface Status {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
operatorStates?: Record<string, OperatorState>;
// additionalFields is reserved for future use
additionalFields?: Record<string, any>;
}
export const defaultStatus = (): Status => ({
});
@@ -1,49 +0,0 @@
/*
* This file was generated by grafana-app-sdk. DO NOT EDIT.
*/
import { Spec } from './types.spec.gen';
import { Status } from './types.status.gen';
export interface Metadata {
name: string;
namespace: string;
generateName?: string;
selfLink?: string;
uid?: string;
resourceVersion?: string;
generation?: number;
creationTimestamp?: string;
deletionTimestamp?: string;
deletionGracePeriodSeconds?: number;
labels?: Record<string, string>;
annotations?: Record<string, string>;
ownerReferences?: OwnerReference[];
finalizers?: string[];
managedFields?: ManagedFieldsEntry[];
}
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export interface ManagedFieldsEntry {
manager?: string;
operation?: string;
apiVersion?: string;
time?: string;
fieldsType?: string;
subresource?: string;
}
export interface Plugin {
kind: string;
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}
@@ -1,30 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
export interface Metadata {
updateTimestamp: string;
createdBy: string;
uid: string;
creationTimestamp: string;
deletionTimestamp?: string;
finalizers: string[];
resourceVersion: string;
generation: number;
updatedBy: string;
labels: Record<string, string>;
}
export const defaultMetadata = (): Metadata => ({
updateTimestamp: "",
createdBy: "",
uid: "",
creationTimestamp: "",
finalizers: [],
resourceVersion: "",
generation: 0,
updatedBy: "",
labels: {},
});
@@ -1,13 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface Spec {
id: string;
version: string;
url?: string;
}
export const defaultSpec = (): Spec => ({
id: "",
version: "",
});
@@ -1,30 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface OperatorState {
// lastEvaluation is the ResourceVersion last evaluated
lastEvaluation: string;
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
state: "success" | "in_progress" | "failed";
// descriptiveState is an optional more descriptive state field which has no requirements on format
descriptiveState?: string;
// details contains any extra information that is operator-specific
details?: Record<string, any>;
}
export const defaultOperatorState = (): OperatorState => ({
lastEvaluation: "",
state: "success",
});
export interface Status {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
operatorStates?: Record<string, OperatorState>;
// additionalFields is reserved for future use
additionalFields?: Record<string, any>;
}
export const defaultStatus = (): Status => ({
});
@@ -103,11 +103,10 @@ To configure basic settings for the data source, complete the following steps:
1. Set the data source's basic configuration options:
| Name | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
| **Default** | Sets whether the data source is pre-selected for new panels. |
| **Universe Domain** | The universe domain to connect to. For more information, refer to [Documentation on universe domains](https://docs.cloud.google.com/python/docs/reference/monitoring/latest/google.cloud.monitoring_v3.services.service_monitoring_service.ServiceMonitoringServiceAsyncClient#google_cloud_monitoring_v3_services_service_monitoring_service_ServiceMonitoringServiceAsyncClient_universe_domain). Defaults to `googleapis.com`. |
| Name | Description |
| ----------- | ------------------------------------------------------------------------ |
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
| **Default** | Sets whether the data source is pre-selected for new panels. |
### Provision the data source
@@ -130,7 +129,6 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
universeDomain: googleapis.com
secureJsonData:
privateKey: |
-----BEGIN PRIVATE KEY-----
@@ -154,7 +152,6 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
universeDomain: googleapis.com
privateKeyPath: /etc/secrets/gce.pem
```
@@ -169,7 +166,6 @@ datasources:
access: proxy
jsonData:
authenticationType: gce
universeDomain: googleapis.com
```
## Import pre-configured dashboards
@@ -87,7 +87,6 @@ With a Grafana Enterprise license, you also get access to premium data sources,
- [CockroachDB](/grafana/plugins/grafana-cockroachdb-datasource)
- [Databricks](/grafana/plugins/grafana-databricks-datasource)
- [DataDog](/grafana/plugins/grafana-datadog-datasource)
- [IBM Db2](/grafana/plugins/grafana-ibmdb2-datasource)
- [Drone](/grafana/plugins/grafana-drone-datasource)
- [DynamoDB](/grafana/plugins/grafana-dynamodb-datasource/)
- [Dynatrace](/grafana/plugins/grafana-dynatrace-datasource)
@@ -83,7 +83,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `reportingRetries` | Enables rendering retries for the reporting feature |
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
| `dashboardNewLayouts` | Enables new dashboard layouts |
| `pdfTables` | Enables generating table data as PDF in reporting |
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage. Default is enabled. |
@@ -30,9 +30,7 @@ refs:
# Datagrid
{{< admonition type="caution" >}}
Starting with Grafana 12.4, Datagrid is deprecated. It will be removed in version 13.0.
{{< /admonition >}}
{{< docs/experimental product="The datagrid visualization" featureFlag="`enableDatagridEditing`" >}}
Datagrids offer you the ability to create, edit, and fine-tune data within Grafana. As such, this panel can act as a data source for other panels
inside a dashboard.
+25 -128
View File
@@ -1337,11 +1337,6 @@
"count": 2
}
},
"public/app/features/alerting/unified/api/onCallApi.test.ts": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/alerting/unified/components/AnnotationDetailsField.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -1382,11 +1377,6 @@
"count": 1
}
},
"public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.test.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/alerting/unified/components/import-to-gma/NamespaceAndGroupFilter.tsx": {
"no-restricted-syntax": {
"count": 2
@@ -1627,31 +1617,11 @@
"count": 1
}
},
"public/app/features/alerting/unified/mocks/server/configure.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/alerting/unified/mocks/server/handlers/plugins.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/alerting/unified/rule-editor/clone.utils.test.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/alerting/unified/rule-editor/formDefaults.ts": {
"no-restricted-syntax": {
"count": 6
}
},
"public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/alerting/unified/types/alerting.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 5
@@ -1662,16 +1632,6 @@
"count": 1
}
},
"public/app/features/alerting/unified/utils/config.test.ts": {
"no-restricted-syntax": {
"count": 6
}
},
"public/app/features/alerting/unified/utils/config.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/alerting/unified/utils/datasource.ts": {
"no-restricted-syntax": {
"count": 2
@@ -1703,20 +1663,12 @@
"count": 1
}
},
"public/app/features/alerting/unified/utils/rules.test.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/alerting/unified/utils/rules.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 3
},
"@typescript-eslint/no-explicit-any": {
"count": 1
},
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx": {
@@ -1772,16 +1724,6 @@
"count": 1
}
},
"public/app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice.test.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/connections/tabs/ConnectData/ConnectData.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -2121,11 +2063,6 @@
"count": 1
}
},
"public/app/features/dashboard/components/GenAI/utils.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx": {
"no-restricted-syntax": {
"count": 3
@@ -2952,71 +2889,6 @@
"count": 1
}
},
"public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts": {
"no-restricted-syntax": {
"count": 6
}
},
"public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.test.ts": {
"no-restricted-syntax": {
"count": 6
}
},
"public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts": {
"no-restricted-syntax": {
"count": 6
}
},
"public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.test.ts": {
"no-restricted-syntax": {
"count": 6
}
},
"public/app/features/plugins/extensions/usePluginComponent.test.tsx": {
"no-restricted-syntax": {
"count": 3
}
},
"public/app/features/plugins/extensions/usePluginComponents.test.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/plugins/extensions/usePluginFunctions.test.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/plugins/extensions/usePluginLinks.test.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/plugins/extensions/utils.test.tsx": {
"no-restricted-syntax": {
"count": 27
}
},
"public/app/features/plugins/extensions/utils.tsx": {
"no-restricted-syntax": {
"count": 7
}
},
"public/app/features/plugins/extensions/validators.test.tsx": {
"no-restricted-syntax": {
"count": 30
}
},
"public/app/features/plugins/extensions/validators.ts": {
"no-restricted-syntax": {
"count": 4
}
},
"public/app/features/plugins/sandbox/codeLoader.ts": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/plugins/sandbox/distortions.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -3743,21 +3615,46 @@
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 2
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": {
"no-restricted-syntax": {
"count": 1
-38
View File
@@ -117,8 +117,6 @@ module.exports = [
'scripts/grafana-server/tmp',
'packages/grafana-ui/src/graveyard', // deprecated UI components slated for removal
'public/build-swagger', // swagger build output
'apps/plugins/plugin/src/generated/meta/v0alpha1',
'apps/plugins/plugin/src/generated/plugin/v0alpha1',
],
},
...grafanaConfig,
@@ -577,42 +575,6 @@ module.exports = [
"Property[key.name='a11y'][value.type='ObjectExpression'] Property[key.name='test'][value.value='off']",
message: 'Skipping a11y tests is not allowed. Please fix the component or story instead.',
},
{
selector: 'MemberExpression[object.name="config"][property.name="apps"]',
message:
'Usage of config.apps is not allowed. Use the function getAppPluginMetas or useAppPluginMetas from @grafana/runtime instead',
},
],
},
},
{
files: [...commonTestIgnores],
ignores: [
// FIXME: Remove once all enterprise issues are fixed -
// we don't have a suppressions file/approach for enterprise code yet
...enterpriseIgnores,
],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'MemberExpression[object.name="config"][property.name="apps"]',
message:
'Usage of config.apps is not allowed. Use the function getAppPluginMetas or useAppPluginMetas from @grafana/runtime instead',
},
],
},
},
{
files: [...enterpriseIgnores],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'MemberExpression[object.name="config"][property.name="apps"]',
message:
'Usage of config.apps is not allowed. Use the function getAppPluginMetas or useAppPluginMetas from @grafana/runtime instead',
},
],
},
},
+12 -11
View File
@@ -44,8 +44,8 @@ require (
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad
github.com/blevesearch/bleve/v2 v2.5.7 // @grafana/grafana-search-and-storage
github.com/blevesearch/bleve_index_api v1.3.0 // @grafana/grafana-search-and-storage
github.com/blevesearch/bleve/v2 v2.5.0 // @grafana/grafana-search-and-storage
github.com/blevesearch/bleve_index_api v1.2.7 // @grafana/grafana-search-and-storage
github.com/blugelabs/bluge v0.2.2 // @grafana/grafana-backend-group
github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // @grafana/grafana-backend-group
@@ -365,22 +365,22 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.26 // indirect
github.com/blevesearch/geo v0.1.20 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.8 // indirect
github.com/blevesearch/zapx/v11 v11.4.1 // indirect
github.com/blevesearch/zapx/v12 v12.4.1 // indirect
github.com/blevesearch/zapx/v13 v13.4.1 // indirect
github.com/blevesearch/zapx/v14 v14.4.1 // indirect
github.com/blevesearch/zapx/v15 v15.4.1 // indirect
github.com/blevesearch/zapx/v16 v16.2.2 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/blugelabs/ice v1.0.0 // indirect
github.com/blugelabs/ice/v2 v2.0.1 // indirect
@@ -443,6 +443,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.1 // indirect
+24 -22
View File
@@ -931,14 +931,14 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E=
github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4=
github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk=
github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY=
github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
@@ -947,8 +947,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+
github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o=
github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4=
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
@@ -960,18 +960,18 @@ github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+
github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE=
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk=
github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM=
@@ -1442,6 +1442,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
+2 -27
View File
@@ -520,40 +520,14 @@ github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC
github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E=
github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA=
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
github.com/blevesearch/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI=
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLsL+B5A=
github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc=
github.com/blevesearch/stempel v0.2.0/go.mod h1:wjeTHqQv+nQdbPuJ/YcvOjTInA2EIc6Ks1FoSUzSLvc=
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
@@ -1024,6 +998,8 @@ github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975/go.mod h1:FGdGvhI40Dq+CTQaSzK9evuve774cgOUdGfVO04OXkw=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f h1:9tRhudagkQO2s61SLFLSziIdCm7XlkfypVKDxpcHokg=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f/go.mod h1:AsVdCBeDFN9QbgpJg+8voDAcgsW0RmNvBd70ecMMdC0=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae/go.mod h1:6CJ1uXmLZ13ufpO9xE4pST+DyaBt0uszzrV0YnoaVLQ=
github.com/grafana/sqlds/v4 v4.2.4/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU=
@@ -1116,7 +1092,6 @@ github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU=
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jsternberg/zap-logfmt v1.3.0 h1:z1n1AOHVVydOOVuyphbOKyR4NICDQFiJMn1IK5hVQ5Y=
github.com/jsternberg/zap-logfmt v1.3.0/go.mod h1:N3DENp9WNmCZxvkBD/eReWwz1149BK6jEN9cQ4fNwZE=
-1
View File
@@ -82,7 +82,6 @@ module.exports = {
// Decoupled plugins run their own tests so ignoring them here.
'<rootDir>/public/app/plugins/datasource/azuremonitor',
'<rootDir>/public/app/plugins/datasource/cloud-monitoring',
'<rootDir>/public/app/plugins/datasource/elasticsearch',
'<rootDir>/public/app/plugins/datasource/grafana-postgresql-datasource',
'<rootDir>/public/app/plugins/datasource/grafana-pyroscope-datasource',
'<rootDir>/public/app/plugins/datasource/grafana-testdata-datasource',
-2
View File
@@ -237,8 +237,6 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable
+2 -2
View File
@@ -293,8 +293,8 @@
"@grafana/plugin-ui": "^0.11.1",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "6.52.2",
"@grafana/scenes-react": "6.52.2",
"@grafana/scenes": "v6.52.1",
"@grafana/scenes-react": "v6.52.1",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
+1 -10
View File
@@ -35,14 +35,6 @@
},
"./test": {
"@grafana-app/source": "./test/index.ts"
},
"./themes/schema.generated.json": {
"@grafana-app/source": "./src/themes/schema.generated.json",
"default": "./dist/esm/themes/schema.generated.json"
},
"./themes/definitions/*.json": {
"@grafana-app/source": "./src/themes/themeDefinitions/*.json",
"default": "./dist/esm/themes/themeDefinitions/*.json"
}
},
"publishConfig": {
@@ -60,7 +52,7 @@
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js",
"postpack": "mv package.json.bak package.json",
"themes-schema": "tsx ./scripts/generateSchema.ts"
"themes-schema": "tsx ./src/themes/scripts/generateSchema.ts"
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",
@@ -110,7 +102,6 @@
"react-dom": "18.3.1",
"rimraf": "6.0.1",
"rollup": "^4.22.4",
"rollup-plugin-copy": "3.5.0",
"rollup-plugin-esbuild": "6.2.1",
"rollup-plugin-node-externals": "^8.0.0",
"tsx": "^4.21.0",
+2 -21
View File
@@ -1,40 +1,21 @@
import json from '@rollup/plugin-json';
import { createRequire } from 'node:module';
import copy from 'rollup-plugin-copy';
import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts';
const rq = createRequire(import.meta.url);
const pkg = rq('./package.json');
const grafanaDataPlugins = [
...plugins,
copy({
targets: [
{
src: 'src/themes/schema.generated.json',
dest: 'dist/esm/',
},
{
src: 'src/themes/themeDefinitions/*.json',
dest: 'dist/esm/',
},
],
flatten: false,
}),
json(),
];
export default [
{
input: entryPoint,
plugins: grafanaDataPlugins,
plugins: [...plugins, json()],
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
{
input: 'src/unstable.ts',
plugins: grafanaDataPlugins,
plugins: [...plugins, json()],
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
@@ -1,22 +0,0 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { NewThemeOptionsSchema } from '../src/themes/createTheme';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const jsonOut = path.join(__dirname, '..', 'src', 'themes', 'schema.generated.json');
fs.writeFileSync(
jsonOut,
JSON.stringify(
NewThemeOptionsSchema.toJSONSchema({
target: 'draft-07',
}),
undefined,
2
)
);
console.log('Successfully generated theme schema');
+1 -1
View File
@@ -844,6 +844,7 @@ export {
DataLinkConfigOrigin,
SupportedTransformationType,
type InternalDataLink,
type LinkTarget,
type LinkModel,
type LinkModelSupplier,
VariableOrigin,
@@ -851,7 +852,6 @@ export {
VariableSuggestionsScope,
OneClickMode,
} from './types/dataLink';
export { type LinkTarget } from './types/linkTarget';
export {
type Action,
type ActionModel,
@@ -93,6 +93,7 @@ export { DataTransformerID } from '../transformations/transformers/ids';
export { mergeTransformer } from '../transformations/transformers/merge';
export { getThemeById } from '../themes/registry';
export * as experimentalThemeDefinitions from '../themes/themeDefinitions';
export { GrafanaEdition } from '../types/config';
export { SIPrefix } from '../valueFormats/symbolFormatters';
+1 -27
View File
@@ -1,18 +1,7 @@
import { Registry, RegistryItem } from '../utils/Registry';
import { createTheme, NewThemeOptionsSchema } from './createTheme';
import aubergine from './themeDefinitions/aubergine.json';
import debug from './themeDefinitions/debug.json';
import desertbloom from './themeDefinitions/desertbloom.json';
import gildedgrove from './themeDefinitions/gildedgrove.json';
import gloom from './themeDefinitions/gloom.json';
import mars from './themeDefinitions/mars.json';
import matrix from './themeDefinitions/matrix.json';
import sapphiredusk from './themeDefinitions/sapphiredusk.json';
import synthwave from './themeDefinitions/synthwave.json';
import tron from './themeDefinitions/tron.json';
import victorian from './themeDefinitions/victorian.json';
import zen from './themeDefinitions/zen.json';
import * as extraThemes from './themeDefinitions';
import { GrafanaTheme2 } from './types';
export interface ThemeRegistryItem extends RegistryItem {
@@ -20,21 +9,6 @@ export interface ThemeRegistryItem extends RegistryItem {
build: () => GrafanaTheme2;
}
const extraThemes: { [key: string]: unknown } = {
aubergine,
debug,
desertbloom,
gildedgrove,
gloom,
mars,
matrix,
sapphiredusk,
synthwave,
tron,
victorian,
zen,
};
/**
* @internal
* Only for internal use, never use this from a plugin
@@ -0,0 +1,19 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { NewThemeOptionsSchema } from '../createTheme';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
fs.writeFileSync(
path.join(__dirname, '../schema.generated.json'),
JSON.stringify(
NewThemeOptionsSchema.toJSONSchema({
target: 'draft-07',
}),
undefined,
2
)
);
@@ -0,0 +1,12 @@
export { default as aubergine } from './aubergine.json';
export { default as debug } from './debug.json';
export { default as desertbloom } from './desertbloom.json';
export { default as gildedgrove } from './gildedgrove.json';
export { default as mars } from './mars.json';
export { default as matrix } from './matrix.json';
export { default as sapphiredusk } from './sapphiredusk.json';
export { default as synthwave } from './synthwave.json';
export { default as tron } from './tron.json';
export { default as victorian } from './victorian.json';
export { default as zen } from './zen.json';
export { default as gloom } from './gloom.json';
@@ -32,7 +32,6 @@ export type AppPluginConfig = {
path: string;
version: string;
preload: boolean;
/** @deprecated it will be removed in a future release */
angular: AngularMeta;
loadingStrategy: PluginLoadingStrategy;
dependencies: PluginDependencies;
@@ -220,7 +219,6 @@ export interface GrafanaConfig {
snapshotEnabled: boolean;
datasources: { [str: string]: DataSourceInstanceSettings };
panels: { [key: string]: PanelPluginMeta };
/** @deprecated it will be removed in a future release */
apps: Record<string, AppPluginConfig>;
auth: AuthSettings;
minRefreshInterval: string;
+2 -1
View File
@@ -1,6 +1,5 @@
import { ScopedVars } from './ScopedVars';
import { ExploreCorrelationHelperData, ExplorePanelsState } from './explore';
import { LinkTarget } from './linkTarget';
import { InterpolateFunction } from './panel';
import { DataQuery } from './query';
import { TimeRange } from './time';
@@ -89,6 +88,8 @@ export interface InternalDataLink<T extends DataQuery = any> {
range?: TimeRange;
}
export type LinkTarget = '_blank' | '_self' | undefined;
/**
* Processed Link Model. The values are ready to use
*/
+1 -9
View File
@@ -356,7 +356,7 @@ export interface FeatureToggles {
*/
dashboardScene?: boolean;
/**
* Enables new dashboard layouts
* Enables experimental new dashboard layouts
*/
dashboardNewLayouts?: boolean;
/**
@@ -531,10 +531,6 @@ export interface FeatureToggles {
*/
alertingListViewV2?: boolean;
/**
* Enables the new Alerting navigation structure with improved menu grouping
*/
alertingNavigationV2?: boolean;
/**
* Enables saved searches for alert rules list
*/
alertingSavedSearches?: boolean;
@@ -1255,8 +1251,4 @@ export interface FeatureToggles {
* Enables profiles exemplars support in profiles drilldown
*/
profilesExemplars?: boolean;
/**
* Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods
*/
alertingSyncDispatchTimer?: boolean;
}
@@ -1,4 +0,0 @@
/**
* Target for links - controls whether link opens in new tab or same tab
*/
export type LinkTarget = '_blank' | '_self' | undefined;
+1 -1
View File
@@ -1,7 +1,7 @@
import { ComponentType } from 'react';
import { LinkTarget } from './dataLink';
import { IconName } from './icon';
import { LinkTarget } from './linkTarget';
export interface NavLinkDTO {
id?: string;
-2
View File
@@ -11,7 +11,6 @@ import { DataFrame } from './dataFrame';
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
import { FieldConfigSource } from './fieldOverrides';
import { IconName } from './icon';
import { LinkTarget } from './linkTarget';
import { OptionEditorConfig } from './options';
import { PluginMeta } from './plugin';
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
@@ -192,7 +191,6 @@ export interface PanelMenuItem {
onClick?: (event: React.MouseEvent) => void;
shortcut?: string;
href?: string;
target?: LinkTarget;
subMenu?: PanelMenuItem[];
}
@@ -53,7 +53,6 @@ export interface PluginError {
pluginType?: PluginType;
}
/** @deprecated it will be removed in a future release */
export interface AngularMeta {
detected: boolean;
hideDeprecation: boolean;
@@ -91,8 +91,6 @@ export interface VariableOption {
text: string | string[];
value: string | string[];
isNone?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
properties?: Record<string, any>;
}
export interface IntervalVariableModel extends VariableWithOptions {
@@ -120,7 +118,6 @@ export interface QueryVariableModel extends VariableWithMultiSupport {
definition: string;
sort: VariableSort;
queryValue?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
query: any;
regex: string;
regexApplyTo?: VariableRegexApplyTo;
@@ -196,7 +193,6 @@ export interface BaseVariableModel {
skipUrlSync: boolean;
index: number;
state: LoadingState;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any | null;
description: string | null;
usedInRepeat?: boolean;
+1 -1
View File
@@ -9,4 +9,4 @@
* and be subject to the standard policies
*/
export {};
export { default as themeJsonSchema } from './themes/schema.generated.json';
+1 -2
View File
@@ -8,8 +8,7 @@
"emitDeclarationOnly": true,
"isolatedModules": true,
"rootDirs": ["."],
"moduleResolution": "bundler",
"resolveJsonModule": true
"moduleResolution": "bundler"
},
"exclude": ["dist/**/*"],
"include": [
-1
View File
@@ -86,7 +86,6 @@ export class GrafanaBootConfig {
snapshotEnabled = true;
datasources: { [str: string]: DataSourceInstanceSettings } = {};
panels: { [key: string]: PanelPluginMeta } = {};
/** @deprecated it will be removed in a future release, use isAppPluginInstalled or getAppPluginVersion instead */
apps: Record<string, AppPluginConfigGrafanaData> = {};
auth: AuthSettings = {};
minRefreshInterval = '';
-2
View File
@@ -77,5 +77,3 @@ export {
getCorrelationsService,
setCorrelationsService,
} from './services/CorrelationsService';
export { getAppPluginVersion, isAppPluginInstalled } from './services/pluginMeta/apps';
export { useAppPluginInstalled, useAppPluginVersion } from './services/pluginMeta/hooks';
@@ -29,5 +29,3 @@ export {
export { UserStorage } from '../utils/userStorage';
export { initOpenFeature, evaluateBooleanFlag } from './openFeature';
export { getAppPluginMeta, getAppPluginMetas, setAppPluginMetas } from '../services/pluginMeta/apps';
export { useAppPluginMeta, useAppPluginMetas } from '../services/pluginMeta/hooks';
@@ -1,258 +0,0 @@
import { evaluateBooleanFlag } from '../../internal/openFeature';
import {
getAppPluginMeta,
getAppPluginMetas,
getAppPluginVersion,
isAppPluginInstalled,
setAppPluginMetas,
} from './apps';
import { initPluginMetas } from './plugins';
import { app } from './test-fixtures/config.apps';
jest.mock('./plugins', () => ({ ...jest.requireActual('./plugins'), initPluginMetas: jest.fn() }));
jest.mock('../../internal/openFeature', () => ({
...jest.requireActual('../../internal/openFeature'),
evaluateBooleanFlag: jest.fn(),
}));
const initPluginMetasMock = jest.mocked(initPluginMetas);
const evaluateBooleanFlagMock = jest.mocked(evaluateBooleanFlag);
describe('when useMTPlugins flag is enabled and apps is not initialized', () => {
beforeEach(() => {
setAppPluginMetas({});
jest.resetAllMocks();
initPluginMetasMock.mockResolvedValue({ items: [] });
evaluateBooleanFlagMock.mockReturnValue(true);
});
it('getAppPluginMetas should call initPluginMetas and return correct result', async () => {
const apps = await getAppPluginMetas();
expect(apps).toEqual([]);
expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
});
it('getAppPluginMeta should call initPluginMetas and return correct result', async () => {
const result = await getAppPluginMeta('myorg-someplugin-app');
expect(result).toEqual(null);
expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
});
it('isAppPluginInstalled should call initPluginMetas and return false', async () => {
const installed = await isAppPluginInstalled('myorg-someplugin-app');
expect(installed).toEqual(false);
expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
});
it('getAppPluginVersion should call initPluginMetas and return null', async () => {
const result = await getAppPluginVersion('myorg-someplugin-app');
expect(result).toEqual(null);
expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
});
});
describe('when useMTPlugins flag is enabled and apps is initialized', () => {
beforeEach(() => {
setAppPluginMetas({ 'myorg-someplugin-app': app });
jest.resetAllMocks();
evaluateBooleanFlagMock.mockReturnValue(true);
});
it('getAppPluginMetas should not call initPluginMetas and return correct result', async () => {
const apps = await getAppPluginMetas();
expect(apps).toEqual([app]);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginMeta should not call initPluginMetas and return correct result', async () => {
const result = await getAppPluginMeta('myorg-someplugin-app');
expect(result).toEqual(app);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginMeta should return null if the pluginId is not found', async () => {
const result = await getAppPluginMeta('otherorg-otherplugin-app');
expect(result).toEqual(null);
});
it('isAppPluginInstalled should not call initPluginMetas and return true', async () => {
const installed = await isAppPluginInstalled('myorg-someplugin-app');
expect(installed).toEqual(true);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('isAppPluginInstalled should return false if the pluginId is not found', async () => {
const result = await isAppPluginInstalled('otherorg-otherplugin-app');
expect(result).toEqual(false);
});
it('getAppPluginVersion should not call initPluginMetas and return correct result', async () => {
const result = await getAppPluginVersion('myorg-someplugin-app');
expect(result).toEqual('1.0.0');
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginVersion should return null if the pluginId is not found', async () => {
const result = await getAppPluginVersion('otherorg-otherplugin-app');
expect(result).toEqual(null);
});
});
describe('when useMTPlugins flag is disabled and apps is not initialized', () => {
beforeEach(() => {
setAppPluginMetas({});
jest.resetAllMocks();
evaluateBooleanFlagMock.mockReturnValue(false);
});
it('getAppPluginMetas should not call initPluginMetas and return correct result', async () => {
const apps = await getAppPluginMetas();
expect(apps).toEqual([]);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginMeta should not call initPluginMetas and return correct result', async () => {
const result = await getAppPluginMeta('myorg-someplugin-app');
expect(result).toEqual(null);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('isAppPluginInstalled should not call initPluginMetas and return false', async () => {
const result = await isAppPluginInstalled('myorg-someplugin-app');
expect(result).toEqual(false);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginVersion should not call initPluginMetas and return correct result', async () => {
const result = await getAppPluginVersion('myorg-someplugin-app');
expect(result).toEqual(null);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
});
describe('when useMTPlugins flag is disabled and apps is initialized', () => {
beforeEach(() => {
setAppPluginMetas({ 'myorg-someplugin-app': app });
jest.resetAllMocks();
evaluateBooleanFlagMock.mockReturnValue(false);
});
it('getAppPluginMetas should not call initPluginMetas and return correct result', async () => {
const apps = await getAppPluginMetas();
expect(apps).toEqual([app]);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginMeta should not call initPluginMetas and return correct result', async () => {
const result = await getAppPluginMeta('myorg-someplugin-app');
expect(result).toEqual(app);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginMeta should return null if the pluginId is not found', async () => {
const result = await getAppPluginMeta('otherorg-otherplugin-app');
expect(result).toEqual(null);
});
it('isAppPluginInstalled should not call initPluginMetas and return true', async () => {
const result = await isAppPluginInstalled('myorg-someplugin-app');
expect(result).toEqual(true);
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('isAppPluginInstalled should return false if the pluginId is not found', async () => {
const result = await isAppPluginInstalled('otherorg-otherplugin-app');
expect(result).toEqual(false);
});
it('getAppPluginVersion should not call initPluginMetas and return correct result', async () => {
const result = await getAppPluginVersion('myorg-someplugin-app');
expect(result).toEqual('1.0.0');
expect(initPluginMetasMock).not.toHaveBeenCalled();
});
it('getAppPluginVersion should return null if the pluginId is not found', async () => {
const result = await getAppPluginVersion('otherorg-otherplugin-app');
expect(result).toEqual(null);
});
});
describe('immutability', () => {
beforeEach(() => {
setAppPluginMetas({ 'myorg-someplugin-app': app });
jest.resetAllMocks();
evaluateBooleanFlagMock.mockReturnValue(false);
});
it('getAppPluginMetas should return a deep clone', async () => {
const mutatedApps = await getAppPluginMetas();
// assert we have correct props
expect(mutatedApps).toHaveLength(1);
expect(mutatedApps[0].dependencies.grafanaDependency).toEqual('>=10.4.0');
expect(mutatedApps[0].extensions.addedLinks).toHaveLength(0);
// mutate deep props
mutatedApps[0].dependencies.grafanaDependency = '';
mutatedApps[0].extensions.addedLinks.push({ targets: [], title: '', description: '' });
// assert we have mutated props
expect(mutatedApps[0].dependencies.grafanaDependency).toEqual('');
expect(mutatedApps[0].extensions.addedLinks).toHaveLength(1);
expect(mutatedApps[0].extensions.addedLinks[0]).toEqual({ targets: [], title: '', description: '' });
const apps = await getAppPluginMetas();
// assert that we have not mutated the source
expect(apps[0].dependencies.grafanaDependency).toEqual('>=10.4.0');
expect(apps[0].extensions.addedLinks).toHaveLength(0);
});
it('getAppPluginMeta should return a deep clone', async () => {
const mutatedApp = await getAppPluginMeta('myorg-someplugin-app');
// assert we have correct props
expect(mutatedApp).toBeDefined();
expect(mutatedApp!.dependencies.grafanaDependency).toEqual('>=10.4.0');
expect(mutatedApp!.extensions.addedLinks).toHaveLength(0);
// mutate deep props
mutatedApp!.dependencies.grafanaDependency = '';
mutatedApp!.extensions.addedLinks.push({ targets: [], title: '', description: '' });
// assert we have mutated props
expect(mutatedApp!.dependencies.grafanaDependency).toEqual('');
expect(mutatedApp!.extensions.addedLinks).toHaveLength(1);
expect(mutatedApp!.extensions.addedLinks[0]).toEqual({ targets: [], title: '', description: '' });
const result = await getAppPluginMeta('myorg-someplugin-app');
// assert that we have not mutated the source
expect(result).toBeDefined();
expect(result!.dependencies.grafanaDependency).toEqual('>=10.4.0');
expect(result!.extensions.addedLinks).toHaveLength(0);
});
});
@@ -1,71 +0,0 @@
import type { AppPluginConfig } from '@grafana/data';
import { config } from '../../config';
import { evaluateBooleanFlag } from '../../internal/openFeature';
import { getAppPluginMapper } from './mappers/mappers';
import { initPluginMetas } from './plugins';
import type { AppPluginMetas } from './types';
let apps: AppPluginMetas = {};
function initialized(): boolean {
return Boolean(Object.keys(apps).length);
}
async function initAppPluginMetas(): Promise<void> {
if (!evaluateBooleanFlag('useMTPlugins', false)) {
// eslint-disable-next-line no-restricted-syntax
apps = config.apps;
return;
}
const metas = await initPluginMetas();
const mapper = getAppPluginMapper();
apps = mapper(metas);
}
export async function getAppPluginMetas(): Promise<AppPluginConfig[]> {
if (!initialized()) {
await initAppPluginMetas();
}
return Object.values(structuredClone(apps));
}
export async function getAppPluginMeta(pluginId: string): Promise<AppPluginConfig | null> {
if (!initialized()) {
await initAppPluginMetas();
}
const app = apps[pluginId];
return app ? structuredClone(app) : null;
}
/**
* Check if an app plugin is installed. The function does not check if the app plugin is enabled.
* @param pluginId - The id of the app plugin.
* @returns True if the app plugin is installed, false otherwise.
*/
export async function isAppPluginInstalled(pluginId: string): Promise<boolean> {
const app = await getAppPluginMeta(pluginId);
return Boolean(app);
}
/**
* Get the version of an app plugin.
* @param pluginId - The id of the app plugin.
* @returns The version of the app plugin, or null if the plugin is not installed.
*/
export async function getAppPluginVersion(pluginId: string): Promise<string | null> {
const app = await getAppPluginMeta(pluginId);
return app?.version ?? null;
}
export function setAppPluginMetas(override: AppPluginMetas): void {
if (process.env.NODE_ENV !== 'test') {
throw new Error('setAppPluginMetas() function can only be called from tests.');
}
apps = structuredClone(override);
}
@@ -1,214 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import {
getAppPluginMeta,
getAppPluginMetas,
getAppPluginVersion,
isAppPluginInstalled,
setAppPluginMetas,
} from './apps';
import { useAppPluginMeta, useAppPluginMetas, useAppPluginInstalled, useAppPluginVersion } from './hooks';
import { apps } from './test-fixtures/config.apps';
const actualApps = jest.requireActual<typeof import('./apps')>('./apps');
jest.mock('./apps', () => ({
...jest.requireActual('./apps'),
getAppPluginMetas: jest.fn(),
getAppPluginMeta: jest.fn(),
isAppPluginInstalled: jest.fn(),
getAppPluginVersion: jest.fn(),
}));
const getAppPluginMetaMock = jest.mocked(getAppPluginMeta);
const getAppPluginMetasMock = jest.mocked(getAppPluginMetas);
const isAppPluginInstalledMock = jest.mocked(isAppPluginInstalled);
const getAppPluginVersionMock = jest.mocked(getAppPluginVersion);
describe('useAppPluginMeta', () => {
beforeEach(() => {
setAppPluginMetas(apps);
jest.resetAllMocks();
getAppPluginMetaMock.mockImplementation(actualApps.getAppPluginMeta);
});
it('should return correct default values', async () => {
const { result } = renderHook(() => useAppPluginMeta('grafana-exploretraces-app'));
expect(result.current.loading).toEqual(true);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toBeUndefined();
await waitFor(() => expect(result.current.loading).toEqual(true));
});
it('should return correct values after loading', async () => {
const { result } = renderHook(() => useAppPluginMeta('grafana-exploretraces-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual(apps['grafana-exploretraces-app']);
});
it('should return correct values if the pluginId does not exist', async () => {
const { result } = renderHook(() => useAppPluginMeta('otherorg-otherplugin-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual(null);
});
it('should return correct values if useAppPluginMeta throws', async () => {
getAppPluginMetaMock.mockRejectedValue(new Error('Some error'));
const { result } = renderHook(() => useAppPluginMeta('otherorg-otherplugin-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toEqual(new Error('Some error'));
expect(result.current.value).toBeUndefined();
});
});
describe('useAppPluginMetas', () => {
beforeEach(() => {
setAppPluginMetas(apps);
jest.resetAllMocks();
getAppPluginMetasMock.mockImplementation(actualApps.getAppPluginMetas);
});
it('should return correct default values', async () => {
const { result } = renderHook(() => useAppPluginMetas());
expect(result.current.loading).toEqual(true);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toBeUndefined();
await waitFor(() => expect(result.current.loading).toEqual(true));
});
it('should return correct values after loading', async () => {
const { result } = renderHook(() => useAppPluginMetas());
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual(Object.values(apps));
});
it('should return correct values if useAppPluginMetas throws', async () => {
getAppPluginMetasMock.mockRejectedValue(new Error('Some error'));
const { result } = renderHook(() => useAppPluginMetas());
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toEqual(new Error('Some error'));
expect(result.current.value).toBeUndefined();
});
});
describe('useAppPluginInstalled', () => {
beforeEach(() => {
setAppPluginMetas(apps);
jest.resetAllMocks();
isAppPluginInstalledMock.mockImplementation(actualApps.isAppPluginInstalled);
});
it('should return correct default values', async () => {
const { result } = renderHook(() => useAppPluginInstalled('grafana-exploretraces-app'));
expect(result.current.loading).toEqual(true);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toBeUndefined();
await waitFor(() => expect(result.current.loading).toEqual(true));
});
it('should return correct values after loading', async () => {
const { result } = renderHook(() => useAppPluginInstalled('grafana-exploretraces-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual(true);
});
it('should return correct values if the pluginId does not exist', async () => {
const { result } = renderHook(() => useAppPluginInstalled('otherorg-otherplugin-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual(false);
});
it('should return correct values if isAppPluginInstalled throws', async () => {
isAppPluginInstalledMock.mockRejectedValue(new Error('Some error'));
const { result } = renderHook(() => useAppPluginInstalled('otherorg-otherplugin-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toEqual(new Error('Some error'));
expect(result.current.value).toBeUndefined();
});
});
describe('useAppPluginVersion', () => {
beforeEach(() => {
setAppPluginMetas(apps);
jest.resetAllMocks();
getAppPluginVersionMock.mockImplementation(actualApps.getAppPluginVersion);
});
it('should return correct default values', async () => {
const { result } = renderHook(() => useAppPluginVersion('grafana-exploretraces-app'));
expect(result.current.loading).toEqual(true);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toBeUndefined();
await waitFor(() => expect(result.current.loading).toEqual(true));
});
it('should return correct values after loading', async () => {
const { result } = renderHook(() => useAppPluginVersion('grafana-exploretraces-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual('1.2.2');
});
it('should return correct values if the pluginId does not exist', async () => {
const { result } = renderHook(() => useAppPluginVersion('otherorg-otherplugin-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.value).toEqual(null);
});
it('should return correct values if getAppPluginVersion throws', async () => {
getAppPluginVersionMock.mockRejectedValue(new Error('Some error'));
const { result } = renderHook(() => useAppPluginVersion('otherorg-otherplugin-app'));
await waitFor(() => expect(result.current.loading).toEqual(false));
expect(result.current.loading).toEqual(false);
expect(result.current.error).toEqual(new Error('Some error'));
expect(result.current.value).toBeUndefined();
});
});
@@ -1,35 +0,0 @@
import { useAsync } from 'react-use';
import { getAppPluginMeta, getAppPluginMetas, getAppPluginVersion, isAppPluginInstalled } from './apps';
export function useAppPluginMetas() {
const { loading, error, value } = useAsync(async () => getAppPluginMetas());
return { loading, error, value };
}
export function useAppPluginMeta(pluginId: string) {
const { loading, error, value } = useAsync(async () => getAppPluginMeta(pluginId));
return { loading, error, value };
}
/**
* Hook that checks if an app plugin is installed. The hook does not check if the app plugin is enabled.
* @param pluginId - The ID of the app plugin.
* @returns loading, error, value of the app plugin installed status.
* The value is true if the app plugin is installed, false otherwise.
*/
export function useAppPluginInstalled(pluginId: string) {
const { loading, error, value } = useAsync(async () => isAppPluginInstalled(pluginId));
return { loading, error, value };
}
/**
* Hook that gets the version of an app plugin.
* @param pluginId - The ID of the app plugin.
* @returns loading, error, value of the app plugin version.
* The value is the version of the app plugin, or null if the plugin is not installed.
*/
export function useAppPluginVersion(pluginId: string) {
const { loading, error, value } = useAsync(async () => getAppPluginVersion(pluginId));
return { loading, error, value };
}
@@ -1,7 +0,0 @@
import { AppPluginMetasMapper, PluginMetasResponse } from '../types';
import { v0alpha1AppMapper } from './v0alpha1AppMapper';
export function getAppPluginMapper(): AppPluginMetasMapper<PluginMetasResponse> {
return v0alpha1AppMapper;
}
@@ -1,84 +0,0 @@
import { apps } from '../test-fixtures/config.apps';
import { v0alpha1Response } from '../test-fixtures/v0alpha1Response';
import { v0alpha1AppMapper } from './v0alpha1AppMapper';
const PLUGIN_IDS = v0alpha1Response.items
.filter((i) => i.spec.pluginJson.type === 'app')
.map((i) => ({ pluginId: i.spec.pluginJson.id }));
describe('v0alpha1AppMapper', () => {
describe.each(PLUGIN_IDS)('when called for pluginId:$pluginId', ({ pluginId }) => {
it('should map id property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].id).toEqual(apps[pluginId].id);
});
it('should map path property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].path).toEqual(apps[pluginId].path);
});
it('should map version property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].version).toEqual(apps[pluginId].version);
});
it('should map preload property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].preload).toEqual(apps[pluginId].preload);
});
it('should map angular property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].angular).toEqual({});
});
it('should map loadingStrategy property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].loadingStrategy).toEqual(apps[pluginId].loadingStrategy);
});
it('should map dependencies property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].dependencies).toEqual(apps[pluginId].dependencies);
});
it('should map extensions property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].extensions.addedComponents).toEqual(apps[pluginId].extensions.addedComponents);
expect(result[pluginId].extensions.addedFunctions).toEqual(apps[pluginId].extensions.addedFunctions);
expect(result[pluginId].extensions.addedLinks).toEqual(apps[pluginId].extensions.addedLinks);
expect(result[pluginId].extensions.exposedComponents).toEqual(apps[pluginId].extensions.exposedComponents);
expect(result[pluginId].extensions.extensionPoints).toEqual(apps[pluginId].extensions.extensionPoints);
});
it('should map moduleHash property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].moduleHash).toEqual(apps[pluginId].moduleHash);
});
it('should map buildMode property correctly', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(result[pluginId].buildMode).toEqual(apps[pluginId].buildMode);
});
});
it('should only map specs with type app', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(v0alpha1Response.items).toHaveLength(58);
expect(Object.keys(result)).toHaveLength(5);
expect(Object.keys(result)).toEqual(Object.keys(apps));
});
});
@@ -1,111 +0,0 @@
import {
type AngularMeta,
type AppPluginConfig,
type PluginDependencies,
type PluginExtensions,
PluginLoadingStrategy,
type PluginType,
} from '@grafana/data';
import type { AppPluginMetas, AppPluginMetasMapper, PluginMetasResponse } from '../types';
import type { Spec as v0alpha1Spec } from '../types/types.spec.gen';
function angularyMapper(spec: v0alpha1Spec): AngularMeta {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {} as AngularMeta;
}
function dependenciesMapper(spec: v0alpha1Spec): PluginDependencies {
const plugins = (spec.pluginJson.dependencies?.plugins ?? []).map((v) => ({
...v,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
type: v.type as PluginType,
version: '',
}));
const dependencies: PluginDependencies = {
...spec.pluginJson.dependencies,
extensions: {
exposedComponents: spec.pluginJson.dependencies.extensions?.exposedComponents ?? [],
},
grafanaDependency: spec.pluginJson.dependencies.grafanaDependency,
grafanaVersion: spec.pluginJson.dependencies.grafanaVersion ?? '',
plugins,
};
return dependencies;
}
function extensionsMapper(spec: v0alpha1Spec): PluginExtensions {
const addedComponents = spec.pluginJson.extensions?.addedComponents ?? [];
const addedFunctions = spec.pluginJson.extensions?.addedFunctions ?? [];
const addedLinks = spec.pluginJson.extensions?.addedLinks ?? [];
const exposedComponents = (spec.pluginJson.extensions?.exposedComponents ?? []).map((v) => ({
...v,
description: v.description ?? '',
title: v.title ?? '',
}));
const extensionPoints = (spec.pluginJson.extensions?.extensionPoints ?? []).map((v) => ({
...v,
description: v.description ?? '',
title: v.title ?? '',
}));
const extensions: PluginExtensions = {
addedComponents,
addedFunctions,
addedLinks,
exposedComponents,
extensionPoints,
};
return extensions;
}
function loadingStrategyMapper(spec: v0alpha1Spec): PluginLoadingStrategy {
const loadingStrategy = spec.module?.loadingStrategy ?? PluginLoadingStrategy.fetch;
if (loadingStrategy === PluginLoadingStrategy.script) {
return PluginLoadingStrategy.script;
}
return PluginLoadingStrategy.fetch;
}
function specMapper(spec: v0alpha1Spec): AppPluginConfig {
const { id, info, preload = false } = spec.pluginJson;
const angular = angularyMapper(spec);
const dependencies = dependenciesMapper(spec);
const extensions = extensionsMapper(spec);
const loadingStrategy = loadingStrategyMapper(spec);
const path = spec.module?.path ?? '';
const version = info.version;
const buildMode = spec.pluginJson.buildMode ?? 'production';
const moduleHash = spec.module?.hash;
return {
id,
angular,
dependencies,
extensions,
loadingStrategy,
path,
preload,
version,
buildMode,
moduleHash,
};
}
export const v0alpha1AppMapper: AppPluginMetasMapper<PluginMetasResponse> = (response) => {
const result: AppPluginMetas = {};
return response.items.reduce((acc, curr) => {
if (curr.spec.pluginJson.type !== 'app') {
return acc;
}
const config = specMapper(curr.spec);
acc[config.id] = config;
return acc;
}, result);
};
@@ -1,153 +0,0 @@
import { evaluateBooleanFlag } from '../../internal/openFeature';
import { clearCache, initPluginMetas } from './plugins';
import { v0alpha1Meta } from './test-fixtures/v0alpha1Response';
jest.mock('../../internal/openFeature', () => ({
...jest.requireActual('../../internal/openFeature'),
evaluateBooleanFlag: jest.fn(),
}));
const evaluateBooleanFlagMock = jest.mocked(evaluateBooleanFlag);
describe('when useMTPlugins toggle is enabled and cache is not initialized', () => {
const originalFetch = global.fetch;
beforeEach(() => {
jest.resetAllMocks();
clearCache();
evaluateBooleanFlagMock.mockReturnValue(true);
});
afterEach(() => {
global.fetch = originalFetch;
});
it('initPluginMetas should call loadPluginMetas and return correct result if response is ok', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve({ items: [v0alpha1Meta] }),
});
const response = await initPluginMetas();
expect(response.items).toHaveLength(1);
expect(response.items[0]).toEqual(v0alpha1Meta);
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith('/apis/plugins.grafana.app/v0alpha1/namespaces/default/metas');
});
it('initPluginMetas should call loadPluginMetas and return correct result if response is not ok', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 404,
statusText: 'Not found',
});
await expect(initPluginMetas()).rejects.toThrow(new Error(`Failed to load plugin metas 404:Not found`));
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith('/apis/plugins.grafana.app/v0alpha1/namespaces/default/metas');
});
});
describe('when useMTPlugins toggle is enabled and cache is initialized', () => {
const originalFetch = global.fetch;
beforeEach(() => {
jest.resetAllMocks();
clearCache();
evaluateBooleanFlagMock.mockReturnValue(true);
});
afterEach(() => {
global.fetch = originalFetch;
});
it('initPluginMetas should return cache', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve({ items: [v0alpha1Meta] }),
});
const original = await initPluginMetas();
const cached = await initPluginMetas();
expect(original).toEqual(cached);
expect(global.fetch).toHaveBeenCalledTimes(1);
});
it('initPluginMetas should return inflight promise', async () => {
jest.useFakeTimers();
global.fetch = jest.fn().mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve({ items: [v0alpha1Meta] }),
});
const original = initPluginMetas();
const cached = initPluginMetas();
await jest.runAllTimersAsync();
expect(original).toEqual(cached);
expect(global.fetch).toHaveBeenCalledTimes(1);
});
});
describe('when useMTPlugins toggle is disabled and cache is not initialized', () => {
const originalFetch = global.fetch;
beforeEach(() => {
jest.resetAllMocks();
clearCache();
global.fetch = jest.fn();
evaluateBooleanFlagMock.mockReturnValue(false);
});
afterEach(() => {
global.fetch = originalFetch;
});
it('initPluginMetas should call loadPluginMetas and return correct result if response is ok', async () => {
const response = await initPluginMetas();
expect(response.items).toHaveLength(0);
expect(global.fetch).not.toHaveBeenCalled();
});
});
describe('when useMTPlugins toggle is disabled and cache is initialized', () => {
const originalFetch = global.fetch;
beforeEach(() => {
jest.resetAllMocks();
clearCache();
global.fetch = jest.fn();
evaluateBooleanFlagMock.mockReturnValue(false);
});
afterEach(() => {
global.fetch = originalFetch;
});
it('initPluginMetas should return cache', async () => {
const original = await initPluginMetas();
const cached = await initPluginMetas();
expect(original).toEqual(cached);
expect(global.fetch).not.toHaveBeenCalled();
});
it('initPluginMetas should return inflight promise', async () => {
jest.useFakeTimers();
const original = initPluginMetas();
const cached = initPluginMetas();
await jest.runAllTimersAsync();
expect(original).toEqual(cached);
expect(global.fetch).not.toHaveBeenCalled();
});
});
@@ -1,41 +0,0 @@
import { config } from '../../config';
import { evaluateBooleanFlag } from '../../internal/openFeature';
import type { PluginMetasResponse } from './types';
let initPromise: Promise<PluginMetasResponse> | null = null;
function getApiVersion(): string {
return 'v0alpha1';
}
async function loadPluginMetas(): Promise<PluginMetasResponse> {
if (!evaluateBooleanFlag('useMTPlugins', false)) {
const result = { items: [] };
return result;
}
const metas = await fetch(`/apis/plugins.grafana.app/${getApiVersion()}/namespaces/${config.namespace}/metas`);
if (!metas.ok) {
throw new Error(`Failed to load plugin metas ${metas.status}:${metas.statusText}`);
}
const result = await metas.json();
return result;
}
export function initPluginMetas(): Promise<PluginMetasResponse> {
if (!initPromise) {
initPromise = loadPluginMetas();
}
return initPromise;
}
export function clearCache() {
if (process.env.NODE_ENV !== 'test') {
throw new Error('clearCache() function can only be called from tests.');
}
initPromise = null;
}
@@ -1,303 +0,0 @@
import { cloneDeep } from 'lodash';
import { AngularMeta, AppPluginConfig, PluginLoadingStrategy } from '@grafana/data';
import { AppPluginMetas } from '../types';
export const app: AppPluginConfig = cloneDeep({
id: 'myorg-someplugin-app',
path: 'public/plugins/myorg-someplugin-app/module.js',
version: '1.0.0',
preload: false,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
angular: { detected: false } as AngularMeta,
loadingStrategy: PluginLoadingStrategy.script,
extensions: {
addedLinks: [],
addedComponents: [],
exposedComponents: [],
extensionPoints: [],
addedFunctions: [],
},
dependencies: {
grafanaDependency: '>=10.4.0',
grafanaVersion: '*',
plugins: [],
extensions: {
exposedComponents: [],
},
},
buildMode: 'production',
});
export const apps: AppPluginMetas = cloneDeep({
'grafana-exploretraces-app': {
id: 'grafana-exploretraces-app',
path: 'public/plugins/grafana-exploretraces-app/module.js',
version: '1.2.2',
preload: true,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
angular: { detected: false } as AngularMeta,
loadingStrategy: PluginLoadingStrategy.script,
extensions: {
addedLinks: [
{
targets: ['grafana/dashboard/panel/menu'],
title: 'Open in Traces Drilldown',
description: 'Open current query in the Traces Drilldown app',
},
{
targets: ['grafana/explore/toolbar/action'],
title: 'Open in Grafana Traces Drilldown',
description: 'Try our new queryless experience for traces',
},
],
addedComponents: [
{
targets: ['grafana-asserts-app/entity-assertions-widget/v1'],
title: 'Asserts widget',
description: 'A block with assertions for a given service',
},
{
targets: ['grafana-asserts-app/insights-timeline-widget/v1'],
title: 'Insights Timeline Widget',
description: 'Widget for displaying insights timeline in other apps',
},
],
exposedComponents: [
{
id: 'grafana-exploretraces-app/open-in-explore-traces-button/v1',
title: 'Open in Traces Drilldown button',
description: 'A button that opens a traces view in the Traces Drilldown app.',
},
{
id: 'grafana-exploretraces-app/embedded-trace-exploration/v1',
title: 'Embedded Trace Exploration',
description:
'A component that renders a trace exploration view that can be embedded in other parts of Grafana.',
},
],
extensionPoints: [
{
id: 'grafana-exploretraces-app/investigation/v1',
title: '',
description: '',
},
{
id: 'grafana-exploretraces-app/get-logs-drilldown-link/v1',
title: '',
description: '',
},
],
addedFunctions: [],
},
dependencies: {
grafanaDependency: '>=11.5.0',
grafanaVersion: '*',
plugins: [],
extensions: {
exposedComponents: [
'grafana-asserts-app/entity-assertions-widget/v1',
'grafana-asserts-app/insights-timeline-widget/v1',
],
},
},
buildMode: 'production',
},
'grafana-lokiexplore-app': {
id: 'grafana-lokiexplore-app',
path: 'public/plugins/grafana-lokiexplore-app/module.js',
version: '1.0.32',
preload: true,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
angular: { detected: false } as AngularMeta,
loadingStrategy: PluginLoadingStrategy.script,
extensions: {
addedLinks: [
{
targets: [
'grafana/dashboard/panel/menu',
'grafana/explore/toolbar/action',
'grafana-metricsdrilldown-app/open-in-logs-drilldown/v1',
'grafana-assistant-app/navigateToDrilldown/v1',
],
title: 'Open in Grafana Logs Drilldown',
description: 'Open current query in the Grafana Logs Drilldown view',
},
],
addedComponents: [
{
targets: ['grafana-asserts-app/insights-timeline-widget/v1'],
title: 'Insights Timeline Widget',
description: 'Widget for displaying insights timeline in other apps',
},
],
exposedComponents: [
{
id: 'grafana-lokiexplore-app/open-in-explore-logs-button/v1',
title: 'Open in Logs Drilldown button',
description: 'A button that opens a logs view in the Logs Drilldown app.',
},
{
id: 'grafana-lokiexplore-app/embedded-logs-exploration/v1',
title: 'Embedded Logs Exploration',
description:
'A component that renders a logs exploration view that can be embedded in other parts of Grafana.',
},
],
extensionPoints: [
{
id: 'grafana-lokiexplore-app/investigation/v1',
title: '',
description: '',
},
],
addedFunctions: [
{
targets: ['grafana-exploretraces-app/get-logs-drilldown-link/v1'],
title: 'Open Logs Drilldown',
description: 'Returns url to logs drilldown app',
},
],
},
dependencies: {
grafanaDependency: '>=11.6.0',
grafanaVersion: '*',
plugins: [],
extensions: {
exposedComponents: [
'grafana-adaptivelogs-app/temporary-exemptions/v1',
'grafana-lokiexplore-app/embedded-logs-exploration/v1',
'grafana-asserts-app/insights-timeline-widget/v1',
'grafana/add-to-dashboard-form/v1',
],
},
},
buildMode: 'production',
},
'grafana-metricsdrilldown-app': {
id: 'grafana-metricsdrilldown-app',
path: 'public/plugins/grafana-metricsdrilldown-app/module.js',
version: '1.0.26',
preload: true,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
angular: { detected: false } as AngularMeta,
loadingStrategy: PluginLoadingStrategy.script,
extensions: {
addedLinks: [
{
targets: [
'grafana/dashboard/panel/menu',
'grafana/explore/toolbar/action',
'grafana-assistant-app/navigateToDrilldown/v1',
'grafana/alerting/alertingrule/queryeditor',
],
title: 'Open in Grafana Metrics Drilldown',
description: 'Open current query in the Grafana Metrics Drilldown view',
},
{
targets: ['grafana-metricsdrilldown-app/grafana-assistant-app/navigateToDrilldown/v0-alpha'],
title: 'Navigate to metrics drilldown',
description: 'Build a url path to the metrics drilldown',
},
{
targets: ['grafana/datasources/config/actions', 'grafana/datasources/config/status'],
title: 'Open in Metrics Drilldown',
description: 'Browse metrics in Grafana Metrics Drilldown',
},
],
addedComponents: [],
exposedComponents: [
{
id: 'grafana-metricsdrilldown-app/label-breakdown-component/v1',
title: 'Label Breakdown',
description: 'A metrics label breakdown view from the Metrics Drilldown app.',
},
{
id: 'grafana-metricsdrilldown-app/knowledge-graph-insight-metrics/v1',
title: 'Knowledge Graph Source Metrics',
description: 'Explore the underlying metrics related to a Knowledge Graph insight',
},
],
extensionPoints: [
{
id: 'grafana-exploremetrics-app/investigation/v1',
title: '',
description: '',
},
{
id: 'grafana-metricsdrilldown-app/open-in-logs-drilldown/v1',
title: '',
description: '',
},
],
addedFunctions: [],
},
dependencies: {
grafanaDependency: '>=11.6.0',
grafanaVersion: '*',
plugins: [],
extensions: {
exposedComponents: ['grafana/add-to-dashboard-form/v1'],
},
},
buildMode: 'production',
},
'grafana-pyroscope-app': {
id: 'grafana-pyroscope-app',
path: 'public/plugins/grafana-pyroscope-app/module.js',
version: '1.14.2',
preload: true,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
angular: { detected: false } as AngularMeta,
loadingStrategy: PluginLoadingStrategy.script,
extensions: {
addedLinks: [
{
targets: [
'grafana/explore/toolbar/action',
'grafana/traceview/details',
'grafana-assistant-app/navigateToDrilldown/v1',
],
title: 'Open in Grafana Profiles Drilldown',
description: 'Try our new queryless experience for profiles',
},
],
addedComponents: [],
exposedComponents: [
{
id: 'grafana-pyroscope-app/embedded-profiles-exploration/v1',
title: 'Embedded Profiles Exploration',
description:
'A component that renders a profiles exploration view that can be embedded in other parts of Grafana.',
},
],
extensionPoints: [
{
id: 'grafana-pyroscope-app/investigation/v1',
title: '',
description: '',
},
{
id: 'grafana-pyroscope-app/settings/v1',
title: '',
description: '',
},
],
addedFunctions: [],
},
dependencies: {
grafanaDependency: '>=11.5.0',
grafanaVersion: '*',
plugins: [],
extensions: {
exposedComponents: [
'grafana-o11yinsights-app/insights-launcher/v1',
'grafana-adaptiveprofiles-app/resolution-boost/v1',
],
},
},
buildMode: 'production',
},
[app.id]: app,
});
@@ -1,10 +0,0 @@
import type { AppPluginConfig } from '@grafana/data';
import type { Meta } from './types/meta_object_gen';
export type AppPluginMetas = Record<string, AppPluginConfig>;
export type AppPluginMetasMapper<T> = (response: T) => AppPluginMetas;
export interface PluginMetasResponse {
items: Meta[];
}
@@ -1,49 +0,0 @@
/*
* This file was generated by grafana-app-sdk. DO NOT EDIT.
*/
import { Spec } from './types.spec.gen';
import { Status } from './types.status.gen';
export interface Metadata {
name: string;
namespace: string;
generateName?: string;
selfLink?: string;
uid?: string;
resourceVersion?: string;
generation?: number;
creationTimestamp?: string;
deletionTimestamp?: string;
deletionGracePeriodSeconds?: number;
labels?: Record<string, string>;
annotations?: Record<string, string>;
ownerReferences?: OwnerReference[];
finalizers?: string[];
managedFields?: ManagedFieldsEntry[];
}
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export interface ManagedFieldsEntry {
manager?: string;
operation?: string;
apiVersion?: string;
time?: string;
fieldsType?: string;
subresource?: string;
}
export interface Meta {
kind: string;
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}
@@ -1,278 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// JSON configuration schema for Grafana plugins
// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json
export interface JSONData {
// Unique name of the plugin
id: string;
// Plugin type
type: "app" | "datasource" | "panel" | "renderer";
// Human-readable name of the plugin
name: string;
// Metadata for the plugin
info: Info;
// Dependency information
dependencies: Dependencies;
// Optional fields
alerting?: boolean;
annotations?: boolean;
autoEnabled?: boolean;
backend?: boolean;
buildMode?: string;
builtIn?: boolean;
category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other";
enterpriseFeatures?: EnterpriseFeatures;
executable?: string;
hideFromList?: boolean;
// +listType=atomic
includes?: Include[];
logs?: boolean;
metrics?: boolean;
multiValueFilterOperators?: boolean;
pascalName?: string;
preload?: boolean;
queryOptions?: QueryOptions;
// +listType=atomic
routes?: Route[];
skipDataQuery?: boolean;
state?: "alpha" | "beta";
streaming?: boolean;
suggestions?: boolean;
tracing?: boolean;
iam?: IAM;
// +listType=atomic
roles?: Role[];
extensions?: Extensions;
}
export const defaultJSONData = (): JSONData => ({
id: "",
type: "app",
name: "",
info: defaultInfo(),
dependencies: defaultDependencies(),
});
export interface Info {
// Required fields
// +listType=set
keywords: string[];
logos: {
small: string;
large: string;
};
updated: string;
version: string;
// Optional fields
author?: {
name?: string;
email?: string;
url?: string;
};
description?: string;
// +listType=atomic
links?: {
name?: string;
url?: string;
}[];
// +listType=atomic
screenshots?: {
name?: string;
path?: string;
}[];
}
export const defaultInfo = (): Info => ({
keywords: [],
logos: {
small: "",
large: "",
},
updated: "",
version: "",
});
export interface Dependencies {
// Required field
grafanaDependency: string;
// Optional fields
grafanaVersion?: string;
// +listType=set
// +listMapKey=id
plugins?: {
id: string;
type: "app" | "datasource" | "panel";
name: string;
}[];
extensions?: {
// +listType=set
exposedComponents?: string[];
};
}
export const defaultDependencies = (): Dependencies => ({
grafanaDependency: "",
});
export interface EnterpriseFeatures {
// Allow additional properties
healthDiagnosticsErrors?: boolean;
}
export const defaultEnterpriseFeatures = (): EnterpriseFeatures => ({
healthDiagnosticsErrors: false,
});
export interface Include {
uid?: string;
type?: "dashboard" | "page" | "panel" | "datasource";
name?: string;
component?: string;
role?: "Admin" | "Editor" | "Viewer" | "None";
action?: string;
path?: string;
addToNav?: boolean;
defaultNav?: boolean;
icon?: string;
}
export const defaultInclude = (): Include => ({
});
export interface QueryOptions {
maxDataPoints?: boolean;
minInterval?: boolean;
cacheTimeout?: boolean;
}
export const defaultQueryOptions = (): QueryOptions => ({
});
export interface Route {
path?: string;
method?: string;
url?: string;
reqSignedIn?: boolean;
reqRole?: string;
reqAction?: string;
// +listType=atomic
headers?: string[];
body?: Record<string, any>;
tokenAuth?: {
url?: string;
// +listType=set
scopes?: string[];
params?: Record<string, any>;
};
jwtTokenAuth?: {
url?: string;
// +listType=set
scopes?: string[];
params?: Record<string, any>;
};
// +listType=atomic
urlParams?: {
name?: string;
content?: string;
}[];
}
export const defaultRoute = (): Route => ({
});
export interface IAM {
// +listType=atomic
permissions?: {
action?: string;
scope?: string;
}[];
}
export const defaultIAM = (): IAM => ({
});
export interface Role {
role?: {
name?: string;
description?: string;
// +listType=atomic
permissions?: {
action?: string;
scope?: string;
}[];
};
// +listType=set
grants?: string[];
}
export const defaultRole = (): Role => ({
});
export interface Extensions {
// +listType=atomic
addedComponents?: {
// +listType=set
targets: string[];
title: string;
description?: string;
}[];
// +listType=atomic
addedLinks?: {
// +listType=set
targets: string[];
title: string;
description?: string;
}[];
// +listType=atomic
addedFunctions?: {
// +listType=set
targets: string[];
title: string;
description?: string;
}[];
// +listType=set
// +listMapKey=id
exposedComponents?: {
id: string;
title?: string;
description?: string;
}[];
// +listType=set
// +listMapKey=id
extensionPoints?: {
id: string;
title?: string;
description?: string;
}[];
}
export const defaultExtensions = (): Extensions => ({
});
export interface Spec {
pluginJson: JSONData;
class: "core" | "external";
module?: {
path: string;
hash?: string;
loadingStrategy?: "fetch" | "script";
};
baseURL?: string;
signature?: {
status: "internal" | "valid" | "invalid" | "modified" | "unsigned";
type?: "grafana" | "commercial" | "community" | "private" | "private-glob";
org?: string;
};
angular?: {
detected: boolean;
};
translations?: Record<string, string>;
// +listType=atomic
children?: string[];
}
export const defaultSpec = (): Spec => ({
pluginJson: defaultJSONData(),
class: "core",
});
@@ -1,30 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface OperatorState {
// lastEvaluation is the ResourceVersion last evaluated
lastEvaluation: string;
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
state: "success" | "in_progress" | "failed";
// descriptiveState is an optional more descriptive state field which has no requirements on format
descriptiveState?: string;
// details contains any extra information that is operator-specific
details?: Record<string, any>;
}
export const defaultOperatorState = (): OperatorState => ({
lastEvaluation: "",
state: "success",
});
export interface Status {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
operatorStates?: Record<string, OperatorState>;
// additionalFields is reserved for future use
additionalFields?: Record<string, any>;
}
export const defaultStatus = (): Status => ({
});
@@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "%VERSION%";
export const pluginVersion = "12.4.0-pre";
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);
@@ -231,10 +231,6 @@ export const defaultVariableModel: Partial<VariableModel> = {
* Option to be selected in a variable.
*/
export interface VariableOption {
/**
* Additional properties for multi-props variables
*/
properties?: Record<string, string>;
/**
* Whether the option is selected or not
*/
@@ -715,9 +715,7 @@ VariableOption: {
// Text to be displayed for the option
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
value: string | [...string]
}
// Query variable specification
-2
View File
@@ -903,8 +903,6 @@ type VariableOption struct {
Text StringOrArrayOfString `json:"text"`
// Value of the option
Value StringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewVariableOption creates a new VariableOption object.
+3 -19
View File
@@ -574,8 +574,8 @@ var (
},
{
Name: "dashboardNewLayouts",
Description: "Enables new dashboard layouts",
Stage: FeatureStagePublicPreview,
Description: "Enables experimental new dashboard layouts",
Stage: FeatureStageExperimental,
FrontendOnly: false, // The restore backend feature changes behavior based on this flag
Owner: grafanaDashboardsSquad,
},
@@ -879,13 +879,6 @@ var (
Owner: grafanaAlertingSquad,
FrontendOnly: true,
},
{
Name: "alertingNavigationV2",
Description: "Enables the new Alerting navigation structure with improved menu grouping",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
FrontendOnly: false,
},
{
Name: "alertingSavedSearches",
Description: "Enables saved searches for alert rules list",
@@ -988,8 +981,7 @@ var (
Stage: FeatureStageDeprecated,
Owner: grafanaPartnerPluginsSquad,
Expression: "true", // Enabled by default for now
},
{
}, {
Name: "alertingFilterV2",
Description: "Enable the new alerting search experience",
Stage: FeatureStageExperimental,
@@ -2077,14 +2069,6 @@ var (
Owner: grafanaObservabilityTracesAndProfilingSquad,
FrontendOnly: false,
},
{
Name: "alertingSyncDispatchTimer",
Description: "Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
RequiresRestart: true,
HideFromDocs: true,
},
}
)
+1 -3
View File
@@ -79,7 +79,7 @@ annotationPermissionUpdate,GA,@grafana/identity-access-team,false,false,false
dashboardSceneForViewers,GA,@grafana/dashboards-squad,false,false,true
dashboardSceneSolo,GA,@grafana/dashboards-squad,false,false,true
dashboardScene,GA,@grafana/dashboards-squad,false,false,true
dashboardNewLayouts,preview,@grafana/dashboards-squad,false,false,false
dashboardNewLayouts,experimental,@grafana/dashboards-squad,false,false,false
dashboardUndoRedo,experimental,@grafana/dashboards-squad,false,false,true
unlimitedLayoutsNesting,experimental,@grafana/dashboards-squad,false,false,true
drilldownRecommendations,experimental,@grafana/dashboards-squad,false,false,true
@@ -121,7 +121,6 @@ dashboardLibrary,experimental,@grafana/sharing-squad,false,false,false
suggestedDashboards,experimental,@grafana/sharing-squad,false,false,false
dashboardTemplates,preview,@grafana/sharing-squad,false,false,false
alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true
alertingNavigationV2,experimental,@grafana/alerting-squad,false,false,false
alertingSavedSearches,experimental,@grafana/alerting-squad,false,false,true
alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false
preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false
@@ -281,4 +280,3 @@ multiPropsVariables,experimental,@grafana/dashboards-squad,false,false,true
smoothingTransformation,experimental,@grafana/datapro,false,false,true
secretsManagementAppPlatformAwsKeeper,experimental,@grafana/grafana-operator-experience-squad,false,false,false
profilesExemplars,experimental,@grafana/observability-traces-and-profiling,false,false,false
alertingSyncDispatchTimer,experimental,@grafana/alerting-squad,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
79 dashboardSceneForViewers GA @grafana/dashboards-squad false false true
80 dashboardSceneSolo GA @grafana/dashboards-squad false false true
81 dashboardScene GA @grafana/dashboards-squad false false true
82 dashboardNewLayouts preview experimental @grafana/dashboards-squad false false false
83 dashboardUndoRedo experimental @grafana/dashboards-squad false false true
84 unlimitedLayoutsNesting experimental @grafana/dashboards-squad false false true
85 drilldownRecommendations experimental @grafana/dashboards-squad false false true
121 suggestedDashboards experimental @grafana/sharing-squad false false false
122 dashboardTemplates preview @grafana/sharing-squad false false false
123 alertingListViewV2 privatePreview @grafana/alerting-squad false false true
alertingNavigationV2 experimental @grafana/alerting-squad false false false
124 alertingSavedSearches experimental @grafana/alerting-squad false false true
125 alertingDisableSendAlertsExternal experimental @grafana/alerting-squad false false false
126 preserveDashboardStateWhenNavigating experimental @grafana/dashboards-squad false false false
280 smoothingTransformation experimental @grafana/datapro false false true
281 secretsManagementAppPlatformAwsKeeper experimental @grafana/grafana-operator-experience-squad false false false
282 profilesExemplars experimental @grafana/observability-traces-and-profiling false false false
alertingSyncDispatchTimer experimental @grafana/alerting-squad false true false
+1 -9
View File
@@ -260,7 +260,7 @@ const (
FlagAnnotationPermissionUpdate = "annotationPermissionUpdate"
// FlagDashboardNewLayouts
// Enables new dashboard layouts
// Enables experimental new dashboard layouts
FlagDashboardNewLayouts = "dashboardNewLayouts"
// FlagPdfTables
@@ -371,10 +371,6 @@ const (
// Enables a flow to get started with a new dashboard from a template
FlagDashboardTemplates = "dashboardTemplates"
// FlagAlertingNavigationV2
// Enables the new Alerting navigation structure with improved menu grouping
FlagAlertingNavigationV2 = "alertingNavigationV2"
// FlagAlertingDisableSendAlertsExternal
// Disables the ability to send alerts to an external Alertmanager datasource.
FlagAlertingDisableSendAlertsExternal = "alertingDisableSendAlertsExternal"
@@ -793,8 +789,4 @@ const (
// FlagProfilesExemplars
// Enables profiles exemplars support in profiles drilldown
FlagProfilesExemplars = "profilesExemplars"
// FlagAlertingSyncDispatchTimer
// Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods
FlagAlertingSyncDispatchTimer = "alertingSyncDispatchTimer"
)
+5 -35
View File
@@ -348,18 +348,6 @@
"expression": "true"
}
},
{
"metadata": {
"name": "alertingNavigationV2",
"resourceVersion": "1768320918269",
"creationTimestamp": "2026-01-13T16:15:18Z"
},
"spec": {
"description": "Enables the new Alerting navigation structure with improved menu grouping",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad"
}
},
{
"metadata": {
"name": "alertingNotificationHistory",
@@ -523,20 +511,6 @@
"frontend": true
}
},
{
"metadata": {
"name": "alertingSyncDispatchTimer",
"resourceVersion": "1766161788928",
"creationTimestamp": "2025-12-19T16:29:48Z"
},
"spec": {
"description": "Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad",
"requiresRestart": true,
"hideFromDocs": true
}
},
{
"metadata": {
"name": "alertingTriage",
@@ -688,8 +662,7 @@
"metadata": {
"name": "auditLoggingAppPlatform",
"resourceVersion": "1767013056996",
"creationTimestamp": "2025-12-29T12:57:36Z",
"deletionTimestamp": "2026-01-06T09:18:36Z"
"creationTimestamp": "2025-12-29T12:57:36Z"
},
"spec": {
"description": "Enable audit logging with Kubernetes under app platform",
@@ -1042,15 +1015,12 @@
{
"metadata": {
"name": "dashboardNewLayouts",
"resourceVersion": "1768382835527",
"creationTimestamp": "2024-10-23T08:55:45Z",
"annotations": {
"grafana.app/updatedTimestamp": "2026-01-14 09:27:15.527103 +0000 UTC"
}
"resourceVersion": "1764664939750",
"creationTimestamp": "2024-10-23T08:55:45Z"
},
"spec": {
"description": "Enables new dashboard layouts",
"stage": "preview",
"description": "Enables experimental new dashboard layouts",
"stage": "experimental",
"codeowner": "@grafana/dashboards-squad"
}
},
+2 -1
View File
@@ -54,7 +54,8 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
}
//nolint:staticcheck // not yet migrated to OpenFeature
if c.HasRole(identity.RoleAdmin) &&
s.features.IsEnabledGlobally(featuremgmt.FlagProvisioning) {
(s.cfg.StackID == "" || // show OnPrem even when provisioning is disabled
s.features.IsEnabledGlobally(featuremgmt.FlagProvisioning)) {
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
Text: "Provisioning",
Id: "provisioning",
-4
View File
@@ -213,9 +213,6 @@ func (ng *AlertNG) init() error {
SkipVerify: ng.Cfg.Smtp.SkipVerify,
StaticHeaders: ng.Cfg.Smtp.StaticHeaders,
}
runtimeConfig := remoteClient.RuntimeConfig{
DispatchTimer: notifier.GetDispatchTimer(ng.FeatureToggles).String(),
}
cfg := remote.AlertmanagerConfig{
BasicAuthPassword: ng.Cfg.UnifiedAlerting.RemoteAlertmanager.Password,
@@ -225,7 +222,6 @@ func (ng *AlertNG) init() error {
ExternalURL: ng.Cfg.AppURL,
SmtpConfig: smtpCfg,
Timeout: ng.Cfg.UnifiedAlerting.RemoteAlertmanager.Timeout,
RuntimeConfig: runtimeConfig,
}
autogenFn := func(ctx context.Context, logger log.Logger, orgID int64, cfg *definitions.PostableApiAlertingConfig, invalidReceiverAction notifier.InvalidReceiversAction) error {
return notifier.AddAutogenConfig(ctx, logger, ng.store, orgID, cfg, invalidReceiverAction, ng.FeatureToggles)
@@ -33,9 +33,6 @@ const (
// How long we keep silences in the kvstore after they've expired.
silenceRetention = 5 * 24 * time.Hour
// How long we keep flushes in the kvstore after they've expired.
flushRetention = 5 * 24 * time.Hour
)
type AlertingStore interface {
@@ -47,10 +44,8 @@ type AlertingStore interface {
type stateStore interface {
SaveSilences(ctx context.Context, st alertingNotify.State) (int64, error)
SaveNotificationLog(ctx context.Context, st alertingNotify.State) (int64, error)
SaveFlushLog(ctx context.Context, st alertingNotify.State) (int64, error)
GetSilences(ctx context.Context) (string, error)
GetNotificationLog(ctx context.Context) (string, error)
GetFlushLog(ctx context.Context) (string, error)
}
type alertmanager struct {
@@ -106,10 +101,6 @@ func NewAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store A
if err != nil {
return nil, err
}
flushLog, err := stateStore.GetFlushLog(ctx)
if err != nil {
return nil, err
}
silencesOptions := maintenanceOptions{
initialState: silences,
@@ -132,29 +123,12 @@ func NewAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store A
}
l := log.New("ngalert.notifier")
dispatchTimer := GetDispatchTimer(featureToggles)
var flushLogOptions *maintenanceOptions
if dispatchTimer == alertingNotify.DispatchTimerSync {
flushLogOptions = &maintenanceOptions{
initialState: flushLog,
retention: flushRetention,
maintenanceFrequency: maintenanceInterval,
maintenanceFunc: func(state alertingNotify.State) (int64, error) {
// Detached context here is to make sure that when the service is shut down the persist operation is executed.
return stateStore.SaveFlushLog(context.Background(), state)
},
}
}
opts := alertingNotify.GrafanaAlertmanagerOpts{
ExternalURL: cfg.AppURL,
AlertStoreCallback: nil,
PeerTimeout: cfg.UnifiedAlerting.HAPeerTimeout,
Silences: silencesOptions,
Nflog: nflogOptions,
FlushLog: flushLogOptions,
DispatchTimer: dispatchTimer,
Limits: alertingNotify.Limits{
MaxSilences: cfg.UnifiedAlerting.AlertmanagerMaxSilencesCount,
MaxSilenceSizeBytes: cfg.UnifiedAlerting.AlertmanagerMaxSilenceSizeBytes,
@@ -1,16 +0,0 @@
package notifier
import (
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
// GetDispatchTimer returns the appropriate dispatch timer based on feature toggles.
func GetDispatchTimer(features featuremgmt.FeatureToggles) (dt alertingNotify.DispatchTimer) {
//nolint:staticcheck // not yet migrated to OpenFeature
enabled := features.IsEnabledGlobally(featuremgmt.FlagAlertingSyncDispatchTimer)
if enabled {
dt = alertingNotify.DispatchTimerSync
}
return
}
@@ -1,36 +0,0 @@
package notifier
import (
"testing"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/stretchr/testify/require"
)
func TestGetDispatchTimer(t *testing.T) {
tests := []struct {
name string
featureFlagValue bool
expected alertingNotify.DispatchTimer
}{
{
name: "feature flag enabled returns sync timer",
featureFlagValue: true,
expected: alertingNotify.DispatchTimerSync,
},
{
name: "feature flag disabled returns default timer",
featureFlagValue: false,
expected: alertingNotify.DispatchTimerDefault,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagAlertingSyncDispatchTimer, tt.featureFlagValue)
result := GetDispatchTimer(features)
require.Equal(t, tt.expected, result)
})
}
}
@@ -15,7 +15,6 @@ const (
KVNamespace = "alertmanager"
NotificationLogFilename = "notifications"
SilencesFilename = "silences"
FlushLogFilename = "flushes"
)
// FileStore is in charge of persisting the alertmanager files to the database.
@@ -43,10 +42,6 @@ func (fileStore *FileStore) GetNotificationLog(ctx context.Context) (string, err
return fileStore.contentFor(ctx, NotificationLogFilename)
}
func (fileStore *FileStore) GetFlushLog(ctx context.Context) (string, error) {
return fileStore.contentFor(ctx, FlushLogFilename)
}
// contentFor returns the content for the given Alertmanager kvstore key.
func (fileStore *FileStore) contentFor(ctx context.Context, filename string) (string, error) {
// Then, let's attempt to read it from the database.
@@ -79,11 +74,6 @@ func (fileStore *FileStore) SaveNotificationLog(ctx context.Context, st alerting
return fileStore.persist(ctx, NotificationLogFilename, st)
}
// SaveFlushLog saves the flush log to the database and returns the size of the unencoded state.
func (fileStore *FileStore) SaveFlushLog(ctx context.Context, st alertingNotify.State) (int64, error) {
return fileStore.persist(ctx, FlushLogFilename, st)
}
// persist takes care of persisting the binary representation of internal state to the database as a base64 encoded string.
func (fileStore *FileStore) persist(ctx context.Context, filename string, st alertingNotify.State) (int64, error) {
var size int64
@@ -106,48 +106,3 @@ func TestFileStore_NotificationLog(t *testing.T) {
t.Errorf("Unexpected Diff: %v", cmp.Diff(newState, decoded))
}
}
func TestFileStore_FlushLog(t *testing.T) {
store := fakes.NewFakeKVStore(t)
ctx := context.Background()
var orgId int64 = 1
// Initialize kvstore with empty flush log state.
initialState := flushLogState{} // FlushLog uses the same structure as nflog
decodedState, err := initialState.MarshalBinary()
require.NoError(t, err)
encodedState := base64.StdEncoding.EncodeToString(decodedState)
err = store.Set(ctx, orgId, KVNamespace, FlushLogFilename, encodedState)
require.NoError(t, err)
fs := NewFileStore(orgId, store)
// Load initial (empty).
flushLog, err := fs.GetFlushLog(ctx)
require.NoError(t, err)
decoded, err := decodeFlushLogState(strings.NewReader(flushLog))
require.NoError(t, err)
if !cmp.Equal(initialState, decoded) {
t.Errorf("Unexpected Diff: %v", cmp.Diff(initialState, decoded))
}
// Save new flush log state.
now := time.Now()
oneHour := now.Add(time.Hour)
v1 := createFlushLog(1, now, oneHour)
v2 := createFlushLog(2, now, oneHour)
newState := flushLogState{1: v1, 2: v2}
size, err := fs.SaveFlushLog(ctx, newState)
require.NoError(t, err)
require.Greater(t, size, int64(0))
// Load new.
flushLog, err = fs.GetFlushLog(ctx)
require.NoError(t, err)
decoded, err = decodeFlushLogState(strings.NewReader(flushLog))
require.NoError(t, err)
if !cmp.Equal(newState, decoded) {
t.Errorf("Unexpected Diff: %v", cmp.Diff(newState, decoded))
}
}
@@ -82,7 +82,6 @@ type Alertmanager interface {
type ExternalState struct {
Silences []byte
Nflog []byte
FlushLog []byte
}
// StateMerger describes a type that is able to merge external state (nflog, silences) with its own.
@@ -379,7 +378,7 @@ func (moa *MultiOrgAlertmanager) SyncAlertmanagersForOrgs(ctx context.Context, o
func (moa *MultiOrgAlertmanager) cleanupOrphanLocalOrgState(ctx context.Context,
activeOrganizations map[int64]struct{},
) {
storedFiles := []string{NotificationLogFilename, SilencesFilename, FlushLogFilename}
storedFiles := []string{NotificationLogFilename, SilencesFilename}
for _, fileName := range storedFiles {
keys, err := moa.kvStore.Keys(ctx, kvstore.AllOrganizations, KVNamespace, fileName)
if err != nil {
+1 -4
View File
@@ -5,8 +5,5 @@ func (am *alertmanager) MergeState(state ExternalState) error {
if err := am.Base.MergeNflog(state.Nflog); err != nil {
return err
}
if err := am.Base.MergeSilences(state.Silences); err != nil {
return err
}
return am.Base.MergeFlushLog(state.FlushLog)
return am.Base.MergeSilences(state.Silences)
}
+4 -48
View File
@@ -11,7 +11,6 @@ import (
"time"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
"github.com/prometheus/alertmanager/flushlog/flushlogpb"
"github.com/prometheus/alertmanager/nflog/nflogpb"
"github.com/prometheus/alertmanager/silence/silencepb"
"github.com/prometheus/common/model"
@@ -229,13 +228,15 @@ func (f *FakeOrgStore) FetchOrgIds(_ context.Context) ([]int64, error) {
return f.orgs, nil
}
type NoValidation struct{}
type NoValidation struct {
}
func (n NoValidation) Validate(_ models.NotificationSettings) error {
return nil
}
type RejectingValidation struct{}
type RejectingValidation struct {
}
func (n RejectingValidation) Validate(s models.NotificationSettings) error {
return ErrorReceiverDoesNotExist{ErrorReferenceInvalid: ErrorReferenceInvalid{Reference: s.Receiver}}
@@ -364,51 +365,6 @@ func createNotificationLog(groupKey string, receiverName string, sentAt, expires
}
}
// https://github.com/grafana/prometheus-alertmanager/blob/main/flushlog/flushlog.go#L136-L136
type flushLogState map[uint64]*flushlogpb.MeshFlushLog
func (s flushLogState) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
for _, e := range s {
if _, err := pbutil.WriteDelimited(&buf, e); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
func createFlushLog(groupFingerprint uint64, ts, expiresAt time.Time) *flushlogpb.MeshFlushLog {
return &flushlogpb.MeshFlushLog{
FlushLog: &flushlogpb.FlushLog{
GroupFingerprint: groupFingerprint,
Timestamp: ts,
},
ExpiresAt: expiresAt,
}
}
// decodeFlushLogState copied from decodeState in prometheus-alertmanager/flushlog/flushlog.go
func decodeFlushLogState(r io.Reader) (flushLogState, error) {
st := flushLogState{}
for {
var e flushlogpb.MeshFlushLog
_, err := pbutil.ReadDelimited(r, &e)
if err == nil {
if e.FlushLog == nil || e.FlushLog.GroupFingerprint == 0 || e.FlushLog.Timestamp.IsZero() {
return nil, errInvalidState
}
st[e.FlushLog.GroupFingerprint] = &e
continue
}
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
return st, nil
}
type call struct {
Method string
Args []interface{}
+4 -20
View File
@@ -47,7 +47,6 @@ import (
type stateStore interface {
GetSilences(ctx context.Context) (string, error)
GetNotificationLog(ctx context.Context) (string, error)
GetFlushLog(ctx context.Context) (string, error)
}
// AutogenFn is a function that adds auto-generated routes to a configuration.
@@ -87,8 +86,6 @@ type Alertmanager struct {
promoteConfig bool
externalURL string
runtimeConfig remoteClient.RuntimeConfig
}
type AlertmanagerConfig struct {
@@ -114,9 +111,6 @@ type AlertmanagerConfig struct {
// Timeout for the HTTP client.
Timeout time.Duration
// RuntimeConfig specifies runtime behavior settings for the remote Alertmanager.
RuntimeConfig remoteClient.RuntimeConfig
}
func (cfg *AlertmanagerConfig) Validate() error {
@@ -209,7 +203,6 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto
externalURL: cfg.ExternalURL,
promoteConfig: cfg.PromoteConfig,
smtp: cfg.SmtpConfig,
runtimeConfig: cfg.RuntimeConfig,
}
// Parse the default configuration once and remember its hash so we can compare it later.
@@ -338,11 +331,10 @@ func (am *Alertmanager) buildConfiguration(ctx context.Context, raw []byte, crea
AlertmanagerConfig: mergeResult.Config,
Templates: templates,
},
CreatedAt: createdAtEpoch,
Promoted: am.promoteConfig,
ExternalURL: am.externalURL,
SmtpConfig: am.smtp,
RuntimeConfig: am.runtimeConfig,
CreatedAt: createdAtEpoch,
Promoted: am.promoteConfig,
ExternalURL: am.externalURL,
SmtpConfig: am.smtp,
}
cfgHash, err := calculateUserGrafanaConfigHash(payload)
@@ -396,8 +388,6 @@ func (am *Alertmanager) GetRemoteState(ctx context.Context) (notifier.ExternalSt
rs.Silences = p.Data
case "nfl":
rs.Nflog = p.Data
case "fls":
rs.FlushLog = p.Data
default:
return rs, fmt.Errorf("unknown part key %q", p.Key)
}
@@ -687,12 +677,6 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) {
}
parts = append(parts, alertingClusterPB.Part{Key: notifier.NotificationLogFilename, Data: []byte(notificationLog)})
flushLog, err := am.state.GetFlushLog(ctx)
if err != nil {
return "", fmt.Errorf("error getting flush log: %w", err)
}
parts = append(parts, alertingClusterPB.Part{Key: notifier.FlushLogFilename, Data: []byte(flushLog)})
fs := alertingClusterPB.FullState{
Parts: parts,
}
@@ -29,10 +29,6 @@ func (u *GrafanaAlertmanagerConfig) MarshalJSON() ([]byte, error) {
return definition.MarshalJSONWithSecrets((*cfg)(u))
}
type RuntimeConfig struct {
DispatchTimer string `json:"dispatch_timer"`
}
type UserGrafanaConfig struct {
GrafanaAlertmanagerConfig GrafanaAlertmanagerConfig `json:"configuration"`
Hash string `json:"configuration_hash"`
@@ -41,7 +37,6 @@ type UserGrafanaConfig struct {
Promoted bool `json:"promoted"`
ExternalURL string `json:"external_url"`
SmtpConfig SmtpConfig `json:"smtp_config"`
RuntimeConfig RuntimeConfig `json:"runtime_config"`
}
func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error) {
-1
View File
@@ -600,7 +600,6 @@ type Cfg struct {
IndexRebuildInterval time.Duration
IndexCacheTTL time.Duration
IndexMinUpdateInterval time.Duration // Don't update index if it was updated less than this interval ago.
IndexScoringModel string // Note: Temporary config to switch the index scoring model and will be removed soon.
MaxFileIndexAge time.Duration // Max age of file-based indexes. Index older than this will be rebuilt asynchronously.
MinFileIndexBuildVersion string // Minimum version of Grafana that built the file-based index. If index was built with older Grafana, it will be rebuilt asynchronously.
EnableSharding bool
-4
View File
@@ -123,10 +123,6 @@ func (cfg *Cfg) setUnifiedStorageConfig() {
cfg.IndexRebuildInterval = section.Key("index_rebuild_interval").MustDuration(24 * time.Hour)
cfg.IndexCacheTTL = section.Key("index_cache_ttl").MustDuration(10 * time.Minute)
cfg.IndexMinUpdateInterval = section.Key("index_min_update_interval").MustDuration(0)
cfg.IndexScoringModel = section.Key("index_scoring_model").MustString("")
if cfg.IndexScoringModel != "" {
cfg.Logger.Info("Index scoring model set", "model", cfg.IndexScoringModel)
}
cfg.SprinklesApiServer = section.Key("sprinkles_api_server").String()
cfg.SprinklesApiServerPageLimit = section.Key("sprinkles_api_server_page_limit").MustInt(10000)
cfg.CACertPath = section.Key("ca_cert_path").String()
-31
View File
@@ -9,13 +9,11 @@ import "resource.proto";
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
service ResourceIndex {
// Query for documents
rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse);
// Get the resource stats
rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse);
// Rebuild the search index
rpc RebuildIndexes(RebuildIndexesRequest) returns (RebuildIndexesResponse);
}
@@ -51,20 +49,6 @@ message ResourceStatsResponse {
repeated Stats stats = 2;
}
// This controls what query and analyzers are applied to the specified field
// See: https://blevesearch.com/docs/Analyzers/
enum QueryFieldType {
// Picks a reasonable analyzer given the input. Currently this always uses TEXT
// In the future, it may change to depend on the indexed field type
DEFAULT = 0;
// Use free text analyzer. The query is broken into a normalized set of tokens
TEXT = 1;
// The query must exactly match the indexed token
KEYWORD = 2;
// Like a text query, but the position and offsets influence the score
PHRASE = 3;
}
// Search within a single resource
message ResourceSearchRequest {
message Sort {
@@ -80,18 +64,6 @@ message ResourceSearchRequest {
// date queries
}
// Defines the field in the index to query
// Boost is optional, and allows weighting the field higher in the results
message QueryField {
// The field name in the index to query
string name = 1;
QueryFieldType type = 2;
// Boost value for this field
float boost = 3;
}
// The key must include namespace + group + resource
ListOptions options = 1;
@@ -127,9 +99,6 @@ message ResourceSearchRequest {
int64 page = 11;
int64 permission = 12;
// Optionally specify which fields are included in the query
repeated QueryField query_fields = 13;
}
message ResourceSearchResponse {
+1
View File
@@ -290,6 +290,7 @@ const SEARCH_FIELD_NAMESPACE = "namespace"
const SEARCH_FIELD_NAME = "name"
const SEARCH_FIELD_RV = "rv"
const SEARCH_FIELD_TITLE = "title"
const SEARCH_FIELD_TITLE_NGRAM = "title_ngram"
const SEARCH_FIELD_TITLE_PHRASE = "title_phrase" // filtering/sorting on title by full phrase
const SEARCH_FIELD_DESCRIPTION = "description"
const SEARCH_FIELD_TAGS = "tags"

Some files were not shown because too many files have changed in this diff Show More