diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 0ede0dd5ca5..9a9712a51c0 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -460,7 +460,7 @@ func (a *dashboardSqlAccess) buildSaveDashboardCommand(ctx context.Context, orgI } var userID int64 - if claims.IsIdentityType(user.GetIdentityType(), claims.TypeUser) { + if claims.IsIdentityType(user.GetIdentityType(), claims.TypeUser) || claims.IsIdentityType(user.GetIdentityType(), claims.TypeServiceAccount) { var err error userID, err = identity.UserIdentifier(user.GetSubject()) if err != nil { diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index da8d2bfe25d..e33841c044c 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -350,6 +350,42 @@ func TestBuildSaveDashboardCommand(t *testing.T) { require.True(t, cmd.Overwrite) }) } + + t.Run("service account should have userID set", func(t *testing.T) { + mockStore := &dashboards.FakeDashboardStore{} + access := &dashboardSqlAccess{ + dashStore: mockStore, + log: log.New("test"), + } + + dash := &dashboardV1.Dashboard{ + TypeMeta: metav1.TypeMeta{ + APIVersion: dashboardV1.APIVERSION, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-dash", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "Test Dashboard", + }, + }, + } + + ctx := identity.WithRequester(context.Background(), &user.SignedInUser{ + UserID: 123, + OrgID: 1, + OrgRole: "Editor", + IsServiceAccount: true, + }) + + mockStore.On("GetDashboard", mock.Anything, mock.Anything).Return(nil, nil).Once() + cmd, created, err := access.buildSaveDashboardCommand(ctx, 1, dash) + require.NoError(t, err) + require.True(t, created) + require.NotNil(t, cmd) + require.Equal(t, int64(123), cmd.UserID, "service account user ID should be set correctly") + }) } func TestParseLibraryPanelRow(t *testing.T) { diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index 858116ee9b7..199d45c4bf0 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -593,19 +593,19 @@ func unstructuredToLegacyDashboardVersionWithUsers( obj := vspec.MetaAccessor - var createdBy *user.User + createdByID := int64(0) if creator, ok := users[obj.GetCreatedBy()]; ok { - createdBy = creator + createdByID = creator.ID } // if updated by is set, then this version of the dashboard was "created" - // by that user - if updater, ok := users[obj.GetUpdatedBy()]; ok { - createdBy = updater - } - - createdByID := int64(0) - if createdBy != nil { - createdByID = createdBy.ID + // by that user. note: this will be empty for the first version of a dashboard in unistore, + // but will be set in legacy + if obj.GetUpdatedBy() != "" { + if updater, ok := users[obj.GetUpdatedBy()]; ok { + createdByID = updater.ID + } else { + createdByID = -1 + } } created := obj.GetCreationTimestamp().Time diff --git a/pkg/services/dashboardversion/dashverimpl/dashver_test.go b/pkg/services/dashboardversion/dashverimpl/dashver_test.go index e49df4e5ab0..909e98ee33c 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver_test.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver_test.go @@ -128,6 +128,59 @@ func TestDashboardVersionService(t *testing.T) { }) }) + t.Run("Get dashboard versions, with annonymous update", func(t *testing.T) { + dashboardService := dashboards.NewFakeDashboardService(t) + dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()} + mockCli := new(client.MockK8sHandler) + dashboardVersionService.k8sclient = mockCli + dashboardVersionService.features = featuremgmt.WithFeatures() + dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil) + + creationTimestamp := time.Now().Add(time.Hour * -24).UTC() + updatedTimestamp := time.Now().UTC().Truncate(time.Second) + dash := &unstructured.Unstructured{ + Object: map[string]any{ + "metadata": map[string]any{ + "name": "uid", + "resourceVersion": "12", + "generation": int64(10), + "labels": map[string]any{ + utils.LabelKeyDeprecatedInternalID: "42", // nolint:staticcheck + }, + "annotations": map[string]any{ + utils.AnnoKeyCreatedBy: "user:1", + utils.AnnoKeyUpdatedBy: "user:", + }, + }, + "spec": map[string]any{ + "hello": "world", + }, + }} + dash.SetCreationTimestamp(v1.NewTime(creationTimestamp)) + obj, err := utils.MetaAccessor(dash) + require.NoError(t, err) + obj.SetUpdatedTimestamp(&updatedTimestamp) + mockCli.On("GetUsersFromMeta", mock.Anything, []string{"user:1", "user:"}).Return(map[string]*user.User{"user:1": {ID: 1}}, nil) + mockCli.On("List", mock.Anything, int64(1), mock.Anything).Return(&unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{*dash}}, nil).Once() + res, err := dashboardVersionService.Get(context.Background(), &dashver.GetDashboardVersionQuery{ + DashboardID: 42, + OrgID: 1, + Version: 10, + }) + require.Nil(t, err) + require.Equal(t, res, &dashver.DashboardVersionDTO{ + ID: 10, + Version: 10, + ParentVersion: 9, + DashboardID: 42, + DashboardUID: "uid", + CreatedBy: -1, + Created: updatedTimestamp, + Data: simplejson.NewFromAny(map[string]any{"uid": "uid", "version": int64(10), "hello": "world"}), + }) + }) + t.Run("should dashboard not found error when k8s returns not found", func(t *testing.T) { dashboardService := dashboards.NewFakeDashboardService(t) dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}