From 2e7cc68394109a189ee56700bf79f143f95f48f8 Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 9 Apr 2024 12:13:27 +0200 Subject: [PATCH 01/12] Alerting: Remove CleanUp method from the Alertmanager (#85650) Alerting: Remove Cleanup method from the Alertmanager --- pkg/services/ngalert/notifier/alertmanager.go | 3 - .../alertmanager_mock/Alertmanager.go | 94 ++++++++++------ .../ngalert/notifier/multiorg_alertmanager.go | 5 +- pkg/services/ngalert/remote/alertmanager.go | 3 - .../remote/forked_alertmanager_test.go | 16 --- .../ngalert/remote/mock/remoteAlertmanager.go | 102 ++++++++++++------ .../remote_primary_forked_alertmanager.go | 5 - .../remote_secondary_forked_alertmanager.go | 5 - 8 files changed, 131 insertions(+), 102 deletions(-) diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index 8755eb2baa9..3eaf3073754 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -417,9 +417,6 @@ func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.Po return am.Base.PutAlerts(alerts) } -// CleanUp no-ops as no files are stored on disk. -func (am *alertmanager) CleanUp() {} - // AlertValidationError is the error capturing the validation errors // faced on the alerts. type AlertValidationError struct { diff --git a/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go b/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go index fa94d199e65..243297b297d 100644 --- a/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.34.2. DO NOT EDIT. +// Code generated by mockery v2.42.1. DO NOT EDIT. package alertmanager_mock @@ -34,6 +34,10 @@ func (_m *AlertmanagerMock) EXPECT() *AlertmanagerMock_Expecter { func (_m *AlertmanagerMock) ApplyConfig(_a0 context.Context, _a1 *models.AlertConfiguration) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for ApplyConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.AlertConfiguration) error); ok { r0 = rf(_a0, _a1) @@ -73,42 +77,14 @@ func (_c *AlertmanagerMock_ApplyConfig_Call) RunAndReturn(run func(context.Conte return _c } -// CleanUp provides a mock function with given fields: -func (_m *AlertmanagerMock) CleanUp() { - _m.Called() -} - -// AlertmanagerMock_CleanUp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanUp' -type AlertmanagerMock_CleanUp_Call struct { - *mock.Call -} - -// CleanUp is a helper method to define mock.On call -func (_e *AlertmanagerMock_Expecter) CleanUp() *AlertmanagerMock_CleanUp_Call { - return &AlertmanagerMock_CleanUp_Call{Call: _e.mock.On("CleanUp")} -} - -func (_c *AlertmanagerMock_CleanUp_Call) Run(run func()) *AlertmanagerMock_CleanUp_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *AlertmanagerMock_CleanUp_Call) Return() *AlertmanagerMock_CleanUp_Call { - _c.Call.Return() - return _c -} - -func (_c *AlertmanagerMock_CleanUp_Call) RunAndReturn(run func()) *AlertmanagerMock_CleanUp_Call { - _c.Call.Return(run) - return _c -} - // CreateSilence provides a mock function with given fields: _a0, _a1 func (_m *AlertmanagerMock) CreateSilence(_a0 context.Context, _a1 *v2models.PostableSilence) (string, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for CreateSilence") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *v2models.PostableSilence) (string, error)); ok { @@ -162,6 +138,10 @@ func (_c *AlertmanagerMock_CreateSilence_Call) RunAndReturn(run func(context.Con func (_m *AlertmanagerMock) DeleteSilence(_a0 context.Context, _a1 string) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for DeleteSilence") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(_a0, _a1) @@ -205,6 +185,10 @@ func (_c *AlertmanagerMock_DeleteSilence_Call) RunAndReturn(run func(context.Con func (_m *AlertmanagerMock) GetAlertGroups(ctx context.Context, active bool, silenced bool, inhibited bool, filter []string, receiver string) (v2models.AlertGroups, error) { ret := _m.Called(ctx, active, silenced, inhibited, filter, receiver) + if len(ret) == 0 { + panic("no return value specified for GetAlertGroups") + } + var r0 v2models.AlertGroups var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool, bool, bool, []string, string) (v2models.AlertGroups, error)); ok { @@ -264,6 +248,10 @@ func (_c *AlertmanagerMock_GetAlertGroups_Call) RunAndReturn(run func(context.Co func (_m *AlertmanagerMock) GetAlerts(ctx context.Context, active bool, silenced bool, inhibited bool, filter []string, receiver string) (v2models.GettableAlerts, error) { ret := _m.Called(ctx, active, silenced, inhibited, filter, receiver) + if len(ret) == 0 { + panic("no return value specified for GetAlerts") + } + var r0 v2models.GettableAlerts var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool, bool, bool, []string, string) (v2models.GettableAlerts, error)); ok { @@ -323,6 +311,10 @@ func (_c *AlertmanagerMock_GetAlerts_Call) RunAndReturn(run func(context.Context func (_m *AlertmanagerMock) GetReceivers(ctx context.Context) ([]v2models.Receiver, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetReceivers") + } + var r0 []v2models.Receiver var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]v2models.Receiver, error)); ok { @@ -377,6 +369,10 @@ func (_c *AlertmanagerMock_GetReceivers_Call) RunAndReturn(run func(context.Cont func (_m *AlertmanagerMock) GetSilence(_a0 context.Context, _a1 string) (v2models.GettableSilence, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for GetSilence") + } + var r0 v2models.GettableSilence var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (v2models.GettableSilence, error)); ok { @@ -430,6 +426,10 @@ func (_c *AlertmanagerMock_GetSilence_Call) RunAndReturn(run func(context.Contex func (_m *AlertmanagerMock) GetStatus() definitions.GettableStatus { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetStatus") + } + var r0 definitions.GettableStatus if rf, ok := ret.Get(0).(func() definitions.GettableStatus); ok { r0 = rf() @@ -471,6 +471,10 @@ func (_c *AlertmanagerMock_GetStatus_Call) RunAndReturn(run func() definitions.G func (_m *AlertmanagerMock) ListSilences(_a0 context.Context, _a1 []string) (v2models.GettableSilences, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for ListSilences") + } + var r0 v2models.GettableSilences var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string) (v2models.GettableSilences, error)); ok { @@ -526,6 +530,10 @@ func (_c *AlertmanagerMock_ListSilences_Call) RunAndReturn(run func(context.Cont func (_m *AlertmanagerMock) PutAlerts(_a0 context.Context, _a1 definitions.PostableAlerts) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for PutAlerts") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, definitions.PostableAlerts) error); ok { r0 = rf(_a0, _a1) @@ -569,6 +577,10 @@ func (_c *AlertmanagerMock_PutAlerts_Call) RunAndReturn(run func(context.Context func (_m *AlertmanagerMock) Ready() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Ready") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -610,6 +622,10 @@ func (_c *AlertmanagerMock_Ready_Call) RunAndReturn(run func() bool) *Alertmanag func (_m *AlertmanagerMock) SaveAndApplyConfig(ctx context.Context, config *definitions.PostableUserConfig) error { ret := _m.Called(ctx, config) + if len(ret) == 0 { + panic("no return value specified for SaveAndApplyConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *definitions.PostableUserConfig) error); ok { r0 = rf(ctx, config) @@ -653,6 +669,10 @@ func (_c *AlertmanagerMock_SaveAndApplyConfig_Call) RunAndReturn(run func(contex func (_m *AlertmanagerMock) SaveAndApplyDefaultConfig(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for SaveAndApplyDefaultConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -727,6 +747,10 @@ func (_c *AlertmanagerMock_StopAndWait_Call) RunAndReturn(run func()) *Alertmana func (_m *AlertmanagerMock) TestReceivers(ctx context.Context, c definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { ret := _m.Called(ctx, c) + if len(ret) == 0 { + panic("no return value specified for TestReceivers") + } + var r0 *notifier.TestReceiversResult var r1 error if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error)); ok { @@ -782,6 +806,10 @@ func (_c *AlertmanagerMock_TestReceivers_Call) RunAndReturn(run func(context.Con func (_m *AlertmanagerMock) TestTemplate(ctx context.Context, c definitions.TestTemplatesConfigBodyParams) (*notify.TestTemplatesResults, error) { ret := _m.Called(ctx, c) + if len(ret) == 0 { + panic("no return value specified for TestTemplate") + } + var r0 *notify.TestTemplatesResults var r1 error if rf, ok := ret.Get(0).(func(context.Context, definitions.TestTemplatesConfigBodyParams) (*notify.TestTemplatesResults, error)); ok { diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 54db57c223d..071fbd7cbe0 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -52,8 +52,7 @@ type Alertmanager interface { TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) TestTemplate(ctx context.Context, c apimodels.TestTemplatesConfigBodyParams) (*TestTemplatesResults, error) - // State - CleanUp() + // Lifecycle StopAndWait() Ready() bool } @@ -325,8 +324,6 @@ func (moa *MultiOrgAlertmanager) SyncAlertmanagersForOrgs(ctx context.Context, o moa.logger.Info("Stopping Alertmanager", "org", orgID) am.StopAndWait() moa.logger.Info("Stopped Alertmanager", "org", orgID) - // Clean up all the remaining resources from this alertmanager. - am.CleanUp() } moa.cleanupOrphanLocalOrgState(ctx, orgsFound) diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index 74f29a363af..f587053ab8b 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -401,9 +401,6 @@ func (am *Alertmanager) Ready() bool { return am.ready } -// CleanUp does not have an equivalent in a "remote Alertmanager" context, we don't have files on disk, no-op. -func (am *Alertmanager) CleanUp() {} - // getFullState returns a base64-encoded protobuf message representing the Alertmanager's internal state. func (am *Alertmanager) getFullState(ctx context.Context) (string, error) { var parts []alertingClusterPB.Part diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index b9ae3d28b66..ca829cb068e 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -314,14 +314,6 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { require.ErrorIs(tt, expErr, err) }) - t.Run("CleanUp", func(tt *testing.T) { - // CleanUp should be called only in the internal Alertmanager, - // there's no cleanup to do in the remote one. - internal, _, forked := genTestAlertmanagers(tt, modeRemoteSecondary) - internal.EXPECT().CleanUp().Once() - forked.CleanUp() - }) - t.Run("StopAndWait", func(tt *testing.T) { { // StopAndWait should be called in both Alertmanagers. @@ -589,14 +581,6 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { require.ErrorIs(tt, expErr, err) }) - t.Run("CleanUp", func(tt *testing.T) { - // CleanUp should be called only in the internal Alertmanager, - // there's no cleanup to do in the remote one. - internal, _, forked := genTestAlertmanagers(tt, modeRemotePrimary) - internal.EXPECT().CleanUp().Once() - forked.CleanUp() - }) - t.Run("StopAndWait", func(tt *testing.T) { // StopAndWait should be called on both Alertmanagers. internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) diff --git a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go index 7c05ffe306d..f749e924b71 100644 --- a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go +++ b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.34.2. DO NOT EDIT. +// Code generated by mockery v2.42.1. DO NOT EDIT. package alertmanager_mock @@ -34,6 +34,10 @@ func (_m *RemoteAlertmanagerMock) EXPECT() *RemoteAlertmanagerMock_Expecter { func (_m *RemoteAlertmanagerMock) ApplyConfig(_a0 context.Context, _a1 *models.AlertConfiguration) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for ApplyConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.AlertConfiguration) error); ok { r0 = rf(_a0, _a1) @@ -73,42 +77,14 @@ func (_c *RemoteAlertmanagerMock_ApplyConfig_Call) RunAndReturn(run func(context return _c } -// CleanUp provides a mock function with given fields: -func (_m *RemoteAlertmanagerMock) CleanUp() { - _m.Called() -} - -// RemoteAlertmanagerMock_CleanUp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanUp' -type RemoteAlertmanagerMock_CleanUp_Call struct { - *mock.Call -} - -// CleanUp is a helper method to define mock.On call -func (_e *RemoteAlertmanagerMock_Expecter) CleanUp() *RemoteAlertmanagerMock_CleanUp_Call { - return &RemoteAlertmanagerMock_CleanUp_Call{Call: _e.mock.On("CleanUp")} -} - -func (_c *RemoteAlertmanagerMock_CleanUp_Call) Run(run func()) *RemoteAlertmanagerMock_CleanUp_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *RemoteAlertmanagerMock_CleanUp_Call) Return() *RemoteAlertmanagerMock_CleanUp_Call { - _c.Call.Return() - return _c -} - -func (_c *RemoteAlertmanagerMock_CleanUp_Call) RunAndReturn(run func()) *RemoteAlertmanagerMock_CleanUp_Call { - _c.Call.Return(run) - return _c -} - // CompareAndSendConfiguration provides a mock function with given fields: _a0, _a1 func (_m *RemoteAlertmanagerMock) CompareAndSendConfiguration(_a0 context.Context, _a1 *models.AlertConfiguration) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for CompareAndSendConfiguration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.AlertConfiguration) error); ok { r0 = rf(_a0, _a1) @@ -152,6 +128,10 @@ func (_c *RemoteAlertmanagerMock_CompareAndSendConfiguration_Call) RunAndReturn( func (_m *RemoteAlertmanagerMock) CompareAndSendState(_a0 context.Context) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for CompareAndSendState") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(_a0) @@ -194,6 +174,10 @@ func (_c *RemoteAlertmanagerMock_CompareAndSendState_Call) RunAndReturn(run func func (_m *RemoteAlertmanagerMock) CreateSilence(_a0 context.Context, _a1 *v2models.PostableSilence) (string, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for CreateSilence") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *v2models.PostableSilence) (string, error)); ok { @@ -247,6 +231,10 @@ func (_c *RemoteAlertmanagerMock_CreateSilence_Call) RunAndReturn(run func(conte func (_m *RemoteAlertmanagerMock) DeleteSilence(_a0 context.Context, _a1 string) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for DeleteSilence") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(_a0, _a1) @@ -290,6 +278,10 @@ func (_c *RemoteAlertmanagerMock_DeleteSilence_Call) RunAndReturn(run func(conte func (_m *RemoteAlertmanagerMock) GetAlertGroups(ctx context.Context, active bool, silenced bool, inhibited bool, filter []string, receiver string) (v2models.AlertGroups, error) { ret := _m.Called(ctx, active, silenced, inhibited, filter, receiver) + if len(ret) == 0 { + panic("no return value specified for GetAlertGroups") + } + var r0 v2models.AlertGroups var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool, bool, bool, []string, string) (v2models.AlertGroups, error)); ok { @@ -349,6 +341,10 @@ func (_c *RemoteAlertmanagerMock_GetAlertGroups_Call) RunAndReturn(run func(cont func (_m *RemoteAlertmanagerMock) GetAlerts(ctx context.Context, active bool, silenced bool, inhibited bool, filter []string, receiver string) (v2models.GettableAlerts, error) { ret := _m.Called(ctx, active, silenced, inhibited, filter, receiver) + if len(ret) == 0 { + panic("no return value specified for GetAlerts") + } + var r0 v2models.GettableAlerts var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool, bool, bool, []string, string) (v2models.GettableAlerts, error)); ok { @@ -408,6 +404,10 @@ func (_c *RemoteAlertmanagerMock_GetAlerts_Call) RunAndReturn(run func(context.C func (_m *RemoteAlertmanagerMock) GetReceivers(ctx context.Context) ([]v2models.Receiver, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetReceivers") + } + var r0 []v2models.Receiver var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]v2models.Receiver, error)); ok { @@ -462,6 +462,10 @@ func (_c *RemoteAlertmanagerMock_GetReceivers_Call) RunAndReturn(run func(contex func (_m *RemoteAlertmanagerMock) GetSilence(_a0 context.Context, _a1 string) (v2models.GettableSilence, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for GetSilence") + } + var r0 v2models.GettableSilence var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (v2models.GettableSilence, error)); ok { @@ -515,6 +519,10 @@ func (_c *RemoteAlertmanagerMock_GetSilence_Call) RunAndReturn(run func(context. func (_m *RemoteAlertmanagerMock) GetStatus() definitions.GettableStatus { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetStatus") + } + var r0 definitions.GettableStatus if rf, ok := ret.Get(0).(func() definitions.GettableStatus); ok { r0 = rf() @@ -556,6 +564,10 @@ func (_c *RemoteAlertmanagerMock_GetStatus_Call) RunAndReturn(run func() definit func (_m *RemoteAlertmanagerMock) ListSilences(_a0 context.Context, _a1 []string) (v2models.GettableSilences, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for ListSilences") + } + var r0 v2models.GettableSilences var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string) (v2models.GettableSilences, error)); ok { @@ -611,6 +623,10 @@ func (_c *RemoteAlertmanagerMock_ListSilences_Call) RunAndReturn(run func(contex func (_m *RemoteAlertmanagerMock) PutAlerts(_a0 context.Context, _a1 definitions.PostableAlerts) error { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for PutAlerts") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, definitions.PostableAlerts) error); ok { r0 = rf(_a0, _a1) @@ -654,6 +670,10 @@ func (_c *RemoteAlertmanagerMock_PutAlerts_Call) RunAndReturn(run func(context.C func (_m *RemoteAlertmanagerMock) Ready() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Ready") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -695,6 +715,10 @@ func (_c *RemoteAlertmanagerMock_Ready_Call) RunAndReturn(run func() bool) *Remo func (_m *RemoteAlertmanagerMock) SaveAndApplyConfig(ctx context.Context, config *definitions.PostableUserConfig) error { ret := _m.Called(ctx, config) + if len(ret) == 0 { + panic("no return value specified for SaveAndApplyConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *definitions.PostableUserConfig) error); ok { r0 = rf(ctx, config) @@ -738,6 +762,10 @@ func (_c *RemoteAlertmanagerMock_SaveAndApplyConfig_Call) RunAndReturn(run func( func (_m *RemoteAlertmanagerMock) SaveAndApplyDefaultConfig(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for SaveAndApplyDefaultConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -812,6 +840,10 @@ func (_c *RemoteAlertmanagerMock_StopAndWait_Call) RunAndReturn(run func()) *Rem func (_m *RemoteAlertmanagerMock) TestReceivers(ctx context.Context, c definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { ret := _m.Called(ctx, c) + if len(ret) == 0 { + panic("no return value specified for TestReceivers") + } + var r0 *notifier.TestReceiversResult var r1 error if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error)); ok { @@ -867,6 +899,10 @@ func (_c *RemoteAlertmanagerMock_TestReceivers_Call) RunAndReturn(run func(conte func (_m *RemoteAlertmanagerMock) TestTemplate(ctx context.Context, c definitions.TestTemplatesConfigBodyParams) (*notify.TestTemplatesResults, error) { ret := _m.Called(ctx, c) + if len(ret) == 0 { + panic("no return value specified for TestTemplate") + } + var r0 *notify.TestTemplatesResults var r1 error if rf, ok := ret.Get(0).(func(context.Context, definitions.TestTemplatesConfigBodyParams) (*notify.TestTemplatesResults, error)); ok { diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index a10a2c41397..030cc607fff 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -91,11 +91,6 @@ func (fam *RemotePrimaryForkedAlertmanager) TestTemplate(ctx context.Context, c return fam.remote.TestTemplate(ctx, c) } -func (fam *RemotePrimaryForkedAlertmanager) CleanUp() { - // No cleanup to do in the remote Alertmanager. - fam.internal.CleanUp() -} - func (fam *RemotePrimaryForkedAlertmanager) StopAndWait() { fam.internal.StopAndWait() fam.remote.StopAndWait() diff --git a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go index 7e46e01de62..aedb8b554cf 100644 --- a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go @@ -165,11 +165,6 @@ func (fam *RemoteSecondaryForkedAlertmanager) TestTemplate(ctx context.Context, return fam.internal.TestTemplate(ctx, c) } -func (fam *RemoteSecondaryForkedAlertmanager) CleanUp() { - // No cleanup to do in the remote Alertmanager. - fam.internal.CleanUp() -} - func (fam *RemoteSecondaryForkedAlertmanager) StopAndWait() { // Stop the internal Alertmanager. fam.internal.StopAndWait() From 5c4a2de59bf867916b3970973d67bc59743451a1 Mon Sep 17 00:00:00 2001 From: idafurjes <36131195+idafurjes@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:27:43 +0200 Subject: [PATCH 02/12] Add FolderUID for library elements (#83819) * Revert "Revert "Add FolderUID for library elements" (#83776)" This reverts commit 0dfdb2ae47383beb41aa981313b4f12b4b29a8fa. * Fix bug, dashboard id and library element fodler_id are the corresponding values Dashboard table hold both dahboards and tables --- pkg/api/dashboard_test.go | 2 +- .../dashboardimport/service/service.go | 2 +- .../dashboardimport/service/service_test.go | 19 +++--- pkg/services/folder/folderimpl/folder_test.go | 6 ++ pkg/services/libraryelements/database.go | 20 ++++-- .../libraryelements_create_test.go | 15 ++-- .../libraryelements_delete_test.go | 2 +- .../libraryelements_get_all_test.go | 67 +++++++++++------- .../libraryelements_get_test.go | 4 +- .../libraryelements_patch_test.go | 68 +++++++++++-------- .../libraryelements_permissions_test.go | 37 +++++----- .../libraryelements/libraryelements_test.go | 30 ++++---- pkg/services/libraryelements/model/model.go | 18 ++--- pkg/services/libraryelements/writers.go | 2 + pkg/services/librarypanels/librarypanels.go | 21 +++--- .../librarypanels/librarypanels_test.go | 51 +++++++------- .../sqlstore/migrations/libraryelements.go | 22 ++++++ 17 files changed, 229 insertions(+), 157 deletions(-) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index c53c775426e..be403723bb3 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -1035,7 +1035,7 @@ func (m *mockLibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Con return nil } -func (m *mockLibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func (m *mockLibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error { return nil } diff --git a/pkg/services/dashboardimport/service/service.go b/pkg/services/dashboardimport/service/service.go index 842651e2759..00cd032496c 100644 --- a/pkg/services/dashboardimport/service/service.go +++ b/pkg/services/dashboardimport/service/service.go @@ -141,7 +141,7 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc() // nolint:staticcheck - err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, libraryElements, generatedDash.Get("panels").MustArray(), req.FolderId) + err = s.libraryPanelService.ImportLibraryPanelsForDashboard(ctx, req.User, libraryElements, generatedDash.Get("panels").MustArray(), req.FolderId, req.FolderUid) if err != nil { return nil, err } diff --git a/pkg/services/dashboardimport/service/service_test.go b/pkg/services/dashboardimport/service/service_test.go index 19e0dba8937..e8958f22c9c 100644 --- a/pkg/services/dashboardimport/service/service_test.go +++ b/pkg/services/dashboardimport/service/service_test.go @@ -47,7 +47,7 @@ func TestImportDashboardService(t *testing.T) { importLibraryPanelsForDashboard := false connectLibraryPanelsForDashboardCalled := false libraryPanelService := &libraryPanelServiceMock{ - importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { + importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error { importLibraryPanelsForDashboard = true return nil }, @@ -75,9 +75,8 @@ func TestImportDashboardService(t *testing.T) { Inputs: []dashboardimport.ImportDashboardInput{ {Name: "*", Type: "datasource", Value: "prom"}, }, - User: &user.SignedInUser{UserID: 2, OrgRole: org.RoleAdmin, OrgID: 3}, - // FolderId: 5, - FolderUid: "123", + User: &user.SignedInUser{UserID: 2, OrgRole: org.RoleAdmin, OrgID: 3}, + FolderUid: "folderUID", } resp, err := s.ImportDashboard(context.Background(), req) require.NoError(t, err) @@ -91,7 +90,7 @@ func TestImportDashboardService(t *testing.T) { require.Equal(t, int64(3), importDashboardArg.OrgID) require.Equal(t, int64(2), userID) require.Equal(t, "prometheus", importDashboardArg.Dashboard.PluginID) - require.Equal(t, "123", importDashboardArg.Dashboard.FolderUID) + require.Equal(t, "folderUID", importDashboardArg.Dashboard.FolderUID) panel := importDashboardArg.Dashboard.Data.Get("panels").GetIndex(0) require.Equal(t, "prom", panel.Get("datasource").MustString()) @@ -143,7 +142,7 @@ func TestImportDashboardService(t *testing.T) { {Name: "*", Type: "datasource", Value: "prom"}, }, User: &user.SignedInUser{UserID: 2, OrgRole: org.RoleAdmin, OrgID: 3}, - FolderUid: "123", + FolderUid: "folderUID", } resp, err := s.ImportDashboard(context.Background(), req) require.NoError(t, err) @@ -157,7 +156,7 @@ func TestImportDashboardService(t *testing.T) { require.Equal(t, int64(3), importDashboardArg.OrgID) require.Equal(t, int64(2), userID) require.Equal(t, "", importDashboardArg.Dashboard.PluginID) - require.Equal(t, "123", importDashboardArg.Dashboard.FolderUID) + require.Equal(t, "folderUID", importDashboardArg.Dashboard.FolderUID) panel := importDashboardArg.Dashboard.Data.Get("panels").GetIndex(0) require.Equal(t, "prom", panel.Get("datasource").MustString()) @@ -211,7 +210,7 @@ func (s *dashboardServiceMock) ImportDashboard(ctx context.Context, dto *dashboa type libraryPanelServiceMock struct { librarypanels.Service connectLibraryPanelsForDashboardFunc func(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error - importLibraryPanelsForDashboardFunc func(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error + importLibraryPanelsForDashboardFunc func(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error } var _ librarypanels.Service = (*libraryPanelServiceMock)(nil) @@ -224,9 +223,9 @@ func (s *libraryPanelServiceMock) ConnectLibraryPanelsForDashboard(ctx context.C return nil } -func (s *libraryPanelServiceMock) ImportLibraryPanelsForDashboard(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func (s *libraryPanelServiceMock) ImportLibraryPanelsForDashboard(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error { if s.importLibraryPanelsForDashboardFunc != nil { - return s.importLibraryPanelsForDashboardFunc(ctx, signedInUser, libraryPanels, panels, folderID) + return s.importLibraryPanelsForDashboardFunc(ctx, signedInUser, libraryPanels, panels, folderID, folderUID) } return nil diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 608047fa4d7..56ab7e128a3 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -448,10 +448,12 @@ func TestIntegrationNestedFolderService(t *testing.T) { // nolint:staticcheck libraryElementCmd.FolderID = parent.ID + libraryElementCmd.FolderUID = &parent.UID _, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) require.NoError(t, err) // nolint:staticcheck libraryElementCmd.FolderID = subfolder.ID + libraryElementCmd.FolderUID = &subfolder.UID _, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) require.NoError(t, err) @@ -528,10 +530,12 @@ func TestIntegrationNestedFolderService(t *testing.T) { // nolint:staticcheck libraryElementCmd.FolderID = parent.ID + libraryElementCmd.FolderUID = &parent.UID _, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) require.NoError(t, err) // nolint:staticcheck libraryElementCmd.FolderID = subfolder.ID + libraryElementCmd.FolderUID = &subfolder.UID _, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) require.NoError(t, err) @@ -667,11 +671,13 @@ func TestIntegrationNestedFolderService(t *testing.T) { _ = createRule(t, alertStore, subfolder.UID, "sub alert") // nolint:staticcheck libraryElementCmd.FolderID = subfolder.ID + libraryElementCmd.FolderUID = &subPanel.FolderUID subPanel, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) require.NoError(t, err) } // nolint:staticcheck libraryElementCmd.FolderID = parent.ID + libraryElementCmd.FolderUID = &parent.UID parentPanel, err := lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) require.NoError(t, err) diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index a223107d035..4f5932ec6b5 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -149,14 +149,20 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn } metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc() + // folderUID *string will be changed to string + var folderUID string + if cmd.FolderUID != nil { + folderUID = *cmd.FolderUID + } element := model.LibraryElement{ - OrgID: signedInUser.GetOrgID(), - FolderID: cmd.FolderID, // nolint:staticcheck - UID: createUID, - Name: cmd.Name, - Model: updatedModel, - Version: 1, - Kind: cmd.Kind, + OrgID: signedInUser.GetOrgID(), + FolderID: cmd.FolderID, // nolint:staticcheck + FolderUID: folderUID, + UID: createUID, + Name: cmd.Name, + Model: updatedModel, + Version: 1, + Kind: cmd.Kind, Created: time.Now(), Updated: time.Now(), diff --git a/pkg/services/libraryelements/libraryelements_create_test.go b/pkg/services/libraryelements/libraryelements_create_test.go index 8a1dfac99bc..7adca02ebd1 100644 --- a/pkg/services/libraryelements/libraryelements_create_test.go +++ b/pkg/services/libraryelements/libraryelements_create_test.go @@ -15,7 +15,7 @@ func TestCreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) @@ -28,6 +28,7 @@ func TestCreateLibraryElement(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: sc.initialResult.Result.UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -68,7 +69,7 @@ func TestCreateLibraryElement(t *testing.T) { testScenario(t, "When an admin tries to create a library panel that does not exists using an nonexistent UID, it should succeed", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Nonexistent UID") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Nonexistent UID") command.UID = util.GenerateShortUID() sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) @@ -78,6 +79,7 @@ func TestCreateLibraryElement(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: command.UID, Name: "Nonexistent UID", Kind: int64(model.PanelElement), @@ -118,7 +120,7 @@ func TestCreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an existent UID, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Existing UID") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Existing UID") command.UID = sc.initialResult.Result.UID sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) @@ -128,7 +130,7 @@ func TestCreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an invalid UID, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Invalid UID") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Invalid UID") command.UID = "Testing an invalid UID" sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) @@ -138,7 +140,7 @@ func TestCreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that does not exists using an UID that is too long, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Invalid UID") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Invalid UID") command.UID = "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K" sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) @@ -147,7 +149,7 @@ func TestCreateLibraryElement(t *testing.T) { testScenario(t, "When an admin tries to create a library panel where name and panel title differ, it should not update panel title", func(t *testing.T, sc scenarioContext) { - command := getCreatePanelCommand(1, "Library Panel Name") + command := getCreatePanelCommand(1, sc.folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) @@ -156,6 +158,7 @@ func TestCreateLibraryElement(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.UID, Name: "Library Panel Name", Kind: int64(model.PanelElement), diff --git a/pkg/services/libraryelements/libraryelements_delete_test.go b/pkg/services/libraryelements/libraryelements_delete_test.go index 7493c49461c..597787d6ee0 100644 --- a/pkg/services/libraryelements/libraryelements_delete_test.go +++ b/pkg/services/libraryelements/libraryelements_delete_test.go @@ -74,7 +74,7 @@ func TestDeleteLibraryElement(t *testing.T) { Data: simplejson.NewFromAny(dashJSON), } // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID) require.NoError(t, err) diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index d281155a1ba..29cc7819fca 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -39,7 +39,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all panel elements and both panels and variables exist, it should only return panels", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateVariableCommand(sc.folder.ID, "query0") + command := getCreateVariableCommand(sc.folder.ID, sc.folder.UID, "query0") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -64,6 +64,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -106,7 +107,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all variable elements and both panels and variables exist, it should only return panels", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateVariableCommand(sc.folder.ID, "query0") + command := getCreateVariableCommand(sc.folder.ID, sc.folder.UID, "query0") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -131,6 +132,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "query0", Kind: int64(model.VariableElement), @@ -172,7 +174,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist, it should succeed", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -193,6 +195,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -228,6 +231,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[1].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -270,7 +274,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and sort desc is set, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -294,6 +298,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -329,6 +334,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[1].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -371,7 +377,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to existing types, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateCommandWithModel(sc.folder.ID, "Gauge - Library Panel", model.PanelElement, []byte(` + command := getCreateCommandWithModel(sc.folder.ID, sc.folder.UID, "Gauge - Library Panel", model.PanelElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "id": 1, @@ -385,7 +391,7 @@ func TestGetAllLibraryElements(t *testing.T) { require.Equal(t, 200, resp.Status()) // nolint:staticcheck - command = getCreateCommandWithModel(sc.folder.ID, "BarGauge - Library Panel", model.PanelElement, []byte(` + command = getCreateCommandWithModel(sc.folder.ID, sc.folder.UID, "BarGauge - Library Panel", model.PanelElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "id": 1, @@ -417,6 +423,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 3, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "BarGauge - Library Panel", Kind: int64(model.PanelElement), @@ -452,6 +459,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[1].UID, Name: "Gauge - Library Panel", Kind: int64(model.PanelElement), @@ -494,7 +502,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to a nonexistent type, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateCommandWithModel(sc.folder.ID, "Gauge - Library Panel", model.PanelElement, []byte(` + command := getCreateCommandWithModel(sc.folder.ID, sc.folder.UID, "Gauge - Library Panel", model.PanelElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "id": 1, @@ -529,25 +537,24 @@ func TestGetAllLibraryElements(t *testing.T) { } }) - scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to existing folders, it should succeed and the result should be correct", + scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilterUIDs is set to existing folders, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { newFolder := createFolder(t, sc, "NewFolder") // nolint:staticcheck - command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(newFolder.ID, newFolder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) - // nolint:staticcheck - folderFilter := strconv.FormatInt(newFolder.ID, 10) - + folderFilterUID := newFolder.UID err := sc.reqContext.Req.ParseForm() require.NoError(t, err) - sc.reqContext.Req.Form.Add("folderFilter", folderFilter) + sc.reqContext.Req.Form.Add("folderFilterUIDs", folderFilterUID) resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result libraryElementsSearch err = json.Unmarshal(resp.Body(), &result) + require.NoError(t, err) var expected = libraryElementsSearch{ Result: libraryElementsSearchResult{ @@ -559,6 +566,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: newFolder.ID, // nolint:staticcheck + FolderUID: newFolder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -602,15 +610,15 @@ func TestGetAllLibraryElements(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolder(t, sc, "NewFolder") // nolint:staticcheck - command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(newFolder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) - folderFilter := "2020,2021" + folderFilterUIDs := "2020,2021" err := sc.reqContext.Req.ParseForm() require.NoError(t, err) - sc.reqContext.Req.Form.Add("folderFilter", folderFilter) + sc.reqContext.Req.Form.Add("folderFilterUIDs", folderFilterUIDs) resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -633,7 +641,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to General folder, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -658,6 +666,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -693,6 +702,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[1].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -735,7 +745,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and excludeUID is set, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -759,6 +769,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -801,7 +812,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -825,6 +836,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -867,7 +879,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 2, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -892,6 +904,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -934,7 +947,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in the description, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateCommandWithModel(sc.folder.ID, "Text - Library Panel2", model.PanelElement, []byte(` + command := getCreateCommandWithModel(sc.folder.ID, sc.folder.UID, "Text - Library Panel2", model.PanelElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "id": 1, @@ -968,6 +981,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -1010,7 +1024,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in both name and description, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateCommandWithModel(sc.folder.ID, "Some Other", model.PanelElement, []byte(` + command := getCreateCommandWithModel(sc.folder.ID, sc.folder.UID, "Some Other", model.PanelElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "id": 1, @@ -1042,6 +1056,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Some Other", Kind: int64(model.PanelElement), @@ -1077,6 +1092,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[1].UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -1119,7 +1135,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 1 and searchString is panel2, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -1145,6 +1161,7 @@ func TestGetAllLibraryElements(t *testing.T) { ID: 2, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: result.Result.Elements[0].UID, Name: "Text - Library Panel2", Kind: int64(model.PanelElement), @@ -1187,7 +1204,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString is panel, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -1219,7 +1236,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString does not exist, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel2") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) diff --git a/pkg/services/libraryelements/libraryelements_get_test.go b/pkg/services/libraryelements/libraryelements_get_test.go index 0c8e4893c2d..b441505639b 100644 --- a/pkg/services/libraryelements/libraryelements_get_test.go +++ b/pkg/services/libraryelements/libraryelements_get_test.go @@ -35,6 +35,7 @@ func TestGetLibraryElement(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: res.Result.UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), @@ -123,7 +124,7 @@ func TestGetLibraryElement(t *testing.T) { Data: simplejson.NewFromAny(dashJSON), } // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID) require.NoError(t, err) @@ -133,6 +134,7 @@ func TestGetLibraryElement(t *testing.T) { ID: 1, OrgID: 1, FolderID: 1, // nolint:staticcheck + FolderUID: sc.folder.UID, UID: res.Result.UID, Name: "Text - Library Panel", Kind: int64(model.PanelElement), diff --git a/pkg/services/libraryelements/libraryelements_patch_test.go b/pkg/services/libraryelements/libraryelements_patch_test.go index 0af1d96e7cb..e59a19e29b6 100644 --- a/pkg/services/libraryelements/libraryelements_patch_test.go +++ b/pkg/services/libraryelements/libraryelements_patch_test.go @@ -26,8 +26,9 @@ func TestPatchLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolder(t, sc, "NewFolder") cmd := model.PatchLibraryElementCommand{ - FolderID: newFolder.ID, // nolint:staticcheck - Name: "Panel - New name", + FolderID: newFolder.ID, // nolint:staticcheck + FolderUID: &newFolder.UID, + Name: "Panel - New name", Model: []byte(` { "datasource": "${DS_GDEV-TESTDATA}", @@ -50,6 +51,7 @@ func TestPatchLibraryElement(t *testing.T) { ID: 1, OrgID: 1, FolderID: newFolder.ID, // nolint:staticcheck + FolderUID: newFolder.UID, UID: sc.initialResult.Result.UID, Name: "Panel - New name", Kind: int64(model.PanelElement), @@ -91,9 +93,10 @@ func TestPatchLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolder(t, sc, "NewFolder") cmd := model.PatchLibraryElementCommand{ - FolderID: newFolder.ID, // nolint:staticcheck - Kind: int64(model.PanelElement), - Version: 1, + FolderID: newFolder.ID, // nolint:staticcheck + FolderUID: &newFolder.UID, + Kind: int64(model.PanelElement), + Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.reqContext.Req.Body = mockRequestBody(cmd) @@ -102,6 +105,7 @@ func TestPatchLibraryElement(t *testing.T) { var result = validateAndUnMarshalResponse(t, resp) // nolint:staticcheck sc.initialResult.Result.FolderID = newFolder.ID + sc.initialResult.Result.FolderUID = newFolder.UID sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = userInDbAvatar sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated @@ -176,10 +180,11 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with an UID that is too long, it should fail", func(t *testing.T, sc scenarioContext) { cmd := model.PatchLibraryElementCommand{ - FolderID: -1, // nolint:staticcheck - UID: "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K", - Kind: int64(model.PanelElement), - Version: 1, + FolderID: -1, // nolint:staticcheck + FolderUID: &sc.folder.UID, + UID: "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K", + Kind: int64(model.PanelElement), + Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req.Body = mockRequestBody(cmd) @@ -190,16 +195,17 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with an existing UID, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Existing UID") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Existing UID") command.UID = util.GenerateShortUID() sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) cmd := model.PatchLibraryElementCommand{ - FolderID: -1, // nolint:staticcheck - UID: command.UID, - Kind: int64(model.PanelElement), - Version: 1, + FolderID: -1, // nolint:staticcheck + FolderUID: &sc.folder.UID, + UID: command.UID, + Kind: int64(model.PanelElement), + Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req.Body = mockRequestBody(cmd) @@ -312,7 +318,7 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Another Panel") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Another Panel") sc.ctx.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) @@ -331,14 +337,15 @@ func TestPatchLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolder(t, sc, "NewFolder") // nolint:staticcheck - command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel") + command := getCreatePanelCommand(newFolder.ID, newFolder.UID, "Text - Library Panel") sc.ctx.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) cmd := model.PatchLibraryElementCommand{ - FolderID: 1, // nolint:staticcheck - Version: 1, - Kind: int64(model.PanelElement), + FolderID: 1, // nolint:staticcheck + FolderUID: &sc.folder.UID, + Version: 1, + Kind: int64(model.PanelElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) sc.ctx.Req.Body = mockRequestBody(cmd) @@ -349,23 +356,25 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel in another org, it should fail", func(t *testing.T, sc scenarioContext) { cmd := model.PatchLibraryElementCommand{ - FolderID: sc.folder.ID, // nolint:staticcheck - Version: 1, - Kind: int64(model.PanelElement), + FolderID: sc.folder.ID, // nolint:staticcheck + FolderUID: &sc.folder.UID, + Version: 1, + Kind: int64(model.PanelElement), } sc.reqContext.OrgID = 2 sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req.Body = mockRequestBody(cmd) resp := sc.service.patchHandler(sc.reqContext) - require.Equal(t, 404, resp.Status()) + require.Equal(t, 400, resp.Status()) }) scenarioWithPanel(t, "When an admin tries to patch a library panel with an old version number, it should fail", func(t *testing.T, sc scenarioContext) { cmd := model.PatchLibraryElementCommand{ - FolderID: sc.folder.ID, // nolint:staticcheck - Version: 1, - Kind: int64(model.PanelElement), + FolderID: sc.folder.ID, // nolint:staticcheck + FolderUID: &sc.folder.UID, + Version: 1, + Kind: int64(model.PanelElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req.Body = mockRequestBody(cmd) @@ -379,9 +388,10 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with an other kind, it should succeed but panel should not change", func(t *testing.T, sc scenarioContext) { cmd := model.PatchLibraryElementCommand{ - FolderID: sc.folder.ID, // nolint:staticcheck - Version: 1, - Kind: int64(model.VariableElement), + FolderID: sc.folder.ID, // nolint:staticcheck + FolderUID: &sc.folder.UID, + Version: 1, + Kind: int64(model.VariableElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req.Body = mockRequestBody(cmd) diff --git a/pkg/services/libraryelements/libraryelements_permissions_test.go b/pkg/services/libraryelements/libraryelements_permissions_test.go index bb48d58f532..327366115ae 100644 --- a/pkg/services/libraryelements/libraryelements_permissions_test.go +++ b/pkg/services/libraryelements/libraryelements_permissions_test.go @@ -30,7 +30,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { func(t *testing.T, sc scenarioContext) { sc.reqContext.SignedInUser.OrgRole = testCase.role - command := getCreatePanelCommand(0, "Library Panel Name") + command := getCreatePanelCommand(0, "", "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) @@ -40,7 +40,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolder(t, sc, "Folder") // nolint:staticcheck - command := getCreatePanelCommand(folder.ID, "Library Panel Name") + command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -57,7 +57,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role), func(t *testing.T, sc scenarioContext) { folder := createFolder(t, sc, "Folder") - command := getCreatePanelCommand(0, "Library Panel Name") + command := getCreatePanelCommand(0, "", "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -73,7 +73,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in the General folder, it should return correct status", testCase.role), func(t *testing.T, sc scenarioContext) { - cmd := getCreatePanelCommand(0, "Library Panel Name") + cmd := getCreatePanelCommand(0, "", "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(cmd) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -86,7 +86,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to get a library panel from General folder, it should return correct response", testCase.role), func(t *testing.T, sc scenarioContext) { - cmd := getCreatePanelCommand(0, "Library Panel in General Folder") + cmd := getCreatePanelCommand(0, "", "Library Panel in General Folder") sc.reqContext.Req.Body = mockRequestBody(cmd) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -96,6 +96,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { result.Result.Meta.UpdatedBy.AvatarUrl = userInDbAvatar result.Result.Meta.FolderName = "General" result.Result.Meta.FolderUID = "" + result.Result.FolderUID = "general" sc.reqContext.SignedInUser.OrgRole = testCase.role sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) @@ -111,7 +112,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to get all library panels from General folder, it should return correct response", testCase.role), func(t *testing.T, sc scenarioContext) { - cmd := getCreatePanelCommand(0, "Library Panel in General Folder") + cmd := getCreatePanelCommand(0, "", "Library Panel in General Folder") sc.reqContext.Req.Body = mockRequestBody(cmd) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -183,7 +184,7 @@ func TestLibraryElementCreatePermissions(t *testing.T) { } // nolint:staticcheck - command := getCreatePanelCommand(folder.ID, "Library Panel Name") + command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) @@ -236,7 +237,7 @@ func TestLibraryElementPatchPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { fromFolder := createFolder(t, sc, "FromFolder") // nolint:staticcheck - command := getCreatePanelCommand(fromFolder.ID, "Library Panel Name") + command := getCreatePanelCommand(fromFolder.ID, fromFolder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -248,7 +249,7 @@ func TestLibraryElementPatchPermissions(t *testing.T) { } // nolint:staticcheck - cmd := model.PatchLibraryElementCommand{FolderID: toFolder.ID, Version: 1, Kind: int64(model.PanelElement)} + cmd := model.PatchLibraryElementCommand{FolderID: toFolder.ID, FolderUID: &toFolder.UID, Version: 1, Kind: int64(model.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) sc.reqContext.Req.Body = mockRequestBody(cmd) resp = sc.service.patchHandler(sc.reqContext) @@ -298,7 +299,7 @@ func TestLibraryElementDeletePermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolder(t, sc, "Folder") // nolint:staticcheck - command := getCreatePanelCommand(folder.ID, "Library Panel Name") + command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -317,27 +318,29 @@ func TestLibraryElementDeletePermissions(t *testing.T) { func TestLibraryElementsWithMissingFolders(t *testing.T) { testScenario(t, "When a user tries to create a library panel in a folder that doesn't exist, it should fail", func(t *testing.T, sc scenarioContext) { - command := getCreatePanelCommand(-100, "Library Panel Name") + command := getCreatePanelCommand(0, "badFolderUID", "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) - require.Equal(t, 404, resp.Status()) + fmt.Println(string(resp.Body())) + require.Equal(t, 400, resp.Status()) }) testScenario(t, "When a user tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", func(t *testing.T, sc scenarioContext) { folder := createFolder(t, sc, "Folder") // nolint:staticcheck - command := getCreatePanelCommand(folder.ID, "Library Panel Name") + command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) + folderUID := "badFolderUID" // nolint:staticcheck - cmd := model.PatchLibraryElementCommand{FolderID: -100, Version: 1, Kind: int64(model.PanelElement)} + cmd := model.PatchLibraryElementCommand{FolderID: -100, FolderUID: &folderUID, Version: 1, Kind: int64(model.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) sc.reqContext.Req.Body = mockRequestBody(cmd) resp = sc.service.patchHandler(sc.reqContext) - require.Equal(t, 404, resp.Status()) + require.Equal(t, 400, resp.Status()) }) } @@ -367,7 +370,7 @@ func TestLibraryElementsGetPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolder(t, sc, "Folder") // nolint:staticcheck - cmd := getCreatePanelCommand(folder.ID, "Library Panel") + cmd := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel") sc.reqContext.Req.Body = mockRequestBody(cmd) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) @@ -418,7 +421,7 @@ func TestLibraryElementsGetAllPermissions(t *testing.T) { for i := 1; i <= 2; i++ { folder := createFolder(t, sc, fmt.Sprintf("Folder%d", i)) // nolint:staticcheck - cmd := getCreatePanelCommand(folder.ID, fmt.Sprintf("Library Panel %d", i)) + cmd := getCreatePanelCommand(folder.ID, folder.UID, fmt.Sprintf("Library Panel %d", i)) sc.reqContext.Req.Body = mockRequestBody(cmd) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 588bf8cce3c..189ccfac350 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -88,7 +88,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { Data: simplejson.NewFromAny(dashJSON), } // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID) require.NoError(t, err) @@ -105,7 +105,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreateVariableCommand(sc.folder.ID, "query0") + command := getCreateVariableCommand(sc.folder.ID, sc.folder.UID, "query0") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) @@ -163,7 +163,7 @@ func TestGetLibraryPanelConnections(t *testing.T) { Data: simplejson.NewFromAny(dashJSON), } // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID) err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID) require.NoError(t, err) @@ -202,6 +202,7 @@ type libraryElement struct { OrgID int64 `json:"orgId"` // Deprecated: use FolderUID instead FolderID int64 `json:"folderId"` + FolderUID string `json:"folderUid"` UID string `json:"uid"` Name string `json:"name"` Kind int64 `json:"kind"` @@ -231,8 +232,8 @@ type libraryElementsSearchResult struct { PerPage int `json:"perPage"` } -func getCreatePanelCommand(folderID int64, name string) model.CreateLibraryElementCommand { - command := getCreateCommandWithModel(folderID, name, model.PanelElement, []byte(` +func getCreatePanelCommand(folderID int64, folderUID string, name string) model.CreateLibraryElementCommand { + command := getCreateCommandWithModel(folderID, folderUID, name, model.PanelElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "id": 1, @@ -245,8 +246,8 @@ func getCreatePanelCommand(folderID int64, name string) model.CreateLibraryEleme return command } -func getCreateVariableCommand(folderID int64, name string) model.CreateLibraryElementCommand { - command := getCreateCommandWithModel(folderID, name, model.VariableElement, []byte(` +func getCreateVariableCommand(folderID int64, folderUID, name string) model.CreateLibraryElementCommand { + command := getCreateCommandWithModel(folderID, folderUID, name, model.VariableElement, []byte(` { "datasource": "${DS_GDEV-TESTDATA}", "name": "query0", @@ -258,12 +259,12 @@ func getCreateVariableCommand(folderID int64, name string) model.CreateLibraryEl return command } -func getCreateCommandWithModel(folderID int64, name string, kind model.LibraryElementKind, byteModel []byte) model.CreateLibraryElementCommand { +func getCreateCommandWithModel(folderID int64, folderUID, name string, kind model.LibraryElementKind, byteModel []byte) model.CreateLibraryElementCommand { command := model.CreateLibraryElementCommand{ - FolderID: folderID, // nolint:staticcheck - Name: name, - Model: byteModel, - Kind: int64(kind), + FolderUID: &folderUID, + Name: name, + Model: byteModel, + Kind: int64(kind), } return command @@ -280,9 +281,10 @@ type scenarioContext struct { log log.Logger } -func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64) *dashboards.Dashboard { +func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64, folderUID string) *dashboards.Dashboard { // nolint:staticcheck dash.FolderID = folderID + dash.FolderUID = folderUID dashItem := &dashboards.SaveDashboardDTO{ Dashboard: dash, Message: "", @@ -396,7 +398,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena testScenario(t, desc, func(t *testing.T, sc scenarioContext) { // nolint:staticcheck - command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel") + command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) sc.initialResult = validateAndUnMarshalResponse(t, resp) diff --git a/pkg/services/libraryelements/model/model.go b/pkg/services/libraryelements/model/model.go index df8c1abd7a4..e53ef13816f 100644 --- a/pkg/services/libraryelements/model/model.go +++ b/pkg/services/libraryelements/model/model.go @@ -20,6 +20,7 @@ type LibraryElement struct { OrgID int64 `xorm:"org_id"` // Deprecated: use FolderUID instead FolderID int64 `xorm:"folder_id"` + FolderUID string `xorm:"folder_uid"` UID string `xorm:"uid"` Name string Kind int64 @@ -41,6 +42,7 @@ type LibraryElementWithMeta struct { OrgID int64 `xorm:"org_id"` // Deprecated: use FolderUID instead FolderID int64 `xorm:"folder_id"` + FolderUID string `xorm:"folder_uid"` UID string `xorm:"uid"` Name string Kind int64 @@ -53,7 +55,6 @@ type LibraryElementWithMeta struct { Updated time.Time FolderName string - FolderUID string `xorm:"folder_uid"` ConnectedDashboards int64 CreatedBy int64 UpdatedBy int64 @@ -217,13 +218,14 @@ type GetLibraryElementCommand struct { // SearchLibraryElementsQuery is the query used for searching for Elements type SearchLibraryElementsQuery struct { - PerPage int - Page int - SearchString string - SortDirection string - Kind int - TypeFilter string - ExcludeUID string + PerPage int + Page int + SearchString string + SortDirection string + Kind int + TypeFilter string + ExcludeUID string + // Deprecated: use FolderFilterUIDs instead FolderFilter string FolderFilterUIDs string } diff --git a/pkg/services/libraryelements/writers.go b/pkg/services/libraryelements/writers.go index e1d470f6cdc..18781637fac 100644 --- a/pkg/services/libraryelements/writers.go +++ b/pkg/services/libraryelements/writers.go @@ -82,6 +82,7 @@ type FolderFilter struct { func parseFolderFilter(query model.SearchLibraryElementsQuery) FolderFilter { folderIDs := make([]string, 0) folderUIDs := make([]string, 0) + // nolint:staticcheck hasFolderFilter := len(strings.TrimSpace(query.FolderFilter)) > 0 hasFolderFilterUID := len(strings.TrimSpace(query.FolderFilterUIDs)) > 0 @@ -100,6 +101,7 @@ func parseFolderFilter(query model.SearchLibraryElementsQuery) FolderFilter { if hasFolderFilter { result.includeGeneralFolder = false + // nolint:staticcheck folderIDs = strings.Split(query.FolderFilter, ",") metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc() // nolint:staticcheck diff --git a/pkg/services/librarypanels/librarypanels.go b/pkg/services/librarypanels/librarypanels.go index 244f925ddeb..91fd6107a5d 100644 --- a/pkg/services/librarypanels/librarypanels.go +++ b/pkg/services/librarypanels/librarypanels.go @@ -42,7 +42,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout // Service is a service for operating on library panels. type Service interface { ConnectLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error - ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error + ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error } type LibraryInfo struct { @@ -117,11 +117,11 @@ func connectLibraryPanelsRecursively(c context.Context, panels []any, libraryPan } // ImportLibraryPanelsForDashboard loops through all panels in dashboard JSON and creates any missing library panels in the database. -func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { - return importLibraryPanelsRecursively(c, lps.LibraryElementService, signedInUser, libraryPanels, panels, folderID) +func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error { + return importLibraryPanelsRecursively(c, lps.LibraryElementService, signedInUser, libraryPanels, panels, folderID, folderUID) } -func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error { for _, panel := range panels { panelAsJSON := simplejson.NewFromAny(panel) libraryPanel := panelAsJSON.Get("libraryPanel") @@ -132,7 +132,7 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S // we have a row if panelType == "row" { - err := importLibraryPanelsRecursively(c, service, signedInUser, libraryPanels, panelAsJSON.Get("panels").MustArray(), folderID) + err := importLibraryPanelsRecursively(c, service, signedInUser, libraryPanels, panelAsJSON.Get("panels").MustArray(), folderID, folderUID) if err != nil { return err } @@ -168,11 +168,12 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryPanels).Inc() var cmd = model.CreateLibraryElementCommand{ - FolderID: folderID, // nolint:staticcheck - Name: name, - Model: Model, - Kind: int64(model.PanelElement), - UID: UID, + FolderID: folderID, // nolint:staticcheck + FolderUID: &folderUID, + Name: name, + Model: Model, + Kind: int64(model.PanelElement), + UID: UID, } _, err = service.CreateElement(c, signedInUser, cmd) if err != nil { diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 0474ac609fb..ab36bda9fa1 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -85,8 +85,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash) err := sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB) require.NoError(t, err) @@ -100,8 +99,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { scenarioWithLibraryPanel(t, "When an admin tries to store a dashboard with library panels inside and outside of rows, it should connect all", func(t *testing.T, sc scenarioContext) { cmd := model.CreateLibraryElementCommand{ - FolderID: sc.initialResult.Result.FolderID, // nolint:staticcheck - Name: "Outside row", + Name: "Outside row", Model: []byte(` { "datasource": "${DS_GDEV-TESTDATA}", @@ -111,7 +109,8 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { "description": "A description" } `), - Kind: int64(model.PanelElement), + Kind: int64(model.PanelElement), + FolderUID: &sc.folder.UID, } outsidePanel, err := sc.elementService.CreateElement(sc.ctx, sc.user, cmd) require.NoError(t, err) @@ -184,8 +183,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash) err = sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB) require.NoError(t, err) @@ -231,8 +229,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash) err := sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB) require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error()) @@ -241,8 +238,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { scenarioWithLibraryPanel(t, "When an admin tries to store a dashboard with unused/removed library panels, it should disconnect unused/removed library panels", func(t *testing.T, sc scenarioContext) { unused, err := sc.elementService.CreateElement(sc.ctx, sc.user, model.CreateLibraryElementCommand{ - FolderID: sc.folder.ID, // nolint:staticcheck - Name: "Unused Libray Panel", + Name: "Unused Libray Panel", Model: []byte(` { "datasource": "${DS_GDEV-TESTDATA}", @@ -252,7 +248,8 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { "description": "Unused description" } `), - Kind: int64(model.PanelElement), + Kind: int64(model.PanelElement), + FolderUID: &sc.folder.UID, }) require.NoError(t, err) dashJSON := map[string]any{ @@ -288,8 +285,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - // nolint:staticcheck - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash) err = sc.elementService.ConnectElementsToDashboard(sc.ctx, sc.user, []string{sc.initialResult.Result.UID}, dashInDB.ID) require.NoError(t, err) @@ -398,7 +394,7 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { require.EqualError(t, err, model.ErrLibraryElementNotFound.Error()) - err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0) + err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0, "") require.NoError(t, err) element, err := sc.elementService.GetElement(sc.ctx, sc.user, @@ -439,14 +435,14 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { require.NoError(t, err) // nolint:staticcheck - err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.New(), panels, sc.folder.ID) + err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.New(), panels, sc.folder.ID, sc.folder.UID) require.NoError(t, err) element, err := sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: existingUID, FolderName: dashboards.RootFolderName}) require.NoError(t, err) var expected = getExpected(t, element, existingUID, existingName, sc.initialResult.Result.Model) - expected.FolderID = sc.initialResult.Result.FolderID + expected.FolderUID = sc.initialResult.Result.FolderUID expected.Description = sc.initialResult.Result.Description expected.Meta.FolderUID = sc.folder.UID expected.Meta.FolderName = sc.folder.Title @@ -555,7 +551,7 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { _, err = sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: insideUID, FolderName: dashboards.RootFolderName}) require.EqualError(t, err, model.ErrLibraryElementNotFound.Error()) - err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0) + err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0, "") require.NoError(t, err) element, err := sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: outsideUID, FolderName: dashboards.RootFolderName}) @@ -577,9 +573,11 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { } type libraryPanel struct { - ID int64 - OrgID int64 + ID int64 + OrgID int64 + // Deprecated: use FolderUID instead FolderID int64 + FolderUID string UID string Name string Type string @@ -615,6 +613,7 @@ type libraryElement struct { ID int64 `json:"id"` OrgID int64 `json:"orgId"` FolderID int64 `json:"folderId"` + FolderUID string `json:"folderUid"` UID string `json:"uid"` Name string `json:"name"` Kind int64 `json:"kind"` @@ -648,7 +647,6 @@ func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement return libraryElement{ ID: res.ID, OrgID: res.OrgID, - FolderID: res.FolderID, // nolint:staticcheck UID: res.UID, Name: res.Name, Type: res.Type, @@ -714,9 +712,7 @@ func getExpected(t *testing.T, res model.LibraryElementDTO, UID string, name str } } -func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash *dashboards.Dashboard, folderID int64) *dashboards.Dashboard { - // nolint:staticcheck - dash.FolderID = folderID +func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash *dashboards.Dashboard) *dashboards.Dashboard { dashItem := &dashboards.SaveDashboardDTO{ Dashboard: dash, Message: "", @@ -772,8 +768,9 @@ func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, s testScenario(t, desc, func(t *testing.T, sc scenarioContext) { command := model.CreateLibraryElementCommand{ - FolderID: sc.folder.ID, // nolint:staticcheck - Name: "Text - Library Panel", + FolderID: sc.folder.ID, // nolint:staticcheck + FolderUID: &sc.folder.UID, + Name: "Text - Library Panel", Model: []byte(` { "datasource": "${DS_GDEV-TESTDATA}", @@ -795,7 +792,7 @@ func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, s Result: libraryPanel{ ID: resp.ID, OrgID: resp.OrgID, - FolderID: resp.FolderID, // nolint:staticcheck + FolderUID: resp.FolderUID, UID: resp.UID, Name: resp.Name, Type: resp.Type, diff --git a/pkg/services/sqlstore/migrations/libraryelements.go b/pkg/services/sqlstore/migrations/libraryelements.go index 3215cb085bb..903411997ac 100644 --- a/pkg/services/sqlstore/migrations/libraryelements.go +++ b/pkg/services/sqlstore/migrations/libraryelements.go @@ -61,4 +61,26 @@ func addLibraryElementsMigrations(mg *migrator.Migrator) { mg.AddMigration("alter library_element model to mediumtext", migrator.NewRawSQLMigration(""). Mysql("ALTER TABLE library_element MODIFY model MEDIUMTEXT NOT NULL;")) + + q := `UPDATE library_element + SET folder_uid = dashboard.uid + FROM dashboard + WHERE library_element.folder_id = dashboard.id AND library_element.org_id = dashboard.org_id` + + if mg.Dialect.DriverName() == migrator.MySQL { + q = `UPDATE library_element + SET folder_uid = ( + SELECT dashboard.uid + FROM dashboard + WHERE library_element.folder_id = dashboard.id AND library_element.org_id = dashboard.org_id + )` + } + + mg.AddMigration("add library_element folder uid", migrator.NewAddColumnMigration(libraryElementsV1, &migrator.Column{ + Name: "folder_uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: true, + })) + + mg.AddMigration("populate library_element folder_uid", migrator.NewRawSQLMigration(q)) + + mg.AddMigration("add index library_element org_id-folder_uid-name-kind", migrator.NewAddIndexMigration(libraryElementsV1, &migrator.Index{Cols: []string{"org_id", "folder_uid", "name", "kind"}, Type: migrator.UniqueIndex})) } From 02096c4e22c2813eeb269262579e4aba2f523c2c Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:33:59 +0100 Subject: [PATCH 03/12] Dashboards: Add isViewing to mustKeepProps (#85748) --- public/app/features/dashboard/state/PanelModel.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 08d68628972..97d58089a15 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -98,6 +98,7 @@ const mustKeepProps: { [str: string]: boolean } = { links: true, fullscreen: true, isEditing: true, + isViewing: true, hasRefreshed: true, events: true, cacheTimeout: true, From 00be24d6b3b1a03852d876228e25aac4fbe02044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 9 Apr 2024 13:10:37 +0200 Subject: [PATCH 04/12] Chore: Create `handleReduceMotion` util function to manage animations (#85785) --- .../grafana-ui/src/utils/handleReducedMotion.ts | 16 ++++++++++++++++ packages/grafana-ui/src/utils/index.ts | 1 + 2 files changed, 17 insertions(+) create mode 100644 packages/grafana-ui/src/utils/handleReducedMotion.ts diff --git a/packages/grafana-ui/src/utils/handleReducedMotion.ts b/packages/grafana-ui/src/utils/handleReducedMotion.ts new file mode 100644 index 00000000000..1ca85249dbe --- /dev/null +++ b/packages/grafana-ui/src/utils/handleReducedMotion.ts @@ -0,0 +1,16 @@ +import { CSSInterpolation } from '@emotion/css'; + +/** + * @param styles - Styles to apply when no `prefers-reduced-motion` preference is set. + * @param reducedMotionStyles - Styles to apply when `prefers-reduced-motion` is enabled. + * Applies one of `styles` or `reducedMotionStyles` depending on a users `prefers-reduced-motion` setting. Omitting `reducedMotionStyles` entirely will result in no styles being applied when `prefers-reduced-motion` is enabled. In most cases this is a reasonable default. + */ +export const handleReducedMotion = (styles: CSSInterpolation, reducedMotionStyles?: CSSInterpolation) => { + const result: Record = { + '@media (prefers-reduced-motion: no-preference)': styles, + }; + if (reducedMotionStyles) { + result['@media (prefers-reduced-motion: reduce)'] = reducedMotionStyles; + } + return result; +}; diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts index b4ec365d717..55a1694e8b7 100644 --- a/packages/grafana-ui/src/utils/index.ts +++ b/packages/grafana-ui/src/utils/index.ts @@ -19,5 +19,6 @@ export { createLogger } from './logger'; export { attachDebugger } from './debug'; export * from './nodeGraph'; export { fuzzyMatch } from './fuzzy'; +export { handleReducedMotion } from './handleReducedMotion'; export { ReactUtils }; From 3420e942acaa010761ca705a864a32eb3f49dc5a Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 9 Apr 2024 12:11:22 +0100 Subject: [PATCH 05/12] Chore: Update RPM docs to include beta release references (#85753) * Update RPM docs to include beta release references * andreas/update-installation-docs/ run lint * Don't number lists --------- Co-authored-by: jev forsberg --- .../installation/redhat-rhel-fedora/index.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/sources/setup-grafana/installation/redhat-rhel-fedora/index.md b/docs/sources/setup-grafana/installation/redhat-rhel-fedora/index.md index ba5d83dc62b..92c077900ae 100644 --- a/docs/sources/setup-grafana/installation/redhat-rhel-fedora/index.md +++ b/docs/sources/setup-grafana/installation/redhat-rhel-fedora/index.md @@ -21,10 +21,12 @@ If you install via RPM or the `.tar.gz` file, then you must manually update Graf If you install from the RPM repository, then Grafana is automatically updated every time you update your applications. -| Grafana Version | Package | Repository | -| ------------------ | ------------------ | ------------------------- | -| Grafana Enterprise | grafana-enterprise | `https://rpm.grafana.com` | -| Grafana OSS | grafana | `https://rpm.grafana.com` | +| Grafana Version | Package | Repository | +| ------------------------- | ------------------ | ------------------------------ | +| Grafana Enterprise | grafana-enterprise | `https://rpm.grafana.com` | +| Grafana Enterprise (Beta) | grafana-enterprise | `https://rpm-beta.grafana.com` | +| Grafana OSS | grafana | `https://rpm.grafana.com` | +| Grafana OSS (Beta) | grafana | `https://rpm-beta.grafana.com` | {{% admonition type="note" %}} Grafana Enterprise is the recommended and default edition. It is available for free and includes all the features of the OSS edition. You can also upgrade to the [full Enterprise feature set](/products/enterprise/?utm_source=grafana-install-page), which has support for [Enterprise plugins](/grafana/plugins/?enterprise=1&utcm_source=grafana-install-page). @@ -32,6 +34,10 @@ Grafana Enterprise is the recommended and default edition. It is available for f To install Grafana from the RPM repository, complete the following steps: +{{% admonition type="note" %}} +If you wish to install beta versions of Grafana, substitute the repository URL for the beta URL listed above. +{{% /admonition %}} + 1. Import the GPG key: ```bash @@ -53,12 +59,6 @@ To install Grafana from the RPM repository, complete the following steps: sslcacert=/etc/pki/tls/certs/ca-bundle.crt ``` -1. To prevent beta versions from being installed, add the following exclude line to your `.repo` file. - - ```bash - exclude=*beta* - ``` - 1. To install Grafana OSS, run the following command: ```bash From 5305316f5abfee0e3f5990c3ee39ec4fed9e6b6b Mon Sep 17 00:00:00 2001 From: Kristina Date: Tue, 9 Apr 2024 07:36:46 -0500 Subject: [PATCH 06/12] Explore: Move Query History to be screen wide (#84321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP * Use splitpanewrapper for drawer * Get rich history pulling from multiple datasources * highlight pane * Fix datasource data handling * create ds/explore map, move around ds lookup * Handle no filters * Fix tests and some errors * Fix context menu issue * (Poorly) enable scrolling, fix onClose to function * Remove highlighting, use legacy key, fix casing * fix filtering to handle non-simple data * Fix linter, add translations * Fixing tests~~ * Move to explore drawer and fix some more tests * Kinda fix drawer stuff? * Fix remaining card tests * Fix test * Fix tests * Partially fix starred tab tests * Fix integration tests * Fix remaining tests 🤞 * Add a test and a clarifying comment behind a couple hooks * Remove unused code * Fix button styling and fix animation (but break width) * Make Drawer using parent width (100%) * Fix tests and some small catches * Add tests for selectExploreDSMaps selector --------- Co-authored-by: Piotr Jamroz --- .../src/selectors/pages.ts | 3 + .../TabbedContainer/TabbedContainer.tsx | 5 +- .../history/RichHistoryLocalStorage.test.ts | 2 +- .../core/history/RichHistoryLocalStorage.ts | 7 +- .../history/RichHistoryRemoteStorage.test.ts | 8 +- .../core/history/RichHistoryRemoteStorage.ts | 2 +- .../history/richHistoryLocalStorageUtils.ts | 3 +- public/app/core/utils/richHistoryTypes.ts | 2 +- public/app/features/explore/Explore.test.tsx | 6 +- public/app/features/explore/Explore.tsx | 61 ++---- public/app/features/explore/ExploreDrawer.tsx | 11 +- public/app/features/explore/ExplorePage.tsx | 19 +- .../features/explore/ExplorePaneContainer.tsx | 22 ++- .../explore/ExploreQueryInspector.test.tsx | 1 - .../explore/ExploreQueryInspector.tsx | 7 +- .../app/features/explore/QueryRows.test.tsx | 3 +- .../explore/RichHistory/RichHistory.test.tsx | 18 +- .../explore/RichHistory/RichHistory.tsx | 47 ++--- .../RichHistory/RichHistoryCard.test.tsx | 40 +++- .../explore/RichHistory/RichHistoryCard.tsx | 185 ++++++++++-------- .../RichHistory/RichHistoryContainer.test.tsx | 31 ++- .../RichHistory/RichHistoryContainer.tsx | 61 ++---- .../RichHistoryQueriesTab.test.tsx | 41 ++-- .../RichHistory/RichHistoryQueriesTab.tsx | 53 +++-- .../RichHistorySettingsTab.test.tsx | 6 +- .../RichHistory/RichHistorySettingsTab.tsx | 12 +- .../RichHistoryStarredTab.test.tsx | 32 +-- .../RichHistory/RichHistoryStarredTab.tsx | 50 +++-- .../features/explore/spec/helper/assert.ts | 55 ++---- .../explore/spec/helper/interactions.ts | 51 ++--- .../features/explore/spec/helper/setup.tsx | 9 +- .../explore/spec/queryHistory.test.tsx | 65 +----- .../app/features/explore/state/explorePane.ts | 18 -- public/app/features/explore/state/history.ts | 87 ++++---- public/app/features/explore/state/main.ts | 31 ++- public/app/features/explore/state/query.ts | 5 +- .../features/explore/state/selectors.test.ts | 123 ++++++++++++ .../app/features/explore/state/selectors.ts | 42 ++++ public/app/features/explore/state/utils.ts | 1 - public/app/types/explore.ts | 19 +- public/locales/en-US/grafana.json | 3 +- public/locales/pseudo-LOCALE/grafana.json | 3 +- 42 files changed, 728 insertions(+), 522 deletions(-) create mode 100644 public/app/features/explore/state/selectors.test.ts diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index abda6b077c4..3ae6d8dc99c 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -289,6 +289,9 @@ export const Pages = { table: 'Explore Table', scrollView: 'data-testid explorer scroll view', }, + QueryHistory: { + container: 'data-testid QueryHistory', + }, }, SoloPanel: { url: (page: string) => `/d-solo/${page}`, diff --git a/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx b/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx index 7a527bad325..59e635a3c28 100644 --- a/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx +++ b/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx @@ -21,9 +21,10 @@ export interface TabbedContainerProps { defaultTab?: string; closeIconTooltip?: string; onClose: () => void; + testId?: string; } -export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose }: TabbedContainerProps) { +export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose, testId }: TabbedContainerProps) { const [activeTab, setActiveTab] = useState(tabs.some((tab) => tab.value === defaultTab) ? defaultTab : tabs[0].value); const styles = useStyles2(getStyles); const theme = useTheme2(); @@ -35,7 +36,7 @@ export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose }: const autoHeight = `calc(100% - (${theme.components.menuTabs.height}px + ${theme.spacing(1)}))`; return ( -
+
{tabs.map((t) => ( { const settings: RichHistorySettings = { retentionPeriod: 2, starredTabAsFirstTab: true, - activeDatasourceOnly: true, + activeDatasourcesOnly: true, lastUsedDatasourceFilters: ['foobar'], }; await storage.updateSettings(settings); diff --git a/public/app/core/history/RichHistoryLocalStorage.ts b/public/app/core/history/RichHistoryLocalStorage.ts index ab84d420d49..4e5a1039d85 100644 --- a/public/app/core/history/RichHistoryLocalStorage.ts +++ b/public/app/core/history/RichHistoryLocalStorage.ts @@ -124,8 +124,11 @@ export default class RichHistoryLocalStorage implements RichHistoryStorage { } async getSettings() { + // get the new key without a default. If undefined, use the legacy key, or false as the default + const activeDatasource: boolean | undefined = store.getObject(RICH_HISTORY_SETTING_KEYS.activeDatasourcesOnly); return { - activeDatasourceOnly: store.getObject(RICH_HISTORY_SETTING_KEYS.activeDatasourceOnly, false), + activeDatasourcesOnly: + activeDatasource ?? store.getObject(RICH_HISTORY_SETTING_KEYS.legacyActiveDatasourceOnly, false), retentionPeriod: store.getObject(RICH_HISTORY_SETTING_KEYS.retentionPeriod, 7), starredTabAsFirstTab: store.getBool(RICH_HISTORY_SETTING_KEYS.starredTabAsFirstTab, false), lastUsedDatasourceFilters: store @@ -135,7 +138,7 @@ export default class RichHistoryLocalStorage implements RichHistoryStorage { } async updateSettings(settings: RichHistorySettings) { - store.set(RICH_HISTORY_SETTING_KEYS.activeDatasourceOnly, settings.activeDatasourceOnly); + store.set(RICH_HISTORY_SETTING_KEYS.activeDatasourcesOnly, settings.activeDatasourcesOnly); store.set(RICH_HISTORY_SETTING_KEYS.retentionPeriod, settings.retentionPeriod); store.set(RICH_HISTORY_SETTING_KEYS.starredTabAsFirstTab, settings.starredTabAsFirstTab); store.setObject( diff --git a/public/app/core/history/RichHistoryRemoteStorage.test.ts b/public/app/core/history/RichHistoryRemoteStorage.test.ts index 3fe54d9c519..b3c2ebc0850 100644 --- a/public/app/core/history/RichHistoryRemoteStorage.test.ts +++ b/public/app/core/history/RichHistoryRemoteStorage.test.ts @@ -188,7 +188,7 @@ describe('RichHistoryRemoteStorage', () => { } as UserPreferencesDTO); const settings = await storage.getSettings(); expect(settings).toMatchObject({ - activeDatasourceOnly: false, + activeDatasourcesOnly: false, lastUsedDatasourceFilters: undefined, retentionPeriod: 14, starredTabAsFirstTab: true, @@ -203,7 +203,7 @@ describe('RichHistoryRemoteStorage', () => { } as UserPreferencesDTO); const settings = await storage.getSettings(); expect(settings).toMatchObject({ - activeDatasourceOnly: false, + activeDatasourcesOnly: false, lastUsedDatasourceFilters: undefined, retentionPeriod: 14, starredTabAsFirstTab: false, @@ -212,7 +212,7 @@ describe('RichHistoryRemoteStorage', () => { it('updates user settings', async () => { await storage.updateSettings({ - activeDatasourceOnly: false, + activeDatasourcesOnly: false, lastUsedDatasourceFilters: undefined, retentionPeriod: 14, starredTabAsFirstTab: false, @@ -222,7 +222,7 @@ describe('RichHistoryRemoteStorage', () => { } as Partial); await storage.updateSettings({ - activeDatasourceOnly: false, + activeDatasourcesOnly: false, lastUsedDatasourceFilters: undefined, retentionPeriod: 14, starredTabAsFirstTab: true, diff --git a/public/app/core/history/RichHistoryRemoteStorage.ts b/public/app/core/history/RichHistoryRemoteStorage.ts index 0327444b5a7..2a7d4883916 100644 --- a/public/app/core/history/RichHistoryRemoteStorage.ts +++ b/public/app/core/history/RichHistoryRemoteStorage.ts @@ -85,7 +85,7 @@ export default class RichHistoryRemoteStorage implements RichHistoryStorage { async getSettings(): Promise { const preferences = await this.preferenceService.load(); return { - activeDatasourceOnly: false, + activeDatasourcesOnly: false, lastUsedDatasourceFilters: undefined, retentionPeriod: 14, starredTabAsFirstTab: preferences.queryHistory?.homeTab === 'starred', diff --git a/public/app/core/history/richHistoryLocalStorageUtils.ts b/public/app/core/history/richHistoryLocalStorageUtils.ts index 7f58de9a860..409bc4c64d6 100644 --- a/public/app/core/history/richHistoryLocalStorageUtils.ts +++ b/public/app/core/history/richHistoryLocalStorageUtils.ts @@ -94,6 +94,7 @@ export const sortQueries = (array: RichHistoryQuery[], sortOrder: SortOrder) => export const RICH_HISTORY_SETTING_KEYS = { retentionPeriod: 'grafana.explore.richHistory.retentionPeriod', starredTabAsFirstTab: 'grafana.explore.richHistory.starredTabAsFirstTab', - activeDatasourceOnly: 'grafana.explore.richHistory.activeDatasourceOnly', + legacyActiveDatasourceOnly: 'grafana.explore.richHistory.activeDatasourceOnly', + activeDatasourcesOnly: 'grafana.explore.richHistory.activeDatasourcesOnly', datasourceFilters: 'grafana.explore.richHistory.datasourceFilters', }; diff --git a/public/app/core/utils/richHistoryTypes.ts b/public/app/core/utils/richHistoryTypes.ts index c49029c035b..4a02afbe594 100644 --- a/public/app/core/utils/richHistoryTypes.ts +++ b/public/app/core/utils/richHistoryTypes.ts @@ -14,7 +14,7 @@ export enum SortOrder { export interface RichHistorySettings { retentionPeriod: number; starredTabAsFirstTab: boolean; - activeDatasourceOnly: boolean; + activeDatasourcesOnly: boolean; lastUsedDatasourceFilters?: string[]; } diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index 420fd5fd710..1b9af90f81a 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -10,7 +10,7 @@ import { configureStore } from 'app/store/configureStore'; import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext'; import { Explore, Props } from './Explore'; -import { initialExploreState } from './state/main'; +import { changeShowQueryHistory, initialExploreState } from './state/main'; import { scanStopAction } from './state/query'; import { createEmptyQueryResponse, makeExplorePaneState } from './state/utils'; @@ -51,6 +51,8 @@ const makeEmptyQueryResponse = (loadingState: LoadingState) => { }; const dummyProps: Props = { + setShowQueryInspector: (value: boolean) => {}, + showQueryInspector: false, logsResult: undefined, changeSize: jest.fn(), datasourceInstance: { @@ -98,6 +100,8 @@ const dummyProps: Props = { setSupplementaryQueryEnabled: jest.fn(), correlationEditorDetails: undefined, correlationEditorHelperData: undefined, + showQueryHistory: false, + changeShowQueryHistory: changeShowQueryHistory, }; jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index da1c8540c82..6726e92a4e3 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -41,7 +41,6 @@ import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineCo import { ContentOutlineItem } from './ContentOutline/ContentOutlineItem'; import { CorrelationHelper } from './CorrelationHelper'; import { CustomContainer } from './CustomContainer'; -import ExploreQueryInspector from './ExploreQueryInspector'; import { ExploreToolbar } from './ExploreToolbar'; import { FlameGraphExploreContainer } from './FlameGraph/FlameGraphExploreContainer'; import { GraphContainer } from './Graph/GraphContainer'; @@ -53,12 +52,11 @@ import { NodeGraphContainer } from './NodeGraph/NodeGraphContainer'; import { QueryRows } from './QueryRows'; import RawPrometheusContainer from './RawPrometheus/RawPrometheusContainer'; import { ResponseErrorContainer } from './ResponseErrorContainer'; -import RichHistoryContainer from './RichHistory/RichHistoryContainer'; import { SecondaryActions } from './SecondaryActions'; import TableContainer from './Table/TableContainer'; import { TraceViewContainer } from './TraceView/TraceViewContainer'; import { changeSize } from './state/explorePane'; -import { splitOpen } from './state/main'; +import { changeShowQueryHistory, splitOpen } from './state/main'; import { addQueryRow, modifyQueries, @@ -108,15 +106,11 @@ export interface ExploreProps extends Themeable2 { exploreId: string; theme: GrafanaTheme2; eventBus: EventBus; -} - -enum ExploreDrawer { - RichHistory, - QueryInspector, + setShowQueryInspector: (value: boolean) => void; + showQueryInspector: boolean; } interface ExploreState { - openDrawer?: ExploreDrawer; contentOutlineVisible: boolean; } @@ -156,7 +150,6 @@ export class Explore extends React.PureComponent { constructor(props: Props) { super(props); this.state = { - openDrawer: undefined, contentOutlineVisible: false, }; this.graphEventBus = props.eventBus.newScopedBus('graph', { onlyLocal: false }); @@ -311,20 +304,8 @@ export class Explore extends React.PureComponent { updateTimeRange({ exploreId, absoluteRange }); }; - toggleShowRichHistory = () => { - this.setState((state) => { - return { - openDrawer: state.openDrawer === ExploreDrawer.RichHistory ? undefined : ExploreDrawer.RichHistory, - }; - }); - }; - - toggleShowQueryInspector = () => { - this.setState((state) => { - return { - openDrawer: state.openDrawer === ExploreDrawer.QueryInspector ? undefined : ExploreDrawer.QueryInspector, - }; - }); + toggleShowQueryHistory = () => { + this.props.changeShowQueryHistory(!this.props.showQueryHistory); }; onSplitOpen = (panelType: string) => { @@ -551,17 +532,17 @@ export class Explore extends React.PureComponent { showCustom, showNodeGraph, showFlameGraph, - timeZone, showLogsSample, correlationEditorDetails, correlationEditorHelperData, + showQueryHistory, + showQueryInspector, + setShowQueryInspector, } = this.props; - const { openDrawer, contentOutlineVisible } = this.state; + const { contentOutlineVisible } = this.state; const styles = getStyles(theme); const showPanels = queryResponse && queryResponse.state !== LoadingState.NotStarted; - const showRichHistory = openDrawer === ExploreDrawer.RichHistory; const richHistoryRowButtonHidden = !supportedFeatures().queryHistoryAvailable; - const showQueryInspector = openDrawer === ExploreDrawer.QueryInspector; const showNoData = queryResponse.state === LoadingState.Done && [ @@ -622,11 +603,11 @@ export class Explore extends React.PureComponent { //TODO:unification addQueryRowButtonHidden={false} richHistoryRowButtonHidden={richHistoryRowButtonHidden} - richHistoryButtonActive={showRichHistory} + richHistoryButtonActive={showQueryHistory} queryInspectorButtonActive={showQueryInspector} onClickAddQueryRowButton={this.onClickAddQueryRowButton} - onClickRichHistoryButton={this.toggleShowRichHistory} - onClickQueryInspectorButton={this.toggleShowQueryInspector} + onClickRichHistoryButton={this.toggleShowQueryHistory} + onClickQueryInspectorButton={() => setShowQueryInspector(!showQueryInspector)} /> @@ -664,22 +645,6 @@ export class Explore extends React.PureComponent { {showNoData && {this.renderNoData()}} )} - {showRichHistory && ( - - )} - {showQueryInspector && ( - - )} ); @@ -756,6 +721,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { showLogsSample, correlationEditorHelperData, correlationEditorDetails: explore.correlationEditorDetails, + showQueryHistory: explore.showQueryHistory, }; } @@ -769,6 +735,7 @@ const mapDispatchToProps = { addQueryRow, splitOpen, setSupplementaryQueryEnabled, + changeShowQueryHistory, }; const connector = connect(mapStateToProps, mapDispatchToProps); diff --git a/public/app/features/explore/ExploreDrawer.tsx b/public/app/features/explore/ExploreDrawer.tsx index 3e6fac27359..03c595e88a4 100644 --- a/public/app/features/explore/ExploreDrawer.tsx +++ b/public/app/features/explore/ExploreDrawer.tsx @@ -8,22 +8,20 @@ import { GrafanaTheme2 } from '@grafana/data'; import { getDragStyles, useStyles2, useTheme2 } from '@grafana/ui'; export interface Props { - width: number; children: React.ReactNode; onResize?: ResizeCallback; } export function ExploreDrawer(props: Props) { - const { width, children, onResize } = props; + const { children, onResize } = props; const theme = useTheme2(); const styles = useStyles2(getStyles); const dragStyles = getDragStyles(theme); - const drawerWidth = `${width + 31.5}px`; return ( {children} @@ -58,13 +54,12 @@ const drawerSlide = (theme: GrafanaTheme2) => keyframes` const getStyles = (theme: GrafanaTheme2) => ({ // @ts-expect-error csstype doesn't allow !important. see https://github.com/frenic/csstype/issues/114 fixed: css({ - position: 'fixed !important', + position: 'absolute !important', }), container: css({ bottom: 0, background: theme.colors.background.primary, borderTop: `1px solid ${theme.colors.border.weak}`, - margin: theme.spacing(0, -2, 0, -2), boxShadow: theme.shadows.z3, zIndex: theme.zIndex.navbarFixed, }), diff --git a/public/app/features/explore/ExplorePage.tsx b/public/app/features/explore/ExplorePage.tsx index 9f797a06514..11999d6554d 100644 --- a/public/app/features/explore/ExplorePage.tsx +++ b/public/app/features/explore/ExplorePage.tsx @@ -8,18 +8,21 @@ import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPane import { useGrafana } from 'app/core/context/GrafanaContext'; import { useNavModel } from 'app/core/hooks/useNavModel'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { useSelector } from 'app/types'; +import { useDispatch, useSelector } from 'app/types'; import { ExploreQueryParams } from 'app/types/explore'; import { CorrelationEditorModeBar } from './CorrelationEditorModeBar'; import { ExploreActions } from './ExploreActions'; +import { ExploreDrawer } from './ExploreDrawer'; import { ExplorePaneContainer } from './ExplorePaneContainer'; +import RichHistoryContainer from './RichHistory/RichHistoryContainer'; import { useExplorePageTitle } from './hooks/useExplorePageTitle'; import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'; import { useSplitSizeUpdater } from './hooks/useSplitSizeUpdater'; import { useStateSync } from './hooks/useStateSync'; import { useTimeSrvFix } from './hooks/useTimeSrvFix'; -import { isSplit, selectCorrelationDetails, selectPanesEntries } from './state/selectors'; +import { changeShowQueryHistory } from './state/main'; +import { isSplit, selectCorrelationDetails, selectPanesEntries, selectShowQueryHistory } from './state/selectors'; const MIN_PANE_WIDTH = 200; @@ -36,11 +39,13 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor useExplorePageTitle(props.queryParams); const { chrome } = useGrafana(); const navModel = useNavModel('explore'); + const dispatch = useDispatch(); const { updateSplitSize, widthCalc } = useSplitSizeUpdater(MIN_PANE_WIDTH); const panes = useSelector(selectPanesEntries); const hasSplit = useSelector(isSplit); const correlationDetails = useSelector(selectCorrelationDetails); + const showQueryHistory = useSelector(selectShowQueryHistory); const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false); useEffect(() => { @@ -80,6 +85,15 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor ); })} + {showQueryHistory && ( + + { + dispatch(changeShowQueryHistory(false)); + }} + /> + + )}
); } @@ -92,6 +106,7 @@ const getStyles = (theme: GrafanaTheme2) => { minHeight: 0, height: '100%', position: 'relative', + overflow: 'hidden', }), correlationsEditorIndicator: css({ borderLeft: `4px solid ${theme.colors.primary.main}`, diff --git a/public/app/features/explore/ExplorePaneContainer.tsx b/public/app/features/explore/ExplorePaneContainer.tsx index 7eec0b39ed4..a8b8feace21 100644 --- a/public/app/features/explore/ExplorePaneContainer.tsx +++ b/public/app/features/explore/ExplorePaneContainer.tsx @@ -1,14 +1,15 @@ import { css } from '@emotion/css'; -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { connect } from 'react-redux'; -import { EventBusSrv } from '@grafana/data'; +import { EventBusSrv, getTimeZone } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { CustomScrollbar } from '@grafana/ui'; import { stopQueryState } from 'app/core/utils/explore'; import { StoreState, useSelector } from 'app/types'; import Explore from './Explore'; +import ExploreQueryInspector from './ExploreQueryInspector'; import { getExploreItemSelector } from './state/selectors'; const containerStyles = css({ @@ -27,7 +28,7 @@ interface Props { Connected components subscribe to the store before function components (using hooks) and can react to store changes. Thus, this connector function is called before the parent component (ExplorePage) is rerendered. This means that child components' mapStateToProps will be executed with a zombie `exploreId` that is not present anymore in the store if the pane gets closed. By connecting this component and returning the pane we workaround the zombie children issue here instead of modifying every children. - This is definitely not the ideal solution and we should in the future invest more time in exploring other approaches to better handle this scenario, potentially by refactoring panels to be function components + This is definitely not the ideal solution and we should in the future invest more time in exploring other approaches to better handle this scenario, potentially by refactoring panels to be function components (therefore immune to this behaviour), or by forbidding them to access the store directly and instead pass them all the data they need via props or context. You can read more about this issue here: https://react-redux.js.org/api/hooks#stale-props-and-zombie-children @@ -36,6 +37,7 @@ function ExplorePaneContainerUnconnected({ exploreId }: Props) { useStopQueries(exploreId); const eventBus = useRef(new EventBusSrv()); const ref = useRef(null); + const [showQueryInspector, setShowQueryInspector] = useState(false); useEffect(() => { const bus = eventBus.current; @@ -45,7 +47,19 @@ function ExplorePaneContainerUnconnected({ exploreId }: Props) { return (
- + + {showQueryInspector && ( + setShowQueryInspector(false)} + timeZone={getTimeZone()} + /> + )}
); diff --git a/public/app/features/explore/ExploreQueryInspector.test.tsx b/public/app/features/explore/ExploreQueryInspector.test.tsx index c52d0533a88..64fb4622982 100644 --- a/public/app/features/explore/ExploreQueryInspector.test.tsx +++ b/public/app/features/explore/ExploreQueryInspector.test.tsx @@ -47,7 +47,6 @@ jest.mock('react-virtualized-auto-sizer', () => { const setup = (propOverrides = {}) => { const props: ExploreQueryInspectorProps = { - width: 100, exploreId: 'left', onClose: jest.fn(), timeZone: InternalTimeZones.utc, diff --git a/public/app/features/explore/ExploreQueryInspector.tsx b/public/app/features/explore/ExploreQueryInspector.tsx index 36b371bdc82..f04cc153c89 100644 --- a/public/app/features/explore/ExploreQueryInspector.tsx +++ b/public/app/features/explore/ExploreQueryInspector.tsx @@ -21,17 +21,15 @@ import { GetDataOptions } from '../query/state/PanelQueryRunner'; import { runQueries } from './state/query'; interface DispatchProps { - width: number; exploreId: string; timeZone: TimeZone; onClose: () => void; - isMixed: boolean; } type Props = DispatchProps & ConnectedProps; export function ExploreQueryInspector(props: Props) { - const { width, onClose, queryResponse, timeZone, isMixed, exploreId } = props; + const { onClose, queryResponse, timeZone, isMixed, exploreId } = props; const [dataOptions, setDataOptions] = useState({ withTransforms: false, withFieldConfig: true, @@ -105,7 +103,7 @@ export function ExploreQueryInspector(props: Props) { tabs.push(errorTab); } return ( - + ); @@ -118,6 +116,7 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string } return { queryResponse, + isMixed: item.datasourceInstance?.meta.mixed || false, }; } diff --git a/public/app/features/explore/QueryRows.test.tsx b/public/app/features/explore/QueryRows.test.tsx index 1bcb5111fd6..07e6d5881d2 100644 --- a/public/app/features/explore/QueryRows.test.tsx +++ b/public/app/features/explore/QueryRows.test.tsx @@ -51,10 +51,11 @@ function setup(queries: DataQuery[]) { const leftState = makeExplorePaneState(); const initialState: ExploreState = { + richHistory: [], + showQueryHistory: false, panes: { left: { ...leftState, - richHistory: [], datasourceInstance: datasources['someDs-uid'], queries, correlations: [], diff --git a/public/app/features/explore/RichHistory/RichHistory.test.tsx b/public/app/features/explore/RichHistory/RichHistory.test.tsx index d4199815da1..35987fa1773 100644 --- a/public/app/features/explore/RichHistory/RichHistory.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.test.tsx @@ -1,11 +1,12 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; +import { TestProvider } from 'test/helpers/TestProvider'; import { SortOrder } from 'app/core/utils/richHistory'; import { RichHistory, RichHistoryProps, Tabs } from './RichHistory'; -jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); +jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockReturnValue({ dsToExplore: [] }) })); jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -18,11 +19,14 @@ jest.mock('@grafana/runtime', () => ({ }, })); +jest.mock('react-use', () => ({ + ...jest.requireActual('react-use'), + useAsync: () => ({ loading: false, value: [] }), +})); + const setup = (propOverrides?: Partial) => { const props: RichHistoryProps = { - exploreId: 'left', height: 100, - activeDatasourceInstance: 'Test datasource', richHistory: [], richHistoryTotal: 0, firstTab: Tabs.RichHistory, @@ -42,7 +46,7 @@ const setup = (propOverrides?: Partial) => { richHistorySettings: { retentionPeriod: 0, starredTabAsFirstTab: false, - activeDatasourceOnly: true, + activeDatasourcesOnly: true, lastUsedDatasourceFilters: [], }, updateHistorySearchFilters: jest.fn(), @@ -51,7 +55,11 @@ const setup = (propOverrides?: Partial) => { Object.assign(props, propOverrides); - render(); + render( + + + + ); }; describe('RichHistory', () => { diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index e6dfd82ec7b..fc0158b6220 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -2,6 +2,7 @@ import { debounce } from 'lodash'; import React, { useState, useEffect } from 'react'; import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { TabbedContainer, TabConfig } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { SortOrder, RichHistorySearchFilters, RichHistorySettings } from 'app/core/utils/richHistory'; @@ -33,29 +34,18 @@ export interface RichHistoryProps { richHistorySettings: RichHistorySettings; richHistorySearchFilters?: RichHistorySearchFilters; updateHistorySettings: (settings: RichHistorySettings) => void; - updateHistorySearchFilters: (exploreId: string, filters: RichHistorySearchFilters) => void; - loadRichHistory: (exploreId: string) => void; - loadMoreRichHistory: (exploreId: string) => void; - clearRichHistoryResults: (exploreId: string) => void; + updateHistorySearchFilters: (filters: RichHistorySearchFilters) => void; + loadRichHistory: () => void; + loadMoreRichHistory: () => void; + clearRichHistoryResults: () => void; deleteRichHistory: () => void; - activeDatasourceInstance: string; firstTab: Tabs; - exploreId: string; height: number; onClose: () => void; } export function RichHistory(props: RichHistoryProps) { - const { - richHistory, - richHistoryTotal, - height, - exploreId, - deleteRichHistory, - onClose, - firstTab, - activeDatasourceInstance, - } = props; + const { richHistory, richHistoryTotal, height, deleteRichHistory, onClose, firstTab } = props; const [loading, setLoading] = useState(false); @@ -69,12 +59,12 @@ export function RichHistory(props: RichHistoryProps) { ...filtersToUpdate, page: 1, // always load fresh results when updating filters }; - props.updateHistorySearchFilters(props.exploreId, filters); + props.updateHistorySearchFilters(filters); loadRichHistory(); }; const loadRichHistory = debounce(() => { - props.loadRichHistory(props.exploreId); + props.loadRichHistory(); setLoading(true); }, 300); @@ -87,8 +77,8 @@ export function RichHistory(props: RichHistoryProps) { const toggleStarredTabAsFirstTab = () => updateSettings({ starredTabAsFirstTab: !props.richHistorySettings.starredTabAsFirstTab }); - const toggleActiveDatasourceOnly = () => - updateSettings({ activeDatasourceOnly: !props.richHistorySettings.activeDatasourceOnly }); + const toggleActiveDatasourcesOnly = () => + updateSettings({ activeDatasourcesOnly: !props.richHistorySettings.activeDatasourcesOnly }); useEffect(() => { setLoading(false); @@ -103,12 +93,10 @@ export function RichHistory(props: RichHistoryProps) { totalQueries={richHistoryTotal || 0} loading={loading} updateFilters={updateFilters} - clearRichHistoryResults={() => props.clearRichHistoryResults(props.exploreId)} - loadMoreRichHistory={() => props.loadMoreRichHistory(props.exploreId)} - activeDatasourceInstance={activeDatasourceInstance} + clearRichHistoryResults={() => props.clearRichHistoryResults()} + loadMoreRichHistory={() => props.loadMoreRichHistory()} richHistorySettings={props.richHistorySettings} richHistorySearchFilters={props.richHistorySearchFilters} - exploreId={exploreId} height={height} /> ), @@ -123,13 +111,11 @@ export function RichHistory(props: RichHistoryProps) { queries={richHistory} totalQueries={richHistoryTotal || 0} loading={loading} - activeDatasourceInstance={activeDatasourceInstance} updateFilters={updateFilters} - clearRichHistoryResults={() => props.clearRichHistoryResults(props.exploreId)} - loadMoreRichHistory={() => props.loadMoreRichHistory(props.exploreId)} + clearRichHistoryResults={() => props.clearRichHistoryResults()} + loadMoreRichHistory={() => props.loadMoreRichHistory()} richHistorySettings={props.richHistorySettings} richHistorySearchFilters={props.richHistorySearchFilters} - exploreId={exploreId} /> ), icon: 'star', @@ -142,10 +128,10 @@ export function RichHistory(props: RichHistoryProps) { ), @@ -159,6 +145,7 @@ export function RichHistory(props: RichHistoryProps) { onClose={onClose} defaultTab={firstTab} closeIconTooltip={t('explore.rich-history.close-tooltip', 'Close query history')} + testId={selectors.pages.Explore.QueryHistory.container} /> ); } diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx index 38650bb3f2c..a8cb484823e 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx @@ -1,11 +1,13 @@ import { fireEvent, render, screen, getByText, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { TestProvider } from 'test/helpers/TestProvider'; import { DataSourceApi, DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data'; import { DataQuery, DataSourceRef } from '@grafana/schema'; import { MixedDatasource } from 'app/plugins/datasource/mixed/MixedDataSource'; -import { RichHistoryQuery } from 'app/types'; +import { configureStore } from 'app/store/configureStore'; +import { ExploreState, RichHistoryQuery } from 'app/types'; import { ShowConfirmModalEvent } from 'app/types/events'; import { RichHistoryCard, Props } from './RichHistoryCard'; @@ -47,7 +49,7 @@ class MockDatasourceApi implements DataSourceApi { throw new Error('Method not implemented.'); } getRef(): DataSourceRef { - throw new Error('Method not implemented.'); + return { uid: this.uid, type: this.type }; } } @@ -55,7 +57,7 @@ const dsStore: Record = { alertmanager: new MockDatasourceApi('Alertmanager', 3, 'alertmanager', 'alertmanager'), loki: new MockDatasourceApi('Loki', 2, 'loki', 'loki'), prometheus: new MockDatasourceApi('Prometheus', 1, 'prometheus', 'prometheus', { - getQueryDisplayText: (query: MockQuery) => query.queryText || 'Unknwon query', + getQueryDisplayText: (query: MockQuery) => query.queryText || 'Unknown query', }), mixed: new MixedDatasource({ id: 4, @@ -97,6 +99,7 @@ jest.mock('app/core/utils/explore', () => ({ jest.mock('app/core/app_events', () => ({ publish: jest.fn(), + subscribe: jest.fn(), })); interface MockQuery extends DataQuery { @@ -124,13 +127,31 @@ const setup = (propOverrides?: Partial>) => { deleteHistoryItem: deleteRichHistoryMock, commentHistoryItem: jest.fn(), setQueries: jest.fn(), - exploreId: 'left', - datasourceInstance: dsStore.loki, + datasourceInstances: [dsStore.loki], }; + const store = configureStore({ + explore: { + panes: { + left: { + queries: [{ query: 'query1', refId: 'A' }], + datasourceInstance: dsStore.loki, + queryResponse: {}, + range: { + raw: { from: 'now-1h', to: 'now' }, + }, + }, + }, + } as unknown as ExploreState, + }); + Object.assign(props, propOverrides); - render(); + render( + + + + ); }; const starredQueryWithComment: RichHistoryQuery = { @@ -236,8 +257,11 @@ describe('RichHistoryCard', () => { datasourceName: 'Test datasource', starred: false, comment: '', - queries: [{ query: 'query1', refId: 'A', queryText: 'query1' }], + queries: [ + { query: 'query1', refId: 'A', queryText: 'query1', datasource: { uid: 'prometheus', type: 'prometheus' } }, + ], }, + datasourceInstances: [dsStore.prometheus], }); const copyQueriesButton = await screen.findByRole('button', { name: 'Copy query to clipboard' }); expect(copyQueriesButton).toBeInTheDocument(); @@ -260,6 +284,7 @@ describe('RichHistoryCard', () => { { query: 'query2', refId: 'B', datasource: { uid: 'loki' } }, ], }, + datasourceInstances: [dsStore.loki, dsStore.prometheus, dsStore.mixed], }); const copyQueriesButton = await screen.findByRole('button', { name: 'Copy query to clipboard' }); expect(copyQueriesButton).toBeInTheDocument(); @@ -367,6 +392,7 @@ describe('RichHistoryCard', () => { comment: '', queries, }, + datasourceInstances: [dsStore.loki, dsStore.prometheus, dsStore.mixed], }); const runQueryButton = await screen.findByRole('button', { name: /run query/i }); diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.tsx index 5823340f9d0..9d1dd68aad1 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.tsx @@ -1,12 +1,11 @@ import { css, cx } from '@emotion/css'; import React, { useCallback, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { useAsync } from 'react-use'; import { GrafanaTheme2, DataSourceApi } from '@grafana/data'; -import { config, getDataSourceSrv, reportInteraction, getAppEvents } from '@grafana/runtime'; +import { config, reportInteraction, getAppEvents } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { TextArea, Button, IconButton, useStyles2, LoadingPlaceholder } from '@grafana/ui'; +import { TextArea, Button, IconButton, useStyles2, ToolbarButton, Dropdown, Menu } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import { createSuccessNotification } from 'app/core/copy/appNotification'; import { Trans, t } from 'app/core/internationalization'; @@ -17,18 +16,11 @@ import { changeDatasource } from 'app/features/explore/state/datasource'; import { starHistoryItem, commentHistoryItem, deleteHistoryItem } from 'app/features/explore/state/history'; import { setQueries } from 'app/features/explore/state/query'; import { dispatch } from 'app/store/store'; -import { StoreState } from 'app/types'; +import { useSelector } from 'app/types'; import { ShowConfirmModalEvent } from 'app/types/events'; import { RichHistoryQuery } from 'app/types/explore'; -function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }) { - const explore = state.explore; - const { datasourceInstance } = explore.panes[exploreId]!; - return { - exploreId, - datasourceInstance, - }; -} +import { isSplit, selectExploreDSMaps, selectPanesEntries } from '../state/selectors'; const mapDispatchToProps = { changeDatasource, @@ -38,9 +30,10 @@ const mapDispatchToProps = { setQueries, }; -const connector = connect(mapStateToProps, mapDispatchToProps); +const connector = connect(undefined, mapDispatchToProps); interface OwnProps { + datasourceInstances?: DataSourceApi[]; queryHistoryItem: RichHistoryQuery; } @@ -147,46 +140,29 @@ export function RichHistoryCard(props: Props) { starHistoryItem, deleteHistoryItem, changeDatasource, - exploreId, - datasourceInstance, setQueries, + datasourceInstances, } = props; const [activeUpdateComment, setActiveUpdateComment] = useState(false); + const [openRunQueryButton, setOpenRunQueryButton] = useState(false); const [comment, setComment] = useState(queryHistoryItem.comment); - const { value: historyCardData, loading } = useAsync(async () => { - let datasourceInstance: DataSourceApi | undefined; - try { - datasourceInstance = await getDataSourceSrv().get(queryHistoryItem.datasourceUid); - } catch (e) {} - - return { - datasourceInstance, - queries: await Promise.all( - queryHistoryItem.queries.map(async (query) => { - let datasource; - if (datasourceInstance?.meta.mixed) { - try { - datasource = await getDataSourceSrv().get(query.datasource); - } catch (e) {} - } else { - datasource = datasourceInstance; - } - - return { - query, - datasource, - }; - }) - ), - }; - }, [queryHistoryItem.datasourceUid, queryHistoryItem.queries]); + const panesEntries = useSelector(selectPanesEntries); + const exploreActiveDS = useSelector(selectExploreDSMaps); + const isPaneSplit = useSelector(isSplit); const styles = useStyles2(getStyles); - const onRunQuery = async () => { + const cardRootDatasource = datasourceInstances + ? datasourceInstances.find((di) => di.uid === queryHistoryItem.datasourceUid) + : undefined; + + const isDifferentDatasource = (uid: string, exploreId: string) => + !exploreActiveDS.dsToExplore.find((di) => di.datasource.uid === uid)?.exploreIds.includes(exploreId); + + const onRunQuery = async (exploreId: string) => { const queriesToRun = queryHistoryItem.queries; - const differentDataSource = queryHistoryItem.datasourceUid !== datasourceInstance?.uid; + const differentDataSource = isDifferentDatasource(queryHistoryItem.datasourceUid, exploreId); if (differentDataSource) { await changeDatasource({ exploreId, datasource: queryHistoryItem.datasourceUid }); } @@ -202,16 +178,16 @@ export function RichHistoryCard(props: Props) { const datasources = [...queryHistoryItem.queries.map((query) => query.datasource?.type || 'unknown')]; reportInteraction('grafana_explore_query_history_copy_query', { datasources, - mixed: Boolean(historyCardData?.datasourceInstance?.meta.mixed), + mixed: Boolean(cardRootDatasource?.meta.mixed), }); - if (loading || !historyCardData) { - return; - } - - const queriesText = historyCardData.queries + const queriesText = queryHistoryItem.queries .map((query) => { - return createQueryText(query.query, query.datasource); + let queryDS = datasourceInstances?.find((di) => di.uid === queryHistoryItem.datasourceUid); + if (queryDS?.meta.mixed) { + queryDS = datasourceInstances?.find((di) => di.uid === query.datasource?.uid); + } + return createQueryText(query, queryDS); }) .join('\n'); @@ -258,7 +234,7 @@ export function RichHistoryCard(props: Props) { } }; - const onStarrQuery = () => { + const onStarQuery = () => { starHistoryItem(queryHistoryItem.id, !queryHistoryItem.starred); reportInteraction('grafana_explore_query_history_starred', { queryHistoryEnabled: config.queryHistoryEnabled, @@ -338,7 +314,7 @@ export function RichHistoryCard(props: Props) { onClick={onCopyQuery} tooltip={t('explore.rich-history-card.copy-query-tooltip', 'Copy query to clipboard')} /> - {historyCardData?.datasourceInstance && ( + {cardRootDatasource && ( ); + // exploreId on where the query will be ran, and the datasource ID for the item's DS + const runQueryText = (exploreId: string, dsUid: string) => { + return dsUid !== undefined && isDifferentDatasource(dsUid, exploreId) + ? { + fallbackText: 'Switch data source and run query', + translation: t('explore.rich-history-card.switch-datasource-button', 'Switch data source and run query'), + } + : { + fallbackText: 'Run query', + translation: t('explore.rich-history-card.run-query-button', 'Run query'), + }; + }; + + const runButton = () => { + const disabled = cardRootDatasource?.uid === undefined; + if (!isPaneSplit) { + const exploreId = exploreActiveDS.exploreToDS[0].exploreId; + const buttonText = runQueryText(exploreId, props.queryHistoryItem.datasourceUid); + return ( + + ); + } else { + const menu = ( + + {panesEntries.map((pane, i) => { + const buttonText = runQueryText(pane[0], props.queryHistoryItem.datasourceUid); + const paneLabel = + i === 0 + ? t('explore.rich-history-card.left-pane', 'Left pane') + : t('explore.rich-history-card.right-pane', 'Right pane'); + return ( + { + onRunQuery(pane[0]); + }} + label={`${paneLabel}: ${buttonText.translation}`} + disabled={disabled} + /> + ); + })} + + ); + + return ( + setOpenRunQueryButton(state)} placement="bottom-start" overlay={menu}> + + {t('explore.rich-history-card.run-query-button', 'Run query')} + + + ); + } + }; + return (
- + {queryActionButtons}
- {historyCardData?.queries.map((q, i) => { - return ; + {queryHistoryItem?.queries.map((q, i) => { + const queryDs = datasourceInstances?.find((ds) => ds.uid === q.datasource?.uid); + return ( + + ); })} {!activeUpdateComment && queryHistoryItem.comment && (
- {!activeUpdateComment && ( -
- -
- )} + {!activeUpdateComment &&
{runButton()}
}
- {loading && ( - - )}
); } diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx index 64fed881262..f2a492b0fc7 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx @@ -1,13 +1,12 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { TestProvider } from 'test/helpers/TestProvider'; import { SortOrder } from 'app/core/utils/richHistory'; import { Tabs } from './RichHistory'; import { RichHistoryContainer, Props } from './RichHistoryContainer'; -jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); - jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getDataSourceSrv: () => { @@ -18,11 +17,15 @@ jest.mock('@grafana/runtime', () => ({ reportInteraction: jest.fn(), })); +jest.mock('react-use', () => ({ + ...jest.requireActual('react-use'), + useAsync: () => ({ loading: false, value: [] }), +})); + +jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockReturnValue({ dsToExplore: [] }) })); + const setup = (propOverrides?: Partial) => { const props: Props = { - width: 500, - exploreId: 'left', - activeDatasourceInstance: 'Test datasource', richHistory: [], firstTab: Tabs.RichHistory, deleteRichHistory: jest.fn(), @@ -44,7 +47,7 @@ const setup = (propOverrides?: Partial) => { richHistorySettings: { retentionPeriod: 0, starredTabAsFirstTab: false, - activeDatasourceOnly: true, + activeDatasourcesOnly: true, lastUsedDatasourceFilters: [], }, richHistoryTotal: 0, @@ -52,7 +55,7 @@ const setup = (propOverrides?: Partial) => { Object.assign(props, propOverrides); - return render(); + return render(, { wrapper: TestProvider }); }; describe('RichHistoryContainer', () => { @@ -60,23 +63,15 @@ describe('RichHistoryContainer', () => { const { container } = setup({ richHistorySettings: undefined }); expect(container).toHaveTextContent('Loading...'); }); - it('should render component with correct width', () => { - const { container } = setup(); - expect(container.firstElementChild!.getAttribute('style')).toContain('width: 531.5px'); - }); - it('should render component with correct height', () => { - const { container } = setup(); - expect(container.firstElementChild!.getAttribute('style')).toContain('height: 400px'); - }); it('should re-request rich history every time the component is mounted', () => { const initRichHistory = jest.fn(); const { unmount } = setup({ initRichHistory }); - expect(initRichHistory).toBeCalledTimes(1); + expect(initRichHistory).toHaveBeenCalledTimes(1); unmount(); - expect(initRichHistory).toBeCalledTimes(1); + expect(initRichHistory).toHaveBeenCalledTimes(1); setup({ initRichHistory }); - expect(initRichHistory).toBeCalledTimes(2); + expect(initRichHistory).toHaveBeenCalledTimes(2); }); }); diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx index 60a435586aa..acaf618b636 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx @@ -1,15 +1,14 @@ // Libraries -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { config, reportInteraction } from '@grafana/runtime'; import { useTheme2 } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; // Types -import { ExploreItemState, StoreState } from 'app/types'; +import { StoreState } from 'app/types'; // Components, enums -import { ExploreDrawer } from '../ExploreDrawer'; import { deleteRichHistory, initRichHistory, @@ -24,19 +23,16 @@ import { RichHistory, Tabs } from './RichHistory'; //Actions -function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }) { +function mapStateToProps(state: StoreState) { const explore = state.explore; - const item: ExploreItemState = explore.panes[exploreId]!; - const richHistorySearchFilters = item.richHistorySearchFilters; - const richHistorySettings = explore.richHistorySettings; - const { datasourceInstance } = item; + const richHistorySearchFilters = explore.richHistorySearchFilters; + const { richHistorySettings, richHistory, richHistoryTotal } = explore; + const firstTab = richHistorySettings?.starredTabAsFirstTab ? Tabs.Starred : Tabs.RichHistory; - const { richHistory, richHistoryTotal } = item; return { richHistory, richHistoryTotal, firstTab, - activeDatasourceInstance: datasourceInstance!.name, richHistorySettings, richHistorySearchFilters, }; @@ -55,23 +51,17 @@ const mapDispatchToProps = { const connector = connect(mapStateToProps, mapDispatchToProps); interface OwnProps { - width: number; - exploreId: string; onClose: () => void; } export type Props = ConnectedProps & OwnProps; export function RichHistoryContainer(props: Props) { const theme = useTheme2(); - const [height, setHeight] = useState(theme.components.horizontalDrawer.defaultHeight); const { richHistory, richHistoryTotal, - width, firstTab, - activeDatasourceInstance, - exploreId, deleteRichHistory, initRichHistory, loadRichHistory, @@ -100,30 +90,21 @@ export function RichHistoryContainer(props: Props) { } return ( - { - setHeight(Number(ref.style.height.slice(0, -2))); - }} - > - - + ); } diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx index 9bd74449c9f..06b51138a08 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx @@ -1,17 +1,23 @@ import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; +import * as reactUse from 'react-use'; +import { TestProvider } from 'test/helpers/TestProvider'; +import { MockDataSourceApi } from 'test/mocks/datasource_srv'; import { DataSourceSrv, setDataSourceSrv } from '@grafana/runtime'; import { SortOrder } from 'app/core/utils/richHistoryTypes'; import { RichHistoryQueriesTab, RichHistoryQueriesTabProps } from './RichHistoryQueriesTab'; +const asyncSpy = jest + .spyOn(reactUse, 'useAsync') + .mockReturnValue({ loading: false, value: [new MockDataSourceApi('test-ds')] }); + const setup = (propOverrides?: Partial) => { const props: RichHistoryQueriesTabProps = { queries: [], totalQueries: 0, loading: false, - activeDatasourceInstance: 'test-ds', updateFilters: jest.fn(), clearRichHistoryResults: jest.fn(), loadMoreRichHistory: jest.fn(), @@ -25,39 +31,52 @@ const setup = (propOverrides?: Partial) => { }, richHistorySettings: { retentionPeriod: 30, - activeDatasourceOnly: false, - lastUsedDatasourceFilters: [], + activeDatasourcesOnly: false, + lastUsedDatasourceFilters: ['test-ds'], starredTabAsFirstTab: false, }, - exploreId: 'left', height: 100, }; Object.assign(props, propOverrides); - return render(); + return render(, { wrapper: TestProvider }); }; describe('RichHistoryQueriesTab', () => { beforeAll(() => { + const testDS = new MockDataSourceApi('test-ds'); setDataSourceSrv({ getList() { - return []; + return [testDS]; }, } as unknown as DataSourceSrv); }); - - it('should render', () => { + afterEach(() => { + asyncSpy.mockClear(); + }); + it('should render', async () => { setup(); - expect(screen.queryByText('Filter history')).toBeInTheDocument(); + const filterHistory = await screen.findByText('Filter history'); + expect(filterHistory).toBeInTheDocument(); }); - it('should not regex escape filter input', () => { + it('should not regex escape filter input', async () => { const updateFiltersSpy = jest.fn(); setup({ updateFilters: updateFiltersSpy }); - const input = screen.getByPlaceholderText(/search queries/i); + const input = await screen.findByPlaceholderText(/search queries/i); fireEvent.change(input, { target: { value: '|=' } }); expect(updateFiltersSpy).toHaveBeenCalledWith(expect.objectContaining({ search: '|=' })); }); + + it('should update the filter and get data once on mount, and update the filter when the it changes', async () => { + const updateFiltersSpy = jest.fn(); + setup({ updateFilters: updateFiltersSpy }); + expect(updateFiltersSpy).toHaveBeenCalledTimes(1); + expect(asyncSpy).toHaveBeenCalledTimes(1); + const input = await screen.findByLabelText(/remove/i); + fireEvent.click(input); + expect(updateFiltersSpy).toHaveBeenCalledTimes(2); + }); }); diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx index 7a7b5cc9d0e..469371caa4a 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx @@ -1,8 +1,9 @@ import { css } from '@emotion/css'; import React, { useEffect } from 'react'; +import { useAsync } from 'react-use'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { DataSourceApi, GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { config, getDataSourceSrv } from '@grafana/runtime'; import { Button, FilterInput, MultiSelect, RangeSlider, Select, useStyles2 } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { @@ -13,8 +14,11 @@ import { RichHistorySearchFilters, RichHistorySettings, } from 'app/core/utils/richHistory'; +import { useSelector } from 'app/types'; import { RichHistoryQuery } from 'app/types/explore'; +import { selectExploreDSMaps } from '../state/selectors'; + import { getSortOrderOptions } from './RichHistory'; import RichHistoryCard from './RichHistoryCard'; @@ -22,13 +26,11 @@ export interface RichHistoryQueriesTabProps { queries: RichHistoryQuery[]; totalQueries: number; loading: boolean; - activeDatasourceInstance: string; updateFilters: (filtersToUpdate?: Partial) => void; clearRichHistoryResults: () => void; loadMoreRichHistory: () => void; richHistorySettings: RichHistorySettings; richHistorySearchFilters?: RichHistorySearchFilters; - exploreId: string; height: number; } @@ -122,20 +124,22 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) { clearRichHistoryResults, loadMoreRichHistory, richHistorySettings, - exploreId, height, - activeDatasourceInstance, } = props; + const exploreActiveDS = useSelector(selectExploreDSMaps); const styles = useStyles2(getStyles, height); const listOfDatasources = createDatasourcesList(); + // on mount, set filter to either active datasource or all datasources useEffect(() => { const datasourceFilters = - !richHistorySettings.activeDatasourceOnly && richHistorySettings.lastUsedDatasourceFilters + !richHistorySettings.activeDatasourcesOnly && richHistorySettings.lastUsedDatasourceFilters ? richHistorySettings.lastUsedDatasourceFilters - : [activeDatasourceInstance]; + : exploreActiveDS.dsToExplore + .map((eDs) => listOfDatasources.find((ds) => ds.uid === eDs.datasource?.uid)?.name) + .filter((name): name is string => !!name); const filters: RichHistorySearchFilters = { search: '', sortOrder: SortOrder.Descending, @@ -152,10 +156,33 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // whenever the filter changes, get all datasource information for the filtered datasources + const { value: datasourceFilterApis, loading: loadingDs } = useAsync(async () => { + const datasourcesToGet = + richHistorySearchFilters?.datasourceFilters && richHistorySearchFilters?.datasourceFilters.length > 0 + ? richHistorySearchFilters?.datasourceFilters + : listOfDatasources.map((ds) => ds.uid); + const dsGetProm = await datasourcesToGet.map(async (dsf) => { + try { + // this get works off datasource names + return getDataSourceSrv().get(dsf); + } catch (e) { + return Promise.resolve(); + } + }); + + if (dsGetProm !== undefined) { + const enhancedDatasourceData = (await Promise.all(dsGetProm)).filter((dsi): dsi is DataSourceApi => !!dsi); + return enhancedDatasourceData; + } else { + return []; + } + }, [richHistorySearchFilters?.datasourceFilters]); + if (!richHistorySearchFilters) { return ( - Loading...; + Loading... ); } @@ -199,7 +226,7 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) {
- {!richHistorySettings.activeDatasourceOnly && ( + {!richHistorySettings.activeDatasourcesOnly && ( { @@ -237,13 +264,13 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) {
- {loading && ( + {(loading || loadingDs) && ( Loading results... )} - {!loading && + {!(loading || loadingDs) && Object.keys(mappedQueriesToHeadings).map((heading) => { return (
@@ -266,7 +293,7 @@ export function RichHistoryQueriesTab(props: RichHistoryQueriesTabProps) {
{mappedQueriesToHeadings[heading].map((q) => { - return ; + return ; })}
); diff --git a/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx b/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx index 3d69a6beb8d..3b31f66799e 100644 --- a/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx @@ -7,10 +7,10 @@ const setup = (propOverrides?: Partial) => { const props: RichHistorySettingsProps = { retentionPeriod: 14, starredTabAsFirstTab: true, - activeDatasourceOnly: false, + activeDatasourcesOnly: false, onChangeRetentionPeriod: jest.fn(), toggleStarredTabAsFirstTab: jest.fn(), - toggleactiveDatasourceOnly: jest.fn(), + toggleActiveDatasourcesOnly: jest.fn(), deleteRichHistory: jest.fn(), }; @@ -24,7 +24,7 @@ describe('RichHistorySettings', () => { setup(); expect(screen.queryByText('2 weeks')).toBeInTheDocument(); }); - it('should render component with correctly checked starredTabAsFirstTab and uncheched toggleActiveDatasourceOnly settings', () => { + it('should render component with correctly checked starredTabAsFirstTab and uncheched toggleactiveDatasourcesOnly settings', () => { setup(); const checkboxes = screen.getAllByRole('checkbox'); expect(checkboxes.length).toBe(2); diff --git a/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx b/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx index 5da70895364..d8e4a2eac7a 100644 --- a/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx @@ -16,10 +16,10 @@ import { ShowConfirmModalEvent } from '../../../types/events'; export interface RichHistorySettingsProps { retentionPeriod: number; starredTabAsFirstTab: boolean; - activeDatasourceOnly: boolean; + activeDatasourcesOnly: boolean; onChangeRetentionPeriod: (option: SelectableValue) => void; toggleStarredTabAsFirstTab: () => void; - toggleactiveDatasourceOnly: () => void; + toggleActiveDatasourcesOnly: () => void; deleteRichHistory: () => void; } @@ -54,10 +54,10 @@ export function RichHistorySettingsTab(props: RichHistorySettingsProps) { const { retentionPeriod, starredTabAsFirstTab, - activeDatasourceOnly, + activeDatasourcesOnly, onChangeRetentionPeriod, toggleStarredTabAsFirstTab, - toggleactiveDatasourceOnly, + toggleActiveDatasourcesOnly, deleteRichHistory, } = props; const styles = useStyles2(getStyles); @@ -136,8 +136,8 @@ export function RichHistorySettingsTab(props: RichHistorySettingsProps) { > )} diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx index 56b6ab5d148..e7e64dd0f1f 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx @@ -1,11 +1,12 @@ import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; +import { TestProvider } from 'test/helpers/TestProvider'; import { SortOrder } from 'app/core/utils/richHistory'; import { RichHistoryStarredTab, RichHistoryStarredTabProps } from './RichHistoryStarredTab'; -jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); +jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockReturnValue({ dsToExplore: [] }) })); jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -21,15 +22,13 @@ const setup = (propOverrides?: Partial) => { queries: [], loading: false, totalQueries: 0, - activeDatasourceInstance: '', updateFilters: jest.fn(), loadMoreRichHistory: jest.fn(), clearRichHistoryResults: jest.fn(), - exploreId: 'left', richHistorySettings: { retentionPeriod: 7, starredTabAsFirstTab: false, - activeDatasourceOnly: false, + activeDatasourcesOnly: false, lastUsedDatasourceFilters: [], }, richHistorySearchFilters: { @@ -44,41 +43,46 @@ const setup = (propOverrides?: Partial) => { Object.assign(props, propOverrides); - const container = render(); + const container = render(, { wrapper: TestProvider }); return container; }; describe('RichHistoryStarredTab', () => { describe('sorter', () => { - it('should render sorter', () => { + it('should render sorter', async () => { const container = setup(); - expect(container.queryByLabelText('Sort queries')).toBeInTheDocument(); + const sortText = await container.findByLabelText('Sort queries'); + expect(sortText).toBeInTheDocument(); }); }); describe('select datasource', () => { - it('should render select datasource if activeDatasourceOnly is false', () => { + it('should render select datasource if activeDatasourcesOnly is false', async () => { const container = setup(); - expect(container.queryByLabelText('Filter queries for data sources(s)')).toBeInTheDocument(); + const filterText = await container.findByLabelText('Filter queries for data sources(s)'); + expect(filterText).toBeInTheDocument(); }); - it('should not render select datasource if activeDatasourceOnly is true', () => { + it('should not render select datasource if activeDatasourcesOnly is true', async () => { const container = setup({ richHistorySettings: { retentionPeriod: 7, starredTabAsFirstTab: false, - activeDatasourceOnly: true, + activeDatasourcesOnly: true, lastUsedDatasourceFilters: [], }, }); - expect(container.queryByLabelText('Filter queries for data sources(s)')).not.toBeInTheDocument(); + // trying to wait for placeholder text to render before proceeding does not work + await container.findByPlaceholderText(/search queries/i); + const filterText = container.queryByLabelText('Filter queries for data sources(s)'); + expect(filterText).not.toBeInTheDocument(); }); }); - it('should not regex escape filter input', () => { + it('should not regex escape filter input', async () => { const updateFiltersSpy = jest.fn(); setup({ updateFilters: updateFiltersSpy }); - const input = screen.getByPlaceholderText(/search queries/i); + const input = await screen.findByPlaceholderText(/search queries/i); fireEvent.change(input, { target: { value: '|=' } }); expect(updateFiltersSpy).toHaveBeenCalledWith(expect.objectContaining({ search: '|=' })); diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx index c8b824da52d..f534d084709 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx @@ -1,8 +1,9 @@ import { css } from '@emotion/css'; import React, { useEffect } from 'react'; +import { useAsync } from 'react-use'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { DataSourceApi, GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { config, getDataSourceSrv } from '@grafana/runtime'; import { useStyles2, Select, MultiSelect, FilterInput, Button } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { @@ -11,8 +12,11 @@ import { RichHistorySearchFilters, RichHistorySettings, } from 'app/core/utils/richHistory'; +import { useSelector } from 'app/types'; import { RichHistoryQuery } from 'app/types/explore'; +import { selectExploreDSMaps } from '../state/selectors'; + import { getSortOrderOptions } from './RichHistory'; import RichHistoryCard from './RichHistoryCard'; @@ -20,13 +24,11 @@ export interface RichHistoryStarredTabProps { queries: RichHistoryQuery[]; totalQueries: number; loading: boolean; - activeDatasourceInstance: string; updateFilters: (filtersToUpdate: Partial) => void; clearRichHistoryResults: () => void; loadMoreRichHistory: () => void; richHistorySearchFilters?: RichHistorySearchFilters; richHistorySettings: RichHistorySettings; - exploreId: string; } const getStyles = (theme: GrafanaTheme2) => { @@ -72,24 +74,25 @@ export function RichHistoryStarredTab(props: RichHistoryStarredTabProps) { updateFilters, clearRichHistoryResults, loadMoreRichHistory, - activeDatasourceInstance, richHistorySettings, queries, totalQueries, loading, richHistorySearchFilters, - exploreId, } = props; const styles = useStyles2(getStyles); + const exploreActiveDS = useSelector(selectExploreDSMaps); const listOfDatasources = createDatasourcesList(); useEffect(() => { const datasourceFilters = - richHistorySettings.activeDatasourceOnly && richHistorySettings.lastUsedDatasourceFilters + richHistorySettings.activeDatasourcesOnly && richHistorySettings.lastUsedDatasourceFilters ? richHistorySettings.lastUsedDatasourceFilters - : [activeDatasourceInstance]; + : exploreActiveDS.dsToExplore + .map((eDs) => listOfDatasources.find((ds) => ds.uid === eDs.datasource?.uid)?.name) + .filter((name): name is string => !!name); const filters: RichHistorySearchFilters = { search: '', sortOrder: SortOrder.Descending, @@ -105,6 +108,29 @@ export function RichHistoryStarredTab(props: RichHistoryStarredTabProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const { value: datasourceFilterApis, loading: loadingDs } = useAsync(async () => { + const datasourcesToGet = + richHistorySearchFilters?.datasourceFilters && richHistorySearchFilters?.datasourceFilters.length > 0 + ? richHistorySearchFilters?.datasourceFilters + : listOfDatasources.map((ds) => ds.uid); + const dsGetProm = await datasourcesToGet.map(async (dsf) => { + try { + // this get works off datasource names + return getDataSourceSrv().get(dsf); + } catch (e) { + return Promise.resolve(); + } + }); + + if (dsGetProm !== undefined) { + const enhancedDatasourceData = (await Promise.all(dsGetProm)).filter((dsi): dsi is DataSourceApi => !!dsi); + //setDatasourceFilterApiList(enhancedDatasourceData) + return enhancedDatasourceData; + } else { + return []; + } + }, [richHistorySearchFilters?.datasourceFilters]); + if (!richHistorySearchFilters) { return ( @@ -119,7 +145,7 @@ export function RichHistoryStarredTab(props: RichHistoryStarredTabProps) {
- {!richHistorySettings.activeDatasourceOnly && ( + {!richHistorySettings.activeDatasourcesOnly && ( { @@ -159,14 +185,14 @@ export function RichHistoryStarredTab(props: RichHistoryStarredTabProps) { />
- {loading && ( + {loading && loadingDs && ( Loading results... )} - {!loading && + {!(loading && loadingDs) && queries.map((q) => { - return ; + return ; })} {queries.length && queries.length !== totalQueries ? (
diff --git a/public/app/features/explore/spec/helper/assert.ts b/public/app/features/explore/spec/helper/assert.ts index 123db21a233..6e8f97d15e3 100644 --- a/public/app/features/explore/spec/helper/assert.ts +++ b/public/app/features/explore/spec/helper/assert.ts @@ -1,26 +1,18 @@ import { waitFor } from '@testing-library/react'; -import { getAllByRoleInQueryHistoryTab, withinExplore } from './setup'; +import { withinQueryHistory } from './setup'; -export const assertQueryHistoryExists = async (query: string, exploreId = 'left') => { - const selector = withinExplore(exploreId); +export const assertQueryHistoryExists = async (query: string) => { + const selector = withinQueryHistory(); expect(await selector.findByText('1 queries')).toBeInTheDocument(); const queryItem = selector.getByLabelText('Query text'); expect(queryItem).toHaveTextContent(query); }; -export const assertQueryHistoryContains = async (query: string, exploreId = 'left') => { - const selector = withinExplore(exploreId); +export const assertQueryHistory = async (expectedQueryTexts: string[]) => { + const selector = withinQueryHistory(); - await waitFor(() => { - const containsQuery = selector.getAllByLabelText('Query text').map((e) => (e.textContent || '').includes(query)); - expect(containsQuery).toContain(true); - }); -}; - -export const assertQueryHistory = async (expectedQueryTexts: string[], exploreId = 'left') => { - const selector = withinExplore(exploreId); await waitFor(() => { expect(selector.getByText(new RegExp(`${expectedQueryTexts.length} queries`))).toBeInTheDocument(); const queryTexts = selector.getAllByLabelText('Query text'); @@ -30,15 +22,15 @@ export const assertQueryHistory = async (expectedQueryTexts: string[], exploreId }); }; -export const assertQueryHistoryIsEmpty = async (exploreId = 'left') => { - const selector = withinExplore(exploreId); +export const assertQueryHistoryIsEmpty = async () => { + const selector = withinQueryHistory(); const queryTexts = selector.queryAllByLabelText('Query text'); expect(await queryTexts).toHaveLength(0); }; -export const assertQueryHistoryComment = async (expectedQueryComments: string[], exploreId = 'left') => { - const selector = withinExplore(exploreId); +export const assertQueryHistoryComment = async (expectedQueryComments: string[]) => { + const selector = withinQueryHistory(); await waitFor(() => { expect(selector.getByText(new RegExp(`${expectedQueryComments.length} queries`))).toBeInTheDocument(); const queryComments = selector.getAllByLabelText('Query comment'); @@ -48,25 +40,12 @@ export const assertQueryHistoryComment = async (expectedQueryComments: string[], }); }; -export const assertQueryHistoryIsStarred = async (expectedStars: boolean[], exploreId = 'left') => { - const starButtons = getAllByRoleInQueryHistoryTab(exploreId, 'button', /Star query|Unstar query/); - - await waitFor(() => - expectedStars.forEach((starred, queryIndex) => { - expect(starButtons[queryIndex]).toHaveAccessibleName(starred ? 'Unstar query' : 'Star query'); - }) - ); +export const assertQueryHistoryTabIsSelected = (tabName: 'Query history' | 'Starred' | 'Settings') => { + expect(withinQueryHistory().getByRole('tab', { name: `Tab ${tabName}`, selected: true })).toBeInTheDocument(); }; -export const assertQueryHistoryTabIsSelected = ( - tabName: 'Query history' | 'Starred' | 'Settings', - exploreId = 'left' -) => { - expect(withinExplore(exploreId).getByRole('tab', { name: `Tab ${tabName}`, selected: true })).toBeInTheDocument(); -}; - -export const assertDataSourceFilterVisibility = (visible: boolean, exploreId = 'left') => { - const filterInput = withinExplore(exploreId).queryByLabelText('Filter queries for data sources(s)'); +export const assertDataSourceFilterVisibility = (visible: boolean) => { + const filterInput = withinQueryHistory().queryByLabelText('Filter queries for data sources(s)'); if (visible) { expect(filterInput).toBeInTheDocument(); } else { @@ -74,10 +53,10 @@ export const assertDataSourceFilterVisibility = (visible: boolean, exploreId = ' } }; -export const assertQueryHistoryElementsShown = (shown: number, total: number, exploreId = 'left') => { - expect(withinExplore(exploreId).queryByText(`Showing ${shown} of ${total}`)).toBeInTheDocument(); +export const assertQueryHistoryElementsShown = (shown: number, total: number) => { + expect(withinQueryHistory().queryByText(`Showing ${shown} of ${total}`)).toBeInTheDocument(); }; -export const assertLoadMoreQueryHistoryNotVisible = (exploreId = 'left') => { - expect(withinExplore(exploreId).queryByRole('button', { name: 'Load more' })).not.toBeInTheDocument(); +export const assertLoadMoreQueryHistoryNotVisible = () => { + expect(withinQueryHistory().queryByRole('button', { name: 'Load more' })).not.toBeInTheDocument(); }; diff --git a/public/app/features/explore/spec/helper/interactions.ts b/public/app/features/explore/spec/helper/interactions.ts index 3afd11fef18..5304115ead0 100644 --- a/public/app/features/explore/spec/helper/interactions.ts +++ b/public/app/features/explore/spec/helper/interactions.ts @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { selectors } from '@grafana/e2e-selectors'; -import { getAllByRoleInQueryHistoryTab, withinExplore } from './setup'; +import { getAllByRoleInQueryHistoryTab, withinExplore, withinQueryHistory } from './setup'; export const changeDatasource = async (name: string) => { const datasourcePicker = (await screen.findByTestId(selectors.components.DataSourcePicker.container)).children[0]; @@ -25,56 +25,57 @@ export const runQuery = async (exploreId = 'left') => { await userEvent.click(button); }; -export const openQueryHistory = async (exploreId = 'left') => { - const selector = withinExplore(exploreId); - const button = selector.getByRole('button', { name: 'Query history' }); +export const openQueryHistory = async () => { + const explore = withinExplore('left'); + const button = explore.getByRole('button', { name: 'Query history' }); await userEvent.click(button); - expect(await selector.findByPlaceholderText('Search queries')).toBeInTheDocument(); + expect(await screen.findByPlaceholderText('Search queries')).toBeInTheDocument(); }; -export const closeQueryHistory = async (exploreId = 'left') => { - const closeButton = withinExplore(exploreId).getByRole('button', { name: 'Close query history' }); +export const closeQueryHistory = async () => { + const selector = withinQueryHistory(); + const closeButton = selector.getByRole('button', { name: 'Close query history' }); await userEvent.click(closeButton); }; -export const switchToQueryHistoryTab = async (name: 'Settings' | 'Query History', exploreId = 'left') => { - await userEvent.click(withinExplore(exploreId).getByRole('tab', { name: `Tab ${name}` })); +export const switchToQueryHistoryTab = async (name: 'Settings' | 'Query History') => { + await userEvent.click(withinQueryHistory().getByRole('tab', { name: `Tab ${name}` })); }; -export const selectStarredTabFirst = async (exploreId = 'left') => { - const checkbox = withinExplore(exploreId).getByRole('checkbox', { +export const selectStarredTabFirst = async () => { + const checkbox = withinQueryHistory().getByRole('checkbox', { name: /Change the default active tab from “Query history” to “Starred”/, }); await userEvent.click(checkbox); }; -export const selectOnlyActiveDataSource = async (exploreId = 'left') => { - const checkbox = withinExplore(exploreId).getByLabelText(/Only show queries for data source currently active.*/); +export const selectOnlyActiveDataSource = async () => { + const checkbox = withinQueryHistory().getByLabelText(/Only show queries for data source currently active.*/); await userEvent.click(checkbox); }; -export const starQueryHistory = async (queryIndex: number, exploreId = 'left') => { - await invokeAction(queryIndex, 'Star query', exploreId); +export const starQueryHistory = async (queryIndex: number) => { + await invokeAction(queryIndex, 'Star query'); }; -export const commentQueryHistory = async (queryIndex: number, comment: string, exploreId = 'left') => { - await invokeAction(queryIndex, 'Add comment', exploreId); - const input = withinExplore(exploreId).getByPlaceholderText('An optional description of what the query does.'); +export const commentQueryHistory = async (queryIndex: number, comment: string) => { + await invokeAction(queryIndex, 'Add comment'); + const input = withinQueryHistory().getByPlaceholderText('An optional description of what the query does.'); await userEvent.clear(input); await userEvent.type(input, comment); - await invokeAction(queryIndex, 'Save comment', exploreId); + await invokeAction(queryIndex, 'Save comment'); }; -export const deleteQueryHistory = async (queryIndex: number, exploreId = 'left') => { - await invokeAction(queryIndex, 'Delete query', exploreId); +export const deleteQueryHistory = async (queryIndex: number) => { + await invokeAction(queryIndex, 'Delete query'); }; -export const loadMoreQueryHistory = async (exploreId = 'left') => { - const button = withinExplore(exploreId).getByRole('button', { name: 'Load more' }); +export const loadMoreQueryHistory = async () => { + const button = withinQueryHistory().getByRole('button', { name: 'Load more' }); await userEvent.click(button); }; -const invokeAction = async (queryIndex: number, actionAccessibleName: string | RegExp, exploreId: string) => { - const buttons = getAllByRoleInQueryHistoryTab(exploreId, 'button', actionAccessibleName); +const invokeAction = async (queryIndex: number, actionAccessibleName: string | RegExp) => { + const buttons = getAllByRoleInQueryHistoryTab('button', actionAccessibleName); await userEvent.click(buttons[queryIndex]); }; diff --git a/public/app/features/explore/spec/helper/setup.tsx b/public/app/features/explore/spec/helper/setup.tsx index db5dfa966f5..6630602fa53 100644 --- a/public/app/features/explore/spec/helper/setup.tsx +++ b/public/app/features/explore/spec/helper/setup.tsx @@ -282,6 +282,11 @@ export const withinExplore = (exploreId: string) => { return within(container[exploreId === 'left' ? 0 : 1]); }; +export const withinQueryHistory = () => { + const container = screen.getByTestId('data-testid QueryHistory'); + return within(container); +}; + const exploreTestsHelper: { setupExplore: typeof setupExplore; tearDownExplore?: (options?: TearDownOptions) => void } = { setupExplore, @@ -291,8 +296,8 @@ const exploreTestsHelper: { setupExplore: typeof setupExplore; tearDownExplore?: /** * Optimized version of getAllByRole to avoid timeouts in tests. Please check #70158, #59116 and #47635, #78236. */ -export const getAllByRoleInQueryHistoryTab = (exploreId: string, role: ByRoleMatcher, name: string | RegExp) => { - const selector = withinExplore(exploreId); +export const getAllByRoleInQueryHistoryTab = (role: ByRoleMatcher, name: string | RegExp) => { + const selector = withinQueryHistory(); // Test ID is used to avoid test timeouts reported in const queriesContainer = selector.getByTestId('query-history-queries-tab'); return within(queriesContainer).getAllByRole(role, { name }); diff --git a/public/app/features/explore/spec/queryHistory.test.tsx b/public/app/features/explore/spec/queryHistory.test.tsx index 7aed7e67ba3..66eefeea309 100644 --- a/public/app/features/explore/spec/queryHistory.test.tsx +++ b/public/app/features/explore/spec/queryHistory.test.tsx @@ -12,11 +12,9 @@ import { assertLoadMoreQueryHistoryNotVisible, assertQueryHistory, assertQueryHistoryComment, - assertQueryHistoryContains, assertQueryHistoryElementsShown, assertQueryHistoryExists, assertQueryHistoryIsEmpty, - assertQueryHistoryIsStarred, assertQueryHistoryTabIsSelected, } from './helper/assert'; import { @@ -29,7 +27,6 @@ import { runQuery, selectOnlyActiveDataSource, selectStarredTabFirst, - starQueryHistory, switchToQueryHistoryTab, } from './helper/interactions'; import { makeLogsQueryResponse } from './helper/query'; @@ -141,58 +138,6 @@ describe('Explore: Query History', () => { await assertQueryHistory(['{"expr":"query #2"}', '{"expr":"query #1"}']); }); - describe('updates the state in both Explore panes', () => { - beforeEach(async () => { - const urlParams = { - left: serializeStateToUrlParam({ - datasource: 'loki', - queries: [{ refId: 'A', expr: 'query #1' }], - range: { from: 'now-1h', to: 'now' }, - }), - right: serializeStateToUrlParam({ - datasource: 'loki', - queries: [{ refId: 'A', expr: 'query #2' }], - range: { from: 'now-1h', to: 'now' }, - }), - }; - - const { datasources } = setupExplore({ urlParams }); - jest.mocked(datasources.loki.query).mockReturnValue(makeLogsQueryResponse()); - await waitForExplore(); - await waitForExplore('right'); - - await openQueryHistory('left'); - await openQueryHistory('right'); - }); - - it('initial state is in sync', async () => { - await assertQueryHistoryContains('{"expr":"query #1"}', 'left'); - await assertQueryHistoryContains('{"expr":"query #2"}', 'left'); - await assertQueryHistoryContains('{"expr":"query #1"}', 'right'); - await assertQueryHistoryContains('{"expr":"query #2"}', 'right'); - }); - - it('starred queries are synced', async () => { - // star one one query - await starQueryHistory(1, 'left'); - await assertQueryHistoryIsStarred([false, true], 'left'); - await assertQueryHistoryIsStarred([false, true], 'right'); - expect(reportInteractionMock).toBeCalledWith('grafana_explore_query_history_starred', { - queryHistoryEnabled: false, - newValue: true, - }); - }); - - it('deleted queries are synced', async () => { - await deleteQueryHistory(0, 'left'); - await assertQueryHistory(['{"expr":"query #1"}'], 'left'); - await assertQueryHistory(['{"expr":"query #1"}'], 'right'); - expect(reportInteractionMock).toBeCalledWith('grafana_explore_query_history_deleted', { - queryHistoryEnabled: false, - }); - }); - }); - it('add comments to query history', async () => { const urlParams = { left: serializeStateToUrlParam({ @@ -206,9 +151,9 @@ describe('Explore: Query History', () => { jest.mocked(datasources.loki.query).mockReturnValueOnce(makeLogsQueryResponse()); await waitForExplore(); await openQueryHistory(); - await assertQueryHistory(['{"expr":"query #1"}'], 'left'); + await assertQueryHistory(['{"expr":"query #1"}']); await commentQueryHistory(0, 'test comment'); - await assertQueryHistoryComment(['test comment'], 'left'); + await assertQueryHistoryComment(['test comment']); }); it('removes the query item from the history panel when user deletes a regular query', async () => { @@ -227,13 +172,13 @@ describe('Explore: Query History', () => { await openQueryHistory(); // queries in history - await assertQueryHistory(['{"expr":"query #1"}'], 'left'); + await assertQueryHistory(['{"expr":"query #1"}']); // delete query - await deleteQueryHistory(0, 'left'); + await deleteQueryHistory(0); // there was only one query in history so assert that query history is empty - await assertQueryHistoryIsEmpty('left'); + await assertQueryHistoryIsEmpty(); }); it('updates query history settings', async () => { diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index e6feeda2995..b83eb938b3b 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -20,7 +20,6 @@ import { createAsyncThunk, ThunkResult } from 'app/types'; import { ExploreItemState } from 'app/types/explore'; import { datasourceReducer } from './datasource'; -import { richHistorySearchFiltersUpdatedAction, richHistoryUpdatedAction } from './main'; import { queryReducer, runQueries } from './query'; import { timeReducer, updateTime } from './time'; import { @@ -214,23 +213,6 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac state = datasourceReducer(state, action); state = timeReducer(state, action); - if (richHistoryUpdatedAction.match(action)) { - const { richHistory, total } = action.payload.richHistoryResults; - return { - ...state, - richHistory, - richHistoryTotal: total, - }; - } - - if (richHistorySearchFiltersUpdatedAction.match(action)) { - const richHistorySearchFilters = action.payload.filters; - return { - ...state, - richHistorySearchFilters, - }; - } - if (changeSizeAction.match(action)) { const containerWidth = action.payload.width; return { ...state, containerWidth }; diff --git a/public/app/features/explore/state/history.ts b/public/app/features/explore/state/history.ts index fa8348fd8dc..d582ef42878 100644 --- a/public/app/features/explore/state/history.ts +++ b/public/app/features/explore/state/history.ts @@ -1,3 +1,6 @@ +import { createAction } from '@reduxjs/toolkit'; + +import { HistoryItem } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; import { addToRichHistory, @@ -9,7 +12,7 @@ import { updateRichHistorySettings, updateStarredInRichHistory, } from 'app/core/utils/richHistory'; -import { ExploreItemState, ExploreState, RichHistoryQuery, ThunkResult } from 'app/types'; +import { RichHistoryQuery, ThunkResult } from 'app/types'; import { supportedFeatures } from '../../../core/history/richHistoryStorageProvider'; import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/utils/richHistoryTypes'; @@ -21,7 +24,15 @@ import { richHistoryStorageFullAction, richHistoryUpdatedAction, } from './main'; -import { selectPanesEntries } from './selectors'; + +// +// Actions and Payloads +// + +export interface HistoryUpdatedPayload { + history: HistoryItem[]; +} +export const historyUpdatedAction = createAction('explore/historyUpdated'); // // Action creators @@ -37,27 +48,23 @@ type SyncHistoryUpdatesOptions = { */ const updateRichHistoryState = ({ updatedQuery, deletedId }: SyncHistoryUpdatesOptions): ThunkResult => { return async (dispatch, getState) => { - forEachExplorePane(getState().explore, (item, exploreId) => { - const newRichHistory = item.richHistory - // update - .map((query) => (query.id === updatedQuery?.id ? updatedQuery : query)) - // or remove - .filter((query) => query.id !== deletedId); - const deletedItems = item.richHistory.length - newRichHistory.length; - dispatch( - richHistoryUpdatedAction({ - richHistoryResults: { richHistory: newRichHistory, total: item.richHistoryTotal! - deletedItems }, - exploreId, - }) - ); - }); - }; -}; + const richHistory = getState().explore.richHistory; -const forEachExplorePane = (state: ExploreState, callback: (item: ExploreItemState, exploreId: string) => void) => { - Object.entries(state.panes).forEach(([exploreId, item]) => { - item && callback(item, exploreId); - }); + // update or remove entries + const newRichHistory = richHistory + .map((query) => (query.id === updatedQuery?.id ? updatedQuery : query)) + .filter((query) => query.id !== deletedId); + + const deletedItems = richHistory.length - newRichHistory.length; + dispatch( + richHistoryUpdatedAction({ + richHistoryResults: { + richHistory: newRichHistory, + total: getState().explore.richHistoryTotal! - deletedItems, + }, + }) + ); + }; }; export const addHistoryItem = ( @@ -114,45 +121,41 @@ export const deleteHistoryItem = (id: string): ThunkResult => { }; export const deleteRichHistory = (): ThunkResult => { - return async (dispatch, getState) => { + return async (dispatch) => { await deleteAllFromRichHistory(); - selectPanesEntries(getState()).forEach(([exploreId]) => { - dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory: [], total: 0 }, exploreId })); - dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory: [], total: 0 }, exploreId })); - }); + dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory: [], total: 0 } })); + dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory: [], total: 0 } })); }; }; -export const loadRichHistory = (exploreId: string): ThunkResult => { +export const loadRichHistory = (): ThunkResult => { return async (dispatch, getState) => { - const filters = getState().explore.panes[exploreId]!.richHistorySearchFilters; + const filters = getState().explore.richHistorySearchFilters; if (filters) { const richHistoryResults = await getRichHistory(filters); - dispatch(richHistoryUpdatedAction({ richHistoryResults, exploreId })); + dispatch(richHistoryUpdatedAction({ richHistoryResults })); } }; }; -export const loadMoreRichHistory = (exploreId: string): ThunkResult => { +export const loadMoreRichHistory = (): ThunkResult => { return async (dispatch, getState) => { - const currentFilters = getState().explore.panes[exploreId]?.richHistorySearchFilters; - const currentRichHistory = getState().explore.panes[exploreId]?.richHistory; + const currentFilters = getState().explore.richHistorySearchFilters; + const currentRichHistory = getState().explore.richHistory; if (currentFilters && currentRichHistory) { const nextFilters = { ...currentFilters, page: (currentFilters?.page || 1) + 1 }; const moreRichHistory = await getRichHistory(nextFilters); const richHistory = [...currentRichHistory, ...moreRichHistory.richHistory]; - dispatch(richHistorySearchFiltersUpdatedAction({ filters: nextFilters, exploreId })); - dispatch( - richHistoryUpdatedAction({ richHistoryResults: { richHistory, total: moreRichHistory.total }, exploreId }) - ); + dispatch(richHistorySearchFiltersUpdatedAction({ filters: nextFilters })); + dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory, total: moreRichHistory.total } })); } }; }; -export const clearRichHistoryResults = (exploreId: string): ThunkResult => { +export const clearRichHistoryResults = (): ThunkResult => { return async (dispatch) => { - dispatch(richHistorySearchFiltersUpdatedAction({ filters: undefined, exploreId })); - dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory: [], total: 0 }, exploreId })); + dispatch(richHistorySearchFiltersUpdatedAction({ filters: undefined })); + dispatch(richHistoryUpdatedAction({ richHistoryResults: { richHistory: [], total: 0 } })); }; }; @@ -180,9 +183,9 @@ export const updateHistorySettings = (settings: RichHistorySettings): ThunkResul /** * Assumed this can be called only when settings and filters are initialised */ -export const updateHistorySearchFilters = (exploreId: string, filters: RichHistorySearchFilters): ThunkResult => { +export const updateHistorySearchFilters = (filters: RichHistorySearchFilters): ThunkResult => { return async (dispatch, getState) => { - await dispatch(richHistorySearchFiltersUpdatedAction({ exploreId, filters: { ...filters } })); + await dispatch(richHistorySearchFiltersUpdatedAction({ filters: { ...filters } })); const currentSettings = getState().explore.richHistorySettings!; if (supportedFeatures().lastUsedDataSourcesAvailable) { await dispatch( diff --git a/public/app/features/explore/state/main.ts b/public/app/features/explore/state/main.ts index 7fb7f02622f..07c234fa634 100644 --- a/public/app/features/explore/state/main.ts +++ b/public/app/features/explore/state/main.ts @@ -26,7 +26,7 @@ export interface SyncTimesPayload { } export const syncTimesAction = createAction('explore/syncTimes'); -export const richHistoryUpdatedAction = createAction<{ richHistoryResults: RichHistoryResults; exploreId: string }>( +export const richHistoryUpdatedAction = createAction<{ richHistoryResults: RichHistoryResults }>( 'explore/richHistoryUpdated' ); export const richHistoryStorageFullAction = createAction('explore/richHistoryStorageFullAction'); @@ -34,7 +34,6 @@ export const richHistoryLimitExceededAction = createAction('explore/richHistoryL export const richHistorySettingsUpdatedAction = createAction('explore/richHistorySettingsUpdated'); export const richHistorySearchFiltersUpdatedAction = createAction<{ - exploreId: string; filters?: RichHistorySearchFilters; }>('explore/richHistorySearchFiltersUpdatedAction'); @@ -125,6 +124,8 @@ export const changeCorrelationEditorDetails = createAction('explore/changeShowQueryHistory'); + export interface NavigateToExploreDependencies { timeRange: TimeRange; getExploreUrl: (args: GetExploreUrlArguments) => Promise; @@ -168,6 +169,8 @@ export const initialExploreState: ExploreState = { largerExploreId: undefined, maxedExploreId: undefined, evenSplitPanes: true, + showQueryHistory: false, + richHistory: [], }; /** @@ -243,6 +246,23 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction): }; } + if (richHistoryUpdatedAction.match(action)) { + const { richHistory, total } = action.payload.richHistoryResults; + return { + ...state, + richHistory, + richHistoryTotal: total, + }; + } + + if (richHistorySearchFiltersUpdatedAction.match(action)) { + const richHistorySearchFilters = action.payload.filters; + return { + ...state, + richHistorySearchFilters, + }; + } + if (createNewSplitOpenPane.pending.match(action)) { return { ...state, @@ -303,6 +323,13 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction): }; } + if (changeShowQueryHistory.match(action)) { + return { + ...state, + showQueryHistory: action.payload, + }; + } + const exploreId: string | undefined = action.payload?.exploreId; if (typeof exploreId === 'string') { return { diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 320f1b47108..d6e2d017512 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -494,10 +494,7 @@ async function handleHistory( // Because filtering happens in the backend we cannot add a new entry without checking if it matches currently // used filters. Instead, we refresh the query history list. - // TODO: run only if Query History list is opened (#47252) - for (const exploreId in state.panes) { - await dispatch(loadRichHistory(exploreId)); - } + await dispatch(loadRichHistory()); } interface RunQueriesOptions { diff --git a/public/app/features/explore/state/selectors.test.ts b/public/app/features/explore/state/selectors.test.ts new file mode 100644 index 00000000000..8fda241937b --- /dev/null +++ b/public/app/features/explore/state/selectors.test.ts @@ -0,0 +1,123 @@ +import { DataSourceApi, DataSourceJsonData } from '@grafana/data'; +import { DataQuery } from '@grafana/schema/dist/esm/index'; +import { configureStore } from 'app/store/configureStore'; +import { StoreState, ThunkDispatch } from 'app/types'; + +import { createDefaultInitialState } from './helpers'; +import { selectExploreDSMaps } from './selectors'; + +const { defaultInitialState } = createDefaultInitialState(); + +const datasources: DataSourceApi[] = [ + { + name: 'testDs', + type: 'postgres', + uid: 'ds1', + getRef: () => { + return { type: 'postgres', uid: 'ds1' }; + }, + } as DataSourceApi, + { + name: 'testDs2', + type: 'mysql', + uid: 'ds2', + getRef: () => { + return { type: 'mysql', uid: 'ds2' }; + }, + } as DataSourceApi, +]; + +describe('selectExploreDSMaps', () => { + it('returns datasource information as empty with empty state', () => { + const store: { dispatch: ThunkDispatch; getState: () => StoreState } = configureStore(); + + const dsMaps = selectExploreDSMaps(store.getState()); + expect(dsMaps.dsToExplore).toEqual([]); + expect(dsMaps.exploreToDS).toEqual([]); + }); + + it('returns root datasources from 2 panes with empty queries', () => { + const store: { dispatch: ThunkDispatch; getState: () => StoreState } = configureStore({ + ...defaultInitialState, + explore: { + panes: { + left: { + ...defaultInitialState.explore.panes.left, + datasourceInstance: datasources[0], + queries: [], + }, + right: { + ...defaultInitialState.explore.panes.left, + datasourceInstance: datasources[1], + queries: [], + }, + }, + }, + } as unknown as Partial); + + const dsMaps = selectExploreDSMaps(store.getState()); + expect(dsMaps.dsToExplore.length).toEqual(2); + + // ds 1 + expect(dsMaps.dsToExplore[0].datasource.uid).toEqual('ds1'); + expect(dsMaps.dsToExplore[0].exploreIds.length).toEqual(1); + expect(dsMaps.dsToExplore[0].exploreIds[0]).toEqual('left'); + // ds 2 + expect(dsMaps.dsToExplore[1].datasource.uid).toEqual('ds2'); + expect(dsMaps.dsToExplore[1].exploreIds.length).toEqual(1); + expect(dsMaps.dsToExplore[1].exploreIds[0]).toEqual('right'); + + expect(dsMaps.exploreToDS.length).toEqual(2); + // pane 1 + expect(dsMaps.exploreToDS[0].exploreId).toEqual('left'); + expect(dsMaps.exploreToDS[0].datasources.length).toEqual(1); + expect(dsMaps.exploreToDS[0].datasources[0].uid).toEqual('ds1'); + //pane 2 + expect(dsMaps.exploreToDS[1].exploreId).toEqual('right'); + expect(dsMaps.exploreToDS[1].datasources.length).toEqual(1); + expect(dsMaps.exploreToDS[1].datasources[0].uid).toEqual('ds2'); + }); + + it('returns all datasources from 2 panes with queries', () => { + const store: { dispatch: ThunkDispatch; getState: () => StoreState } = configureStore({ + ...defaultInitialState, + explore: { + panes: { + different: { + ...defaultInitialState.explore.panes.left, + datasourceInstance: datasources[0], + queries: [{ datasource: datasources[1] }], + }, + match: { + ...defaultInitialState.explore.panes.left, + datasourceInstance: datasources[1], + queries: [{ datasource: datasources[1] }], + }, + }, + }, + } as unknown as Partial); + + const dsMaps = selectExploreDSMaps(store.getState()); + expect(dsMaps.dsToExplore.length).toEqual(2); + // ds 1 + expect(dsMaps.dsToExplore[0].datasource.uid).toEqual('ds1'); + expect(dsMaps.dsToExplore[0].exploreIds.length).toEqual(1); + expect(dsMaps.dsToExplore[0].exploreIds[0]).toEqual('different'); + // ds2 + expect(dsMaps.dsToExplore[1].datasource.uid).toEqual('ds2'); + expect(dsMaps.dsToExplore[1].exploreIds.length).toEqual(2); + expect(dsMaps.dsToExplore[1].exploreIds[0]).toEqual('different'); + expect(dsMaps.dsToExplore[1].exploreIds[1]).toEqual('match'); + + expect(dsMaps.exploreToDS.length).toEqual(2); + // pane 1 + expect(dsMaps.exploreToDS[0].exploreId).toEqual('different'); + expect(dsMaps.exploreToDS[0].datasources.length).toEqual(2); + expect(dsMaps.exploreToDS[0].datasources[0].uid).toEqual('ds1'); + expect(dsMaps.exploreToDS[0].datasources[1].uid).toEqual('ds2'); + // pane 2 + expect(dsMaps.exploreToDS[1].exploreId).toEqual('match'); + expect(dsMaps.exploreToDS[1].datasources.length).toEqual(1); + expect(dsMaps.exploreToDS[1].datasources[0].uid).toEqual('ds2'); + }); +}); diff --git a/public/app/features/explore/state/selectors.ts b/public/app/features/explore/state/selectors.ts index a836e174270..a475e8b5b04 100644 --- a/public/app/features/explore/state/selectors.ts +++ b/public/app/features/explore/state/selectors.ts @@ -1,5 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; +import { flatten, uniqBy } from 'lodash'; +import { DataSourceRef } from '@grafana/schema'; import { ExploreItemState, StoreState } from 'app/types'; export const selectPanes = (state: Pick) => state.explore.panes; @@ -23,3 +25,43 @@ export const isLeftPaneSelector = (exploreId: string) => export const getExploreItemSelector = (exploreId: string) => createSelector(selectPanes, (panes) => panes[exploreId]); export const selectCorrelationDetails = createSelector(selectExploreRoot, (state) => state.correlationEditorDetails); + +export const selectShowQueryHistory = createSelector(selectExploreRoot, (state) => state.showQueryHistory); + +export const selectExploreDSMaps = createSelector(selectPanesEntries, (panes) => { + const exploreDSMap = panes + .map(([exploreId, pane]) => { + const rootDatasource = [pane?.datasourceInstance?.getRef()]; + const queryDatasources = pane?.queries.map((q) => q.datasource) || []; + const datasources = [...rootDatasource, ...queryDatasources].filter( + (datasource): datasource is DataSourceRef => !!datasource + ); + + if (datasources === undefined || datasources.length === 0) { + return undefined; + } else { + return { + exploreId, + datasources: uniqBy(datasources, (ds) => ds.uid), + }; + } + }) + .filter((pane): pane is { exploreId: string; datasources: DataSourceRef[] } => !!pane); + + const uniqueDataSources = uniqBy(flatten(exploreDSMap.map((pane) => pane.datasources)), (ds) => ds.uid); + + const dsToExploreMap = uniqueDataSources.map((ds) => { + let exploreIds: string[] = []; + exploreDSMap.forEach((eds) => { + if (eds.datasources.some((edsDs) => edsDs.uid === ds.uid)) { + exploreIds.push(eds.exploreId); + } + }); + return { + datasource: ds, + exploreIds: exploreIds, + }; + }); + + return { exploreToDS: exploreDSMap, dsToExplore: dsToExploreMap }; +}); diff --git a/public/app/features/explore/state/utils.ts b/public/app/features/explore/state/utils.ts index 0918049c03e..9ee49059123 100644 --- a/public/app/features/explore/state/utils.ts +++ b/public/app/features/explore/state/utils.ts @@ -75,7 +75,6 @@ export const makeExplorePaneState = (overrides?: Partial): Exp rawPrometheusResult: null, eventBridge: null as unknown as EventBusExtended, cache: [], - richHistory: [], supplementaryQueries: loadSupplementaryQueries(), panelsState: {}, correlations: undefined, diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index e0b5afaa0df..3f91e26c6d4 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -63,6 +63,18 @@ export interface ExploreState { panes: Record; + /** + * Is the drawer for query history showing + */ + showQueryHistory: boolean; + + /** + * History of all queries + */ + richHistory: RichHistoryQuery[]; + richHistorySearchFilters?: RichHistorySearchFilters; + richHistoryTotal?: number; + /** * Settings for rich history (note: filters are stored per each pane separately) */ @@ -206,13 +218,6 @@ export interface ExploreItemState { showFlameGraph?: boolean; showCustom?: boolean; - /** - * History of all queries - */ - richHistory: RichHistoryQuery[]; - richHistorySearchFilters?: RichHistorySearchFilters; - richHistoryTotal?: number; - /** * We are using caching to store query responses of queries run from logs navigation. * In logs navigation, we do pagination and we don't want our users to unnecessarily run the same queries that they've run just moments before. diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b037d9e8b35..dc683b42f36 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -446,10 +446,11 @@ "delete-query-tooltip": "Delete query", "delete-starred-query-confirmation-text": "Are you sure you want to permanently delete your starred query?", "edit-comment-tooltip": "Edit comment", - "loading-text": "loading...", + "left-pane": "Left pane", "optional-description": "An optional description of what the query does.", "query-comment-label": "Query comment", "query-text-label": "Query text", + "right-pane": "Right pane", "run-query-button": "Run query", "save-comment": "Save comment", "star-query-tooltip": "Star query", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index fe9435e5c32..c6df2a9ed4b 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -446,10 +446,11 @@ "delete-query-tooltip": "Đęľęŧę qūęřy", "delete-starred-query-confirmation-text": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő pęřmäʼnęʼnŧľy đęľęŧę yőūř şŧäřřęđ qūęřy?", "edit-comment-tooltip": "Ēđįŧ čőmmęʼnŧ", - "loading-text": "ľőäđįʼnģ...", + "left-pane": "Ŀęƒŧ päʼnę", "optional-description": "Åʼn őpŧįőʼnäľ đęşčřįpŧįőʼn őƒ ŵĥäŧ ŧĥę qūęřy đőęş.", "query-comment-label": "Qūęřy čőmmęʼnŧ", "query-text-label": "Qūęřy ŧęχŧ", + "right-pane": "Ŗįģĥŧ päʼnę", "run-query-button": "Ŗūʼn qūęřy", "save-comment": "Ŝävę čőmmęʼnŧ", "star-query-tooltip": "Ŝŧäř qūęřy", From 698436aa29aa5cdb86d34ae67d4393ae9d66da0a Mon Sep 17 00:00:00 2001 From: Will Browne Date: Tue, 9 Apr 2024 15:06:05 +0200 Subject: [PATCH 07/12] Plugins: Update to latest go plugin sdk (v0.221.0) (#85796) * update plugin sdk * fix indirect --- go.mod | 4 ++-- go.sum | 4 ++-- pkg/apiserver/go.mod | 4 ++-- pkg/apiserver/go.sum | 6 +++--- pkg/promlib/go.mod | 4 ++-- pkg/promlib/go.sum | 5 ++--- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 616ecb60911..6cc64e33840 100644 --- a/go.mod +++ b/go.mod @@ -51,9 +51,9 @@ require ( github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources - github.com/grafana/grafana-plugin-sdk-go v0.220.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana-plugin-sdk-go v0.221.0 // @grafana/plugins-platform-backend github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/backend-platform - github.com/hashicorp/go-hclog v1.6.2 // @grafana/plugins-platform-backend + github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend github.com/hashicorp/go-version v1.6.0 // @grafana/backend-platform github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-squad-backend diff --git a/go.sum b/go.sum index 0d6a7b2413b..cfb06496152 100644 --- a/go.sum +++ b/go.sum @@ -2186,8 +2186,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.220.0 h1:0UgyATMH9em+qk1YVqplzZpDSMFcRjx6YBjnl2cIe+E= -github.com/grafana/grafana-plugin-sdk-go v0.220.0/go.mod h1:epJ5s1UeH/mzBpRrkiO8iz9xc6uuQ8f4zwdmCA4kYmo= +github.com/grafana/grafana-plugin-sdk-go v0.221.0 h1:gHrZZNyBW7jcLFKd3cCiIcjsp/z77iUZijNct5fMa+g= +github.com/grafana/grafana-plugin-sdk-go v0.221.0/go.mod h1:0lYONo3rJrjIoJNOQP0h2NXmOaF5rFIuH/rwSVSeIrQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index f1fa08c5ec0..40f01f769b6 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,7 +5,7 @@ go 1.21.0 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 - github.com/grafana/grafana-plugin-sdk-go v0.220.0 + github.com/grafana/grafana-plugin-sdk-go v0.221.0 github.com/stretchr/testify v1.9.0 golang.org/x/mod v0.14.0 k8s.io/apimachinery v0.29.2 @@ -60,7 +60,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect - github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/imdario/mergo v0.3.16 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 64d16c6cebd..9af0b3c5de7 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -77,14 +77,14 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/grafana-plugin-sdk-go v0.220.0 h1:0UgyATMH9em+qk1YVqplzZpDSMFcRjx6YBjnl2cIe+E= +github.com/grafana/grafana-plugin-sdk-go v0.221.0 h1:gHrZZNyBW7jcLFKd3cCiIcjsp/z77iUZijNct5fMa+g= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= -github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 1080320cb39..f3b811bc960 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/promlib go 1.21.0 require ( - github.com/grafana/grafana-plugin-sdk-go v0.220.0 + github.com/grafana/grafana-plugin-sdk-go v0.221.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.18.0 @@ -56,7 +56,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect - github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/invopop/jsonschema v0.12.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 3f8486bb132..fb5b90c5aa2 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -40,13 +40,12 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/grafana/grafana-plugin-sdk-go v0.220.0 h1:0UgyATMH9em+qk1YVqplzZpDSMFcRjx6YBjnl2cIe+E= +github.com/grafana/grafana-plugin-sdk-go v0.221.0 h1:gHrZZNyBW7jcLFKd3cCiIcjsp/z77iUZijNct5fMa+g= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= -github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= From 88f116e96b8216ddf21c3af6062956e8b527305e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 9 Apr 2024 15:27:28 +0200 Subject: [PATCH 08/12] CSS: use `prefers-reduced-motion` to let users skip animations (#85450) --- .../grafana-ui/src/components/Dropdown/Dropdown.tsx | 12 ++++++++---- .../src/components/IconButton/IconButton.tsx | 9 ++++++--- .../app/core/components/AppChrome/AppChromeMenu.tsx | 8 +++++--- public/app/core/components/Login/LoginLayout.tsx | 6 ++++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx index e1bc28cab9b..f85465cba7f 100644 --- a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx +++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx @@ -13,7 +13,7 @@ import { import React, { useEffect, useRef, useState } from 'react'; import { CSSTransition } from 'react-transition-group'; -import { ReactUtils } from '../../utils'; +import { ReactUtils, handleReducedMotion } from '../../utils'; import { getPlacement } from '../../utils/tooltipUtils'; import { Portal } from '../Portal/Portal'; import { TooltipPlacement } from '../Tooltip/types'; @@ -114,13 +114,17 @@ const getStyles = (duration: number) => { appear: css({ opacity: '0', position: 'relative', - transform: 'scaleY(0.5)', transformOrigin: 'top', + ...handleReducedMotion({ + transform: 'scaleY(0.5)', + }), }), appearActive: css({ opacity: '1', - transform: 'scaleY(1)', - transition: `transform ${duration}ms cubic-bezier(0.2, 0, 0.2, 1), opacity ${duration}ms cubic-bezier(0.2, 0, 0.2, 1)`, + ...handleReducedMotion({ + transform: 'scaleY(1)', + transition: `transform ${duration}ms cubic-bezier(0.2, 0, 0.2, 1), opacity ${duration}ms cubic-bezier(0.2, 0, 0.2, 1)`, + }), }), }; }; diff --git a/packages/grafana-ui/src/components/IconButton/IconButton.tsx b/packages/grafana-ui/src/components/IconButton/IconButton.tsx index ca40fd8c8fd..fcfbb8f3d2d 100644 --- a/packages/grafana-ui/src/components/IconButton/IconButton.tsx +++ b/packages/grafana-ui/src/components/IconButton/IconButton.tsx @@ -7,6 +7,7 @@ import { useStyles2 } from '../../themes'; import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; import { ComponentSize } from '../../types'; import { IconName, IconSize, IconType } from '../../types/icon'; +import { handleReducedMotion } from '../../utils/handleReducedMotion'; import { Icon } from '../Icon/Icon'; import { getSvgSize } from '../Icon/utils'; import { TooltipPlacement, PopoverContent, Tooltip } from '../Tooltip'; @@ -143,9 +144,11 @@ const getStyles = (theme: GrafanaTheme2, size: IconSize, variant: IconButtonVari height: `${hoverSize}px`, borderRadius: theme.shape.radius.default, content: '""', - transitionDuration: '0.2s', - transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', - transitionProperty: 'opacity', + ...handleReducedMotion({ + transitionDuration: '0.2s', + transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', + transitionProperty: 'opacity', + }), }, '&:focus, &:focus-visible': getFocusStyles(theme), diff --git a/public/app/core/components/AppChrome/AppChromeMenu.tsx b/public/app/core/components/AppChrome/AppChromeMenu.tsx index 7471a2416d2..88b8db031ce 100644 --- a/public/app/core/components/AppChrome/AppChromeMenu.tsx +++ b/public/app/core/components/AppChrome/AppChromeMenu.tsx @@ -6,7 +6,7 @@ import React, { useRef } from 'react'; import CSSTransition from 'react-transition-group/CSSTransition'; import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2, useTheme2 } from '@grafana/ui'; +import { handleReducedMotion, useStyles2, useTheme2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { KioskMode } from 'app/types'; @@ -125,8 +125,10 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => { const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => { const commonTransition = { - transitionDuration: `${animationDuration}ms`, - transitionTimingFunction: theme.transitions.easing.easeInOut, + ...handleReducedMotion({ + transitionDuration: `${animationDuration}ms`, + transitionTimingFunction: theme.transitions.easing.easeInOut, + }), [theme.breakpoints.down('md')]: { overflow: 'hidden', }, diff --git a/public/app/core/components/Login/LoginLayout.tsx b/public/app/core/components/Login/LoginLayout.tsx index 95f95170980..cb391662213 100644 --- a/public/app/core/components/Login/LoginLayout.tsx +++ b/public/app/core/components/Login/LoginLayout.tsx @@ -2,7 +2,7 @@ import { cx, css, keyframes } from '@emotion/css'; import React, { useEffect, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; +import { handleReducedMotion, useStyles2 } from '@grafana/ui'; import { Branding } from '../Branding/Branding'; import { BrandingSettings } from '../Branding/types'; @@ -174,7 +174,9 @@ export const getLoginStyles = (theme: GrafanaTheme2) => { transition: '0.25s ease', }), enterAnimation: css({ - animation: `${flyInAnimation} ease-out 0.2s`, + ...handleReducedMotion({ + animation: `${flyInAnimation} ease-out 0.2s`, + }), }), }; }; From 1b1450e5935c44af2671260a2694306aea06eec0 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:30:58 -0400 Subject: [PATCH 09/12] Docs: add updated youtube link (#85801) Updated youtube link --- docs/sources/whatsnew/whats-new-in-v11-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/whatsnew/whats-new-in-v11-0.md b/docs/sources/whatsnew/whats-new-in-v11-0.md index 19e8ae502a8..18707bd3784 100644 --- a/docs/sources/whatsnew/whats-new-in-v11-0.md +++ b/docs/sources/whatsnew/whats-new-in-v11-0.md @@ -113,7 +113,7 @@ We moved the time picker into the dashboard canvas and now, together with templa If you want to learn more, in detail, about all the improvements we've made, don't miss our blog post. - +{{< youtube id="kcKwBhvrsHc" >}} ### Scenes for viewers From 518d3341d58fd5d5bd4f73058bbbfa68cd0069ba Mon Sep 17 00:00:00 2001 From: Leonor Oliveira <9090754+leonorfmartins@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:08:20 +0100 Subject: [PATCH 10/12] Add Get method in dual writer (#85589) * Add Get method in dual writer * Update pkg/apiserver/rest/dualwriter_mode3.go Co-authored-by: Dan Cech * Actually run the tests * no need for t.Run() * Update pkg/apiserver/rest/dualwriter_mode3.go Co-authored-by: Dan Cech * Update pkg/apiserver/rest/dualwriter_mode3.go Co-authored-by: Dan Cech * Update pkg/apiserver/rest/dualwriter_test.go Co-authored-by: Dan Cech * Log improvements * Fix method name --------- Co-authored-by: Dan Cech --- pkg/apiserver/rest/dualwriter_mode1.go | 5 ++ pkg/apiserver/rest/dualwriter_mode2.go | 5 ++ pkg/apiserver/rest/dualwriter_mode3.go | 22 ++++++++ pkg/apiserver/rest/dualwriter_mode4.go | 5 ++ pkg/apiserver/rest/dualwriter_test.go | 75 ++++++++++++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 pkg/apiserver/rest/dualwriter_test.go diff --git a/pkg/apiserver/rest/dualwriter_mode1.go b/pkg/apiserver/rest/dualwriter_mode1.go index e153cfb95b4..58dd0e0aede 100644 --- a/pkg/apiserver/rest/dualwriter_mode1.go +++ b/pkg/apiserver/rest/dualwriter_mode1.go @@ -32,3 +32,8 @@ func (d *DualWriterMode1) Create(ctx context.Context, obj runtime.Object, create return legacy.Create(ctx, obj, createValidation, options) } + +// Get overrides the default behavior of the DualWriter and reads only to LegacyStorage. +func (d *DualWriterMode1) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return d.Legacy.Get(ctx, name, options) +} diff --git a/pkg/apiserver/rest/dualwriter_mode2.go b/pkg/apiserver/rest/dualwriter_mode2.go index 151c22a7f3c..ecbce6b7589 100644 --- a/pkg/apiserver/rest/dualwriter_mode2.go +++ b/pkg/apiserver/rest/dualwriter_mode2.go @@ -80,3 +80,8 @@ func enrichObject(orig, copy runtime.Object) (runtime.Object, error) { return copy, nil } + +// Get overrides the default behavior of the Storage and retrieves an object from LegacyStorage +func (d *DualWriterMode2) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return d.Legacy.Get(ctx, name, &metav1.GetOptions{}) +} diff --git a/pkg/apiserver/rest/dualwriter_mode3.go b/pkg/apiserver/rest/dualwriter_mode3.go index a6ea69b85f7..517ea89d09b 100644 --- a/pkg/apiserver/rest/dualwriter_mode3.go +++ b/pkg/apiserver/rest/dualwriter_mode3.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" @@ -38,3 +39,24 @@ func (d *DualWriterMode3) Create(ctx context.Context, obj runtime.Object, create } return created, nil } + +// Get overrides the default behavior of the Storage and retrieves an object from Unified Storage +// the object is still fetched from Legacy Storage if it's not found in Unified Storage +func (d *DualWriterMode3) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.Getter) + if !ok { + return nil, fmt.Errorf("legacy storage rest.Getter is missing") + } + + s, err := d.Storage.Get(ctx, name, &metav1.GetOptions{}) + if err == nil { + return s, err + } + if !apierrors.IsNotFound(err) { + return nil, err + } + + klog.Info("object not found in unified storage. Getting it from legacy", "name", name) + + return legacy.Get(ctx, name, &metav1.GetOptions{}) +} diff --git a/pkg/apiserver/rest/dualwriter_mode4.go b/pkg/apiserver/rest/dualwriter_mode4.go index 3b1beb42aef..6dc4b281192 100644 --- a/pkg/apiserver/rest/dualwriter_mode4.go +++ b/pkg/apiserver/rest/dualwriter_mode4.go @@ -23,3 +23,8 @@ func NewDualWriterMode4(legacy LegacyStorage, storage Storage) *DualWriterMode4 func (d *DualWriterMode4) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { return d.Storage.Create(ctx, obj, createValidation, options) } + +// Get overrides the default behavior of the Storage and retrieves an object from Unified Storage +func (d *DualWriterMode4) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return d.Storage.Get(ctx, name, &metav1.GetOptions{}) +} diff --git a/pkg/apiserver/rest/dualwriter_test.go b/pkg/apiserver/rest/dualwriter_test.go new file mode 100644 index 00000000000..f6d5a264803 --- /dev/null +++ b/pkg/apiserver/rest/dualwriter_test.go @@ -0,0 +1,75 @@ +package rest + +import ( + "context" + "testing" + + "github.com/zeebo/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const kind = "dummy" + +func Test_Mode1(t *testing.T) { + var ls = (LegacyStorage)(nil) + var s = (Storage)(nil) + lsSpy := NewLegacyStorageSpyClient(ls) + sSpy := NewStorageSpyClient(s) + + dw := NewDualWriterMode1(lsSpy, sSpy) + + _, err := dw.Get(context.Background(), kind, &metav1.GetOptions{}) + assert.NoError(t, err) + + // it should use the Legacy Get implementation + assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Get")) + assert.Equal(t, 0, sSpy.Counts("Storage.Get")) +} + +func Test_Mode2(t *testing.T) { + var ls = (LegacyStorage)(nil) + var s = (Storage)(nil) + lsSpy := NewLegacyStorageSpyClient(ls) + sSpy := NewStorageSpyClient(s) + + dw := NewDualWriterMode2(lsSpy, sSpy) + + _, err := dw.Get(context.Background(), kind, &metav1.GetOptions{}) + assert.NoError(t, err) + + // it should use the Legacy Get implementation + assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Get")) + assert.Equal(t, 0, sSpy.Counts("Storage.Get")) +} + +func Mode3_Test(t *testing.T) { + var ls = (LegacyStorage)(nil) + var s = (Storage)(nil) + lsSpy := NewLegacyStorageSpyClient(ls) + sSpy := NewStorageSpyClient(s) + + dw := NewDualWriterMode3(lsSpy, sSpy) + + _, err := dw.Get(context.Background(), kind, &metav1.GetOptions{}) + assert.NoError(t, err) + + // it should use the Unified Storage Get implementation + assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Get")) + assert.Equal(t, 1, sSpy.Counts("Storage.Get")) +} + +func Test_Mode4(t *testing.T) { + var ls = (LegacyStorage)(nil) + var s = (Storage)(nil) + lsSpy := NewLegacyStorageSpyClient(ls) + sSpy := NewStorageSpyClient(s) + + dw := NewDualWriterMode3(lsSpy, sSpy) + + _, err := dw.Get(context.Background(), kind, &metav1.GetOptions{}) + assert.NoError(t, err) + + // it should use the Unified Storage Get implementation + assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Get")) + assert.Equal(t, 1, sSpy.Counts("Storage.Get")) +} From 72472e5eb756bca90267f4615eb9dae18857584c Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:39:25 -0400 Subject: [PATCH 11/12] K8s: Add slog wrapper (#84680) --- go.mod | 6 +- pkg/apimachinery/go.sum | 12 + pkg/apiserver/go.mod | 2 + pkg/apiserver/go.sum | 228 ++++++++++++++++++ pkg/build/wire/go.sum | 2 + pkg/extensions/main.go | 2 + pkg/infra/log/slogadapter/adapter.go | 63 +++++ pkg/promlib/go.sum | 300 ++++++++++++++++++++++++ pkg/services/apiserver/options/extra.go | 12 +- pkg/services/apiserver/options/log.go | 54 ----- pkg/util/xorm/go.sum | 17 ++ 11 files changed, 637 insertions(+), 61 deletions(-) create mode 100644 pkg/infra/log/slogadapter/adapter.go delete mode 100644 pkg/services/apiserver/options/log.go diff --git a/go.mod b/go.mod index 6cc64e33840..1f2720d1c1f 100644 --- a/go.mod +++ b/go.mod @@ -161,7 +161,7 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect; @grafana/alerting-squad + github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/igm/sockjs-go/v3 v3.0.2 // indirect @@ -316,7 +316,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect; @grafana/alerting-squad-backend + github.com/hashicorp/golang-lru/v2 v2.0.7 // @grafana/alerting-squad-backend github.com/hashicorp/memberlist v0.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect @@ -407,7 +407,7 @@ require ( github.com/docker/docker v24.0.7+incompatible // @grafana/grafana-release-guild github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect - github.com/go-logr/logr v1.4.1 // @grafana/grafana-app-platform-squad + github.com/go-logr/logr v1.4.1 // indirect; @grafana/grafana-app-platform-squad github.com/go-logr/stdr v1.2.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/imdario/mergo v0.3.16 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 50160411c08..c8ec4e9cce8 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -1,11 +1,17 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -39,12 +45,15 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -57,6 +66,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -78,6 +88,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -91,6 +102,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 40f01f769b6..20a77d049a6 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -6,7 +6,9 @@ require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 github.com/grafana/grafana-plugin-sdk-go v0.221.0 + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/stretchr/testify v1.9.0 + github.com/zeebo/assert v1.3.0 golang.org/x/mod v0.14.0 k8s.io/apimachinery v0.29.2 k8s.io/apiserver v0.29.2 diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 9af0b3c5de7..7f8766f7a9b 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -1,69 +1,121 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= +github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= +github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= +github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= +github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= +github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -72,35 +124,58 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 h1:PxlBVtIFHR/mtWk2i0gTEdCz+jBnqiuHNSki0epDbVs= +github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/grafana-plugin-sdk-go v0.221.0 h1:gHrZZNyBW7jcLFKd3cCiIcjsp/z77iUZijNct5fMa+g= +github.com/grafana/grafana-plugin-sdk-go v0.221.0/go.mod h1:0lYONo3rJrjIoJNOQP0h2NXmOaF5rFIuH/rwSVSeIrQ= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= +github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -109,75 +184,134 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= +github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= @@ -195,80 +329,168 @@ go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6 go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg= go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 h1:RtcvQ4iw3w9NBB5yRwgA4sSa82rfId7n4atVpvKx3bY= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0/go.mod h1:f/PbKbRd4cdUICWell6DmzvVJ7QrmBgFrRHjXmAXbK4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 h1:bAHX+zN/inu+Rbqk51REmC8oXLl+Dw6pp9ldQf/onaY= +go.opentelemetry.io/contrib/propagators/jaeger v1.22.0/go.mod h1:bH9GkgkN21mscXcQP6lQJYI8XnEPDxlTN/ZOBuHDjqE= go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 h1:Q9PrD94WoMolBx44ef5UWWvufpVSME0MiSymXZfedso= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0/go.mod h1:tjp49JHNvreAAoWjdCHIVD7NXMjuJ3Dp/9iNOuPPlC8= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= +gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -279,8 +501,12 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= @@ -292,9 +518,11 @@ k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= +k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2SGwkQasmbeqDo8th5wOBA5h/AjTKA4I= diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index d24a2f2fe06..371f59f5482 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -1,7 +1,9 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 327e2082213..2a8e7d3005d 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -19,6 +19,8 @@ import ( _ "github.com/grafana/dskit/flagext" _ "github.com/grafana/gofpdf" _ "github.com/grpc-ecosystem/go-grpc-middleware" + _ "github.com/hashicorp/go-multierror" + _ "github.com/hashicorp/golang-lru/v2" _ "github.com/linkedin/goavro/v2" _ "github.com/m3db/prometheus_remote_client_golang/promremote" _ "github.com/robfig/cron/v3" diff --git a/pkg/infra/log/slogadapter/adapter.go b/pkg/infra/log/slogadapter/adapter.go new file mode 100644 index 00000000000..759888b3286 --- /dev/null +++ b/pkg/infra/log/slogadapter/adapter.go @@ -0,0 +1,63 @@ +package slogadapter + +import ( + "context" + "log/slog" + + "github.com/grafana/grafana/pkg/infra/log" +) + +var _ slog.Handler = &slogHandler{} + +type slogHandler struct { + log.Logger +} + +// NewSLogHandler returns a new slog.Handler that logs to the given log.Logger. +func New(logger log.Logger) *slogHandler { + return &slogHandler{logger} +} + +// Enabled implements slog.Handler.Enabled. +func (h *slogHandler) Enabled(_ context.Context, _ slog.Level) bool { + return true +} + +// Handle implements slog.Handler.Handle. +func (h *slogHandler) Handle(ctx context.Context, r slog.Record) error { + attrs := make([]any, 0, 2*r.NumAttrs()) + fn := func(attr slog.Attr) bool { + attrs = append(attrs, attr.Key, attr.Value) + return true + } + r.Attrs(fn) + + switch level := r.Level; { + case level < slog.LevelInfo: + h.Debug(r.Message, attrs...) + case level < slog.LevelWarn: + h.Info(r.Message, attrs...) + case level < slog.LevelError: + h.Warn(r.Message, attrs...) + default: + h.Error(r.Message, attrs...) + } + return nil +} + +// WithAttrs implements slog.Handler.WithAttrs. +func (h *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + out := make([]any, 0, 2*len(attrs)) + for _, attr := range attrs { + out = append(out, attr.Key, attr.Value) + } + return New(h.Logger.New(out...)) +} + +// WithGroup implements slog.Handler.WithGroup. +func (h *slogHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + return New(h.Logger.New("group", name)) +} diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index fb5b90c5aa2..fb3121e2f4d 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -1,140 +1,440 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.50.8 h1:gY0WoOW+/Wz6XmYSgDH9ge3wnAevYDSQWPxxJvqAkP4= +github.com/aws/aws-sdk-go v1.50.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= +github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= +github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= +github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= +github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= +github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grafana/grafana-plugin-sdk-go v0.221.0 h1:gHrZZNyBW7jcLFKd3cCiIcjsp/z77iUZijNct5fMa+g= +github.com/grafana/grafana-plugin-sdk-go v0.221.0/go.mod h1:0lYONo3rJrjIoJNOQP0h2NXmOaF5rFIuH/rwSVSeIrQ= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= +github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= +github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 h1:etRZv4bJf9YAuyPWbyFufjkijfeoPSmyA5xNcd4DoyI= github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3/go.mod h1:plwr4+63Q1xL8oIdBDeU854um7Cct0Av8dhP44lutMw= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= +github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 h1:RtcvQ4iw3w9NBB5yRwgA4sSa82rfId7n4atVpvKx3bY= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0/go.mod h1:f/PbKbRd4cdUICWell6DmzvVJ7QrmBgFrRHjXmAXbK4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 h1:bAHX+zN/inu+Rbqk51REmC8oXLl+Dw6pp9ldQf/onaY= +go.opentelemetry.io/contrib/propagators/jaeger v1.22.0/go.mod h1:bH9GkgkN21mscXcQP6lQJYI8XnEPDxlTN/ZOBuHDjqE= go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 h1:Q9PrD94WoMolBx44ef5UWWvufpVSME0MiSymXZfedso= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0/go.mod h1:tjp49JHNvreAAoWjdCHIVD7NXMjuJ3Dp/9iNOuPPlC8= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= +gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= +k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= diff --git a/pkg/services/apiserver/options/extra.go b/pkg/services/apiserver/options/extra.go index fdb394d4d28..aaf1052f3b0 100644 --- a/pkg/services/apiserver/options/extra.go +++ b/pkg/services/apiserver/options/extra.go @@ -1,9 +1,11 @@ package options import ( + "log/slog" "strconv" - "github.com/go-logr/logr" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/log/slogadapter" "github.com/spf13/pflag" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/component-base/logs" @@ -36,10 +38,12 @@ func (o *ExtraOptions) Validate() []error { } func (o *ExtraOptions) ApplyTo(c *genericapiserver.RecommendedConfig) error { - logger := logr.New(newLogAdapter(o.Verbosity)) - klog.SetLoggerWithOptions(logger, klog.ContextualLogger(true)) + handler := slogadapter.New(log.New("grafana-apiserver")) + logger := slog.New(handler) + + klog.SetSlogLogger(logger) if _, err := logs.GlogSetter(strconv.Itoa(o.Verbosity)); err != nil { - logger.Error(err, "failed to set log level") + logger.Error("failed to set log level", "error", err) } c.ExternalAddress = o.ExternalAddress return nil diff --git a/pkg/services/apiserver/options/log.go b/pkg/services/apiserver/options/log.go deleted file mode 100644 index 9e32f66e3b3..00000000000 --- a/pkg/services/apiserver/options/log.go +++ /dev/null @@ -1,54 +0,0 @@ -package options - -import ( - "strings" - - "github.com/go-logr/logr" - - "github.com/grafana/grafana/pkg/infra/log" -) - -var _ logr.LogSink = (*logAdapter)(nil) - -type logAdapter struct { - level int - log log.Logger -} - -func newLogAdapter(level int) *logAdapter { - return &logAdapter{log: log.New("grafana-apiserver"), level: level} -} - -func (l *logAdapter) WithName(name string) logr.LogSink { - l.log = l.log.New("name", name) - return l -} - -func (l *logAdapter) WithValues(keysAndValues ...any) logr.LogSink { - l.log = l.log.New(keysAndValues...) - return l -} - -func (l *logAdapter) Init(_ logr.RuntimeInfo) { - // we aren't using the logr library for logging, so this is a no-op -} - -func (l *logAdapter) Enabled(level int) bool { - return level <= l.level -} - -func (l *logAdapter) Info(level int, msg string, keysAndValues ...any) { - msg = strings.TrimSpace(msg) - // kubernetes uses level 0 for critical messages, so map that to Info - if level == 0 { - l.log.Info(msg, keysAndValues...) - return - } - // every other level is mapped to Debug - l.log.Debug(msg, keysAndValues...) -} - -func (l *logAdapter) Error(err error, msg string, keysAndValues ...any) { - msg = strings.TrimSpace(msg) - l.log.Error(msg, keysAndValues...) -} diff --git a/pkg/util/xorm/go.sum b/pkg/util/xorm/go.sum index 5e9f1440f7e..15e6719de9d 100644 --- a/pkg/util/xorm/go.sum +++ b/pkg/util/xorm/go.sum @@ -1,28 +1,45 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0= +xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= From 27bc0c19ab845094c5d4cbf83532fb4bb99474dd Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Tue, 9 Apr 2024 17:48:10 +0200 Subject: [PATCH 12/12] DashboardScene: Detect changes when editing a panel (#85708) --- .../saving/DashboardSceneChangeTracker.ts | 76 ++++++++-- .../scene/DashboardScene.test.tsx | 133 +++++++++++++++++- 2 files changed, 195 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts index 6fd1df0bc81..26f3287cd7f 100644 --- a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts +++ b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts @@ -2,19 +2,26 @@ import { Unsubscribable } from 'rxjs'; import { SceneDataLayerSet, + SceneDataTransformer, SceneGridLayout, SceneObjectStateChangedEvent, + SceneQueryRunner, SceneRefreshPicker, SceneTimeRange, SceneVariableSet, + VizPanel, behaviors, } from '@grafana/scenes'; import { createWorker } from 'app/features/dashboard-scene/saving/createDetectChangesWorker'; +import { VizPanelManager } from '../panel-edit/VizPanelManager'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene, PERSISTED_PROPS } from '../scene/DashboardScene'; +import { LibraryVizPanel } from '../scene/LibraryVizPanel'; +import { VizPanelLinks } from '../scene/PanelLinks'; +import { PanelTimeRange } from '../scene/PanelTimeRange'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { isSceneVariableInstance } from '../settings/variables/utils'; @@ -30,52 +37,97 @@ export class DashboardSceneChangeTracker { } private onStateChanged({ payload }: SceneObjectStateChangedEvent) { + // If there are no changes in the state, the check is not needed + if (Object.keys(payload.partialUpdate).length === 0) { + return; + } + + // Any change in the panel should trigger a change detection + // The VizPanelManager includes configuration for the panel like repeat + // The PanelTimeRange includes the overrides configuration + if ( + payload.changedObject instanceof VizPanel || + payload.changedObject instanceof DashboardGridItem || + payload.changedObject instanceof PanelTimeRange + ) { + return this.detectSaveModelChanges(); + } + // VizPanelManager includes the repeat configuration + if (payload.changedObject instanceof VizPanelManager) { + if ( + Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'repeat') || + Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'repeatDirection') || + Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'maxPerRow') + ) { + return this.detectSaveModelChanges(); + } + } + // SceneQueryRunner includes the DS configuration + if (payload.changedObject instanceof SceneQueryRunner) { + if (!Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'data')) { + return this.detectSaveModelChanges(); + } + } + // SceneDataTransformer includes the transformation configuration + if (payload.changedObject instanceof SceneDataTransformer) { + if (!Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'data')) { + return this.detectSaveModelChanges(); + } + } + if (payload.changedObject instanceof VizPanelLinks) { + return this.detectSaveModelChanges(); + } + if (payload.changedObject instanceof LibraryVizPanel) { + if (Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'name')) { + return this.detectSaveModelChanges(); + } + } if (payload.changedObject instanceof SceneRefreshPicker) { if ( Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'intervals') || Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'refresh') ) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } } if (payload.changedObject instanceof behaviors.CursorSync) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (payload.changedObject instanceof SceneDataLayerSet) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (payload.changedObject instanceof DashboardGridItem) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (payload.changedObject instanceof SceneGridLayout) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (payload.changedObject instanceof DashboardScene) { if (Object.keys(payload.partialUpdate).some((key) => PERSISTED_PROPS.includes(key))) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } } if (payload.changedObject instanceof SceneTimeRange) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (payload.changedObject instanceof DashboardControls) { if (Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'hideTimeControls')) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } } if (payload.changedObject instanceof SceneVariableSet) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (payload.changedObject instanceof DashboardAnnotationsDataLayer) { if (!Object.prototype.hasOwnProperty.call(payload.partialUpdate, 'data')) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } } if (payload.changedObject instanceof behaviors.LiveNowTimer) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } if (isSceneVariableInstance(payload.changedObject)) { - this.detectSaveModelChanges(); + return this.detectSaveModelChanges(); } } diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index 7c01aca36c8..43e4cf7907d 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -1,4 +1,4 @@ -import { CoreApp } from '@grafana/data'; +import { CoreApp, LoadingState, getDefaultTimeRange } from '@grafana/data'; import { sceneGraph, SceneGridLayout, @@ -9,6 +9,7 @@ import { VizPanel, SceneGridRow, behaviors, + SceneDataTransformer, } from '@grafana/scenes'; import { Dashboard, DashboardCursorSync } from '@grafana/schema'; import appEvents from 'app/core/app_events'; @@ -30,6 +31,7 @@ import { DashboardControls } from './DashboardControls'; import { DashboardGridItem } from './DashboardGridItem'; import { DashboardScene, DashboardSceneState } from './DashboardScene'; import { LibraryVizPanel } from './LibraryVizPanel'; +import { PanelTimeRange } from './PanelTimeRange'; import { RowActions } from './row-actions/RowActions'; jest.mock('../settings/version-history/HistorySrv'); @@ -270,6 +272,125 @@ describe('DashboardScene', () => { expect(dashboardSceneGraph.getCursorSync(scene)!.state).toEqual(initialState); }); + it('A change to a any VizPanel state should set isDirty true', () => { + const panel = sceneGraph.findObject(scene, (p) => p instanceof VizPanel) as VizPanel; + const prevTitle = panel.state.title; + panel.setState({ title: 'new title' }); + + expect(scene.state.isDirty).toBe(true); + + scene.exitEditMode({ skipConfirm: true }); + const restoredPanel = sceneGraph.findObject(scene, (p) => p instanceof VizPanel) as VizPanel; + expect(restoredPanel.state.title).toBe(prevTitle); + }); + + it('A change to any DashboardGridItem state should set isDirty true', () => { + const dashboardGridItem = sceneGraph.findObject( + scene, + (p) => p instanceof DashboardGridItem + ) as DashboardGridItem; + const prevValue = dashboardGridItem.state.variableName; + + dashboardGridItem.setState({ variableName: 'var1', repeatDirection: 'h', maxPerRow: 2 }); + + expect(scene.state.isDirty).toBe(true); + + scene.exitEditMode({ skipConfirm: true }); + const restoredDashboardGridItem = sceneGraph.findObject( + scene, + (p) => p instanceof DashboardGridItem + ) as DashboardGridItem; + expect(restoredDashboardGridItem.state.variableName).toBe(prevValue); + }); + + it('A change to any LibraryVizPanel name should set isDirty true', () => { + const libraryVizPanel = sceneGraph.findObject(scene, (p) => p instanceof LibraryVizPanel) as LibraryVizPanel; + const prevValue = libraryVizPanel.state.name; + + libraryVizPanel.setState({ name: 'new name' }); + + expect(scene.state.isDirty).toBe(true); + + scene.exitEditMode({ skipConfirm: true }); + const restoredLibraryVizPanel = sceneGraph.findObject( + scene, + (p) => p instanceof LibraryVizPanel + ) as LibraryVizPanel; + expect(restoredLibraryVizPanel.state.name).toBe(prevValue); + }); + + it('A change to any PanelTimeRange state should set isDirty true', () => { + const panelTimeRange = sceneGraph.findObject(scene, (p) => p instanceof PanelTimeRange) as PanelTimeRange; + const prevValue = panelTimeRange.state.from; + + panelTimeRange.setState({ from: 'now-1h', to: 'now' }); + + expect(scene.state.isDirty).toBe(true); + + scene.exitEditMode({ skipConfirm: true }); + const restoredPanelTimeRange = sceneGraph.findObject( + scene, + (p) => p instanceof PanelTimeRange + ) as PanelTimeRange; + expect(restoredPanelTimeRange.state.from).toEqual(prevValue); + }); + + it('A change to any SceneQueryRunner state should set isDirty true', () => { + const queryRunner = sceneGraph.findObject(scene, (p) => p instanceof SceneQueryRunner) as SceneQueryRunner; + const prevValue = queryRunner.state.queries; + + queryRunner.setState({ queries: [{ refId: 'A', datasource: { uid: 'fake-uid', type: 'test' } }] }); + + expect(scene.state.isDirty).toBe(true); + + scene.exitEditMode({ skipConfirm: true }); + const restoredQueryRunner = sceneGraph.findObject( + scene, + (p) => p instanceof SceneQueryRunner + ) as SceneQueryRunner; + expect(restoredQueryRunner.state.queries).toEqual(prevValue); + }); + + it('A change to any SceneDataTransformer state should set isDirty true', () => { + const dataTransformer = sceneGraph.findObject( + scene, + (p) => p instanceof SceneDataTransformer + ) as SceneDataTransformer; + const prevValue = dataTransformer.state.transformations; + + dataTransformer.setState({ transformations: [{ id: 'fake-transformation', options: {} }] }); + + expect(scene.state.isDirty).toBe(true); + + scene.exitEditMode({ skipConfirm: true }); + const restoredDataTransformer = sceneGraph.findObject( + scene, + (p) => p instanceof SceneDataTransformer + ) as SceneDataTransformer; + expect(restoredDataTransformer.state.transformations).toEqual(prevValue); + }); + + it('A change to any SceneDataTransformer data should NOT set isDirty true', () => { + const dataTransformer = sceneGraph.findObject( + scene, + (p) => p instanceof SceneDataTransformer + ) as SceneDataTransformer; + const prevValue = dataTransformer.state.data; + const newData = { state: LoadingState.Done, timeRange: getDefaultTimeRange(), series: [] }; + + dataTransformer.setState({ data: newData }); + + expect(scene.state.isDirty).toBeFalsy(); + + scene.exitEditMode({ skipConfirm: true }); + + const restoredDataTransformer = sceneGraph.findObject( + scene, + (p) => p instanceof SceneDataTransformer + ) as SceneDataTransformer; + expect(restoredDataTransformer.state.data).toEqual(prevValue); + }); + it.each([ { hasChanges: true, hasTimeChanges: false, hasVariableValueChanges: false }, { hasChanges: true, hasTimeChanges: true, hasVariableValueChanges: false }, @@ -967,7 +1088,15 @@ function buildTestScene(overrides?: Partial) { title: 'Panel A', key: 'panel-1', pluginId: 'table', - $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), + $timeRange: new PanelTimeRange({ + from: 'now-12h', + to: 'now', + timeZone: 'browser', + }), + $data: new SceneDataTransformer({ + transformations: [], + $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), + }), }), }), new DashboardGridItem({