diff --git a/.betterer.results b/.betterer.results
index 728031bbbc9..26a99dbab95 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -1600,8 +1600,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "5"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "4"]
],
"public/app/features/alerting/unified/components/contact-points/components/UnusedBadge.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -1615,8 +1614,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "8"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
],
"public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -1801,14 +1799,12 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "6"]
],
"public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "3"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "2"]
],
"public/app/features/alerting/unified/components/receivers/PayloadEditor.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -1903,8 +1899,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "9"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "10"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "11"]
+ [0, 0, 0, "Unexpected any. Specify a different type.", "10"]
],
"public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -1950,9 +1945,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "6"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "4"]
],
"public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -2007,8 +2000,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "12"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "13"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "14"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "15"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "16"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "15"]
],
"public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@@ -2122,14 +2114,12 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "5"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "4"]
],
"public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "3"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "2"]
],
"public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -2172,8 +2162,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "8"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
],
"public/app/features/alerting/unified/components/rule-editor/labels/LabelsFieldInForm.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -2218,8 +2207,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "6"]
],
"public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"]
@@ -2329,8 +2317,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "9"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "10"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "11"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "10"]
],
"public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -2503,9 +2490,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "5"]
],
"public/app/features/alerting/unified/components/settings/ConfigurationDrawer.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
@@ -2570,8 +2555,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "9"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "8"]
],
"public/app/features/alerting/unified/components/silences/SilencesFilter.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
diff --git a/eslint.config.js b/eslint.config.js
index 7c1b5d23fb4..17be1ebc4e0 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -249,6 +249,7 @@ module.exports = [
name: 'grafana/alerting-overrides',
plugins: {
unicorn: unicornPlugin,
+ react: reactPlugin,
},
files: ['public/app/features/alerting/**/*.{ts,tsx,js,jsx}'],
rules: {
@@ -256,6 +257,7 @@ module.exports = [
'dot-notation': 'error',
'prefer-const': 'error',
'react/no-unused-prop-types': 'error',
+ 'react/self-closing-comp': 'error',
'unicorn/no-unused-properties': 'error',
},
},
diff --git a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx
index b447fabf164..2a5dfa1ea85 100644
--- a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx
+++ b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx
@@ -11,7 +11,7 @@ import type { ReactMonacoEditorProps } from './types';
monacoEditorLoader.config({ monaco });
export const ReactMonacoEditor = (props: ReactMonacoEditorProps) => {
- const { beforeMount } = props;
+ const { beforeMount, options, ...restProps } = props;
const theme = useTheme2();
const onMonacoBeforeMount = useCallback(
@@ -23,6 +23,14 @@ export const ReactMonacoEditor = (props: ReactMonacoEditorProps) => {
);
return (
-
+
);
};
diff --git a/pkg/services/accesscontrol/dualwrite/collectors.go b/pkg/services/accesscontrol/dualwrite/collectors.go
index 929c0e2a062..c5fee947a73 100644
--- a/pkg/services/accesscontrol/dualwrite/collectors.go
+++ b/pkg/services/accesscontrol/dualwrite/collectors.go
@@ -414,8 +414,8 @@ func rolePermissionsCollector(store db.DB) legacyTupleCollector {
}
}
-func fixedRolePermissionsCollector(store db.DB) globalTupleCollector {
- return func(ctx context.Context) (map[string]map[string]*openfgav1.TupleKey, error) {
+func fixedRolePermissionsCollector(store db.DB) legacyTupleCollector {
+ return func(ctx context.Context, _ int64) (map[string]map[string]*openfgav1.TupleKey, error) {
var query = `
SELECT r.uid as role_uid, p.action, p.kind, p.identifier
FROM permission p
@@ -457,6 +457,18 @@ func fixedRolePermissionsCollector(store db.DB) globalTupleCollector {
tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
}
+ // For resource actions on folders we need to merge the tuples into one with combined
+ // group_resources.
+ if zanzana.IsFolderResourceTuple(tuple) {
+ key := tupleStringWithoutCondition(tuple)
+ if t, ok := tuples[tuple.Object][key]; ok {
+ zanzana.MergeFolderResourceTuples(t, tuple)
+ } else {
+ tuples[tuple.Object][key] = tuple
+ }
+ continue
+ }
+
tuples[tuple.Object][tuple.String()] = tuple
}
diff --git a/pkg/services/accesscontrol/dualwrite/global_reconciler.go b/pkg/services/accesscontrol/dualwrite/global_reconciler.go
deleted file mode 100644
index 04b381cffa6..00000000000
--- a/pkg/services/accesscontrol/dualwrite/global_reconciler.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package dualwrite
-
-import (
- "context"
- "fmt"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana"
-)
-
-type globalTupleCollector func(ctx context.Context) (map[string]map[string]*openfgav1.TupleKey, error)
-
-type globalReconciler struct {
- name string
- globalCollector globalTupleCollector
- zanzana zanzanaTupleCollector
- client zanzana.Client
-}
-
-func newGlobalReconciler(name string, globalCollector globalTupleCollector, zanzana zanzanaTupleCollector, client zanzana.Client) globalReconciler {
- return globalReconciler{name, globalCollector, zanzana, client}
-}
-
-func (r globalReconciler) reconcile(ctx context.Context) error {
- namespace := zanzana.ClusterNamespace
-
- // 1. Fetch grafana resources stored in grafana db.
- res, err := r.globalCollector(ctx)
- if err != nil {
- return fmt.Errorf("failed to collect legacy tuples for %s: %w", r.name, err)
- }
-
- var (
- writes = []*openfgav1.TupleKey{}
- deletes = []*openfgav1.TupleKeyWithoutCondition{}
- )
-
- for object, tuples := range res {
- // 2. Fetch all tuples for given object.
- // Due to limitations in open fga api we need to collect tuples per object
- zanzanaTuples, err := r.zanzana(ctx, r.client, object, namespace)
- if err != nil {
- return fmt.Errorf("failed to collect zanzanaa tuples for %s: %w", r.name, err)
- }
-
- // 3. Check if tuples from grafana db exists in zanzana and if not add them to writes
- for key, t := range tuples {
- stored, ok := zanzanaTuples[key]
- if !ok {
- writes = append(writes, t)
- continue
- }
-
- // 4. For folder resource tuples we also need to compare the stored group_resources
- if zanzana.IsFolderResourceTuple(t) && t.String() != stored.String() {
- deletes = append(deletes, &openfgav1.TupleKeyWithoutCondition{
- User: t.User,
- Relation: t.Relation,
- Object: t.Object,
- })
-
- writes = append(writes, t)
- }
- }
-
- // 5. Check if tuple from zanzana don't exists in grafana db, if not add them to deletes.
- for key, tuple := range zanzanaTuples {
- _, ok := tuples[key]
- if !ok {
- deletes = append(deletes, &openfgav1.TupleKeyWithoutCondition{
- User: tuple.User,
- Relation: tuple.Relation,
- Object: tuple.Object,
- })
- }
- }
- }
-
- if len(writes) == 0 && len(deletes) == 0 {
- return nil
- }
-
- if len(deletes) > 0 {
- err := batch(deletes, 100, func(items []*openfgav1.TupleKeyWithoutCondition) error {
- return r.client.Write(ctx, &authzextv1.WriteRequest{
- Namespace: namespace,
- Deletes: &authzextv1.WriteRequestDeletes{TupleKeys: zanzana.ToAuthzExtTupleKeysWithoutCondition(items)},
- })
- })
-
- if err != nil {
- return err
- }
- }
-
- if len(writes) > 0 {
- err := batch(writes, 100, func(items []*openfgav1.TupleKey) error {
- return r.client.Write(ctx, &authzextv1.WriteRequest{
- Namespace: namespace,
- Writes: &authzextv1.WriteRequestWrites{TupleKeys: zanzana.ToAuthzExtTupleKeys(items)},
- })
- })
-
- if err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/pkg/services/accesscontrol/dualwrite/reconciler.go b/pkg/services/accesscontrol/dualwrite/reconciler.go
index 88461ed17bf..ac2c979c4d6 100644
--- a/pkg/services/accesscontrol/dualwrite/reconciler.go
+++ b/pkg/services/accesscontrol/dualwrite/reconciler.go
@@ -32,8 +32,9 @@ type ZanzanaReconciler struct {
lock *serverlock.ServerLockService
// reconcilers are migrations that tries to reconcile the state of grafana db to zanzana store.
// These are run periodically to try to maintain a consistent state.
- reconcilers []resourceReconciler
- globalReconcilers []globalReconciler
+ reconcilers []resourceReconciler
+ // globalReconcilers are reconcilers that should only run for cluster namespace
+ globalReconcilers []resourceReconciler
}
func NewZanzanaReconciler(cfg *setting.Cfg, client zanzana.Client, store db.DB, lock *serverlock.ServerLockService, folderService folder.Service) *ZanzanaReconciler {
@@ -93,11 +94,11 @@ func NewZanzanaReconciler(cfg *setting.Cfg, client zanzana.Client, store db.DB,
client,
),
},
- globalReconcilers: []globalReconciler{
- newGlobalReconciler(
+ globalReconcilers: []resourceReconciler{
+ newResourceReconciler(
"fixed role pemissions",
fixedRolePermissionsCollector(store),
- zanzanaCollector([]string{zanzana.RelationAssignee}),
+ zanzanaCollector(zanzana.RelationsFolder),
client,
),
},
@@ -146,7 +147,7 @@ func (r *ZanzanaReconciler) reconcile(ctx context.Context) {
runGlobal := func(ctx context.Context) {
for _, reconciler := range r.globalReconcilers {
r.log.Debug("Performing zanzana reconciliation", "reconciler", reconciler.name)
- if err := reconciler.reconcile(ctx); err != nil {
+ if err := reconciler.reconcile(ctx, zanzana.ClusterNamespace); err != nil {
r.log.Warn("Failed to perform reconciliation for resource", "err", err)
}
}
diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go
index 696f75c1e82..26b82d0151a 100644
--- a/pkg/services/authz/zanzana/zanzana.go
+++ b/pkg/services/authz/zanzana/zanzana.go
@@ -49,8 +49,8 @@ const (
var (
RelationsFolder = common.RelationsFolder
- RelationsFolderResource = common.RelationsFolder
RelationsResouce = common.RelationsResource
+ RelationsFolderResource = common.RelationsFolderResource
)
const (
diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go
index 123ace40c28..cd64949b7e5 100644
--- a/pkg/services/libraryelements/database.go
+++ b/pkg/services/libraryelements/database.go
@@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
+ "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search"
@@ -288,7 +289,6 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D
builder := db.NewSqlBuilder(cfg, features, store.GetDialect(), recursiveQueriesAreSupported)
builder.Write(selectLibraryElementDTOWithMeta)
builder.Write(", ? as folder_name ", cmd.FolderName)
- builder.Write(", COALESCE((SELECT folder.uid FROM folder WHERE folder.id = le.folder_id), '') as folder_uid ")
builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc()
// nolint:staticcheck
@@ -296,7 +296,6 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D
builder.Write(" UNION ")
builder.Write(selectLibraryElementDTOWithMeta)
builder.Write(", dashboard.title as folder_name ")
- builder.Write(", dashboard.uid as folder_uid ")
builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0")
writeParamSelectorSQL(&builder, params...)
@@ -322,6 +321,11 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D
leDtos := make([]model.LibraryElementDTO, len(libraryElements))
for i, libraryElement := range libraryElements {
+ // nolint:staticcheck
+ f, err := l.folderService.Get(c, &folder.GetFolderQuery{OrgID: signedInUser.GetOrgID(), ID: &libraryElement.FolderID, SignedInUser: signedInUser})
+ if err != nil {
+ return []model.LibraryElementDTO{}, err
+ }
var updatedModel json.RawMessage
if libraryElement.Kind == int64(model.PanelElement) {
updatedModel, err = l.addUidToLibraryPanel(libraryElement.Model, libraryElement.UID)
@@ -331,8 +335,8 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc()
- folderUID := libraryElement.FolderUID
- if libraryElement.FolderID == 0 { // nolint:staticcheck
+ folderUID := f.UID
+ if f.ID == 0 { // nolint:staticcheck
folderUID = ac.GeneralFolderUID
}
leDtos[i] = model.LibraryElementDTO{
@@ -348,8 +352,8 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D
Model: updatedModel,
Version: libraryElement.Version,
Meta: model.LibraryElementDTOMeta{
- FolderName: libraryElement.FolderName,
- FolderUID: libraryElement.FolderUID,
+ FolderName: f.Title,
+ FolderUID: folderUID,
ConnectedDashboards: libraryElement.ConnectedDashboards,
Created: libraryElement.Created,
Updated: libraryElement.Updated,
diff --git a/pkg/services/libraryelements/libraryelements_permissions_test.go b/pkg/services/libraryelements/libraryelements_permissions_test.go
index 327366115ae..302936dec17 100644
--- a/pkg/services/libraryelements/libraryelements_permissions_test.go
+++ b/pkg/services/libraryelements/libraryelements_permissions_test.go
@@ -95,7 +95,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) {
result.Result.Meta.UpdatedBy.Name = userInDbName
result.Result.Meta.UpdatedBy.AvatarUrl = userInDbAvatar
result.Result.Meta.FolderName = "General"
- result.Result.Meta.FolderUID = ""
+ result.Result.Meta.FolderUID = "general"
result.Result.FolderUID = "general"
sc.reqContext.SignedInUser.OrgRole = testCase.role
diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go
index a83b1b38735..31f6f79b110 100644
--- a/pkg/services/librarypanels/librarypanels_test.go
+++ b/pkg/services/librarypanels/librarypanels_test.go
@@ -695,7 +695,7 @@ func getExpected(t *testing.T, res model.LibraryElementDTO, UID string, name str
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "General",
- FolderUID: "",
+ FolderUID: res.FolderUID,
ConnectedDashboards: 0,
Created: res.Meta.Created,
Updated: res.Meta.Updated,
diff --git a/pkg/services/rendering/http_mode.go b/pkg/services/rendering/http_mode.go
index 0900f9fd90f..f278acca7d8 100644
--- a/pkg/services/rendering/http_mode.go
+++ b/pkg/services/rendering/http_mode.go
@@ -102,6 +102,8 @@ func (rs *RenderingService) generateImageRendererURL(renderType RenderType, opts
}
func (rs *RenderingService) doRequestAndWriteToFile(ctx context.Context, renderType RenderType, rendererURL *url.URL, timeoutOpts TimeoutOpts, headers map[string][]string) (*Result, error) {
+ logger := rs.log.FromContext(ctx)
+
filePath, err := rs.getNewFilePath(renderType)
if err != nil {
return nil, err
@@ -118,13 +120,13 @@ func (rs *RenderingService) doRequestAndWriteToFile(ctx context.Context, renderT
defer func() {
if err := resp.Body.Close(); err != nil {
- rs.log.Warn("Failed to close response body", "err", err)
+ logger.Warn("Failed to close response body", "err", err)
}
}()
// if we didn't get a 200 response, something went wrong.
if resp.StatusCode != http.StatusOK {
- rs.log.Error("Remote rendering request failed", "error", resp.Status, "url", rendererURL.Query().Get("url"))
+ logger.Error("Remote rendering request failed", "error", resp.Status, "url", rendererURL.Query().Get("url"))
return nil, fmt.Errorf("remote rendering request failed, status code: %d, status: %s", resp.StatusCode,
resp.Status)
}
@@ -148,6 +150,8 @@ func (rs *RenderingService) doRequestAndWriteToFile(ctx context.Context, renderT
}
func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers map[string][]string) (*http.Response, error) {
+ logger := rs.log.FromContext(ctx)
+
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
if err != nil {
return nil, err
@@ -159,12 +163,12 @@ func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers m
req.Header[k] = v
}
- rs.log.Debug("calling remote rendering service", "url", u)
+ logger.Debug("calling remote rendering service", "url", u)
// make request to renderer server
resp, err := netClient.Do(req)
if err != nil {
- rs.log.Error("Failed to send request to remote rendering service", "error", err)
+ logger.Error("Failed to send request to remote rendering service", "error", err)
var urlErr *url.Error
if errors.As(err, &urlErr) {
if urlErr.Timeout() {
@@ -178,9 +182,11 @@ func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers m
}
func (rs *RenderingService) writeResponseToFile(ctx context.Context, resp *http.Response, filePath string) error {
+ logger := rs.log.FromContext(ctx)
+
// check for timeout first
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
- rs.log.Info("Rendering timed out")
+ logger.Error("Rendering timed out")
return ErrTimeout
}
@@ -193,7 +199,7 @@ func (rs *RenderingService) writeResponseToFile(ctx context.Context, resp *http.
defer func() {
if err := out.Close(); err != nil && !errors.Is(err, fs.ErrClosed) {
// We already close the file explicitly in the non-error path, so shouldn't be a problem
- rs.log.Warn("Failed to close file", "path", filePath, "err", err)
+ logger.Warn("Failed to close file", "path", filePath, "err", err)
}
}()
@@ -201,11 +207,11 @@ func (rs *RenderingService) writeResponseToFile(ctx context.Context, resp *http.
if err != nil {
// check that we didn't timeout while receiving the response.
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
- rs.log.Info("Rendering timed out")
+ logger.Error("Rendering timed out")
return ErrTimeout
}
- rs.log.Error("Remote rendering request failed", "error", err)
+ logger.Error("Remote rendering request failed", "error", err)
return fmt.Errorf("remote rendering request failed: %w", err)
}
if err := out.Close(); err != nil {
diff --git a/pkg/services/rendering/plugin_mode.go b/pkg/services/rendering/plugin_mode.go
index 6a69d1c56d0..98eea55eb46 100644
--- a/pkg/services/rendering/plugin_mode.go
+++ b/pkg/services/rendering/plugin_mode.go
@@ -9,6 +9,8 @@ import (
)
func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderType RenderType, renderKey string, opts Opts) (*RenderResult, error) {
+ logger := rs.log.FromContext(ctx)
+
// gives plugin some additional time to timeout and return possible errors.
ctx, cancel := context.WithTimeout(ctx, getRequestTimeout(opts.TimeoutOpts))
defer cancel()
@@ -40,7 +42,7 @@ func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderType Rend
AuthToken: rs.Cfg.RendererAuthToken,
Encoding: string(renderType),
}
- rs.log.Debug("Calling renderer plugin", "req", req)
+ logger.Debug("Calling renderer plugin", "req", req)
rc, err := rs.plugin.Client()
if err != nil {
@@ -48,7 +50,7 @@ func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderType Rend
}
rsp, err := rc.Render(ctx, req)
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
- rs.log.Info("Rendering timed out")
+ logger.Error("Rendering timed out")
return nil, ErrTimeout
}
if err != nil {
@@ -62,6 +64,8 @@ func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderType Rend
}
func (rs *RenderingService) renderCSVViaPlugin(ctx context.Context, renderKey string, opts CSVOpts) (*RenderCSVResult, error) {
+ logger := rs.log.FromContext(ctx)
+
// gives plugin some additional time to timeout and return possible errors.
ctx, cancel := context.WithTimeout(ctx, getRequestTimeout(opts.TimeoutOpts))
defer cancel()
@@ -88,7 +92,7 @@ func (rs *RenderingService) renderCSVViaPlugin(ctx context.Context, renderKey st
Headers: headers,
AuthToken: rs.Cfg.RendererAuthToken,
}
- rs.log.Debug("Calling renderer plugin", "req", req)
+ logger.Debug("Calling renderer plugin", "req", req)
rc, err := rs.plugin.Client()
if err != nil {
@@ -98,7 +102,7 @@ func (rs *RenderingService) renderCSVViaPlugin(ctx context.Context, renderKey st
rsp, err := rc.RenderCSV(ctx, req)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
- rs.log.Info("Rendering timed out")
+ logger.Error("Rendering timed out")
return nil, ErrTimeout
}
diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go
index 36799f69a7a..cb25268154f 100644
--- a/pkg/services/rendering/rendering.go
+++ b/pkg/services/rendering/rendering.go
@@ -271,8 +271,10 @@ func (rs *RenderingService) Render(ctx context.Context, renderType RenderType, o
}
func (rs *RenderingService) render(ctx context.Context, renderType RenderType, opts Opts, renderKeyProvider renderKeyProvider) (*RenderResult, error) {
+ logger := rs.log.FromContext(ctx)
+
if !rs.IsAvailable(ctx) {
- rs.log.Warn("Could not render image, no image renderer found/installed. " +
+ logger.Warn("Could not render image, no image renderer found/installed. " +
"For image rendering support please install the grafana-image-renderer plugin. " +
"Read more at https://grafana.com/docs/grafana/latest/administration/image_rendering/")
if opts.ErrorRenderUnavailable {
@@ -282,7 +284,7 @@ func (rs *RenderingService) render(ctx context.Context, renderType RenderType, o
}
if int(atomic.LoadInt32(&rs.inProgressCount)) > opts.ConcurrentLimit {
- rs.log.Warn("Could not render image, hit the currency limit", "concurrencyLimit", opts.ConcurrentLimit, "path", opts.Path)
+ logger.Warn("Could not render image, hit the currency limit", "concurrencyLimit", opts.ConcurrentLimit, "path", opts.Path)
if opts.ErrorConcurrentLimitReached {
return nil, ErrConcurrentLimitReached
}
@@ -312,7 +314,7 @@ func (rs *RenderingService) render(ctx context.Context, renderType RenderType, o
}
}
- rs.log.Info("Rendering", "path", opts.Path, "userID", opts.AuthOpts.UserID)
+ logger.Info("Rendering", "path", opts.Path, "userID", opts.AuthOpts.UserID)
if math.IsInf(opts.DeviceScaleFactor, 0) || math.IsNaN(opts.DeviceScaleFactor) || opts.DeviceScaleFactor == 0 {
opts.DeviceScaleFactor = 1
}
@@ -325,10 +327,10 @@ func (rs *RenderingService) render(ctx context.Context, renderType RenderType, o
res, err := rs.renderAction(ctx, renderType, renderKey, opts)
if err != nil {
- rs.log.Error("Failed to render image", "path", opts.Path, "error", err)
+ logger.Error("Failed to render image", "path", opts.Path, "error", err)
return nil, err
}
- rs.log.Debug("Successfully rendered image", "path", opts.Path)
+ logger.Debug("Successfully rendered image", "path", opts.Path)
return res, nil
}
@@ -367,15 +369,17 @@ func (rs *RenderingService) SanitizeSVG(ctx context.Context, req *SanitizeSVGReq
}
func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts, renderKeyProvider renderKeyProvider) (*RenderCSVResult, error) {
- if int(atomic.LoadInt32(&rs.inProgressCount)) > opts.ConcurrentLimit {
- return nil, ErrConcurrentLimitReached
- }
+ logger := rs.log.FromContext(ctx)
if !rs.IsAvailable(ctx) {
return nil, ErrRenderUnavailable
}
- rs.log.Info("Rendering", "path", opts.Path)
+ if int(atomic.LoadInt32(&rs.inProgressCount)) > opts.ConcurrentLimit {
+ return nil, ErrConcurrentLimitReached
+ }
+
+ logger.Info("Rendering", "path", opts.Path)
renderKey, err := renderKeyProvider.get(ctx, opts.AuthOpts)
if err != nil {
return nil, err
diff --git a/public/app/features/alerting/unified/components/contact-points/components/Modals.tsx b/public/app/features/alerting/unified/components/contact-points/components/Modals.tsx
index d1d91762662..bdba364879c 100644
--- a/public/app/features/alerting/unified/components/contact-points/components/Modals.tsx
+++ b/public/app/features/alerting/unified/components/contact-points/components/Modals.tsx
@@ -1,6 +1,7 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Modal, ModalProps } from '@grafana/ui';
+import { Trans } from 'app/core/internationalization';
import { stringifyErrorLike } from '../../../utils/misc';
@@ -63,7 +64,7 @@ export const useDeleteContactPointModal = (
{isLoading ? 'Deleting...' : 'Yes, delete contact point'}
diff --git a/public/app/features/alerting/unified/components/export/FileExportPreview.tsx b/public/app/features/alerting/unified/components/export/FileExportPreview.tsx
index 8f23aaccdc9..5670ddc8376 100644
--- a/public/app/features/alerting/unified/components/export/FileExportPreview.tsx
+++ b/public/app/features/alerting/unified/components/export/FileExportPreview.tsx
@@ -6,6 +6,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, Button, ClipboardButton, CodeEditor, TextLink, useStyles2 } from '@grafana/ui';
+import { Trans } from 'app/core/internationalization';
import { ExportFormats, ExportProvider, ProvisioningType, allGrafanaExportProviders } from './providers';
@@ -59,7 +60,7 @@ export function FileExportPreview({ format, textDefinition, downloadFileName, on
textDefinition}>
Copy code
diff --git a/public/app/features/alerting/unified/components/permissions/ManagePermissions.tsx b/public/app/features/alerting/unified/components/permissions/ManagePermissions.tsx
index 6bbe527fafd..aa82b4b794c 100644
--- a/public/app/features/alerting/unified/components/permissions/ManagePermissions.tsx
+++ b/public/app/features/alerting/unified/components/permissions/ManagePermissions.tsx
@@ -30,7 +30,7 @@ export const ManagePermissionsDrawer = ({
const defaultTitle = t('alerting.manage-permissions.title', 'Manage permissions');
return (
-
+
);
};
diff --git a/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx b/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx
index d67a709b4f6..05ace6725db 100644
--- a/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx
+++ b/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx
@@ -15,6 +15,7 @@ import {
clearButtonStyles,
useStyles2,
} from '@grafana/ui';
+import { Trans } from 'app/core/internationalization';
import { AlertmanagerAlert, TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../../api/alertmanagerApi';
@@ -259,7 +260,7 @@ export function AlertInstanceModalSelector({