Compare commits

..

10 Commits

Author SHA1 Message Date
Ryan McKinley 7e256253b8 remove unused function 2026-01-14 15:14:20 +03:00
Ryan McKinley ff9b3722ec Merge remote-tracking branch 'origin/main' into add-history-to-dashboard-api 2026-01-14 15:08:18 +03:00
Jack Westbrook 8bad33de4c Grafana/data: Fix theme types schema resolution (#116240)
* fix(grafana-data): copy theme schema json to types so declaration resolves

* refactor(grafana-data): move node scripts out of source code

* feat(grafana-data): generate types for theme schema

* chore(codeowners): update for grafana-data/scripts file move

* feat(grafana-data): put back copy plugin for theme json files

* revert(grafana-data): remove definition output

* feat(grafana-data): make builds great again

* minor tidy up

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2026-01-14 12:05:23 +00:00
Ryan McKinley 8506a89579 Merge remote-tracking branch 'origin/main' into add-history-to-dashboard-api 2025-12-02 17:22:50 +03:00
Ryan McKinley f34890327f private 2025-12-02 16:34:57 +03:00
Ryan McKinley cf696db273 duplicate the merge code 2025-12-02 16:33:55 +03:00
Ryan McKinley 8b4a040dc8 Merge remote-tracking branch 'origin/main' into add-history-to-dashboard-api 2025-12-02 16:29:14 +03:00
Ryan McKinley 18c587a460 wrapper 2025-12-02 15:00:14 +03:00
Ryan McKinley 36a7c10f67 add history 2025-12-02 13:00:37 +03:00
Ryan McKinley de1405b1e3 add history 2025-12-02 12:55:16 +03:00
29 changed files with 229 additions and 426 deletions
+1
View File
@@ -543,6 +543,7 @@ 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
+10 -1
View File
@@ -35,6 +35,14 @@
},
"./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": {
@@ -52,7 +60,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 ./src/themes/scripts/generateSchema.ts"
"themes-schema": "tsx ./scripts/generateSchema.ts"
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",
@@ -102,6 +110,7 @@
"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",
+21 -2
View File
@@ -1,21 +1,40 @@
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: [...plugins, json()],
plugins: grafanaDataPlugins,
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
{
input: 'src/unstable.ts',
plugins: [...plugins, json()],
plugins: grafanaDataPlugins,
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
@@ -0,0 +1,22 @@
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');
@@ -93,7 +93,6 @@ 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';
+27 -1
View File
@@ -1,7 +1,18 @@
import { Registry, RegistryItem } from '../utils/Registry';
import { createTheme, NewThemeOptionsSchema } from './createTheme';
import * as extraThemes from './themeDefinitions';
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 { GrafanaTheme2 } from './types';
export interface ThemeRegistryItem extends RegistryItem {
@@ -9,6 +20,21 @@ 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
@@ -1,19 +0,0 @@
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
)
);
@@ -1,12 +0,0 @@
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';
+1 -1
View File
@@ -9,4 +9,4 @@
* and be subject to the standard policies
*/
export { default as themeJsonSchema } from './themes/schema.generated.json';
export {};
+2 -1
View File
@@ -8,7 +8,8 @@
"emitDeclarationOnly": true,
"isolatedModules": true,
"rootDirs": ["."],
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"resolveJsonModule": true
},
"exclude": ["dist/**/*"],
"include": [
-19
View File
@@ -8,7 +8,6 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
iamauthorizer "github.com/grafana/grafana/pkg/registry/apis/iam/authorizer"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
@@ -41,22 +40,6 @@ func newIAMAuthorizer(
return authorizer.DecisionAllow, "", nil
})
serviceIdentityAuthorizer := authorizer.AuthorizerFunc(func(
ctx context.Context, attr authorizer.Attributes,
) (authorized authorizer.Decision, reason string, err error) {
if identity.IsServiceIdentity(ctx) {
// A Grafana sub-system should have full access. We trust them to make wise decisions.
return authorizer.DecisionAllow, "", nil
}
req, err := identity.GetRequester(ctx)
if err == nil && req != nil && req.GetIsGrafanaAdmin() {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "", nil
})
// Identity specific resources
legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient)
resourceAuthorizer["display"] = legacyAuthorizer
@@ -74,8 +57,6 @@ func newIAMAuthorizer(
resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = allowAuthorizer
resourceAuthorizer["searchUsers"] = serviceAuthorizer
resourceAuthorizer["searchTeams"] = serviceAuthorizer
// TODO: Implement fine-grained authorization for external group mapping search on the search level
resourceAuthorizer["searchExternalGroupMappings"] = serviceIdentityAuthorizer
return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer}
}
@@ -97,8 +97,8 @@ func (r *TeamBindingAuthorizer) beforeWrite(ctx context.Context, obj runtime.Obj
teamName := concreteObj.Spec.TeamRef.Name
checkReq := types.CheckRequest{
Namespace: authInfo.GetNamespace(),
Group: iamv0.TeamResourceInfo.GroupResource().Group,
Resource: iamv0.TeamResourceInfo.GroupResource().Resource,
Group: iamv0.GROUP,
Resource: iamv0.TeamResourceInfo.GetName(),
Verb: utils.VerbSetPermissions,
Name: teamName,
}
@@ -1,10 +1,6 @@
package externalgroupmapping
import (
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-openapi/pkg/common"
)
import "k8s.io/apiserver/pkg/registry/rest"
type TeamGroupsHandler interface {
rest.Storage
@@ -12,7 +8,3 @@ type TeamGroupsHandler interface {
rest.StorageMetadata
rest.Connecter
}
type SearchHandler interface {
GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes
}
@@ -1,167 +0,0 @@
package externalgroupmapping
import (
"fmt"
"net/http"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/util/errhttp"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
var _ SearchHandler = (*NoopSearchREST)(nil)
type NoopSearchREST struct{}
func ProvideNoopSearchREST() *NoopSearchREST {
return &NoopSearchREST{}
}
func (n *NoopSearchREST) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes {
searchResults := defs["github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList"].Schema
return &builder.APIRoutes{
Namespace: []builder.APIRouteHandler{
{
Path: "searchExternalGroupMappings",
Spec: &spec3.PathProps{
Post: &spec3.Operation{
OperationProps: spec3.OperationProps{
Description: "External Group Mapping search",
Tags: []string{"Search"},
OperationId: "searchExternalGroupMappings",
RequestBody: &spec3.RequestBody{
RequestBodyProps: spec3.RequestBodyProps{
Content: map[string]*spec3.MediaType{
"application/json": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"externalGroups": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
},
},
},
},
},
},
},
},
Parameters: []*spec3.Parameter{
{
ParameterProps: spec3.ParameterProps{
Name: "namespace",
In: "path",
Required: true,
Example: "default",
Description: "workspace",
Schema: spec.StringProperty(),
},
},
{
ParameterProps: spec3.ParameterProps{
Name: "teamName",
In: "query",
Required: false,
Description: "Team name",
Schema: spec.StringProperty(),
},
},
{
ParameterProps: spec3.ParameterProps{
Name: "limit",
In: "query",
Description: "number of results to return",
Example: 30,
Required: false,
Schema: spec.Int64Property(),
},
},
{
ParameterProps: spec3.ParameterProps{
Name: "page",
In: "query",
Description: "page number (starting from 1)",
Example: 1,
Required: false,
Schema: spec.Int64Property(),
},
},
{
ParameterProps: spec3.ParameterProps{
Name: "offset",
In: "query",
Description: "number of results to skip",
Example: 0,
Required: false,
Schema: spec.Int64Property(),
},
},
{
ParameterProps: spec3.ParameterProps{
Name: "sort",
In: "query",
Description: "sortable field",
Example: "",
Examples: map[string]*spec3.Example{
"externalGroup": {
ExampleProps: spec3.ExampleProps{
Summary: "externalGroup ascending",
Value: "externalGroup",
},
},
"-externalGroup": {
ExampleProps: spec3.ExampleProps{
Summary: "externalGroup descending",
Value: "-externalGroup",
},
},
},
Required: false,
Schema: spec.StringProperty(),
},
},
},
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
Default: &spec3.Response{
ResponseProps: spec3.ResponseProps{
Description: "Default OK response",
Content: map[string]*spec3.MediaType{
"application/json": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &searchResults,
},
},
},
},
},
},
},
},
},
},
Handler: n.doSearch,
},
},
}
}
func (n *NoopSearchREST) doSearch(w http.ResponseWriter, r *http.Request) {
errhttp.Write(r.Context(), errors.NewForbidden(iamv0.ExternalGroupMappingResourceInfo.GroupResource(), "", fmt.Errorf("functionality not available")), w)
}
+5 -6
View File
@@ -82,12 +82,11 @@ type IdentityAccessManagementAPIBuilder struct {
reg prometheus.Registerer
logger log.Logger
dual dualwrite.Service
unified resource.ResourceClient
userSearchClient resourcepb.ResourceIndexClient
userSearchHandler *user.SearchHandler
teamSearch *TeamSearchHandler
externalGroupMappingSearchHandler externalgroupmapping.SearchHandler
dual dualwrite.Service
unified resource.ResourceClient
userSearchClient resourcepb.ResourceIndexClient
userSearchHandler *user.SearchHandler
teamSearch *TeamSearchHandler
teamGroupsHandler externalgroupmapping.TeamGroupsHandler
+26 -32
View File
@@ -79,7 +79,6 @@ func RegisterAPIService(
roleBindingsStorage RoleBindingStorageBackend,
externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend,
teamGroupsHandlerImpl externalgroupmapping.TeamGroupsHandler,
externalGroupMappingSearchHandler externalgroupmapping.SearchHandler,
dual dualwrite.Service,
unified resource.ResourceClient,
orgService org.Service,
@@ -102,32 +101,31 @@ func RegisterAPIService(
)
builder := &IdentityAccessManagementAPIBuilder{
store: store,
userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
legacyTeamStore: team.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing),
ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing),
coreRolesStorage: coreRolesStorage,
roleApiInstaller: roleApiInstaller,
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
roleBindingsStorage: roleBindingsStorage,
externalGroupMappingStorage: externalGroupMappingStorageBackend,
teamGroupsHandler: teamGroupsHandlerImpl,
externalGroupMappingSearchHandler: externalGroupMappingSearchHandler,
sso: ssoService,
resourceParentProvider: resourceParentProvider,
authorizer: authorizer,
legacyAccessClient: legacyAccessClient,
accessClient: accessClient,
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
display: user.NewLegacyDisplayREST(store),
reg: reg,
logger: log.New("iam.apis"),
features: features,
dual: dual,
unified: unified,
store: store,
userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
legacyTeamStore: team.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing),
teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing),
ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing),
coreRolesStorage: coreRolesStorage,
roleApiInstaller: roleApiInstaller,
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
roleBindingsStorage: roleBindingsStorage,
externalGroupMappingStorage: externalGroupMappingStorageBackend,
teamGroupsHandler: teamGroupsHandlerImpl,
sso: ssoService,
resourceParentProvider: resourceParentProvider,
authorizer: authorizer,
legacyAccessClient: legacyAccessClient,
accessClient: accessClient,
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
display: user.NewLegacyDisplayREST(store),
reg: reg,
logger: log.New("iam.apis"),
features: features,
dual: dual,
unified: unified,
userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(),
unified, user.NewUserLegacySearchClient(orgService, tracing, cfg), features),
teamSearch: NewTeamSearchHandler(tracing, dual, team.NewLegacyTeamSearchClient(teamService), unified, features),
@@ -627,7 +625,7 @@ func (b *IdentityAccessManagementAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenA
func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
searchRoutes := make([]*builder.APIRoutes, 0, 3)
searchRoutes := make([]*builder.APIRoutes, 0, 2)
if b.userSearchHandler != nil {
searchRoutes = append(searchRoutes, b.userSearchHandler.GetAPIRoutes(defs))
}
@@ -636,10 +634,6 @@ func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion
searchRoutes = append(searchRoutes, b.teamSearch.GetAPIRoutes(defs))
}
if b.externalGroupMappingSearchHandler != nil {
searchRoutes = append(searchRoutes, b.externalGroupMappingSearchHandler.GetAPIRoutes(defs))
}
routes := []*builder.APIRoutes{b.display.GetAPIRoutes(defs)}
routes = append(routes, searchRoutes...)
return mergeAPIRoutes(routes...)
-3
View File
@@ -35,9 +35,6 @@ var WireSetExts = wire.NewSet(
externalgroupmapping.ProvideNoopTeamGroupsREST,
wire.Bind(new(externalgroupmapping.TeamGroupsHandler), new(*externalgroupmapping.NoopTeamGroupsREST)),
externalgroupmapping.ProvideNoopSearchREST,
wire.Bind(new(externalgroupmapping.SearchHandler), new(*externalgroupmapping.NoopSearchREST)),
// Auditing Options
auditing.ProvideNoopBackend,
auditing.ProvideNoopPolicyRuleProvider,
+2 -4
View File
@@ -883,8 +883,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
storageBackendImpl := noopstorage.ProvideStorageBackend()
roleApiInstaller := iam.ProvideNoopRoleApiInstaller()
noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST()
noopSearchREST := externalgroupmapping.ProvideNoopSearchREST()
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, noopSearchREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
if err != nil {
return nil, err
}
@@ -1552,8 +1551,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
storageBackendImpl := noopstorage.ProvideStorageBackend()
roleApiInstaller := iam.ProvideNoopRoleApiInstaller()
noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST()
noopSearchREST := externalgroupmapping.ProvideNoopSearchREST()
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, noopSearchREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
if err != nil {
return nil, err
}
@@ -912,139 +912,6 @@
}
]
},
"/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/searchExternalGroupMappings": {
"post": {
"tags": [
"Search"
],
"description": "External Group Mapping search",
"operationId": "searchExternalGroupMappings",
"parameters": [
{
"name": "namespace",
"in": "path",
"description": "workspace",
"required": true,
"schema": {
"type": "string"
},
"example": "default"
},
{
"name": "teamName",
"in": "query",
"description": "Team name",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "number of results to return",
"schema": {
"type": "integer",
"format": "int64"
},
"example": 30
},
{
"name": "page",
"in": "query",
"description": "page number (starting from 1)",
"schema": {
"type": "integer",
"format": "int64"
},
"example": 1
},
{
"name": "offset",
"in": "query",
"description": "number of results to skip",
"schema": {
"type": "integer",
"format": "int64"
},
"example": 0
},
{
"name": "sort",
"in": "query",
"description": "sortable field",
"schema": {
"type": "string"
},
"examples": {
"": {
"summary": "default sorting",
"value": "externalGroup"
},
"-externalGroup": {
"summary": "externalGroup descending",
"value": "-externalGroup"
},
"externalGroup": {
"summary": "externalGroup ascending",
"value": "externalGroup"
}
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"externalGroups": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"responses": {
"default": {
"description": "Default OK response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"metadata",
"items"
],
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"items": {
"type": "array",
"items": {
"default": {}
}
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"default": {}
}
}
}
}
}
}
}
}
},
"/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/searchTeams": {
"get": {
"tags": [
@@ -19,6 +19,7 @@ export interface RevisionsModel {
data: Dashboard;
}
// TODO: this should be removed entirely
export class HistorySrv {
getHistoryList(dashboardUID: string, options: HistoryListOpts) {
if (typeof dashboardUID !== 'string') {
@@ -35,16 +36,6 @@ export class HistorySrv {
return getBackendSrv().get(`api/dashboards/uid/${dashboardUID}/versions/${version}`);
}
restoreDashboard(dashboardUID: string, version: number) {
if (typeof dashboardUID !== 'string') {
return Promise.resolve({});
}
const url = `api/dashboards/uid/${dashboardUID}/restore`;
return getBackendSrv().post(url, { version });
}
}
const historySrv = new HistorySrv();
@@ -56,6 +56,24 @@ export class UnifiedDashboardAPI
return await this.v1Client.deleteDashboard(uid, showSuccessAlert);
}
async listDashboardHistory(uid: string) {
const v1Response = await this.v1Client.listDashboardHistory(uid);
const filteredV1Items = v1Response.items.filter((item) => !failedFromVersion(item, ['v2']));
if (filteredV1Items.length === v1Response.items.length) {
return v1Response;
}
const v2Response = await this.v2Client.listDashboardHistory(uid);
const filteredV2Items = v2Response.items.filter((item) => !failedFromVersion(item, ['v0', 'v1']));
return {
...v2Response,
// Make sure we display only valid resources
items: [...filteredV1Items, ...filteredV2Items].filter(isResource),
};
}
/**
* List deleted dashboards handling mixed v1/v2 versions or pure v2 dashboards.
*
@@ -12,6 +12,22 @@ import { SaveDashboardCommand } from '../components/SaveDashboard/types';
import { DashboardAPI, ListDeletedDashboardsOptions } from './types';
interface HistoryResult {
continueToken?: string;
versions: RevisionsModel[];
}
interface RevisionsModel {
id: number;
checked: boolean;
uid: string;
parentVersion: number;
version: number;
created: Date;
createdBy: string;
message: string;
data: Dashboard;
}
export class LegacyDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
constructor() {}
@@ -53,6 +69,30 @@ export class LegacyDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard>
return result;
}
async listDashboardHistory(uid: string): Promise<ResourceList<Dashboard, Dashboard, string>> {
const result = await getBackendSrv().get<HistoryResult>(`/api/dashboards/uid/${uid}/versions`);
return {
apiVersion: 'v0alpha1',
kind: 'DashboardList',
metadata: { resourceVersion: '0' },
items: result.versions.map((v) => ({
apiVersion: 'v0alpha1',
kind: 'Dashboard',
metadata: {
name: v.uid,
resourceVersion: v.version.toString(),
generation: v.version,
creationTimestamp: v.created ? v.created.toISOString() : new Date().toISOString(),
annotations: {
'grafana.app/updatedBy': v.createdBy,
'grafana.app/message': v.message,
},
},
spec: v.data,
})),
};
}
/**
* No-op for legacy API
*/
@@ -15,6 +15,8 @@ export interface DashboardAPI<G, T> {
saveDashboard(options: SaveDashboardCommand<T>): Promise<SaveDashboardResponseDTO>;
/** Delete a dashboard */
deleteDashboard(uid: string, showSuccessAlert: boolean): Promise<DeleteDashboardResponse>;
/** List all versions of a dashboard */
listDashboardHistory(uid: string): Promise<ResourceList<T>>;
/** List all deleted dashboards */
listDeletedDashboards(options: ListDeletedDashboardsOptions): Promise<ResourceList<T>>;
/** Restore a deleted dashboard by re-creating it */
+7
View File
@@ -217,6 +217,13 @@ export class K8sDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
}
}
async listDashboardHistory(uid: string) {
return await this.client.list({
labelSelector: 'grafana.app/get-history=true',
fieldSelector: `metadata.name=${uid}`,
});
}
async listDeletedDashboards(options: ListDeletedDashboardsOptions) {
return await this.client.list({ ...options, labelSelector: 'grafana.app/get-trash=true' });
}
+7
View File
@@ -175,6 +175,13 @@ export class K8sDashboardV2API
};
}
async listDashboardHistory(uid: string) {
return await this.client.list({
labelSelector: 'grafana.app/get-history=true',
fieldSelector: `metadata.name=${uid}`,
});
}
listDeletedDashboards(options: ListDeletedDashboardsOptions) {
return this.client.list({ ...options, labelSelector: 'grafana.app/get-trash=true' });
}
@@ -49,6 +49,7 @@ describe('validateUid', () => {
saveDashboard: jest.fn(),
listDeletedDashboards: jest.fn(),
restoreDashboard: jest.fn(),
listDashboardHistory: jest.fn(),
},
v2: {
getDashboardDTO: jest.fn().mockResolvedValue(v2Dashboard),
@@ -56,6 +57,7 @@ describe('validateUid', () => {
saveDashboard: jest.fn(),
listDeletedDashboards: jest.fn(),
restoreDashboard: jest.fn(),
listDashboardHistory: jest.fn(),
},
});
});
@@ -2,8 +2,20 @@ import { css } from '@emotion/css';
import { useId, useState } from 'react';
import { createTheme, GrafanaTheme2, NewThemeOptions } from '@grafana/data';
import { experimentalThemeDefinitions, NewThemeOptionsSchema } from '@grafana/data/internal';
import { themeJsonSchema } from '@grafana/data/unstable';
import { NewThemeOptionsSchema } from '@grafana/data/internal';
import aubergine from '@grafana/data/themes/definitions/aubergine.json';
import debug from '@grafana/data/themes/definitions/debug.json';
import desertbloom from '@grafana/data/themes/definitions/desertbloom.json';
import gildedgrove from '@grafana/data/themes/definitions/gildedgrove.json';
import gloom from '@grafana/data/themes/definitions/gloom.json';
import mars from '@grafana/data/themes/definitions/mars.json';
import matrix from '@grafana/data/themes/definitions/matrix.json';
import sapphiredusk from '@grafana/data/themes/definitions/sapphiredusk.json';
import synthwave from '@grafana/data/themes/definitions/synthwave.json';
import tron from '@grafana/data/themes/definitions/tron.json';
import victorian from '@grafana/data/themes/definitions/victorian.json';
import zen from '@grafana/data/themes/definitions/zen.json';
import themeJsonSchema from '@grafana/data/themes/schema.generated.json';
import { t } from '@grafana/i18n';
import { useChromeHeaderHeight } from '@grafana/runtime';
import { CodeEditor, Combobox, Field, Stack, useStyles2 } from '@grafana/ui';
@@ -34,8 +46,23 @@ const themeMap: Record<string, NewThemeOptions> = {
},
};
const experimentalDefinitions: Record<string, unknown> = {
aubergine,
debug,
desertbloom,
gildedgrove,
gloom,
mars,
matrix,
sapphiredusk,
synthwave,
tron,
victorian,
zen,
};
// Add additional themes
for (const [name, json] of Object.entries(experimentalThemeDefinitions)) {
for (const [name, json] of Object.entries(experimentalDefinitions)) {
const result = NewThemeOptionsSchema.safeParse(json);
if (!result.success) {
console.error(`Invalid theme definition for theme ${name}: ${result.error.message}`);
+1
View File
@@ -11,6 +11,7 @@ failed_checks=()
for file in "$ARTIFACTS_DIR"/*.tgz; do
echo "🔍 Checking NPM package: $file"
# If you need to debug ATTW issues, pass "--format json" to get verbose output.
if ! NODE_OPTIONS="-C @grafana-app/source" yarn attw "$file" --ignore-rules "false-cjs" --profile "node16"; then
echo "attw check failed for $file"
echo ""
+1
View File
@@ -3324,6 +3324,7 @@ __metadata:
react-use: "npm:17.6.0"
rimraf: "npm:6.0.1"
rollup: "npm:^4.22.4"
rollup-plugin-copy: "npm:3.5.0"
rollup-plugin-esbuild: "npm:6.2.1"
rollup-plugin-node-externals: "npm:^8.0.0"
rxjs: "npm:7.8.2"