Merge branch 'main' into drclau/unistor/replace-authenticators-3

This commit is contained in:
Claudiu Dragalina-Paraipan 2024-09-03 17:31:36 +03:00
commit 2441cd8d53
21 changed files with 272 additions and 99 deletions

View File

@ -62,6 +62,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
| `lokiQueryHints` | Enables query hints for Loki | Yes |
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
| `promQLScope` | In-development feature that will allow injection of labels into prometheus queries. | Yes |
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
@ -172,7 +173,6 @@ Experimental features might be changed or removed without prior notice.
| `tableSharedCrosshair` | Enables shared crosshair in table panel |
| `kubernetesFeatureToggles` | Use the kubernetes API for feature toggle management in the frontend |
| `newFolderPicker` | Enables the nested folder picker without having nested folders enabled |
| `promQLScope` | In-development feature that will allow injection of labels into prometheus queries. |
| `sqlExpressions` | Enables using SQL and DuckDB functions as Expressions. |
| `nodeGraphDotLayout` | Changed the layout algorithm for the node graph |
| `kubernetesAggregator` | Enable grafana's embedded kube-aggregator |

View File

@ -608,6 +608,8 @@ github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/drone/drone-runtime v1.1.0 h1:IsKbwiLY6+ViNBzX0F8PERJVZZcEJm9rgxEh3uZP5IE=
github.com/drone/drone-runtime v1.1.0/go.mod h1:+osgwGADc/nyl40J0fdsf8Z09bgcBZXvXXnLOY48zYs=

View File

@ -91,7 +91,7 @@
"@rtk-query/codegen-openapi": "^1.2.0",
"@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1",
"@swc/core": "1.4.2",
"@swc/helpers": "0.5.12",
"@swc/helpers": "0.5.13",
"@testing-library/dom": "10.0.0",
"@testing-library/jest-dom": "6.4.2",
"@testing-library/react": "15.0.2",
@ -175,13 +175,13 @@
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "28.8.1",
"eslint-plugin-jest": "28.8.2",
"eslint-plugin-jest-dom": "^5.4.0",
"eslint-plugin-jsdoc": "48.11.0",
"eslint-plugin-jsx-a11y": "6.9.0",
"eslint-plugin-lodash": "7.4.0",
"eslint-plugin-no-barrel-files": "^1.1.0",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react": "7.35.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-testing-library": "^6.2.2",
"eslint-scope": "^8.0.0",
@ -268,7 +268,7 @@
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/saga-icons": "workspace:*",
"@grafana/scenes": "^5.11.1",
"@grafana/scenes": "5.12.0",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",

View File

@ -81,7 +81,7 @@
"@rollup/plugin-image": "3.0.3",
"@rollup/plugin-node-resolve": "15.2.3",
"@swc/core": "1.4.2",
"@swc/helpers": "0.5.12",
"@swc/helpers": "0.5.13",
"@testing-library/dom": "10.0.0",
"@testing-library/jest-dom": "6.4.2",
"@testing-library/react": "15.0.2",
@ -110,11 +110,11 @@
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "28.8.1",
"eslint-plugin-jest": "28.8.2",
"eslint-plugin-jsdoc": "48.11.0",
"eslint-plugin-jsx-a11y": "6.9.0",
"eslint-plugin-lodash": "7.4.0",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react": "7.35.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-webpack-plugin": "4.2.0",
"fork-ts-checker-webpack-plugin": "9.0.2",

View File

@ -1,3 +1,4 @@
import { CSSInterpolation } from '@emotion/css';
import { css } from '@emotion/react';
import { GrafanaTheme2 } from '@grafana/data';
@ -33,6 +34,27 @@ function buttonSizeMixin(paddingY: string, paddingX: string, fontSize: string, b
};
}
function widthMixin(theme: GrafanaTheme2, max: number) {
let result: CSSInterpolation = {};
for (let i = 1; i <= max; i++) {
const width = `${theme.spacing(2 * i)} !important`;
result[`.width-${i}`] = {
width,
};
result[`.max-width-${i}`] = {
maxWidth: width,
flexGrow: 1,
};
result[`.min-width-${i}`] = {
minWidth: width,
};
result[`.offset-width-${i}`] = {
marginLeft: width,
};
}
return result;
}
export function getUtilityClassStyles(theme: GrafanaTheme2) {
return css({
'.highlight-word': {
@ -140,5 +162,6 @@ export function getUtilityClassStyles(theme: GrafanaTheme2) {
'.typeahead': {
zIndex: theme.zIndex.typeahead,
},
...widthMixin(theme, 30),
});
}

View File

@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/marketplacecatalog"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/client"
"github.com/urfave/cli/v2"
@ -163,7 +164,7 @@ func (s *AwsMarketplacePublishingService) Login(ctx context.Context) error {
return err
}
authString := strings.Split(string(authData), ":")
authData, err = json.Marshal(types.AuthConfig{
authData, err = json.Marshal(registry.AuthConfig{
Username: authString[0],
Password: authString[1],
})

View File

@ -5,7 +5,7 @@ go 1.23.0
// Override docker/docker to avoid:
// go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires
// github.com/docker/docker@v0.0.0-00010101000000-000000000000: invalid version: unknown revision 000000000000
replace github.com/docker/docker => github.com/moby/moby v23.0.4+incompatible
replace github.com/docker/docker => github.com/moby/moby v25.0.2+incompatible
// contains openapi encoder fixes. remove ASAP
replace cuelang.org/go => github.com/grafana/cue v0.0.0-20230926092038-971951014e3f // @grafana/grafana-as-code
@ -61,7 +61,6 @@ require (
github.com/buildkite/yaml v2.1.0+incompatible // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/drone-runners/drone-runner-docker v1.8.2 // indirect
@ -102,6 +101,8 @@ require (
github.com/Khan/genqlient v0.7.0 // indirect
github.com/adrg/xdg v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/term v0.5.0 // indirect

View File

@ -54,6 +54,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -62,9 +64,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
@ -168,8 +170,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/moby v23.0.4+incompatible h1:A/pe8vi9KIKhNbzR0G3wW4ACKDsMgXILBveMqiJNa8M=
github.com/moby/moby v23.0.4+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
github.com/moby/moby v25.0.2+incompatible h1:g2oKRI7vgWkiPHZbBghaPbcV/SuKP1g/YLx0I2nxFT4=
github.com/moby/moby v25.0.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@ -195,6 +197,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sosodev/duration v1.2.0 h1:pqK/FLSjsAADWY74SyWDCjOcd5l7H8GSnnOGEB9A1Us=
github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -238,9 +238,11 @@ func Parse(span trace.Span, query backend.DataQuery, dsScrapeInterval string, in
}()))
}
expr, err = ApplyFiltersAndGroupBy(expr, scopeFilters, model.AdhocFilters, model.GroupByKeys)
if err != nil {
return nil, err
if len(scopeFilters) > 0 || len(model.AdhocFilters) > 0 || len(model.GroupByKeys) > 0 {
expr, err = ApplyFiltersAndGroupBy(expr, scopeFilters, model.AdhocFilters, model.GroupByKeys)
if err != nil {
return nil, err
}
}
}

View File

@ -12,6 +12,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/db"
@ -241,32 +242,40 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Re
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedUserPermissions")
defer span.End()
permissions := []accesscontrol.Permission{}
permissions, err := s.getCachedBasicRolesPermissions(ctx, user, options, permissions)
cacheKey := accesscontrol.GetUserPermissionCacheKey(user)
if cachedPermissions, ok := s.cache.Get(cacheKey); ok {
return cachedPermissions.([]accesscontrol.Permission), nil
}
permissions, err := s.getCachedBasicRolesPermissions(ctx, user, options)
if err != nil {
return nil, err
}
permissions, err = s.getCachedTeamsPermissions(ctx, user, options, permissions)
teamsPermissions, err := s.getCachedTeamsPermissions(ctx, user, options)
if err != nil {
return nil, err
}
permissions = append(permissions, teamsPermissions...)
userManagedPermissions, err := s.getCachedUserDirectPermissions(ctx, user, options)
if err != nil {
return nil, err
}
userPermissions, err := s.getCachedUserDirectPermissions(ctx, user, options)
if err != nil {
return nil, err
}
permissions = append(permissions, userPermissions...)
permissions = append(permissions, userManagedPermissions...)
s.cache.Set(cacheKey, permissions, cacheTTL)
span.SetAttributes(attribute.Int("num_permissions", len(permissions)))
return permissions, nil
}
func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options, permissions []accesscontrol.Permission) ([]accesscontrol.Permission, error) {
func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedBasicRolesPermissions")
defer span.End()
// Viewer role has ~30 permissions, so we can pre-allocate memory
permissions := make([]accesscontrol.Permission, 0, 50)
basicRoles := accesscontrol.GetOrgRoles(user)
span.SetAttributes(attribute.Int("roles", len(basicRoles)))
for _, role := range basicRoles {
@ -330,16 +339,20 @@ func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermi
return permissions, nil
}
func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options, permissions []accesscontrol.Permission) ([]accesscontrol.Permission, error) {
func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedTeamsPermissions")
defer span.End()
teams := user.GetTeams()
orgID := user.GetOrgID()
miss := teams
if len(teams) == 0 {
return []accesscontrol.Permission{}, nil
}
miss := make([]int64, 0)
permissions := make([]accesscontrol.Permission, 0)
if !options.ReloadCache {
miss = make([]int64, 0)
for _, teamID := range teams {
key := accesscontrol.GetTeamPermissionCacheKey(teamID, orgID)
teamPermissions, ok := s.cache.Get(key)
@ -351,6 +364,9 @@ func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.R
miss = append(miss, teamID)
}
}
} else {
// reload cache and fetch all teams permissions
miss = teams
}
if len(miss) > 0 {
@ -373,7 +389,7 @@ func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.R
}
func (s *Service) ClearUserPermissionCache(user identity.Requester) {
s.cache.Delete(accesscontrol.GetPermissionCacheKey(user))
s.cache.Delete(accesscontrol.GetUserPermissionCacheKey(user))
s.cache.Delete(accesscontrol.GetUserDirectPermissionCacheKey(user))
}

View File

@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
func GetPermissionCacheKey(user identity.Requester) string {
func GetUserPermissionCacheKey(user identity.Requester) string {
return fmt.Sprintf("rbac-permissions-%s", user.GetCacheKey())
}

View File

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
)
@ -68,7 +69,7 @@ func TestPermissionCacheKey(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, GetPermissionCacheKey(tc.signedInUser))
assert.Equal(t, tc.expected, GetUserPermissionCacheKey(tc.signedInUser))
})
}
}

View File

@ -1039,8 +1039,9 @@ var (
{
Name: "promQLScope",
Description: "In-development feature that will allow injection of labels into prometheus queries.",
Stage: FeatureStageExperimental,
Stage: FeatureStageGeneralAvailability,
Owner: grafanaObservabilityMetricsSquad,
Expression: "true",
},
{
Name: "sqlExpressions",

View File

@ -136,7 +136,7 @@ newFolderPicker,experimental,@grafana/grafana-frontend-platform,false,false,true
jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,false,true,false
onPremToCloudMigrations,preview,@grafana/grafana-operator-experience-squad,false,false,false
alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,false,false,false
promQLScope,experimental,@grafana/observability-metrics,false,false,false
promQLScope,GA,@grafana/observability-metrics,false,false,false
sqlExpressions,experimental,@grafana/grafana-app-platform-squad,false,false,false
nodeGraphDotLayout,experimental,@grafana/observability-traces-and-profiling,false,false,true
groupToNestedTableTransformation,GA,@grafana/dataviz-squad,false,false,true

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
136 jitterAlertRulesWithinGroups preview @grafana/alerting-squad false true false
137 onPremToCloudMigrations preview @grafana/grafana-operator-experience-squad false false false
138 alertingSaveStatePeriodic privatePreview @grafana/alerting-squad false false false
139 promQLScope experimental GA @grafana/observability-metrics false false false
140 sqlExpressions experimental @grafana/grafana-app-platform-squad false false false
141 nodeGraphDotLayout experimental @grafana/observability-traces-and-profiling false false true
142 groupToNestedTableTransformation GA @grafana/dataviz-squad false false true

View File

@ -2188,13 +2188,17 @@
{
"metadata": {
"name": "promQLScope",
"resourceVersion": "1718727528075",
"creationTimestamp": "2024-01-29T20:22:17Z"
"resourceVersion": "1724076197892",
"creationTimestamp": "2024-01-29T20:22:17Z",
"annotations": {
"grafana.app/updatedTimestamp": "2024-08-19 14:03:17.892558375 +0000 UTC"
}
},
"spec": {
"description": "In-development feature that will allow injection of labels into prometheus queries.",
"stage": "experimental",
"codeowner": "@grafana/observability-metrics"
"stage": "GA",
"codeowner": "@grafana/observability-metrics",
"expression": "true"
}
},
{

View File

@ -1,11 +1,18 @@
import { VariableRefresh } from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { setPluginImportUtils } from '@grafana/runtime';
import { SceneGridLayout, VizPanel } from '@grafana/scenes';
import { SceneGridLayout, SceneVariableSet, TestVariable, VizPanel } from '@grafana/scenes';
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
import { activateFullSceneTree, buildPanelRepeaterScene } from '../utils/test-utils';
import { DashboardGridItem, DashboardGridItemState } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
setPluginImportUtils({
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
@ -85,6 +92,142 @@ describe('PanelRepeaterGridItem', () => {
expect(repeater.state.repeatedPanels?.length).toBe(1);
});
it('Should redo the repeat when editing panel and then returning to dashboard', async () => {
const panel = new DashboardGridItem({
variableName: 'server',
repeatedPanels: [],
body: new VizPanel({
title: 'Panel $server',
}),
});
const variable = new TestVariable({
name: 'server',
query: 'A.*',
value: ALL_VARIABLE_VALUE,
text: ALL_VARIABLE_TEXT,
isMulti: true,
includeAll: true,
delayMs: 0,
optionsToReturn: [
{ label: 'A', value: '1' },
{ label: 'B', value: '2' },
{ label: 'C', value: '3' },
{ label: 'D', value: '4' },
{ label: 'E', value: '5' },
],
});
const scene = new DashboardScene({
$variables: new SceneVariableSet({
variables: [variable],
}),
body: new SceneGridLayout({
children: [panel],
}),
});
const deactivate = activateFullSceneTree(scene);
await new Promise((r) => setTimeout(r, 10));
expect(panel.state.repeatedPanels?.length).toBe(5);
const vizPanel = panel.state.body as VizPanel;
expect(vizPanel.state.title).toBe('Panel $server');
// mimic going to panel edit
deactivate();
await new Promise((r) => setTimeout(r, 10));
vizPanel.setState({ title: 'Changed' });
//mimic returning to dashboard from panel edit cloning panel
panel.setState({ body: vizPanel.clone() });
// mimic returning to dashboard
activateFullSceneTree(scene);
await new Promise((r) => setTimeout(r, 10));
expect(panel.state.repeatedPanels?.length).toBe(5);
expect((panel.state.repeatedPanels![0] as VizPanel).state.title).toBe('Changed');
});
it('Should only redo the repeat of an edited panel, not all panels in dashboard', async () => {
const panel = new DashboardGridItem({
variableName: 'server',
repeatedPanels: [],
body: new VizPanel({
title: 'Panel $server',
}),
});
const panel2 = new DashboardGridItem({
variableName: 'server',
repeatedPanels: [],
body: new VizPanel({
title: 'Panel $server 2',
}),
});
const variable = new TestVariable({
name: 'server',
query: 'A.*',
value: ALL_VARIABLE_VALUE,
text: ALL_VARIABLE_TEXT,
isMulti: true,
includeAll: true,
delayMs: 0,
optionsToReturn: [
{ label: 'A', value: '1' },
{ label: 'B', value: '2' },
{ label: 'C', value: '3' },
{ label: 'D', value: '4' },
{ label: 'E', value: '5' },
],
});
const scene = new DashboardScene({
$variables: new SceneVariableSet({
variables: [variable],
}),
body: new SceneGridLayout({
children: [panel, panel2],
}),
});
const deactivate = activateFullSceneTree(scene);
await new Promise((r) => setTimeout(r, 10));
expect(panel.state.repeatedPanels?.length).toBe(5);
const vizPanel = panel.state.body as VizPanel;
expect(vizPanel.state.title).toBe('Panel $server');
// mimic going to panel edit
deactivate();
await new Promise((r) => setTimeout(r, 10));
vizPanel.setState({ title: 'Changed' });
//mimic returning to dashboard from panel edit cloning panel
panel.setState({ body: vizPanel.clone() });
const performRepeatMock = jest.spyOn(panel, 'performRepeat');
// mimic returning to dashboard
activateFullSceneTree(scene);
await new Promise((r) => setTimeout(r, 10));
expect(performRepeatMock).toHaveBeenCalledTimes(1); // only for the edited panel
expect(panel.state.repeatedPanels?.length).toBe(5);
expect((panel.state.repeatedPanels![0] as VizPanel).state.title).toBe('Changed');
});
it('Should display a panel when there are variable errors', () => {
const { scene, repeater } = buildPanelRepeaterScene({
variableQueryTime: 0,

View File

@ -45,6 +45,7 @@ export type RepeatDirection = 'v' | 'h';
export class DashboardGridItem extends SceneObjectBase<DashboardGridItemState> implements SceneGridItemLike {
private _libPanelSubscription: Unsubscribable | undefined;
private _prevRepeatValues?: VariableValueSingle[];
private _oldBody?: VizPanel | LibraryVizPanel | AddLibraryPanelDrawer;
protected _variableDependency = new DashboardGridItemVariableDependencyHandler(this);
@ -57,6 +58,11 @@ export class DashboardGridItem extends SceneObjectBase<DashboardGridItemState> i
private _activationHandler() {
if (this.state.variableName) {
this._subs.add(this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState)));
if (this._oldBody !== this.state.body) {
this._prevRepeatValues = undefined;
}
this._oldBody = this.state.body;
this.performRepeat();
}

View File

@ -2602,3 +2602,7 @@ label.cr1 {
input[type='checkbox'].cr1:checked + label {
background: url($checkboxImageUrl) 0px -18px no-repeat;
}
.max-width {
width: 100%;
}

View File

@ -3,8 +3,5 @@
@import 'base/grid';
@import 'base/font_awesome';
// UTILS
@import 'utils/widths';
// ANGULAR
@import 'angular';

View File

@ -1,32 +0,0 @@
.max-width {
width: 100%;
}
.width-auto {
width: auto;
}
// widths
@for $i from 1 through 30 {
.width-#{$i} {
width: ($spacer * $i) !important;
}
}
@for $i from 1 through 30 {
.max-width-#{$i} {
max-width: ($spacer * $i) !important;
flex-grow: 1;
}
}
@for $i from 1 through 30 {
.min-width-#{$i} {
min-width: ($spacer * $i) !important;
}
}
@for $i from 1 through 30 {
.offset-width-#{$i} {
margin-left: ($spacer * $i) !important;
}
}

View File

@ -3769,7 +3769,7 @@ __metadata:
"@rollup/plugin-image": "npm:3.0.3"
"@rollup/plugin-node-resolve": "npm:15.2.3"
"@swc/core": "npm:1.4.2"
"@swc/helpers": "npm:0.5.12"
"@swc/helpers": "npm:0.5.13"
"@testing-library/dom": "npm:10.0.0"
"@testing-library/jest-dom": "npm:6.4.2"
"@testing-library/react": "npm:15.0.2"
@ -3801,11 +3801,11 @@ __metadata:
eslint: "npm:8.57.0"
eslint-config-prettier: "npm:9.1.0"
eslint-plugin-import: "npm:^2.26.0"
eslint-plugin-jest: "npm:28.8.1"
eslint-plugin-jest: "npm:28.8.2"
eslint-plugin-jsdoc: "npm:48.11.0"
eslint-plugin-jsx-a11y: "npm:6.9.0"
eslint-plugin-lodash: "npm:7.4.0"
eslint-plugin-react: "npm:7.35.0"
eslint-plugin-react: "npm:7.35.1"
eslint-plugin-react-hooks: "npm:4.6.0"
eslint-webpack-plugin: "npm:4.2.0"
eventemitter3: "npm:5.0.1"
@ -3932,9 +3932,9 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/scenes@npm:^5.11.1":
version: 5.11.2
resolution: "@grafana/scenes@npm:5.11.2"
"@grafana/scenes@npm:5.12.0":
version: 5.12.0
resolution: "@grafana/scenes@npm:5.12.0"
dependencies:
"@floating-ui/react": "npm:0.26.16"
"@grafana/e2e-selectors": "npm:^11.0.0"
@ -3951,7 +3951,7 @@ __metadata:
"@grafana/ui": ">=10.4"
react: ^18.0.0
react-dom: ^18.0.0
checksum: 10/1f6cded27acac813b1f039fa656efa476bcb2a444217c78c707441698d8d2dc053745fadcbad2dbe94a252d2613f1b32ac120fb11d887bb14f08a0bbea4c423b
checksum: 10/17e1e1b2928c06ad9c898e1988ece2a2113ca3177f510036b42eb4032d6582e783ec28f1f3110dc67acda4ec56d2747ae23c2d57593bc2dac0ff60b2fffeda61
languageName: node
linkType: hard
@ -8822,12 +8822,12 @@ __metadata:
languageName: node
linkType: hard
"@swc/helpers@npm:0.5.12, @swc/helpers@npm:^0.5.0":
version: 0.5.12
resolution: "@swc/helpers@npm:0.5.12"
"@swc/helpers@npm:0.5.13, @swc/helpers@npm:^0.5.0":
version: 0.5.13
resolution: "@swc/helpers@npm:0.5.13"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10/f04a4728c38a6e75a85b077408e175e1abbc1650a76e4b78008d6380ca1422d9f7f4f9fe61b42f8fb889140f05ced6a5a9983037a8d5d8086bf6bc80a0b2118b
checksum: 10/6ba2f7e215d32d71fce139e2cfc426b3ed7eaa709febdeb07b97260a4c9eea4784cf047cc1271be273990b08220b576b94a42b5780947c0b3be84973a847a24d
languageName: node
linkType: hard
@ -16588,9 +16588,9 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-jest@npm:28.8.1":
version: 28.8.1
resolution: "eslint-plugin-jest@npm:28.8.1"
"eslint-plugin-jest@npm:28.8.2":
version: 28.8.2
resolution: "eslint-plugin-jest@npm:28.8.2"
dependencies:
"@typescript-eslint/utils": "npm:^6.0.0 || ^7.0.0 || ^8.0.0"
peerDependencies:
@ -16602,7 +16602,7 @@ __metadata:
optional: true
jest:
optional: true
checksum: 10/d148255d9e131103fc6be708874043f679c84137db140832523ab2481d17683d13ed41a15626f24b098ba5674520c1c316243a02d32d64f87cede57b0d84a46a
checksum: 10/5868bc0f825fdb5c26ff5939a55baa6e6b9bf9c7de5a45388babb876b17e0253a1e8c4d343b404fd5fcd2b7ab1808b47e20b06fb193a4e73f145fe25473eada4
languageName: node
linkType: hard
@ -16727,9 +16727,9 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-react@npm:7.35.0":
version: 7.35.0
resolution: "eslint-plugin-react@npm:7.35.0"
"eslint-plugin-react@npm:7.35.1":
version: 7.35.1
resolution: "eslint-plugin-react@npm:7.35.1"
dependencies:
array-includes: "npm:^3.1.8"
array.prototype.findlast: "npm:^1.2.5"
@ -16751,7 +16751,7 @@ __metadata:
string.prototype.repeat: "npm:^1.0.0"
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
checksum: 10/fa0a54f9ea249cf89d92bb5983bf7df741da3709a0ebd6a885a67d05413ed302fd8b64c9dc819b33df8efa6d8b06f5e56b1f6965a9be7cc3e79054da4dbae5ed
checksum: 10/5bbae54dcef5a84bd71277315238d63caaa23effecd1a376b83ccf1cf033770ee44b763300f9fb55c569d7688ebc93d12728018dcfe240423c6059cc7284ba3f
languageName: node
linkType: hard
@ -18511,7 +18511,7 @@ __metadata:
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/saga-icons": "workspace:*"
"@grafana/scenes": "npm:^5.11.1"
"@grafana/scenes": "npm:5.12.0"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/tsconfig": "npm:^2.0.0"
@ -18547,7 +18547,7 @@ __metadata:
"@rtk-query/codegen-openapi": "npm:^1.2.0"
"@rtsao/plugin-proposal-class-properties": "npm:7.0.1-patch.1"
"@swc/core": "npm:1.4.2"
"@swc/helpers": "npm:0.5.12"
"@swc/helpers": "npm:0.5.13"
"@testing-library/dom": "npm:10.0.0"
"@testing-library/jest-dom": "npm:6.4.2"
"@testing-library/react": "npm:15.0.2"
@ -18657,13 +18657,13 @@ __metadata:
eslint: "npm:8.57.0"
eslint-config-prettier: "npm:9.1.0"
eslint-plugin-import: "npm:^2.26.0"
eslint-plugin-jest: "npm:28.8.1"
eslint-plugin-jest: "npm:28.8.2"
eslint-plugin-jest-dom: "npm:^5.4.0"
eslint-plugin-jsdoc: "npm:48.11.0"
eslint-plugin-jsx-a11y: "npm:6.9.0"
eslint-plugin-lodash: "npm:7.4.0"
eslint-plugin-no-barrel-files: "npm:^1.1.0"
eslint-plugin-react: "npm:7.35.0"
eslint-plugin-react: "npm:7.35.1"
eslint-plugin-react-hooks: "npm:4.6.0"
eslint-plugin-testing-library: "npm:^6.2.2"
eslint-scope: "npm:^8.0.0"