Dashboards: add isPublic to dto and remove public endpoint call (#113334)

---------

Co-authored-by: Matheus Macabu <macabu.matheus@gmail.com>
This commit is contained in:
Stephanie Hingtgen
2025-11-04 16:57:05 -06:00
committed by GitHub
co-authored by Matheus Macabu
parent 867e8bb98f
commit 4cecab3185
26 changed files with 120 additions and 34 deletions
+3 -2
View File
@@ -3,8 +3,9 @@ package dashboard
// Information about how the requesting user can use a given dashboard
type DashboardAccess struct {
// Metadata fields
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
IsPublic bool `json:"isPublic"`
// The permissions part
CanSave bool `json:"canSave"`
@@ -12,8 +12,9 @@ type DashboardWithAccessInfo struct {
// +k8s:deepcopy-gen=true
type DashboardAccess struct {
// Metadata fields
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
IsPublic bool `json:"isPublic"`
// The permissions part
CanSave bool `json:"canSave"`
@@ -112,6 +112,7 @@ func Convert_dashboard_AnnotationPermission_To_v0alpha1_AnnotationPermission(in
func autoConvert_v0alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -129,6 +130,7 @@ func Convert_v0alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *Dashboard
func autoConvert_dashboard_DashboardAccess_To_v0alpha1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -170,6 +170,13 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardAccess(ref common.ReferenceCall
Format: "",
},
},
"isPublic": {
SchemaProps: spec.SchemaProps{
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"canSave": {
SchemaProps: spec.SchemaProps{
Description: "The permissions part",
@@ -212,7 +219,7 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardAccess(ref common.ReferenceCall
},
},
},
Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
},
},
Dependencies: []string{
@@ -123,8 +123,9 @@ type DashboardWithAccessInfo struct {
// +k8s:deepcopy-gen=true
type DashboardAccess struct {
// Metadata fields
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
IsPublic bool `json:"isPublic"`
// The permissions part
CanSave bool `json:"canSave"`
@@ -118,6 +118,7 @@ func Convert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission(in *
func autoConvert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -135,6 +136,7 @@ func Convert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardA
func autoConvert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -165,6 +165,13 @@ func schema_pkg_apis_dashboard_v1beta1_DashboardAccess(ref common.ReferenceCallb
Format: "",
},
},
"isPublic": {
SchemaProps: spec.SchemaProps{
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"canSave": {
SchemaProps: spec.SchemaProps{
Description: "The permissions part",
@@ -207,7 +214,7 @@ func schema_pkg_apis_dashboard_v1beta1_DashboardAccess(ref common.ReferenceCallb
},
},
},
Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
},
},
Dependencies: []string{
@@ -123,8 +123,9 @@ type DashboardWithAccessInfo struct {
// +k8s:deepcopy-gen=true
type DashboardAccess struct {
// Metadata fields
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
IsPublic bool `json:"isPublic"`
// The permissions part
CanSave bool `json:"canSave"`
@@ -118,6 +118,7 @@ func Convert_dashboard_AnnotationPermission_To_v2alpha1_AnnotationPermission(in
func autoConvert_v2alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -135,6 +136,7 @@ func Convert_v2alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *Dashboard
func autoConvert_dashboard_DashboardAccess_To_v2alpha1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -265,6 +265,13 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAccess(ref common.ReferenceCall
Format: "",
},
},
"isPublic": {
SchemaProps: spec.SchemaProps{
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"canSave": {
SchemaProps: spec.SchemaProps{
Description: "The permissions part",
@@ -307,7 +314,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAccess(ref common.ReferenceCall
},
},
},
Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
},
},
Dependencies: []string{
@@ -123,8 +123,9 @@ type DashboardWithAccessInfo struct {
// +k8s:deepcopy-gen=true
type DashboardAccess struct {
// Metadata fields
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
Slug string `json:"slug,omitempty"`
Url string `json:"url,omitempty"`
IsPublic bool `json:"isPublic"`
// The permissions part
CanSave bool `json:"canSave"`
@@ -118,6 +118,7 @@ func Convert_dashboard_AnnotationPermission_To_v2beta1_AnnotationPermission(in *
func autoConvert_v2beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -135,6 +136,7 @@ func Convert_v2beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardA
func autoConvert_dashboard_DashboardAccess_To_v2beta1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error {
out.Slug = in.Slug
out.Url = in.Url
out.IsPublic = in.IsPublic
out.CanSave = in.CanSave
out.CanEdit = in.CanEdit
out.CanAdmin = in.CanAdmin
@@ -269,6 +269,13 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardAccess(ref common.ReferenceCallb
Format: "",
},
},
"isPublic": {
SchemaProps: spec.SchemaProps{
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"canSave": {
SchemaProps: spec.SchemaProps{
Description: "The permissions part",
@@ -311,7 +318,7 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardAccess(ref common.ReferenceCallb
},
},
},
Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"},
},
},
Dependencies: []string{
@@ -845,6 +845,7 @@ export type DashboardAccess = {
/** The permissions part */
canSave: boolean;
canStar: boolean;
isPublic: boolean;
/** Metadata fields */
slug?: string;
url?: string;
@@ -27,6 +27,7 @@ const dashboardToAppPlatform = (dashboard: (typeof mockTree)[number]['item']) =>
},
status: {},
// TODO: Eventually add access properties, as required by tests
access: {},
};
};
+5
View File
@@ -53,6 +53,7 @@ import (
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/user"
@@ -112,6 +113,7 @@ type DashboardsAPIBuilder struct {
dualWriter dualwrite.Service
folderClientProvider client.K8sHandlerProvider
libraryPanels libraryelements.Service // for legacy library panels
publicDashboardService publicdashboards.Service
isStandalone bool // skips any handling including anything to do with legacy storage
}
@@ -140,6 +142,7 @@ func RegisterAPIService(
restConfigProvider apiserver.RestConfigProvider,
userService user.Service,
libraryPanels libraryelements.Service,
publicDashboardService publicdashboards.Service,
) *DashboardsAPIBuilder {
dbp := legacysql.NewDatabaseProvider(sql)
namespacer := request.GetNamespaceMapper(cfg)
@@ -163,6 +166,7 @@ func RegisterAPIService(
dualWriter: dual,
folderClientProvider: newSimpleFolderClientProvider(folderClient),
libraryPanels: libraryPanels,
publicDashboardService: publicDashboardService,
legacy: &DashboardStorage{
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter, dashboardPermissionsSvc, accessControl, features),
@@ -652,6 +656,7 @@ func (b *DashboardsAPIBuilder) storageForVersion(
b.accessControl,
opts.Scheme,
newDTOFunc,
b.publicDashboardService,
)
if err != nil {
return err
+23 -14
View File
@@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
@@ -28,13 +29,14 @@ type dtoBuilder = func(dashboard runtime.Object, access *dashboard.DashboardAcce
// The DTO returns everything the UI needs in a single request
type DTOConnector struct {
getter rest.Getter
legacy legacy.DashboardAccess
unified resource.ResourceClient
largeObjects apistore.LargeObjectSupport
accessControl accesscontrol.AccessControl
scheme *runtime.Scheme
builder dtoBuilder
getter rest.Getter
legacy legacy.DashboardAccess
unified resource.ResourceClient
largeObjects apistore.LargeObjectSupport
accessControl accesscontrol.AccessControl
scheme *runtime.Scheme
builder dtoBuilder
publicDashboardService publicdashboards.Service
}
func NewDTOConnector(
@@ -45,15 +47,17 @@ func NewDTOConnector(
accessControl accesscontrol.AccessControl,
scheme *runtime.Scheme,
builder dtoBuilder,
publicDashboardService publicdashboards.Service,
) (rest.Storage, error) {
return &DTOConnector{
getter: getter,
legacy: legacyAccess,
accessControl: accessControl,
unified: resourceClient,
largeObjects: largeObjects,
builder: builder,
scheme: scheme,
getter: getter,
legacy: legacyAccess,
accessControl: accessControl,
unified: resourceClient,
largeObjects: largeObjects,
builder: builder,
scheme: scheme,
publicDashboardService: publicDashboardService,
}, nil
}
@@ -154,6 +158,11 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob
access.Slug = slugify.Slugify(title)
access.Url = dashboards.GetDashboardFolderURL(false, name, access.Slug)
pubDash, err := r.publicDashboardService.FindByDashboardUid(ctx, user.GetOrgID(), name)
if err == nil && pubDash != nil {
access.IsPublic = true
}
dash, err := r.builder(rawobj, access)
if err != nil {
responder.Error(err)
+2 -2
View File
@@ -840,7 +840,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService)
ldapImpl := service12.ProvideService(cfg, featureToggles, ssosettingsimplService)
apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService, publicDashboardServiceImpl)
snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer)
dataSourceAPIBuilder, err := datasource.RegisterAPIService(configProvider, featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, accessControl, registerer)
if err != nil {
@@ -1474,7 +1474,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService)
ldapImpl := service12.ProvideService(cfg, featureToggles, ssosettingsimplService)
apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService, publicDashboardServiceImpl)
snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer)
dataSourceAPIBuilder, err := datasource.RegisterAPIService(configProvider, featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, accessControl, registerer)
if err != nil {
@@ -2324,6 +2324,7 @@
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v0alpha1.DashboardAccess": {
"type": "object",
"required": [
"isPublic",
"canSave",
"canEdit",
"canAdmin",
@@ -2356,6 +2357,10 @@
"type": "boolean",
"default": false
},
"isPublic": {
"type": "boolean",
"default": false
},
"slug": {
"description": "Metadata fields",
"type": "string"
@@ -1018,6 +1018,7 @@
"description": "Information about how the requesting user can use a given dashboard",
"type": "object",
"required": [
"isPublic",
"canSave",
"canEdit",
"canAdmin",
@@ -1050,6 +1051,10 @@
"type": "boolean",
"default": false
},
"isPublic": {
"type": "boolean",
"default": false
},
"slug": {
"description": "Metadata fields",
"type": "string"
@@ -1019,6 +1019,7 @@
"description": "Information about how the requesting user can use a given dashboard",
"type": "object",
"required": [
"isPublic",
"canSave",
"canEdit",
"canAdmin",
@@ -1051,6 +1052,10 @@
"type": "boolean",
"default": false
},
"isPublic": {
"type": "boolean",
"default": false
},
"slug": {
"description": "Metadata fields",
"type": "string"
@@ -13,17 +13,26 @@ export const PublicDashboardBadge = ({ dashboard }: ToolbarActionProps) => {
return null;
}
return <PublicDashboardBadgeInternal uid={dashboard.state.uid} />;
return (
<PublicDashboardBadgeInternal
uid={dashboard.state.uid}
hasPublicDashboard={dashboard.state.meta.publicDashboardEnabled}
/>
);
};
// Used in old architecture
export const PublicDashboardBadgeLegacy = PublicDashboardBadgeInternal;
function PublicDashboardBadgeInternal({ uid }: { uid: string }) {
const { data: publicDashboard } = useGetPublicDashboardQuery(uid);
function PublicDashboardBadgeInternal({ uid, hasPublicDashboard }: { uid: string; hasPublicDashboard?: boolean }) {
const { data: publicDashboard } = useGetPublicDashboardQuery(uid, {
skip: hasPublicDashboard !== undefined && !hasPublicDashboard,
});
const styles = useStyles2(getStyles);
if (!publicDashboard) {
const showBadge = hasPublicDashboard !== undefined ? hasPublicDashboard : !!publicDashboard;
if (!showBadge) {
return null;
}
@@ -148,6 +148,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
folderUid: metadata.annotations?.[AnnoKeyFolder],
isSnapshot: Boolean(metadata.annotations?.[AnnoKeyDashboardIsSnapshot]),
isEmbedded: Boolean(metadata.annotations?.[AnnoKeyEmbedded]),
publicDashboardEnabled: dto.access.isPublic,
// UI-only metadata, ref: DashboardModel.initMeta
showSettings: Boolean(dto.access.canEdit),
@@ -232,6 +232,7 @@ export function ensureV1Response(
uid: dashboard.metadata.name,
k8s: dashboard.metadata,
version: dashboard.metadata.generation,
publicDashboardEnabled: dashboard.access.isPublic,
},
dashboard: spec,
};
@@ -253,6 +254,7 @@ export function ensureV1Response(
canShare: dashboard.access.canShare,
canStar: dashboard.access.canStar,
annotationsPermissions: dashboard.access.annotationsPermissions,
publicDashboardEnabled: dashboard.access.isPublic,
},
dashboard: transformDashboardV2SpecToV1(spec, dashboard.metadata),
};
@@ -33,6 +33,7 @@ export interface DashboardWithAccessInfo<T> extends Resource<T, Status, 'Dashboa
canStar?: boolean;
canAdmin?: boolean;
annotationsPermissions?: AnnotationsPermissions;
isPublic?: boolean;
}; // TODO...
}
+1
View File
@@ -137,6 +137,7 @@ export class K8sDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
k8s: dash.metadata,
version: dash.metadata.generation,
created: dash.metadata.creationTimestamp,
publicDashboardEnabled: dash.access.isPublic,
},
dashboard: {
...dash.spec,