From f71cda7c9239ea7af1d1bbb4dde3a814bcd326a8 Mon Sep 17 00:00:00 2001 From: Tigor Hutasuhut Date: Tue, 4 Jun 2024 21:08:36 +0700 Subject: [PATCH] schedule-history: revamp schedule history page --- api/schedule_history_last.go | 24 +++ api/schedule_history_list.go | 123 +++++++++---- api/utils/utils.go | 7 + pkg/pubsub/pubsub.go | 6 +- server/routes/page_schedule_history.go | 12 +- views/components/action_button.templ | 4 +- views/icons/chevrons_bold.templ | 1 - views/schedulehistories/view.templ | 235 ++++++++++++++----------- 8 files changed, 261 insertions(+), 151 deletions(-) create mode 100644 api/schedule_history_last.go create mode 100644 api/utils/utils.go diff --git a/api/schedule_history_last.go b/api/schedule_history_last.go new file mode 100644 index 0000000..179479c --- /dev/null +++ b/api/schedule_history_last.go @@ -0,0 +1,24 @@ +package api + +import ( + "net/http" + + "github.com/stephenafamo/bob/dialect/sqlite/sm" + "github.com/tigorlazuardi/redmage/models" + "github.com/tigorlazuardi/redmage/pkg/errs" + "golang.org/x/net/context" +) + +func (api *API) ScheduleHistoryLatest(ctx context.Context) (result *models.ScheduleHistory, err error) { + ctx, span := tracer.Start(ctx, "*API.ScheduleHistoryLatest") + defer span.End() + + result, err = models.ScheduleHistories.Query(ctx, api.db, sm.OrderBy(models.ScheduleHistoryColumns.CreatedAt).Desc()).One() + if err != nil { + if err.Error() == "sql: no rows in result set" { + return result, errs.Wrapw(err, "last schedule history not found").Code(http.StatusNotFound) + } + return result, errs.Wrapw(err, "failed to find last schedule history") + } + return result, nil +} diff --git a/api/schedule_history_list.go b/api/schedule_history_list.go index 2f3fdf6..cf6f8b6 100644 --- a/api/schedule_history_list.go +++ b/api/schedule_history_list.go @@ -3,30 +3,28 @@ package api import ( "context" "strconv" - "strings" "time" "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/sqlite" "github.com/stephenafamo/bob/dialect/sqlite/dialect" "github.com/stephenafamo/bob/dialect/sqlite/sm" + "github.com/tigorlazuardi/redmage/api/utils" "github.com/tigorlazuardi/redmage/models" "github.com/tigorlazuardi/redmage/pkg/errs" ) type ScheduleHistoryListParams struct { Subreddit string - After time.Time - Before time.Time + Time time.Time + Direction string - Limit int64 - Offset int64 - OrderBy string - Sort string + Limit int64 + Offset int64 } func (params *ScheduleHistoryListParams) FillFromQuery(query Queryable) { params.Subreddit = query.Get("subreddit") + params.Direction = query.Get("direction") params.Limit, _ = strconv.ParseInt(query.Get("limit"), 10, 64) if params.Limit < 1 { params.Limit = 100 @@ -42,33 +40,29 @@ func (params *ScheduleHistoryListParams) FillFromQuery(query Queryable) { now := time.Now() - afterInt, _ := strconv.ParseInt(query.Get("after"), 10, 64) - if afterInt > 0 { - params.After = time.Unix(afterInt, 0) - } else if afterInt < 0 { - params.After = now.Add(time.Duration(afterInt) * time.Second) + timeInt, _ := strconv.ParseInt(query.Get("time"), 10, 64) + if timeInt > 0 { + params.Time = time.Unix(timeInt, 0) + } else if timeInt < 0 { + params.Time = now.Add(time.Duration(timeInt) * time.Second) } - - beforeInt, _ := strconv.ParseInt(query.Get("before"), 10, 64) - if beforeInt > 0 { - params.Before = time.Unix(beforeInt, 0) - } else if beforeInt < 0 { - params.Before = now.Add(time.Duration(beforeInt) * time.Second) + if params.Time.After(now) { + params.Time = time.Time{} } - - params.OrderBy = query.Get("order_by") - params.Sort = query.Get("sort") } func (params ScheduleHistoryListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) { if params.Subreddit != "" { expr = append(expr, models.SelectWhere.ScheduleHistories.Subreddit.EQ(params.Subreddit)) } - if !params.After.IsZero() { - expr = append(expr, models.SelectWhere.ScheduleHistories.CreatedAt.GTE(params.After.Unix())) - } - if !params.Before.IsZero() { - expr = append(expr, models.SelectWhere.ScheduleHistories.CreatedAt.LTE(params.Before.Unix())) + if !params.Time.IsZero() { + if params.Direction == "before" { + expr = append(expr, + models.SelectWhere.ScheduleHistories.CreatedAt.GTE(params.Time.Unix()), + ) + } else { + expr = append(expr, models.SelectWhere.ScheduleHistories.CreatedAt.LT(params.Time.Unix())) + } } return expr @@ -82,15 +76,7 @@ func (params ScheduleHistoryListParams) Query() (expr []bob.Mod[*dialect.SelectQ if params.Offset > 0 { expr = append(expr, sm.Offset(params.Offset)) } - if params.OrderBy != "" { - if strings.ToLower(params.Sort) == "desc" { - expr = append(expr, sm.OrderBy(sqlite.Quote(params.OrderBy)).Desc()) - } else { - expr = append(expr, sm.OrderBy(sqlite.Quote(params.OrderBy)).Asc()) - } - } else { - expr = append(expr, sm.OrderBy(models.ScheduleHistoryColumns.CreatedAt).Desc(), sm.OrderBy(models.ScheduleHistoryColumns.Status).Desc()) - } + expr = append(expr, sm.OrderBy(models.ScheduleHistoryColumns.CreatedAt).Desc()) return expr } @@ -100,6 +86,71 @@ type ScheduleHistoryListResult struct { Total int64 `json:"count"` } +func (result ScheduleHistoryListResult) GetLast() *models.ScheduleHistory { + if len(result.Schedules) > 0 { + return result.Schedules[len(result.Schedules)-1] + } + return nil +} + +func (result ScheduleHistoryListResult) GetLastTime() time.Time { + if schedule := result.GetLast(); schedule != nil { + return time.Unix(schedule.CreatedAt, 0) + } + return time.Now() +} + +func (result ScheduleHistoryListResult) GetFirstTime() time.Time { + if schedule := result.GetFirst(); schedule != nil { + return time.Unix(schedule.CreatedAt, 0) + } + return time.Now() +} + +func (result ScheduleHistoryListResult) GetFirst() *models.ScheduleHistory { + if len(result.Schedules) > 0 { + return result.Schedules[0] + } + return nil +} + +func (result ScheduleHistoryListResult) SplitByDay() (out []ScheduleHistoryListResultDay) { + out = make([]ScheduleHistoryListResultDay, 0, 4) + + var lastDay time.Time + var lastIdx int + for _, schedule := range result.Schedules { + t := utils.StartOfDay(time.Unix(schedule.CreatedAt, 0).In(time.Local)) + if !t.Equal(lastDay) { + out = append(out, ScheduleHistoryListResultDay{ + Date: t, + }) + lastDay = t + lastIdx = len(out) - 1 + + out[lastIdx].Schedules = append(out[lastIdx].Schedules, schedule) + out[lastIdx].Total += 1 + } else { + out[lastIdx].Schedules = append(out[lastIdx].Schedules, schedule) + out[lastIdx].Total += 1 + } + } + + return +} + +type ScheduleHistoryListResultDay struct { + Date time.Time `json:"date"` + ScheduleHistoryListResult +} + +func (resultDay ScheduleHistoryListResultDay) GetLast() *models.ScheduleHistory { + if len(resultDay.Schedules) > 0 { + return resultDay.Schedules[len(resultDay.Schedules)-1] + } + return nil +} + func (api *API) ScheduleHistoryList(ctx context.Context, params ScheduleHistoryListParams) (result ScheduleHistoryListResult, err error) { ctx, span := tracer.Start(ctx, "*API.ScheduleHistoryList") defer span.End() diff --git a/api/utils/utils.go b/api/utils/utils.go new file mode 100644 index 0000000..fae1f0b --- /dev/null +++ b/api/utils/utils.go @@ -0,0 +1,7 @@ +package utils + +import "time" + +func StartOfDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) +} diff --git a/pkg/pubsub/pubsub.go b/pkg/pubsub/pubsub.go index 3267dfa..3e1ee24 100644 --- a/pkg/pubsub/pubsub.go +++ b/pkg/pubsub/pubsub.go @@ -1,11 +1,11 @@ package pubsub import ( + "github.com/ThreeDotsLabs/watermill" "github.com/ThreeDotsLabs/watermill-bolt/pkg/bolt" "github.com/ThreeDotsLabs/watermill/message" "github.com/tigorlazuardi/redmage/config" "github.com/tigorlazuardi/redmage/pkg/errs" - "github.com/tigorlazuardi/redmage/pkg/log" "go.etcd.io/bbolt" ) @@ -23,7 +23,7 @@ func NewPublisher(db *bbolt.DB) (message.Publisher, error) { return bolt.NewPublisher(db, bolt.PublisherConfig{ Common: bolt.CommonConfig{ Bucket: []bolt.BucketName{bolt.BucketName("watermill")}, - Logger: &log.WatermillLogger{}, + Logger: watermill.NopLogger{}, }, }) } @@ -33,7 +33,7 @@ func NewSubscriber(db *bbolt.DB) (message.Subscriber, error) { Common: bolt.CommonConfig{ Bucket: []bolt.BucketName{bolt.BucketName("watermill")}, Marshaler: nil, - Logger: &log.WatermillLogger{}, + Logger: watermill.NopLogger{}, }, }) } diff --git a/server/routes/page_schedule_history.go b/server/routes/page_schedule_history.go index 1480afa..8009800 100644 --- a/server/routes/page_schedule_history.go +++ b/server/routes/page_schedule_history.go @@ -18,7 +18,7 @@ func (routes *Routes) PageScheduleHistory(rw http.ResponseWriter, req *http.Requ var data schedulehistories.Data data.Params.FillFromQuery(req.URL.Query()) - result, err := routes.API.ScheduleHistoryListByDate(ctx, data.Params) + result, err := routes.API.ScheduleHistoryList(ctx, data.Params) if err != nil { log.New(ctx).Err(err).Error("Failed to list schedule histories") code, message := errs.HTTPMessage(err) @@ -30,7 +30,15 @@ func (routes *Routes) PageScheduleHistory(rw http.ResponseWriter, req *http.Requ return } - data.ScheduleHistories = result.Schedules + data.ScheduleHistories = result + + if latest, _ := routes.API.ScheduleHistoryLatest(ctx); latest != nil { + if first := data.ScheduleHistories.GetFirst(); first != nil { + if first.ID == latest.ID { + data.IsCurrent = true + } + } + } if err := schedulehistories.View(c, data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("Failed to render schedule histories view") diff --git a/views/components/action_button.templ b/views/components/action_button.templ index 3b4ade3..a50ee48 100644 --- a/views/components/action_button.templ +++ b/views/components/action_button.templ @@ -6,20 +6,20 @@ templ ActionButton(components ...templ.Component) {