diff --git a/docs/sources/explore/index.md b/docs/sources/explore/index.md index 7dca10ce46c..2b3bd3fbe24 100644 --- a/docs/sources/explore/index.md +++ b/docs/sources/explore/index.md @@ -53,7 +53,7 @@ You can close the newly created query by clicking on the Close Split button. > Share shortened link is only available in Grafana 7.3 and above. -The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs containing complex query parameters. You can create a shortened link by clicking on the **Share** option in Explore toolbar. +The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs containing complex query parameters. You can create a shortened link by clicking on the **Share** option in Explore toolbar. Please note that any shortened links that are never used will be automatically deleted after 7 days. ## Query history diff --git a/pkg/models/shorturl.go b/pkg/models/shorturl.go index e3965a3213e..480f09ca058 100644 --- a/pkg/models/shorturl.go +++ b/pkg/models/shorturl.go @@ -2,6 +2,7 @@ package models import ( "errors" + "time" ) var ( @@ -17,3 +18,9 @@ type ShortUrl struct { CreatedAt int64 LastSeenAt int64 } + +type DeleteShortUrlCommand struct { + OlderThan time.Time + + NumDeleted int64 +} diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index ec71b3ad553..442180bf367 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -7,6 +7,8 @@ import ( "path" "time" + "github.com/grafana/grafana/pkg/services/shorturls" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/serverlock" @@ -20,6 +22,7 @@ type CleanUpService struct { log log.Logger Cfg *setting.Cfg `inject:""` ServerLockService *serverlock.ServerLockService `inject:""` + ShortURLService *shorturls.ShortURLService `inject:""` } func init() { @@ -46,6 +49,7 @@ func (srv *CleanUpService) Run(ctx context.Context) error { srv.deleteExpiredDashboardVersions() srv.cleanUpOldAnnotations(ctxWithTimeout) srv.expireOldUserInvites() + srv.deleteStaleShortURLs() err := srv.ServerLockService.LockAndExecute(ctx, "delete old login attempts", time.Minute*10, func() { srv.deleteOldLoginAttempts() @@ -151,3 +155,14 @@ func (srv *CleanUpService) expireOldUserInvites() { srv.log.Debug("Expired user invites", "rows affected", cmd.NumExpired) } } + +func (srv *CleanUpService) deleteStaleShortURLs() { + cmd := models.DeleteShortUrlCommand{ + OlderThan: time.Now().Add(-time.Hour * 24 * 7), + } + if err := srv.ShortURLService.DeleteStaleShortURLs(context.Background(), &cmd); err != nil { + srv.log.Error("Problem deleting stale short urls", "error", err.Error()) + } else { + srv.log.Debug("Deleted short urls", "rows affected", cmd.NumDeleted) + } +} diff --git a/pkg/services/shorturls/short_url_service.go b/pkg/services/shorturls/short_url_service.go index 5e8fb122b43..6699744bafa 100644 --- a/pkg/services/shorturls/short_url_service.go +++ b/pkg/services/shorturls/short_url_service.go @@ -76,3 +76,16 @@ func (s ShortURLService) CreateShortURL(ctx context.Context, user *models.Signed return &shortURL, nil } + +func (s ShortURLService) DeleteStaleShortURLs(ctx context.Context, cmd *models.DeleteShortUrlCommand) error { + return s.SQLStore.WithTransactionalDbSession(ctx, func(session *sqlstore.DBSession) error { + var rawSql = "DELETE FROM short_url WHERE created_at <= ? AND (last_seen_at IS NULL OR last_seen_at = 0)" + + if result, err := session.Exec(rawSql, cmd.OlderThan.Unix()); err != nil { + return err + } else if cmd.NumDeleted, err = result.RowsAffected(); err != nil { + return err + } + return nil + }) +} diff --git a/pkg/services/shorturls/short_url_service_test.go b/pkg/services/shorturls/short_url_service_test.go index 4cc2db24d1b..9a6c66d388a 100644 --- a/pkg/services/shorturls/short_url_service_test.go +++ b/pkg/services/shorturls/short_url_service_test.go @@ -47,6 +47,31 @@ func TestShortURLService(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedTime.Unix(), updatedShortURL.LastSeenAt) }) + + t.Run("and stale short urls can be deleted", func(t *testing.T) { + staleShortURL, err := service.CreateShortURL(context.Background(), user, refPath) + require.NoError(t, err) + require.NotNil(t, staleShortURL) + require.NotEmpty(t, staleShortURL.Uid) + require.Equal(t, int64(0), staleShortURL.LastSeenAt) + + cmd := models.DeleteShortUrlCommand{OlderThan: time.Unix(staleShortURL.CreatedAt, 0)} + err = service.DeleteStaleShortURLs(context.Background(), &cmd) + require.NoError(t, err) + require.Equal(t, int64(1), cmd.NumDeleted) + + t.Run("and previously accessed short urls will still exist", func(t *testing.T) { + updatedShortURL, err := service.GetShortURLByUID(context.Background(), user, existingShortURL.Uid) + require.NoError(t, err) + require.NotNil(t, updatedShortURL) + }) + + t.Run("and no action when no stale short urls exist", func(t *testing.T) { + cmd := models.DeleteShortUrlCommand{OlderThan: time.Unix(existingShortURL.CreatedAt, 0)} + require.NoError(t, err) + require.Equal(t, int64(0), cmd.NumDeleted) + }) + }) }) t.Run("User cannot look up nonexistent short URLs", func(t *testing.T) { diff --git a/pkg/services/sqlstore/temp_user_test.go b/pkg/services/sqlstore/temp_user_test.go index e5ba591f8fb..2a5e3e1711e 100644 --- a/pkg/services/sqlstore/temp_user_test.go +++ b/pkg/services/sqlstore/temp_user_test.go @@ -4,6 +4,7 @@ package sqlstore import ( "testing" + "time" "github.com/grafana/grafana/pkg/models" . "github.com/smartystreets/goconvey/convey" @@ -68,16 +69,18 @@ func TestTempUserCommandsAndQueries(t *testing.T) { }) Convey("Should be able expire temp user", func() { - cmd2 := models.ExpireTempUsersCommand{OlderThan: timeNow()} + createdAt := time.Unix(cmd.Result.Created, 0) + cmd2 := models.ExpireTempUsersCommand{OlderThan: createdAt.Add(1 * time.Second)} err := ExpireOldUserInvites(&cmd2) So(err, ShouldBeNil) - So(cmd2.NumExpired, ShouldEqual, 1) + So(cmd2.NumExpired, ShouldEqual, int64(1)) Convey("Should do nothing when no temp users to expire", func() { - cmd2 = models.ExpireTempUsersCommand{OlderThan: timeNow()} + createdAt := time.Unix(cmd.Result.Created, 0) + cmd2 := models.ExpireTempUsersCommand{OlderThan: createdAt.Add(1 * time.Second)} err := ExpireOldUserInvites(&cmd2) So(err, ShouldBeNil) - So(cmd2.NumExpired, ShouldEqual, 0) + So(cmd2.NumExpired, ShouldEqual, int64(0)) }) }) })