From 2f4a973b4513c383de3b47d9f2d550eca7c888da Mon Sep 17 00:00:00 2001 From: Aleksandr Trushkin Date: Wed, 7 Feb 2024 12:24:02 +0300 Subject: [PATCH] add analytics cli commands --- cmd/cli/components/di.go | 8 +- cmd/cli/main.go | 150 ++++++++++++++++++++++++++++++++++ internal/storage/badger/db.go | 3 +- 3 files changed, 159 insertions(+), 2 deletions(-) diff --git a/cmd/cli/components/di.go b/cmd/cli/components/di.go index 6cc0475..9017de7 100644 --- a/cmd/cli/components/di.go +++ b/cmd/cli/components/di.go @@ -62,8 +62,14 @@ func SetupDI(ctx context.Context, cfgpath string, verbose bool, logAsJSON bool) writer = os.Stdout } - log := zerolog.New(writer).With().Timestamp().Str("app", "converter").Logger() + log := zerolog. + New(writer). + With(). + Timestamp(). + Str("app", "converter"). + Logger() if verbose { + return log.Level(zerolog.DebugLevel), nil } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 26156b6..40ff044 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -54,6 +54,14 @@ func runcli(ctx context.Context) (err error) { func setupDI() cli.BeforeFunc { return func(ctx context.Context, cmd *cli.Command) error { + if out := cmd.String("output"); out != "" { + var err error + cmd.Writer, err = os.Create(out) + if err != nil { + return fmt.Errorf("setting writer: %w", err) + } + } + cfgpath := cmd.String("config") debugLevel := cmd.Bool("verbose") jsonFormat := cmd.Bool("json") @@ -69,6 +77,13 @@ func setupDI() cli.BeforeFunc { func releaseDI() cli.AfterFunc { return func(ctx context.Context, c *cli.Command) error { + if f, ok := c.Writer.(*os.File); ok { + err := f.Close() + if err != nil { + println("unable to close output file:", err.Error()) + } + } + return components.Shutdown() } } @@ -95,6 +110,12 @@ func setupCLI() *cli.Command { Usage: "enables json log format", Persistent: true, }, + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Usage: "Defines output for commands", + TakesFile: true, + }, }, Before: setupDI(), @@ -348,10 +369,30 @@ func newViewItemsCmd() *cli.Command { Commands: []*cli.Command{ newViewItemsGetCmd(), newViewItemsCountCmd(), + newViewItemsUniqueParams(), + newViewItemsParamsKnownValues(), }, } } +func newViewItemsUniqueParams() *cli.Command { + return &cli.Command{ + Name: "unique-params", + Usage: "Show all stored unique param values", + Description: "This command iterates over each item and collect keys of params in a dict and then" + + " print it to the output. It's useful to find all unique parameters", + Action: decorateAction(viewItemsUniqueParamsAction), + } +} + +func newViewItemsParamsKnownValues() *cli.Command { + return &cli.Command{ + Name: "params-values", + Usage: "Show all values of requested parameters", + Action: decorateAction(viewItemsParamsKnownValuesAction), + } +} + func newViewItemsCountCmd() *cli.Command { return &cli.Command{ Name: "count", @@ -437,6 +478,115 @@ func viewItemsGetAction(ctx context.Context, c *cli.Command) error { return nil } +func viewItemsUniqueParamsAction(ctx context.Context, c *cli.Command) error { + repository, err := components.GetRepository() + if err != nil { + return fmt.Errorf("getting repository: %w", err) + } + + knownParams := map[string]struct{}{} + iter, err := repository.GoodsItem().ListIter(ctx, 1) + if err != nil { + return fmt.Errorf("getting list iter: %w", err) + } + for item := range iter { + for k := range item.Parameters { + knownParams[k] = struct{}{} + } + } + + bw := bufio.NewWriter(c.Writer) + for paramName := range knownParams { + _, err = bw.WriteString(paramName + "\n") + if err != nil { + return fmt.Errorf("unable to write: %w", err) + } + } + + return bw.Flush() +} + +type chanIter[T any] struct { + in <-chan T + err error + next T +} + +func (i *chanIter[T]) Next() (ok bool) { + if i.err != nil { + return false + } + + i.next, ok = <-i.in + if !ok { + i.err = errors.New("channel closed") + } + + return ok +} + +func (i *chanIter[T]) Get() T { + return i.next +} + +func (i *chanIter[T]) Err() error { + return i.err +} + +func (i *chanIter[T]) Close() { + for range i.in { + } +} + +func getItemsIter(ctx context.Context, r entity.GoodsItemRepository) *chanIter[entity.GoodsItem] { + in, err := r.ListIter(ctx, 3) + + return &chanIter[entity.GoodsItem]{ + in: in, + err: err, + } +} + +func viewItemsParamsKnownValuesAction(ctx context.Context, c *cli.Command) error { + repository, err := components.GetRepository() + if err != nil { + return fmt.Errorf("getting repository: %w", err) + } + log, err := components.GetLogger() + if err != nil { + return fmt.Errorf("getting logger: %w", err) + } + + params := c.Args().Slice() + requestedValues := make(map[string]map[string]struct{}, len(params)) + for _, param := range params { + log.Debug().Str("param", param).Msg("registering param") + requestedValues[param] = make(map[string]struct{}, 16) + } + + iter := getItemsIter(ctx, repository.GoodsItem()) + for iter.Next() { + item := iter.Get() + for k, v := range item.Parameters { + if _, ok := requestedValues[k]; ok { + requestedValues[k][v] = struct{}{} + } + } + } + + tbl := table.New("key", "values").WithWriter(c.Writer) + for k, v := range requestedValues { + values := make([]string, 0, len(v)) + for value := range v { + values = append(values, strconv.Quote(value)) + } + tbl.AddRow(k, values) + } + tbl.Print() + + return nil +} + func viewItemsCountAction(ctx context.Context, c *cli.Command) error { r, err := components.GetRepository() if err != nil { diff --git a/internal/storage/badger/db.go b/internal/storage/badger/db.go index b79f10e..429ef6f 100644 --- a/internal/storage/badger/db.go +++ b/internal/storage/badger/db.go @@ -35,10 +35,11 @@ func Open(ctx context.Context, path string, debug bool, log zerolog.Logger) (*ba log: log.With().Str("db", "badger").Logger(), } - level := badger.INFO + level := badger.WARNING if debug { level = badger.DEBUG } + opts := badger.DefaultOptions(path). WithLogger(bl). WithLoggingLevel(level).