Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync

This commit is contained in:
Ryan McKinley committed 2025-01-09 14:10:06 +03:00
commit cd231ffcfb
43 files changed
+228 -229

No files matched your search

+14 -30
View File
@@ -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"],
+2
View File
@@ -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)
}
}
+1 -1
View File
@@ -49,8 +49,8 @@ const (
var (
RelationsFolder = common.RelationsFolder
RelationsFolderResource = common.RelationsFolder
RelationsResouce = common.RelationsResource
RelationsFolderResource = common.RelationsFolderResource
)
const (
+10 -6
View File
@@ -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,
+14 -8
View File
@@ -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 {
+8 -4
View File
@@ -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
}
+13 -9
View File
@@ -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
@@ -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>
);
};
@@ -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
@@ -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>
</>
@@ -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>
@@ -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"
@@ -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">
@@ -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
@@ -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>
@@ -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">
@@ -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) {
@@ -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',
},
+4 -1
View File
@@ -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', () => ({