Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync
This commit is contained in:
commit
cd231ffcfb
43 files changed
+228
-229
No files matched your search
+14
-30
@@ -1600,8 +1600,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"],
|
||||
@@ -1615,8 +1614,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
@@ -1801,14 +1799,12 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/PayloadEditor.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
@@ -1903,8 +1899,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"],
|
||||
@@ -1950,9 +1945,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
@@ -2007,8 +2000,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "12"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "14"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "15"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "16"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"],
|
||||
@@ -2172,8 +2162,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"],
|
||||
@@ -2218,8 +2207,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"]
|
||||
@@ -2329,8 +2317,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "10"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "11"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans /> or use t()", "0"],
|
||||
@@ -2503,9 +2490,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/settings/ConfigurationDrawer.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
@@ -2570,8 +2555,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "5"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/silences/SilencesFilter.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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 (
|
||||
<Editor {...props} theme={theme.isDark ? 'grafana-dark' : 'grafana-light'} beforeMount={onMonacoBeforeMount} />
|
||||
<Editor
|
||||
{...restProps}
|
||||
options={{
|
||||
...options,
|
||||
fontFamily: theme.typography.code.fontFamily,
|
||||
}}
|
||||
theme={theme.isDark ? 'grafana-dark' : 'grafana-light'}
|
||||
beforeMount={onMonacoBeforeMount}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ const (
|
||||
|
||||
var (
|
||||
RelationsFolder = common.RelationsFolder
|
||||
RelationsFolderResource = common.RelationsFolder
|
||||
RelationsResouce = common.RelationsResource
|
||||
RelationsFolderResource = common.RelationsFolderResource
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-1
@@ -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'}
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" onClick={handleDismiss} disabled={isLoading}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
|
||||
@@ -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
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<ClipboardButton icon="copy" getText={() => textDefinition}>
|
||||
Copy code
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ManagePermissionsDrawer = ({
|
||||
const defaultTitle = t('alerting.manage-permissions.title', 'Manage permissions');
|
||||
return (
|
||||
<Drawer onClose={onClose} title={title || defaultTitle} subtitle={resourceName}>
|
||||
<Permissions {...permissionsProps} canSetPermissions></Permissions>
|
||||
<Permissions {...permissionsProps} canSetPermissions />
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-1
@@ -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({
|
||||
</div>
|
||||
<Modal.ButtonRow>
|
||||
<Button type="button" variant="secondary" onClick={onDismiss}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { Alert, Button, LinkButton, Stack } from '@grafana/ui';
|
||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
@@ -103,7 +104,7 @@ export const GlobalConfigForm = ({ config, alertManagerSourceName }: Props) => {
|
||||
variant="secondary"
|
||||
href={makeAMLink('alerting/notifications', alertManagerSourceName)}
|
||||
>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</LinkButton>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
@@ -324,7 +324,7 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
|
||||
{isGrafanaAlertManager && (
|
||||
<>
|
||||
<div {...rowSplitter.secondaryProps}>
|
||||
<div {...rowSplitter.splitterProps}></div>
|
||||
<div {...rowSplitter.splitterProps} />
|
||||
<TemplatePreview
|
||||
payload={payload}
|
||||
templateName={watch('title')}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th />
|
||||
<th>Template group</th>
|
||||
<Authorize
|
||||
actions={[
|
||||
@@ -189,7 +189,7 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
|
||||
</tr>
|
||||
{isExpanded && (
|
||||
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
||||
<td></td>
|
||||
<td />
|
||||
<td colSpan={2}>
|
||||
<DetailsField label="" horizontal={true}>
|
||||
<TemplateEditor
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ export const GenerateAlertDataModal = ({ isOpen, onDismiss, onAccept }: Props) =
|
||||
</Stack>
|
||||
</Card>
|
||||
</>
|
||||
<div className={styles.onSubmitWrapper}></div>
|
||||
<div className={styles.onSubmitWrapper} />
|
||||
{alerts.length > 0 && (
|
||||
<Stack direction="column" gap={1}>
|
||||
<h5> Review alert data to add to the payload:</h5>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isFetchError } from '@grafana/runtime';
|
||||
import { Alert, Button, Field, Input, LinkButton, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { useValidateContactPoint } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
|
||||
import { ManagePermissions } from 'app/features/alerting/unified/components/permissions/ManagePermissions';
|
||||
|
||||
@@ -224,7 +225,7 @@ export function ReceiverForm<R extends ChannelValues>({
|
||||
data-testid="cancel-button"
|
||||
href={makeAMLink('/alerting/notifications', alertManagerSourceName)}
|
||||
>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</LinkButton>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) =
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
{!readOnly && <th></th>}
|
||||
{!readOnly && <th />}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
+2
-2
@@ -232,7 +232,7 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
|
||||
</Stack>
|
||||
<div className={styles.actions}>
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -248,7 +248,7 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
|
||||
return onClose();
|
||||
}}
|
||||
>
|
||||
Save
|
||||
<Trans i18nKey="common.save">Save</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
clearButtonStyles,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { DashboardModel } from '../../../../dashboard/state/DashboardModel';
|
||||
import { dashboardApi } from '../../api/dashboardApi';
|
||||
@@ -243,7 +244,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
||||
</div>
|
||||
<Modal.ButtonRow>
|
||||
<Button type="button" variant="secondary" onClick={onDismiss} fill="text">
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import { Button, ConfirmModal, Spinner, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule';
|
||||
import {
|
||||
getRuleGroupLocationFromFormValues,
|
||||
@@ -265,7 +266,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
Save rule and exit
|
||||
</Button>
|
||||
<Button variant="secondary" disabled={isSubmitting} type="button" onClick={cancelRuleCreation} size="sm">
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
{existing ? (
|
||||
<Button fill="outline" variant="destructive" type="button" onClick={() => setShowDeleteModal(true)} size="sm">
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import { useAsync } from 'react-use';
|
||||
|
||||
import { Button, LinkButton, LoadingPlaceholder, Stack } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { AppChromeUpdate } from '../../../../../../core/components/AppChrome/AppChromeUpdate';
|
||||
import {
|
||||
@@ -89,7 +90,7 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor
|
||||
|
||||
const actionButtons = [
|
||||
<LinkButton href={returnTo} key="cancel" size="sm" variant="secondary" onClick={() => submit(undefined)}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</LinkButton>,
|
||||
<Button key="export-rule" size="sm" onClick={formAPI.handleSubmit((formValues) => submit(formValues), onInvalid)}>
|
||||
Export
|
||||
|
||||
+2
-2
@@ -40,13 +40,13 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Stack direction="row" alignItems="center">
|
||||
<div className={styles.firstAlertManagerLine}></div>
|
||||
<div className={styles.firstAlertManagerLine} />
|
||||
<div className={styles.alertManagerName}>
|
||||
Alertmanager:
|
||||
<img src={alertManager.imgUrl} alt="Alert manager logo" className={styles.img} />
|
||||
{alertManagerName}
|
||||
</div>
|
||||
<div className={styles.secondAlertManagerLine}></div>
|
||||
<div className={styles.secondAlertManagerLine} />
|
||||
</Stack>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<ContactPointSelector alertManager={alertManagerName} onSelectContactPoint={onSelectContactPoint} />
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Controller, FormProvider, useFieldArray, useForm, useFormContext } from
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { Button, Field, InlineLabel, Input, LoadingPlaceholder, Space, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
|
||||
import { labelsApi } from '../../../api/labelsApi';
|
||||
import { usePluginBridge } from '../../../hooks/usePluginBridge';
|
||||
@@ -88,7 +88,7 @@ export function LabelsSubForm({ dataSourceName, onClose, initialLabels }: Labels
|
||||
<Space v={1} />
|
||||
<div className={styles.confirmButton}>
|
||||
<Button type="button" variant="secondary" onClick={onCancel}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit">Save</Button>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -49,13 +49,13 @@ function NotificationPreviewByAlertManager({
|
||||
<div className={styles.alertManagerRow}>
|
||||
{!onlyOneAM && (
|
||||
<Stack direction="row" alignItems="center">
|
||||
<div className={styles.firstAlertManagerLine}></div>
|
||||
<div className={styles.firstAlertManagerLine} />
|
||||
<div className={styles.alertManagerName}>
|
||||
<Trans i18nKey="alerting.notification-preview.alertmanager">Alertmanager:</Trans>
|
||||
<img src={alertManagerSource.imgUrl} alt="" className={styles.img} />
|
||||
{alertManagerSource.name}
|
||||
</div>
|
||||
<div className={styles.secondAlertManagerLine}></div>
|
||||
<div className={styles.secondAlertManagerLine} />
|
||||
</Stack>
|
||||
)}
|
||||
<Stack gap={1} direction="column">
|
||||
|
||||
+1
-1
@@ -652,7 +652,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P
|
||||
|
||||
{isPreviewLoading && (
|
||||
<Button icon="spinner" type="button" variant="destructive" onClick={cancelQueries}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{!isPreviewLoading && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FieldValues, FormProvider, RegisterOptions, useForm, useFormContext } f
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, Badge, Button, Field, Input, Label, LinkButton, Modal, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { CombinedRuleGroup, CombinedRuleNamespace, RuleGroupIdentifier } from 'app/types/unified-alerting';
|
||||
import { RulerRuleDTO } from 'app/types/unified-alerting-dto';
|
||||
@@ -384,7 +385,7 @@ export function EditRuleGroupModal(props: ModalProps): React.ReactElement {
|
||||
onClick={() => onClose(false)}
|
||||
fill="outline"
|
||||
>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" disabled={!isDirty || !isValid || loading}>
|
||||
{loading ? 'Saving...' : 'Save'}
|
||||
|
||||
@@ -84,7 +84,7 @@ const FilterOptions = () => {
|
||||
<Label>
|
||||
<Trans i18nKey="alerting.search.property.namespace">Folder / Namespace</Trans>
|
||||
</Label>
|
||||
<Select options={[]} onChange={() => {}}></Select>
|
||||
<Select options={[]} onChange={() => {}} />
|
||||
<Label>
|
||||
<Trans i18nKey="alerting.search.property.rule-name">Alerting rule name</Trans>
|
||||
</Label>
|
||||
@@ -100,7 +100,7 @@ const FilterOptions = () => {
|
||||
<Label>
|
||||
<Trans i18nKey="alerting.search.property.data-source">Data source</Trans>
|
||||
</Label>
|
||||
<Select options={[]} onChange={() => {}}></Select>
|
||||
<Select options={[]} onChange={() => {}} />
|
||||
<Label>
|
||||
<Trans i18nKey="alerting.search.property.state">State</Trans>
|
||||
</Label>
|
||||
|
||||
@@ -5,6 +5,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, Button, CodeEditor, ConfirmModal, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { reportFormErrors } from '../../Analytics';
|
||||
import { useAlertmanagerConfig } from '../../hooks/useAlertmanagerConfig';
|
||||
@@ -157,11 +158,11 @@ export default function AlertmanagerConfig({ alertmanagerName, onDismiss, onSave
|
||||
)}
|
||||
<Spacer />
|
||||
<Button variant="secondary" onClick={() => onDismiss()} disabled={isOperating}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
{!readOnly && (
|
||||
<Button variant="primary" onClick={handleSave} disabled={isOperating}>
|
||||
Save
|
||||
<Trans i18nKey="common.save">Save</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -28,11 +28,11 @@ const SilencedAlertsTable = ({ silencedAlerts }: Props) => {
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th />
|
||||
<th>
|
||||
<Trans i18nKey="silences-table.header.state">State</Trans>
|
||||
</th>
|
||||
<th></th>
|
||||
<th />
|
||||
<th>
|
||||
<Trans i18nKey="silences-table.header.alert-name">Alert name</Trans>
|
||||
</th>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const SilencedAlertsTableRow = ({ alert, className }: Props) => {
|
||||
</tr>
|
||||
{!isCollapsed && (
|
||||
<tr className={className}>
|
||||
<td></td>
|
||||
<td />
|
||||
<td colSpan={5}>
|
||||
<AlertLabels labels={alert.labels} size="sm" />
|
||||
</td>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
useStyles2,
|
||||
withErrorBoundary,
|
||||
} from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { SilenceCreatedResponse, alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi';
|
||||
import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/constants';
|
||||
import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource';
|
||||
@@ -258,7 +259,7 @@ export const SilencesEditor = ({
|
||||
)}
|
||||
{!isLoading && <Button type="submit">Save silence</Button>}
|
||||
<LinkButton onClick={onCancelHandler} variant={'secondary'}>
|
||||
Cancel
|
||||
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
|
||||
</LinkButton>
|
||||
</Stack>
|
||||
</form>
|
||||
|
||||
@@ -43,7 +43,7 @@ interface DataSourceLoaderProps {
|
||||
}
|
||||
|
||||
export function GrafanaDataSourceLoader() {
|
||||
return <DataSourceSection name="Grafana" application="grafana" uid="grafana" isLoading={true}></DataSourceSection>;
|
||||
return <DataSourceSection name="Grafana" application="grafana" uid="grafana" isLoading={true} />;
|
||||
}
|
||||
|
||||
export function DataSourceLoader({ uid, name }: DataSourceLoaderProps) {
|
||||
|
||||
+1
-1
@@ -443,7 +443,7 @@ export function getAnnotationQueryKind(annotationQuery: AnnotationQuery): string
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultDataSourceRef(): DataSourceRef | undefined {
|
||||
export function getDefaultDataSourceRef(): DataSourceRef | undefined {
|
||||
// we need to return the default datasource configured in the BootConfig
|
||||
const defaultDatasource = config.bootData.settings.defaultDatasource;
|
||||
|
||||
|
||||
@@ -9,12 +9,72 @@ import {
|
||||
AnnoKeyUpdatedBy,
|
||||
AnnoKeyUpdatedTimestamp,
|
||||
} from 'app/features/apiserver/types';
|
||||
import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2';
|
||||
import { DashboardDataDTO, DashboardDTO } from 'app/types';
|
||||
|
||||
import { getPanelQueries, ResponseTransformers } from './ResponseTransformers';
|
||||
import { getDefaultDatasource, getPanelQueries, ResponseTransformers } from './ResponseTransformers';
|
||||
import { DashboardWithAccessInfo } from './types';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
bootData: {
|
||||
...jest.requireActual('@grafana/runtime').config.bootData,
|
||||
settings: {
|
||||
...jest.requireActual('@grafana/runtime').config.bootData.settings,
|
||||
datasources: {
|
||||
PromTest: {
|
||||
uid: 'xyz-abc',
|
||||
name: 'PromTest',
|
||||
id: 'prometheus',
|
||||
meta: {
|
||||
id: 'prometheus',
|
||||
name: 'PromTest',
|
||||
type: 'datasource',
|
||||
},
|
||||
isDefault: true,
|
||||
apiVersion: 'v2',
|
||||
},
|
||||
'-- Grafana --': {
|
||||
uid: 'grafana',
|
||||
name: '-- Grafana --',
|
||||
id: 'grafana',
|
||||
meta: {
|
||||
id: 'grafana',
|
||||
name: '-- Grafana --',
|
||||
type: 'datasource',
|
||||
},
|
||||
isDefault: false,
|
||||
},
|
||||
},
|
||||
|
||||
defaultDatasource: 'PromTest',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('ResponseTransformers', () => {
|
||||
describe('getDefaultDataSource', () => {
|
||||
it('should return prometheus as default', () => {
|
||||
expect(getDefaultDatasource()).toEqual({
|
||||
apiVersion: 'v2',
|
||||
uid: 'PromTest',
|
||||
type: 'prometheus',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultDataSourceRef', () => {
|
||||
it('should return prometheus as default', () => {
|
||||
expect(getDefaultDataSourceRef()).toEqual({
|
||||
uid: 'PromTest',
|
||||
type: 'prometheus',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('v1 -> v2 transformation', () => {
|
||||
it('should transform DashboardDTO to DashboardWithAccessInfo<DashboardV2Spec>', () => {
|
||||
const dashboardV1: DashboardDataDTO = {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
AnnoKeyUpdatedBy,
|
||||
AnnoKeyUpdatedTimestamp,
|
||||
} from 'app/features/apiserver/types';
|
||||
import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2';
|
||||
import { transformCursorSyncV2ToV1 } from 'app/features/dashboard-scene/serialization/transformToV1TypesUtils';
|
||||
import {
|
||||
transformCursorSynctoEnum,
|
||||
@@ -226,6 +227,11 @@ function getElementsFromPanels(panels: Panel[]): [DashboardV2Spec['elements'], D
|
||||
|
||||
// iterate over panels
|
||||
for (const p of panels) {
|
||||
// FIXME: for now we should skip row panels
|
||||
if (p.type === 'row') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const queries = getPanelQueries(
|
||||
(p.targets as unknown as DataQuery[]) || [],
|
||||
p.datasource || getDefaultDatasource()
|
||||
@@ -291,20 +297,23 @@ function getElementsFromPanels(panels: Panel[]): [DashboardV2Spec['elements'], D
|
||||
}
|
||||
|
||||
function getDefaultDatasourceType() {
|
||||
const datasources = config.datasources;
|
||||
// find default datasource in datasources
|
||||
return Object.values(datasources).find((ds) => ds.isDefault)!.type;
|
||||
// if there is no default datasource, return 'grafana' as default
|
||||
return getDefaultDataSourceRef()?.type ?? 'grafana';
|
||||
}
|
||||
|
||||
function getDefaultDatasource(): DataSourceRef {
|
||||
const datasources = config.datasources;
|
||||
export function getDefaultDatasource(): DataSourceRef {
|
||||
const configDefaultDS = getDefaultDataSourceRef() ?? { type: 'grafana', uid: '-- Grafana --' };
|
||||
|
||||
if (configDefaultDS.uid && !configDefaultDS.apiVersion) {
|
||||
// get api version from config
|
||||
const dsInstance = config.bootData.settings.datasources[configDefaultDS.uid];
|
||||
configDefaultDS.apiVersion = dsInstance.apiVersion ?? undefined;
|
||||
}
|
||||
|
||||
// find default datasource in datasources
|
||||
const defaultDs = Object.values(datasources).find((ds) => ds.isDefault)!;
|
||||
return {
|
||||
apiVersion: defaultDs.apiVersion,
|
||||
type: defaultDs.type,
|
||||
uid: defaultDs.uid,
|
||||
apiVersion: configDefaultDS.apiVersion,
|
||||
type: configDefaultDS.type,
|
||||
uid: configDefaultDS.uid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -407,7 +416,8 @@ function getVariables(vars: VariableModel[]): DashboardV2Spec['variables'] {
|
||||
variables.push(dv);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Variable transformation not implemented: ${v.type}`);
|
||||
// do not throw error, just log it
|
||||
console.error(`Variable transformation not implemented: ${v.type}`);
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
|
||||
@@ -88,12 +88,14 @@ const saveDashboardResponse = {
|
||||
},
|
||||
};
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => ({
|
||||
get: () => mockDashboardDto,
|
||||
put: jest.fn().mockResolvedValue(saveDashboardResponse),
|
||||
post: jest.fn().mockResolvedValue(saveDashboardResponse),
|
||||
}),
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
buildInfo: {
|
||||
version: '11.5.0-test-version-string',
|
||||
},
|
||||
|
||||
@@ -27,10 +27,13 @@ const mockDashboardDto: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
};
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => ({
|
||||
get: () => mockDashboardDto,
|
||||
}),
|
||||
config: {},
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('app/features/live/dashboard/dashboardWatcher', () => ({
|
||||
|
||||
Reference in new issue
Block a user