From 01db7136020175519ed79f05428d7cc3df9b592a Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 24 Feb 2025 15:00:03 +0100 Subject: [PATCH 1/5] chore: update dependabot --- .github/dependabot.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5426aac..5c19264 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,8 +18,9 @@ updates: interval: 'weekly' groups: gomod-security: - applies-to: security-updates patterns: ['*'] + applies-to: security-updates gomod-update: applies-to: version-updates + update-types: ['minor', 'patch'] patterns: ['*'] From 0bd4c829872ac1331ff579c81aad56b9905dfbb5 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 24 Feb 2025 15:00:11 +0100 Subject: [PATCH 2/5] chore: update lint settings --- .golangci.yml | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 4d411c4..688f65d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,10 @@ +# yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json +# https://golangci-lint.run/usage/configuration/ + run: - timeout: 5m + go: 1.24.0 + build-tags: [safe] + modules-download-mode: readonly issues: exclude-dirs: @@ -95,7 +100,7 @@ linters-settings: linters: disable-all: true enable: - ## Enabled by default linters: + ## Default linters - errcheck # errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] - gosimple # (megacheck) Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false] - govet # (vet, vetshadow) Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false] @@ -103,12 +108,12 @@ linters: - staticcheck # (megacheck) It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false] - unused # (megacheck) Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] - ## Disabled by your configuration linters: + ## Recommended linters - asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false] - asciicheck # checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false] - bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false] - bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] - - canonicalheader # Checks whether net/http.Header uses canonical header [fast: false, auto-fix: false] + - canonicalheader # checks whether net/http.Header uses canonical header [fast: false, auto-fix: false] - containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false] - contextcheck # check whether the function uses a non-inherited context [fast: false, auto-fix: false] - copyloopvar # (go >= 1.22) copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false] @@ -118,7 +123,8 @@ linters: - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false] - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false] - exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false] - - fatcontext #Detects nested contexts in loops [fast: false, auto-fix: false] + - exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. [auto-fix] + - fatcontext # detects nested contexts in loops and function literals [fast: false, auto-fix: false] #- forbidigo # Forbids identifiers [fast: false, auto-fix: false] - forcetypeassert # finds forced type assertions [fast: true, auto-fix: false] - gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. [fast: true, auto-fix: false] @@ -134,6 +140,7 @@ linters: - gosec # (gas) Inspects source code for security problems [fast: false, auto-fix: false] - gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase [fast: false, auto-fix: false] - grouper # Analyze expression groups. [fast: true, auto-fix: false] + - iface # Detect the incorrect use of interfaces, helping developers avoid interface pollution. [fast: false, auto-fix: false] - importas # Enforces consistent import aliases [fast: false, auto-fix: false] - inamedparam # reports interfaces with unnamed method parameters [fast: true, auto-fix: false] - intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false] @@ -154,6 +161,7 @@ linters: - predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false] - promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false] - reassign # Checks that package variables are not reassigned [fast: false, auto-fix: false] + - recvcheck # checks for receiver type consistency [fast: false, auto-fix: false] - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false] - rowserrcheck # checks whether Rows.Err of rows is checked successfully [fast: false, auto-fix: false] - spancheck # Checks for mistakes with OpenTelemetry/Census spans. [fast: false, auto-fix: false] @@ -166,34 +174,37 @@ linters: - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. [fast: false, auto-fix: false] - unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false] - usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false] + - usetesting # Reports uses of functions with replacement inside the testing package. [auto-fix] - wastedassign # Finds wasted assignment statements [fast: false, auto-fix: false] - whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc. [fast: true, auto-fix: true] - ## Don't enable + ## Discouraged linters #- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false] #- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] + #- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] #- dupl # Tool for code clone detection [fast: true, auto-fix: false] #- dupword # checks for duplicate words in the source code [fast: true, auto-fix: false] #- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] #- err113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false] #- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false] - #- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. [auto-fix] + #- funlen # Tool for detection of long functions [fast: true, auto-fix: false] #- gci # Gci controls Go package import order and makes it always deterministic. [fast: true, auto-fix: true] + #- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false] #- gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false] #- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] + #- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] #- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] #- godot # Check if comments end in a period [fast: true, auto-fix: true] - #- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] + #- godox # Tool for detection of comment keywords [fast: true, auto-fix: false] #- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true] - #- funlen # Tool for detection of long functions [fast: true, auto-fix: false] - #- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false] - #- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] #- interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false] - #- lll # Reports long lines [fast: true, auto-fix: false] + #- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false] #- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false] + #- lll # Reports long lines [fast: true, auto-fix: false] #- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false] #- nestif # Reports deeply nested if statements [fast: true, auto-fix: false] #- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false] + #- mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false] #- perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false] #- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false] #- protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true] From 0d9f21312900ef41f07c08215e13a0294e444c78 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 24 Feb 2025 15:01:31 +0100 Subject: [PATCH 3/5] feat: add list command --- cmd/{tagmanager => provision}/server.go | 22 +++++++++- cmd/{tagmanager => provision}/web.go | 28 ++++++++++++- cmd/root.go | 4 +- cmd/tagmanager.go | 21 ---------- cmd/tagmanager/tags.go | 46 --------------------- cmd/tags.go | 54 +++++++++++++++++++++++++ cmd/typescript.go | 5 ++- 7 files changed, 108 insertions(+), 72 deletions(-) rename cmd/{tagmanager => provision}/server.go (89%) rename cmd/{tagmanager => provision}/web.go (69%) delete mode 100644 cmd/tagmanager.go delete mode 100644 cmd/tagmanager/tags.go create mode 100644 cmd/tags.go diff --git a/cmd/tagmanager/server.go b/cmd/provision/server.go similarity index 89% rename from cmd/tagmanager/server.go rename to cmd/provision/server.go index b83bbb4..da7a7fb 100644 --- a/cmd/tagmanager/server.go +++ b/cmd/provision/server.go @@ -1,4 +1,4 @@ -package tagmanager +package provision import ( pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd" @@ -15,6 +15,7 @@ import ( umamiprovider "github.com/foomo/sesamy-cli/pkg/provider/umami" "github.com/foomo/sesamy-cli/pkg/tagmanager" "github.com/pkg/errors" + "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -26,6 +27,7 @@ func NewServer(root *cobra.Command) { Short: "Provision Google Tag Manager Server Container", RunE: func(cmd *cobra.Command, args []string) error { l := pkgcmd.Logger() + l.Info("☕ Provisioning Server Container") tags, err := cmd.Flags().GetStringSlice("tags") if err != nil { @@ -124,6 +126,24 @@ func NewServer(root *cobra.Command) { } } + if missed := tm.Missed(); len(tags) == 0 && len(missed) > 0 { + tree := pterm.TreeNode{ + Text: "♻️ Missed resources (potentially garbage)", + } + for k, i := range missed { + child := pterm.TreeNode{ + Text: k, + } + for _, s := range i { + child.Children = append(child.Children, pterm.TreeNode{Text: s}) + } + tree.Children = append(tree.Children, child) + } + if err := pterm.DefaultTree.WithRoot(tree).Render(); err != nil { + l.Warn("failed to render missed resources", "error", err) + } + } + return nil }, } diff --git a/cmd/tagmanager/web.go b/cmd/provision/web.go similarity index 69% rename from cmd/tagmanager/web.go rename to cmd/provision/web.go index b1c2694..d28b758 100644 --- a/cmd/tagmanager/web.go +++ b/cmd/provision/web.go @@ -1,4 +1,4 @@ -package tagmanager +package provision import ( pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd" @@ -10,6 +10,7 @@ import ( hotjarprovider "github.com/foomo/sesamy-cli/pkg/provider/hotjar" "github.com/foomo/sesamy-cli/pkg/tagmanager" "github.com/pkg/errors" + "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -21,6 +22,7 @@ func NewWeb(root *cobra.Command) { Short: "Provision Google Tag Manager Web Container", RunE: func(cmd *cobra.Command, args []string) error { l := pkgcmd.Logger() + l.Info("☕ Provisioning Web Container") tags, err := cmd.Flags().GetStringSlice("tags") if err != nil { @@ -45,41 +47,65 @@ func NewWeb(root *cobra.Command) { } if pkgcmd.Tag(googletagprovider.Tag, tags) { + l.Info("🅿️ Running provider", "name", googletagprovider.Name, "tag", googletagprovider.Tag) if err := googletagprovider.Web(tm, cfg.GoogleTag); err != nil { return errors.Wrap(err, "failed to provision google tag provider") } } if cfg.GoogleAnalytics.Enabled && pkgcmd.Tag(googleanaylticsprovider.Tag, tags) { + l.Info("🅿️ Running provider", "name", googleanaylticsprovider.Name, "tag", googleanaylticsprovider.Tag) if err := googleanaylticsprovider.Web(tm, cfg.GoogleAnalytics); err != nil { return errors.Wrap(err, "failed to provision google analytics provider") } } if cfg.Emarsys.Enabled && pkgcmd.Tag(emarsysprovider.Tag, tags) { + l.Info("🅿️ Running provider", "name", emarsysprovider.Name, "tag", emarsysprovider.Tag) if err := emarsysprovider.Web(tm, cfg.Emarsys); err != nil { return errors.Wrap(err, "failed to provision emarsys provider") } } if cfg.Hotjar.Enabled && pkgcmd.Tag(hotjarprovider.Tag, tags) { + l.Info("🅿️ Running provider", "name", hotjarprovider.Name, "tag", hotjarprovider.Tag) if err := hotjarprovider.Web(tm, cfg.Hotjar); err != nil { return errors.Wrap(err, "failed to provision hotjar provider") } } if cfg.Criteo.Enabled && pkgcmd.Tag(criteoprovider.Tag, tags) { + l.Info("🅿️ Running provider", "name", criteoprovider.Name, "tag", criteoprovider.Tag) if err := criteoprovider.Web(l, tm, cfg.Criteo); err != nil { return errors.Wrap(err, "failed to provision criteo provider") } } if cfg.Cookiebot.Enabled && pkgcmd.Tag(cookiebotprovider.Tag, tags) { + l.Info("🅿️ Running provider", "name", cookiebotprovider.Name, "tag", cookiebotprovider.Tag) if err := cookiebotprovider.Web(tm, cfg.Cookiebot); err != nil { return errors.Wrap(err, "failed to provision cookiebot provider") } } + if missed := tm.Missed(); len(tags) == 0 && len(missed) > 0 { + tree := pterm.TreeNode{ + Text: "♻️ Missed resources (potentially garbage)", + } + for k, i := range missed { + child := pterm.TreeNode{ + Text: k, + } + for _, s := range i { + child.Children = append(child.Children, pterm.TreeNode{Text: s}) + } + tree.Children = append(tree.Children, child) + } + if err := pterm.DefaultTree.WithRoot(tree).Render(); err != nil { + l.Warn("failed to render missed resources", "error", err) + } + } + return nil }, } diff --git a/cmd/root.go b/cmd/root.go index 10878e5..01352aa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,7 +14,9 @@ func init() { root = NewRoot() NewConfig(root) NewVersion(root) - NewTagmanager(root) + NewTags(root) + NewList(root) + NewProvision(root) NewTypeScript(root) cobra.OnInitialize(pkgcmd.InitConfig) } diff --git a/cmd/tagmanager.go b/cmd/tagmanager.go deleted file mode 100644 index f574acd..0000000 --- a/cmd/tagmanager.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "github.com/foomo/sesamy-cli/cmd/tagmanager" - "github.com/spf13/cobra" -) - -// NewTagmanager represents the tagmanager command -func NewTagmanager(root *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "tagmanager", - Short: "Provision Google Tag Manager containers", - } - - tagmanager.NewTags(cmd) - tagmanager.NewServer(cmd) - tagmanager.NewWeb(cmd) - root.AddCommand(cmd) - - return cmd -} diff --git a/cmd/tagmanager/tags.go b/cmd/tagmanager/tags.go deleted file mode 100644 index c18e144..0000000 --- a/cmd/tagmanager/tags.go +++ /dev/null @@ -1,46 +0,0 @@ -package tagmanager - -import ( - conversionlinkerprovider "github.com/foomo/sesamy-cli/pkg/provider/conversionlinker" - criteoprovider "github.com/foomo/sesamy-cli/pkg/provider/criteo" - emarsysprovider "github.com/foomo/sesamy-cli/pkg/provider/emarsys" - facebookprovider "github.com/foomo/sesamy-cli/pkg/provider/facebook" - googleadsprovider "github.com/foomo/sesamy-cli/pkg/provider/googleads" - googleanalyticsprovider "github.com/foomo/sesamy-cli/pkg/provider/googleanalytics" - googletagmanagerprovider "github.com/foomo/sesamy-cli/pkg/provider/googletagmanager" - microsoftadsprovider "github.com/foomo/sesamy-cli/pkg/provider/microsoftads" - tracifyprovider "github.com/foomo/sesamy-cli/pkg/provider/tracify" - umamiprovider "github.com/foomo/sesamy-cli/pkg/provider/umami" - "github.com/pterm/pterm" - "github.com/spf13/cobra" -) - -// NewTags represents the tags command -func NewTags(root *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "tags", - Short: "Print out all available tags", - RunE: func(cmd *cobra.Command, args []string) error { - // Define the data for the first table - data := pterm.TableData{ - {"Name", "Tag"}, - {conversionlinkerprovider.Name, conversionlinkerprovider.Tag}, - {criteoprovider.Name, criteoprovider.Tag}, - {emarsysprovider.Name, emarsysprovider.Tag}, - {facebookprovider.Name, facebookprovider.Tag}, - {googleadsprovider.Name, googleadsprovider.Tag}, - {googleanalyticsprovider.Name, googleanalyticsprovider.Tag}, - {googletagmanagerprovider.Name, googletagmanagerprovider.Tag}, - {microsoftadsprovider.Name, microsoftadsprovider.Tag}, - {tracifyprovider.Name, tracifyprovider.Tag}, - {umamiprovider.Name, umamiprovider.Tag}, - } - - return pterm.DefaultTable.WithHasHeader().WithData(data).Render() - }, - } - - root.AddCommand(cmd) - - return cmd -} diff --git a/cmd/tags.go b/cmd/tags.go new file mode 100644 index 0000000..ccc1d53 --- /dev/null +++ b/cmd/tags.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "github.com/foomo/sesamy-cli/pkg/provider/conversionlinker" + "github.com/foomo/sesamy-cli/pkg/provider/cookiebot" + "github.com/foomo/sesamy-cli/pkg/provider/criteo" + "github.com/foomo/sesamy-cli/pkg/provider/emarsys" + "github.com/foomo/sesamy-cli/pkg/provider/facebook" + "github.com/foomo/sesamy-cli/pkg/provider/googleads" + "github.com/foomo/sesamy-cli/pkg/provider/googleanalytics" + "github.com/foomo/sesamy-cli/pkg/provider/googleconsent" + "github.com/foomo/sesamy-cli/pkg/provider/googletag" + "github.com/foomo/sesamy-cli/pkg/provider/googletagmanager" + "github.com/foomo/sesamy-cli/pkg/provider/hotjar" + "github.com/foomo/sesamy-cli/pkg/provider/microsoftads" + "github.com/foomo/sesamy-cli/pkg/provider/tracify" + "github.com/foomo/sesamy-cli/pkg/provider/umami" + "github.com/pterm/pterm" + "github.com/spf13/cobra" +) + +// NewTags represents the tags command +func NewTags(root *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "tags", + Short: "Print out all available tags", + RunE: func(cmd *cobra.Command, args []string) error { + // Define the data for the first table + data := pterm.TableData{ + {"Name", "Tag"}, + {conversionlinker.Name, conversionlinker.Tag}, + {cookiebot.Name, cookiebot.Tag}, + {criteo.Name, criteo.Tag}, + {emarsys.Name, emarsys.Tag}, + {facebook.Name, facebook.Tag}, + {googleads.Name, googleads.Tag}, + {googleanalytics.Name, googleanalytics.Tag}, + {googletag.Name, googletag.Tag}, + {googleconsent.Name, googleconsent.Tag}, + {googletagmanager.Name, googletagmanager.Tag}, + {hotjar.Name, hotjar.Tag}, + {microsoftads.Name, microsoftads.Tag}, + {tracify.Name, tracify.Tag}, + {umami.Name, umami.Tag}, + } + + return pterm.DefaultTable.WithHasHeader().WithData(data).Render() + }, + } + + root.AddCommand(cmd) + + return cmd +} diff --git a/cmd/typescript.go b/cmd/typescript.go index 2d60daf..5ced2ca 100644 --- a/cmd/typescript.go +++ b/cmd/typescript.go @@ -1,16 +1,17 @@ package cmd import ( + "maps" "os" "path" "path/filepath" + "slices" "github.com/foomo/gocontemplate/pkg/contemplate" pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd" "github.com/foomo/sesamy-cli/pkg/typescript/generator" "github.com/pkg/errors" "github.com/spf13/cobra" - "golang.org/x/exp/maps" ) // NewTypeScript represents the typescript command @@ -45,7 +46,7 @@ func NewTypeScript(root *cobra.Command) *cobra.Command { return errors.Wrap(err, "failed to create typescript output directory") } - l.InfoContext(cmd.Context(), "generated typescript code", "dir", outPath, "files", maps.Keys(files)) + l.InfoContext(cmd.Context(), "generated typescript code", "dir", outPath, "files", slices.AppendSeq(make([]string, 0, len(files)), maps.Keys(files))) for filename, file := range files { if err = os.WriteFile(path.Join(outPath, filename), []byte(file.String()), 0600); err != nil { return errors.Wrap(err, "failed to write typescript code") From 71a42cd1a172c0e2a3bbcade8eab39c6ddd72461 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 24 Feb 2025 15:02:04 +0100 Subject: [PATCH 4/5] feat(tagmanager): add event data & lookup tables --- cmd/list.go | 20 ++ cmd/list/list.go | 70 +++++ cmd/list/server.go | 55 ++++ cmd/list/web.go | 54 ++++ cmd/provision.go | 20 ++ go.mod | 21 +- go.sum | 40 +-- pkg/code/file.go | 5 +- pkg/config/googletagmanager.go | 6 +- pkg/config/googletagmanagercontainer.go | 5 +- ...oogletagmanagerservercontainervariables.go | 8 + pkg/config/lookuptable.go | 10 + pkg/provider/googletagmanager/server.go | 14 + pkg/provider/microsoftads/constants.go | 2 +- pkg/provider/microsoftads/server.go | 2 +- pkg/tagmanager/server/variable/lookuptable.go | 69 +++++ pkg/tagmanager/tagmanager.go | 281 ++++++++++++------ pkg/typescript/generator/package.go | 4 +- pkg/typescript/import.go | 5 +- pkg/utils/contemplate.go | 11 +- sesamy.schema.json | 53 +++- test/tagmanager/clientserver_test.go | 3 +- test/tagmanager/clientweb_test.go | 3 +- 23 files changed, 625 insertions(+), 136 deletions(-) create mode 100644 cmd/list.go create mode 100644 cmd/list/list.go create mode 100644 cmd/list/server.go create mode 100644 cmd/list/web.go create mode 100644 cmd/provision.go create mode 100644 pkg/config/googletagmanagerservercontainervariables.go create mode 100644 pkg/config/lookuptable.go create mode 100644 pkg/tagmanager/server/variable/lookuptable.go diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..f9f5fe9 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/foomo/sesamy-cli/cmd/list" + "github.com/spf13/cobra" +) + +// NewList represents the list command +func NewList(root *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List Google Tag Manager containers", + } + + list.NewServer(cmd) + list.NewWeb(cmd) + root.AddCommand(cmd) + + return cmd +} diff --git a/cmd/list/list.go b/cmd/list/list.go new file mode 100644 index 0000000..7ce9d54 --- /dev/null +++ b/cmd/list/list.go @@ -0,0 +1,70 @@ +package list + +import ( + "bytes" + "fmt" + "log/slog" + "os" + "strings" + + "github.com/alecthomas/chroma/quick" + "github.com/foomo/sesamy-cli/pkg/tagmanager" + "github.com/itchyny/json2yaml" +) + +func dump(i interface{ MarshalJSON() ([]byte, error) }, err error) error { + if err != nil { + return err + } + out, err := i.MarshalJSON() + if err != nil { + return err + } + // if err := json.Indent(ret, out, "", " "); err != nil { + // return err + // } + // fmt.Println(ret.String()) + var output strings.Builder + if err := json2yaml.Convert(&output, bytes.NewBuffer(out)); err != nil { + return err + } + // fmt.Print(output.String()) + return quick.Highlight(os.Stdout, output.String(), "yaml", "terminal", "monokai") +} + +func list(l *slog.Logger, tm *tagmanager.TagManager, resource string) error { + switch resource { + case "clients": + return dump(tm.Service().Accounts.Containers.Workspaces.Clients.List(tm.WorkspacePath()).Do()) + case "tags": + return dump(tm.Service().Accounts.Containers.Workspaces.Tags.List(tm.WorkspacePath()).Do()) + case "built-in-variables": + return dump(tm.Service().Accounts.Containers.Workspaces.BuiltInVariables.List(tm.WorkspacePath()).Do()) + case "folders": + return dump(tm.Service().Accounts.Containers.Workspaces.Folders.List(tm.WorkspacePath()).Do()) + case "variables": + return dump(tm.Service().Accounts.Containers.Workspaces.Variables.List(tm.WorkspacePath()).Do()) + case "templates": + return dump(tm.Service().Accounts.Containers.Workspaces.Templates.List(tm.WorkspacePath()).Do()) + case "templates-data": + r, err := tm.Service().Accounts.Containers.Workspaces.Templates.List(tm.WorkspacePath()).Do() + if err != nil { + return err + } + for _, template := range r.Template { + l.Info("---- Template data: " + template.Name + " ----------------------") + fmt.Println(template.TemplateData) + } + return nil + case "gtag-config": + return dump(tm.Service().Accounts.Containers.Workspaces.GtagConfig.List(tm.WorkspacePath()).Do()) + case "triggers": + return dump(tm.Service().Accounts.Containers.Workspaces.Triggers.List(tm.WorkspacePath()).Do()) + case "transformations": + return dump(tm.Service().Accounts.Containers.Workspaces.Transformations.List(tm.WorkspacePath()).Do()) + case "zones": + return dump(tm.Service().Accounts.Containers.Workspaces.Zones.List(tm.WorkspacePath()).Do()) + default: + return fmt.Errorf("unknown resource %s", resource) + } +} diff --git a/cmd/list/server.go b/cmd/list/server.go new file mode 100644 index 0000000..35b19a1 --- /dev/null +++ b/cmd/list/server.go @@ -0,0 +1,55 @@ +package list + +import ( + pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd" + "github.com/foomo/sesamy-cli/pkg/tagmanager" + "github.com/spf13/cobra" +) + +// NewServer represents the server command +func NewServer(root *cobra.Command) { + cmd := &cobra.Command{ + Use: "server", + Short: "List Google Tag Manager Server Container", + Args: cobra.OnlyValidArgs, + ValidArgs: []cobra.Completion{ + "built-in-variables", + "clients", + "folders", + "gtag-config", + "tags", + "templates", + "templates-data", + "transformations", + "triggers", + "variables", + "zones", + }, + RunE: func(cmd *cobra.Command, args []string) error { + resource := args[0] + l := pkgcmd.Logger() + l.Info("☕ Listing Server Container resources: " + resource) + + cfg, err := pkgcmd.ReadConfig(l, cmd) + if err != nil { + return err + } + + tm, err := tagmanager.New( + cmd.Context(), + l, + cfg.GoogleTagManager.AccountID, + cfg.GoogleTagManager.ServerContainer, + tagmanager.WithRequestQuota(cfg.GoogleAPI.RequestQuota), + tagmanager.WithClientOptions(cfg.GoogleAPI.GetClientOption()), + ) + if err != nil { + return err + } + + return list(l, tm, resource) + }, + } + + root.AddCommand(cmd) +} diff --git a/cmd/list/web.go b/cmd/list/web.go new file mode 100644 index 0000000..1d500b2 --- /dev/null +++ b/cmd/list/web.go @@ -0,0 +1,54 @@ +package list + +import ( + pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd" + "github.com/foomo/sesamy-cli/pkg/tagmanager" + "github.com/spf13/cobra" +) + +// NewWeb represents the web command +func NewWeb(root *cobra.Command) { + cmd := &cobra.Command{ + Use: "web", + Short: "List Google Tag Manager Web Container", + Args: cobra.OnlyValidArgs, + ValidArgs: []cobra.Completion{ + "built-in-variables", + "folders", + "gtag-config", + "tags", + "templates", + "templates-data", + "transformations", + "triggers", + "variables", + "zones", + }, + RunE: func(cmd *cobra.Command, args []string) error { + resource := args[0] + l := pkgcmd.Logger() + l.Info("☕ Listing Web Container resources: " + resource) + + cfg, err := pkgcmd.ReadConfig(l, cmd) + if err != nil { + return err + } + + tm, err := tagmanager.New( + cmd.Context(), + l, + cfg.GoogleTagManager.AccountID, + cfg.GoogleTagManager.WebContainer, + tagmanager.WithRequestQuota(cfg.GoogleAPI.RequestQuota), + tagmanager.WithClientOptions(cfg.GoogleAPI.GetClientOption()), + ) + if err != nil { + return err + } + + return list(l, tm, resource) + }, + } + + root.AddCommand(cmd) +} diff --git a/cmd/provision.go b/cmd/provision.go new file mode 100644 index 0000000..f664e3e --- /dev/null +++ b/cmd/provision.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/foomo/sesamy-cli/cmd/provision" + "github.com/spf13/cobra" +) + +// NewProvision represents the provision command +func NewProvision(root *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "provision", + Short: "Provision Google Tag Manager containers", + } + + provision.NewServer(cmd) + provision.NewWeb(cmd) + root.AddCommand(cmd) + + return cmd +} diff --git a/go.mod b/go.mod index c9c8240..af96a1a 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,27 @@ module github.com/foomo/sesamy-cli -go 1.23.4 +go 1.24.0 require ( + github.com/alecthomas/chroma v0.10.0 github.com/fatih/structtag v1.2.0 github.com/foomo/go v0.0.3 github.com/foomo/gocontemplate v0.1.4 github.com/foomo/sesamy-go v0.8.1 github.com/invopop/jsonschema v0.13.0 + github.com/itchyny/json2yaml v0.1.4 github.com/joho/godotenv v1.5.1 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 github.com/pkg/errors v0.9.1 github.com/pterm/pterm v0.12.80 - github.com/spf13/cobra v1.9.0 + github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 github.com/stoewer/go-strcase v1.3.0 github.com/stretchr/testify v1.10.0 github.com/wissance/stringFormatter v1.3.0 - golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 - google.golang.org/api v0.221.0 + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa + google.golang.org/api v0.222.0 ) require ( @@ -33,6 +35,7 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/containerd/console v1.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/foomo/gostandards v0.2.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -52,10 +55,10 @@ require ( github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -68,15 +71,15 @@ require ( go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.33.0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/oauth2 v0.26.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.28.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect + golang.org/x/tools v0.30.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect google.golang.org/grpc v1.70.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 58be20b..0e656be 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= 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= @@ -34,6 +36,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -77,6 +81,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/itchyny/json2yaml v0.1.4 h1:/pErVOXGG5iTyXHi/QKR4y3uzhLjGTEmmJIy97YT+k8= +github.com/itchyny/json2yaml v0.1.4/go.mod h1:6iudhBZdarpjLFRNj+clWLAkGft+9uCcjAZYXUH9eGI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -126,20 +132,20 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.0 h1:Py5fIuq/lJsRYxcxfOtsJqpmwJWCMOUy2tMJYV8TNHE= -github.com/spf13/cobra v1.9.0/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= @@ -187,12 +193,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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= @@ -237,16 +243,16 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.221.0 h1:qzaJfLhDsbMeFee8zBRdt/Nc+xmOuafD/dbdgGfutOU= -google.golang.org/api v0.221.0/go.mod h1:7sOU2+TL4TxUTdbi0gWgAIg7tH5qBXxoyhtL+9x3biQ= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4= +google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= diff --git a/pkg/code/file.go b/pkg/code/file.go index 026caff..e3a6984 100644 --- a/pkg/code/file.go +++ b/pkg/code/file.go @@ -1,10 +1,9 @@ package code import ( + "maps" "slices" "strings" - - "golang.org/x/exp/maps" ) const ( @@ -62,7 +61,7 @@ func (f *File) AddSection(id int) { func (f *File) String() string { b := &strings.Builder{} - sections := maps.Keys(f.Sections) + sections := slices.AppendSeq(make([]int, 0, len(f.Sections)), maps.Keys(f.Sections)) slices.Sort(sections) for _, id := range sections { diff --git a/pkg/config/googletagmanager.go b/pkg/config/googletagmanager.go index 0b4a8f7..4f42131 100644 --- a/pkg/config/googletagmanager.go +++ b/pkg/config/googletagmanager.go @@ -1,8 +1,12 @@ package config type GoogleTagManager struct { - AccountID string `json:"accountId" yaml:"accountId"` + // Google Tag Manager account id + AccountID string `json:"accountId" yaml:"accountId"` + // Google Tag Manager web container settings WebContainer GoogleTagManagerContainer `json:"webContainer" yaml:"webContainer"` // Google Tag Manager server container settings ServerContainer GoogleTagManagerContainer `json:"serverContainer" yaml:"serverContainer"` + // Google Tag Manager server container variables + ServerContaienrVariables GoogleTagManagerServerContainerVariables `json:"serverContainerVariables" yaml:"serverContainerVariables"` } diff --git a/pkg/config/googletagmanagercontainer.go b/pkg/config/googletagmanagercontainer.go index d06d249..a09181b 100644 --- a/pkg/config/googletagmanagercontainer.go +++ b/pkg/config/googletagmanagercontainer.go @@ -1,7 +1,10 @@ package config type GoogleTagManagerContainer struct { - TagID string `json:"tagId" yaml:"tagId"` + // The container tag id + TagID string `json:"tagId" yaml:"tagId"` + // The container id ContainerID string `json:"containerId" yaml:"containerId"` + // The workspace id that should be used by the api WorkspaceID string `json:"workspaceId" yaml:"workspaceId"` } diff --git a/pkg/config/googletagmanagerservercontainervariables.go b/pkg/config/googletagmanagerservercontainervariables.go new file mode 100644 index 0000000..7ff5390 --- /dev/null +++ b/pkg/config/googletagmanagerservercontainervariables.go @@ -0,0 +1,8 @@ +package config + +type GoogleTagManagerServerContainerVariables struct { + // List of event data variables + EventData []string `json:"eventData" yaml:"eventData"` + // Map of lookup table variables + LookupTables map[string]LookupTable `json:"lookupTables" yaml:"lookupTables"` +} diff --git a/pkg/config/lookuptable.go b/pkg/config/lookuptable.go new file mode 100644 index 0000000..f671df4 --- /dev/null +++ b/pkg/config/lookuptable.go @@ -0,0 +1,10 @@ +package config + +type LookupTable struct { + // Input source + Input string `json:"input" yaml:"input"` + // Key value data map + KeyTable map[string]string `json:"keyTable" yaml:"keyTable"` + // Vaule key data map + ValueTable map[string]string `json:"valueTable" yaml:"valueTable"` +} diff --git a/pkg/provider/googletagmanager/server.go b/pkg/provider/googletagmanager/server.go index 519c4a2..f3d433b 100644 --- a/pkg/provider/googletagmanager/server.go +++ b/pkg/provider/googletagmanager/server.go @@ -5,6 +5,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/googletagmanager/server/client" "github.com/foomo/sesamy-cli/pkg/provider/googletagmanager/server/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager" + variable2 "github.com/foomo/sesamy-cli/pkg/tagmanager/server/variable" ) func Server(tm *tagmanager.TagManager, cfg config.GoogleTagManager, enableGeoResolution bool) error { @@ -33,5 +34,18 @@ func Server(tm *tagmanager.TagManager, cfg config.GoogleTagManager, enableGeoRes } } + { // create variables + for _, value := range cfg.ServerContaienrVariables.EventData { + if _, err := tm.UpsertVariable(variable2.NewEventData(value)); err != nil { + return err + } + } + for key, value := range cfg.ServerContaienrVariables.LookupTables { + if _, err := tm.UpsertVariable(variable2.NewLookupTable(key, value)); err != nil { + return err + } + } + } + return nil } diff --git a/pkg/provider/microsoftads/constants.go b/pkg/provider/microsoftads/constants.go index 2326e2e..0ab8cc6 100644 --- a/pkg/provider/microsoftads/constants.go +++ b/pkg/provider/microsoftads/constants.go @@ -1,4 +1,4 @@ -package facebook +package microsoftads const ( Tag = "mads" diff --git a/pkg/provider/microsoftads/server.go b/pkg/provider/microsoftads/server.go index f5b9ba0..a8f3219 100644 --- a/pkg/provider/microsoftads/server.go +++ b/pkg/provider/microsoftads/server.go @@ -1,4 +1,4 @@ -package facebook +package microsoftads import ( "log/slog" diff --git a/pkg/tagmanager/server/variable/lookuptable.go b/pkg/tagmanager/server/variable/lookuptable.go new file mode 100644 index 0000000..3144eed --- /dev/null +++ b/pkg/tagmanager/server/variable/lookuptable.go @@ -0,0 +1,69 @@ +package variable + +import ( + "github.com/foomo/sesamy-cli/pkg/config" + "google.golang.org/api/tagmanager/v2" +) + +func LookupTableName(v string) string { + return "lookup_table." + v +} + +func NewLookupTable(name string, data config.LookupTable) *tagmanager.Variable { + var list []*tagmanager.Parameter + for k, v := range data.KeyTable { + list = append(list, &tagmanager.Parameter{ + Type: "map", + Map: []*tagmanager.Parameter{ + { + Key: "key", + Type: "template", + Value: k, + }, + { + Key: "value", + Type: "template", + Value: v, + }, + }, + }) + } + for k, v := range data.ValueTable { + list = append(list, &tagmanager.Parameter{ + Type: "map", + Map: []*tagmanager.Parameter{ + { + Key: "key", + Type: "template", + Value: v, + }, + { + Key: "value", + Type: "template", + Value: k, + }, + }, + }) + } + return &tagmanager.Variable{ + Name: LookupTableName(name), + Parameter: []*tagmanager.Parameter{ + { + Key: "setDefaultValue", + Type: "boolean", + Value: "false", + }, + { + Key: "input", + Type: "template", + Value: data.Input, + }, + { + Key: "map", + Type: "list", + List: list, + }, + }, + Type: "smm", + } +} diff --git a/pkg/tagmanager/tagmanager.go b/pkg/tagmanager/tagmanager.go index 6e89aee..79c17e1 100644 --- a/pkg/tagmanager/tagmanager.go +++ b/pkg/tagmanager/tagmanager.go @@ -25,18 +25,57 @@ type ( requestThrottler *time.Ticker // cache service *tagmanager.Service - clients map[string]*tagmanager.Client - folders map[string]*tagmanager.Folder - variables map[string]*tagmanager.Variable - builtInVariables map[string]*tagmanager.BuiltInVariable - triggers map[string]*tagmanager.Trigger - tags map[string]*tagmanager.Tag - customTemplates map[string]*tagmanager.CustomTemplate - transformations map[string]*tagmanager.Transformation + clients *AccessedMap[*tagmanager.Client] + folders *AccessedMap[*tagmanager.Folder] + variables *AccessedMap[*tagmanager.Variable] + builtInVariables *AccessedMap[*tagmanager.BuiltInVariable] + triggers *AccessedMap[*tagmanager.Trigger] + tags *AccessedMap[*tagmanager.Tag] + customTemplates *AccessedMap[*tagmanager.CustomTemplate] + transformations *AccessedMap[*tagmanager.Transformation] } Option func(*TagManager) ) +type AccessedMap[T any] struct { + data map[string]T + keys map[string]bool +} + +func NewAccessedMap[T any](data map[string]T) *AccessedMap[T] { + return &AccessedMap[T]{ + data: data, + keys: make(map[string]bool, len(data)), + } +} + +func (l AccessedMap[T]) Has(key string) bool { + _, ok := l.data[key] + return ok +} + +func (l AccessedMap[T]) Get(key string) T { + if l.Has(key) { + l.keys[key] = true + } + return l.data[key] +} + +func (l AccessedMap[T]) Set(key string, value T) { + l.keys[key] = true + l.data[key] = value +} + +func (l AccessedMap[T]) Misssed() map[string]T { + ret := map[string]T{} + for k := range l.data { + if !l.keys[k] { + ret[k] = l.data[k] + } + } + return ret +} + // ------------------------------------------------------------------------------------------------ // ~ Options // ------------------------------------------------------------------------------------------------ @@ -102,6 +141,51 @@ func New(ctx context.Context, l *slog.Logger, accountID string, container config // ~ Getter // ------------------------------------------------------------------------------------------------ +func (t *TagManager) Missed() map[string][]string { + ret := map[string][]string{} + if t.clients != nil { + for _, i2 := range t.clients.Misssed() { + ret["Clients"] = append(ret["Clients"], i2.Name) + } + } + if t.folders != nil { + for _, i2 := range t.folders.Misssed() { + ret["Folders"] = append(ret["Folders"], i2.Name) + } + } + if t.variables != nil { + for _, i2 := range t.variables.Misssed() { + ret["Variables"] = append(ret["Variables"], i2.Name) + } + } + if t.builtInVariables != nil { + for _, i2 := range t.builtInVariables.Misssed() { + ret["Built In Variables"] = append(ret["Built In Variables"], i2.Name) + } + } + if t.triggers != nil { + for _, i2 := range t.triggers.Misssed() { + ret["Triggers"] = append(ret["Triggers"], i2.Name) + } + } + if t.tags != nil { + for _, i2 := range t.tags.Misssed() { + ret["Tags"] = append(ret["Tags"], i2.Name) + } + } + if t.customTemplates != nil { + for _, i2 := range t.customTemplates.Misssed() { + ret["Custom Templates"] = append(ret["Custom Templates"], i2.Name) + } + } + if t.transformations != nil { + for _, i2 := range t.transformations.Misssed() { + ret["Transformations"] = append(ret["Transformations"], i2.Name) + } + } + return ret +} + func (t *TagManager) AccountID() string { return t.accountID } @@ -163,16 +247,16 @@ func (t *TagManager) LookupClient(name string) (*tagmanager.Client, error) { return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } -func (t *TagManager) LoadClients() (map[string]*tagmanager.Client, error) { +func (t *TagManager) LoadClients() (*AccessedMap[*tagmanager.Client], error) { if t.clients == nil { - t.l.Info("🛄 Loading list", "type", "Client") + t.l.Info("└ ⬇︎ Loading list", "type", "Client") r, err := t.Service().Accounts.Containers.Workspaces.Clients.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -182,7 +266,7 @@ func (t *TagManager) LoadClients() (map[string]*tagmanager.Client, error) { for _, value := range r.Client { res[value.Name] = value } - t.clients = res + t.clients = NewAccessedMap(res) } return t.clients, nil @@ -194,16 +278,16 @@ func (t *TagManager) LookupFolder(name string) (*tagmanager.Folder, error) { return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } -func (t *TagManager) LoadFolders() (map[string]*tagmanager.Folder, error) { +func (t *TagManager) LoadFolders() (*AccessedMap[*tagmanager.Folder], error) { if t.folders == nil { - t.l.Info("🛄 Loading list", "type", "Folder") + t.l.Info("└ ⬇︎ Loading list", "type", "Folder") r, err := t.Service().Accounts.Containers.Workspaces.Folders.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -213,7 +297,7 @@ func (t *TagManager) LoadFolders() (map[string]*tagmanager.Folder, error) { for _, value := range r.Folder { res[value.Name] = value } - t.folders = res + t.folders = NewAccessedMap(res) } return t.folders, nil } @@ -224,16 +308,16 @@ func (t *TagManager) LookupVariable(name string) (*tagmanager.Variable, error) { return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } -func (t *TagManager) LoadVariables() (map[string]*tagmanager.Variable, error) { +func (t *TagManager) LoadVariables() (*AccessedMap[*tagmanager.Variable], error) { if t.variables == nil { - t.l.Info("🛄 Loading list", "type", "Variable") + t.l.Info("└ ⬇︎ Loading list", "type", "Variable") r, err := t.Service().Accounts.Containers.Workspaces.Variables.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -243,7 +327,7 @@ func (t *TagManager) LoadVariables() (map[string]*tagmanager.Variable, error) { for _, value := range r.Variable { res[value.Name] = value } - t.variables = res + t.variables = NewAccessedMap(res) } return t.variables, nil @@ -255,16 +339,16 @@ func (t *TagManager) GetBuiltInVariable(typeName string) (*tagmanager.BuiltInVar return nil, err } - if _, ok := elems[typeName]; !ok { + if !elems.Has(typeName) { return nil, ErrNotFound } - return elems[typeName], nil + return elems.Get(typeName), nil } -func (t *TagManager) LoadBuiltInVariables() (map[string]*tagmanager.BuiltInVariable, error) { +func (t *TagManager) LoadBuiltInVariables() (*AccessedMap[*tagmanager.BuiltInVariable], error) { if t.builtInVariables == nil { - t.l.Info("🛄 Loading list", "type", "BuiltInVariable") + t.l.Info("└ ⬇︎ Loading list", "type", "BuiltInVariable") r, err := t.Service().Accounts.Containers.Workspaces.BuiltInVariables.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -274,7 +358,7 @@ func (t *TagManager) LoadBuiltInVariables() (map[string]*tagmanager.BuiltInVaria for _, value := range r.BuiltInVariable { res[value.Type] = value } - t.builtInVariables = res + t.builtInVariables = NewAccessedMap(res) } return t.builtInVariables, nil @@ -286,11 +370,11 @@ func (t *TagManager) LookupTrigger(name string) (*tagmanager.Trigger, error) { return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } func (t *TagManager) LookupTemplate(name string) (*tagmanager.CustomTemplate, error) { @@ -299,11 +383,11 @@ func (t *TagManager) LookupTemplate(name string) (*tagmanager.CustomTemplate, er return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } func (t *TagManager) LookupTransformation(name string) (*tagmanager.Transformation, error) { @@ -312,16 +396,16 @@ func (t *TagManager) LookupTransformation(name string) (*tagmanager.Transformati return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } -func (t *TagManager) LoadTriggers() (map[string]*tagmanager.Trigger, error) { +func (t *TagManager) LoadTriggers() (*AccessedMap[*tagmanager.Trigger], error) { if t.triggers == nil { - t.l.Info("🛄 Loading list", "type", "Trigger") + t.l.Info("└ ⬇︎ Loading list", "type", "Trigger") r, err := t.Service().Accounts.Containers.Workspaces.Triggers.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -331,7 +415,7 @@ func (t *TagManager) LoadTriggers() (map[string]*tagmanager.Trigger, error) { for _, value := range r.Trigger { res[value.Name] = value } - t.triggers = res + t.triggers = NewAccessedMap(res) } return t.triggers, nil @@ -343,16 +427,16 @@ func (t *TagManager) LookupTag(name string) (*tagmanager.Tag, error) { return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } -func (t *TagManager) LoadTags() (map[string]*tagmanager.Tag, error) { +func (t *TagManager) LoadTags() (*AccessedMap[*tagmanager.Tag], error) { if t.tags == nil { - t.l.Info("🛄 Loading list", "type", "Tag") + t.l.Info("└ ⬇︎ Loading list", "type", "Tag") r, err := t.Service().Accounts.Containers.Workspaces.Tags.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -362,7 +446,7 @@ func (t *TagManager) LoadTags() (map[string]*tagmanager.Tag, error) { for _, value := range r.Tag { res[value.Name] = value } - t.tags = res + t.tags = NewAccessedMap(res) } return t.tags, nil @@ -374,16 +458,16 @@ func (t *TagManager) CustomTemplate(name string) (*tagmanager.CustomTemplate, er return nil, err } - if _, ok := elems[name]; !ok { + if !elems.Has(name) { return nil, ErrNotFound } - return elems[name], nil + return elems.Get(name), nil } -func (t *TagManager) LoadCustomTemplates() (map[string]*tagmanager.CustomTemplate, error) { +func (t *TagManager) LoadCustomTemplates() (*AccessedMap[*tagmanager.CustomTemplate], error) { if t.customTemplates == nil { - t.l.Info("🛄 Loading list", "type", "CustomTemplate") + t.l.Info("└ ⬇︎ Loading list", "type", "CustomTemplate") r, err := t.Service().Accounts.Containers.Workspaces.Templates.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -393,15 +477,15 @@ func (t *TagManager) LoadCustomTemplates() (map[string]*tagmanager.CustomTemplat for _, value := range r.Template { res[value.Name] = value } - t.customTemplates = res + t.customTemplates = NewAccessedMap(res) } return t.customTemplates, nil } -func (t *TagManager) LoadTransformations() (map[string]*tagmanager.Transformation, error) { +func (t *TagManager) LoadTransformations() (*AccessedMap[*tagmanager.Transformation], error) { if t.transformations == nil { - t.l.Info("🛄 Loading list", "type", "Transformation") + t.l.Info("└ ⬇︎ Loading list", "type", "Transformation") r, err := t.Service().Accounts.Containers.Workspaces.Transformations.List(t.WorkspacePath()).Do() if err != nil { return nil, err @@ -411,7 +495,7 @@ func (t *TagManager) LoadTransformations() (map[string]*tagmanager.Transformatio for _, value := range r.Transformation { res[value.Name] = value } - t.transformations = res + t.transformations = NewAccessedMap(res) } return t.transformations, nil @@ -436,14 +520,17 @@ func (t *TagManager) UpsertClient(item *tagmanager.Client) (*tagmanager.Client, return nil, err } + var value *tagmanager.Client if cache == nil { - l.Info("🚀 New") - t.clients[item.Name], err = t.Service().Accounts.Containers.Workspaces.Clients.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Clients.Create(t.WorkspacePath(), item).Do() + t.clients.Set(item.Name, value) } else if item.Notes == cache.Notes { - l.Info("✅ OK", "id", cache.ClientId) + l.Info("└ ✔︎ OK", "id", cache.ClientId) } else { - l.Info("🔄 Update", "id", cache.ClientId) - t.clients[item.Name], err = t.Service().Accounts.Containers.Workspaces.Clients.Update(t.WorkspacePath()+"/clients/"+cache.ClientId, item).Do() + l.Info("└ 🔄 Update", "id", cache.ClientId) + value, err = t.Service().Accounts.Containers.Workspaces.Clients.Update(t.WorkspacePath()+"/clients/"+cache.ClientId, item).Do() + t.clients.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { @@ -474,14 +561,17 @@ func (t *TagManager) UpsertTransformation(item *tagmanager.Transformation) (*tag return nil, err } + var value *tagmanager.Transformation if cache == nil { - l.Info("🚀 New") - t.transformations[item.Name], err = t.Service().Accounts.Containers.Workspaces.Transformations.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Transformations.Create(t.WorkspacePath(), item).Do() + t.transformations.Set(item.Name, value) } else if item.Notes == cache.Notes { - l.Info("✅ OK", "id", cache.TransformationId) + l.Info("└ ✔︎ OK", "id", cache.TransformationId) } else { - l.Info("🔄 Update", "id", cache.TransformationId) - t.transformations[item.Name], err = t.Service().Accounts.Containers.Workspaces.Transformations.Update(t.WorkspacePath()+"/transformations/"+cache.TransformationId, item).Do() + l.Info("└ 🔄 Update", "id", cache.TransformationId) + value, err = t.Service().Accounts.Containers.Workspaces.Transformations.Update(t.WorkspacePath()+"/transformations/"+cache.TransformationId, item).Do() + t.transformations.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { @@ -509,14 +599,17 @@ func (t *TagManager) UpsertFolder(name string) (*tagmanager.Folder, error) { return nil, err } + var value *tagmanager.Folder if cache == nil { - l.Info("🚀 New") - t.folders[name], err = t.Service().Accounts.Containers.Workspaces.Folders.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Folders.Create(t.WorkspacePath(), item).Do() + t.folders.Set(item.Name, value) } else if item.Notes == cache.Notes { - l.Info("✅ OK", "id", item.FolderId) + l.Info("└ ✔︎ OK", "id", item.FolderId) } else { - l.Info("🔄 Update", "id", cache.FolderId) - t.folders[name], err = t.Service().Accounts.Containers.Workspaces.Folders.Update(t.WorkspacePath()+"/folders/"+cache.FolderId, item).Do() + l.Info("└ 🔄 Update", "id", cache.FolderId) + value, err = t.Service().Accounts.Containers.Workspaces.Folders.Update(t.WorkspacePath()+"/folders/"+cache.FolderId, item).Do() + t.folders.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { @@ -547,14 +640,17 @@ func (t *TagManager) UpsertVariable(item *tagmanager.Variable) (*tagmanager.Vari return nil, err } + var value *tagmanager.Variable if cache == nil { - l.Info("🚀 New") - t.variables[item.Name], err = t.Service().Accounts.Containers.Workspaces.Variables.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Variables.Create(t.WorkspacePath(), item).Do() + t.variables.Set(item.Name, value) } else if item.Notes == cache.Notes { - l.Info("✅ OK", "id", cache.VariableId) + l.Info("└ ✔︎ OK", "id", cache.VariableId) } else { - l.Info("🔄 Update", "id", cache.VariableId) - t.variables[item.Name], err = t.Service().Accounts.Containers.Workspaces.Variables.Update(t.WorkspacePath()+"/variables/"+cache.VariableId, item).Do() + l.Info("└ 🔄 Update", "id", cache.VariableId) + value, err = t.Service().Accounts.Containers.Workspaces.Variables.Update(t.WorkspacePath()+"/variables/"+cache.VariableId, item).Do() + t.variables.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { @@ -575,18 +671,18 @@ func (t *TagManager) EnableBuiltInVariable(typeName string) (*tagmanager.BuiltIn } if cache != nil { - l.Info("✅ OK") + l.Info("└ ✔︎ OK") return cache, nil } - l.Info("🚀 New") + l.Info("└ 🚀 New") resp, err := t.Service().Accounts.Containers.Workspaces.BuiltInVariables.Create(t.WorkspacePath()).Type(typeName).Do() if err != nil { return nil, errors.Wrap(err, "failed to create built-in variable") } for _, builtInVariable := range resp.BuiltInVariable { - t.builtInVariables[builtInVariable.Type] = builtInVariable + t.builtInVariables.Set(builtInVariable.Type, builtInVariable) } return t.GetBuiltInVariable(typeName) @@ -611,14 +707,17 @@ func (t *TagManager) UpsertTrigger(item *tagmanager.Trigger) (*tagmanager.Trigge return nil, err } + var value *tagmanager.Trigger if cache == nil { - l.Info("🚀 New") - t.triggers[item.Name], err = t.Service().Accounts.Containers.Workspaces.Triggers.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Triggers.Create(t.WorkspacePath(), item).Do() + t.triggers.Set(item.Name, value) } else if item.Notes == cache.Notes { - l.Info("✅ OK", "id", cache.TriggerId) + l.Info("└ ✔︎ OK", "id", cache.TriggerId) } else { - l.Info("🔄 Update", "id", cache.TriggerId) - t.triggers[item.Name], err = t.Service().Accounts.Containers.Workspaces.Triggers.Update(t.WorkspacePath()+"/triggers/"+cache.TriggerId, item).Do() + l.Info("└ 🔄 Update", "id", cache.TriggerId) + value, err = t.Service().Accounts.Containers.Workspaces.Triggers.Update(t.WorkspacePath()+"/triggers/"+cache.TriggerId, item).Do() + t.triggers.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { @@ -649,14 +748,17 @@ func (t *TagManager) UpsertTag(item *tagmanager.Tag) (*tagmanager.Tag, error) { return nil, err } + var value *tagmanager.Tag if cache == nil { - l.Info("🚀 New") - t.tags[item.Name], err = t.Service().Accounts.Containers.Workspaces.Tags.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Tags.Create(t.WorkspacePath(), item).Do() + t.tags.Set(item.Name, value) } else if item.Notes == cache.Notes { - l.Info("✅ OK", "id", cache.TagId) + l.Info("└ ✔︎ OK", "id", cache.TagId) } else { - l.Info("🔄 Update", "id", cache.TagId) - t.tags[item.Name], err = t.Service().Accounts.Containers.Workspaces.Tags.Update(t.WorkspacePath()+"/tags/"+cache.TagId, item).Do() + l.Info("└ 🔄 Update", "id", cache.TagId) + value, err = t.Service().Accounts.Containers.Workspaces.Tags.Update(t.WorkspacePath()+"/tags/"+cache.TagId, item).Do() + t.tags.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { @@ -679,14 +781,17 @@ func (t *TagManager) UpsertCustomTemplate(item *tagmanager.CustomTemplate) (*tag return nil, err } + var value *tagmanager.CustomTemplate if cache == nil { - l.Info("🚀 New") - t.customTemplates[item.Name], err = t.Service().Accounts.Containers.Workspaces.Templates.Create(t.WorkspacePath(), item).Do() + l.Info("└ 🚀 New") + value, err = t.Service().Accounts.Containers.Workspaces.Templates.Create(t.WorkspacePath(), item).Do() + t.customTemplates.Set(item.Name, value) } else if item.TemplateData == cache.TemplateData { - l.Info("✅ OK", "id", cache.TemplateId) + l.Info("└ ✔︎ OK", "id", cache.TemplateId) } else { - l.Info("🔄 Update", "id", cache.TemplateId) - t.customTemplates[item.Name], err = t.Service().Accounts.Containers.Workspaces.Templates.Update(t.WorkspacePath()+"/templates/"+cache.TemplateId, item).Do() + l.Info("└ 🔄 Update", "id", cache.TemplateId) + value, err = t.Service().Accounts.Containers.Workspaces.Templates.Update(t.WorkspacePath()+"/templates/"+cache.TemplateId, item).Do() + t.customTemplates.Set(item.Name, value) } if err != nil { if out, err := json.MarshalIndent(item, "", " "); err == nil { diff --git a/pkg/typescript/generator/package.go b/pkg/typescript/generator/package.go index f94f363..9f51f57 100644 --- a/pkg/typescript/generator/package.go +++ b/pkg/typescript/generator/package.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/types" "log/slog" + "maps" "slices" "strings" @@ -14,7 +15,6 @@ import ( "github.com/foomo/sesamy-cli/pkg/code" "github.com/foomo/sesamy-cli/pkg/typescript" "github.com/stoewer/go-strcase" - "golang.org/x/exp/maps" ) const FallbackType = "any" @@ -119,7 +119,7 @@ func (p *Package) renderTypeBasic(typeName string, typeObject types.Object) { } } - names := maps.Keys(res) + names := slices.AppendSeq(make([]string, 0, len(res)), maps.Keys(res)) slices.Sort(names) typeValues := &strings.Builder{} diff --git a/pkg/typescript/import.go b/pkg/typescript/import.go index 121edac..4d79418 100644 --- a/pkg/typescript/import.go +++ b/pkg/typescript/import.go @@ -2,10 +2,9 @@ package typescript import ( "fmt" + "maps" "slices" "strings" - - "golang.org/x/exp/maps" ) type Import struct { @@ -46,7 +45,7 @@ func (i *Import) SetDefault(v string) { } func (i *Import) Modules() []string { - ret := maps.Keys(i.modules) + ret := slices.AppendSeq(make([]string, 0, len(i.modules)), maps.Keys(i.modules)) slices.Sort(ret) return ret } diff --git a/pkg/utils/contemplate.go b/pkg/utils/contemplate.go index c7d2e23..e568eb0 100644 --- a/pkg/utils/contemplate.go +++ b/pkg/utils/contemplate.go @@ -21,7 +21,7 @@ func LoadEventParams(cfg contemplate.Config) (map[string]map[string]string, erro for _, typ := range cfgPkg.Types { eventParams, err := getEventParams(pkg.LookupScopeType(typ)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to load event params: "+typ) } ret[strcase.SnakeCase(typ)] = eventParams } @@ -32,6 +32,15 @@ func LoadEventParams(cfg contemplate.Config) (map[string]map[string]string, erro func getEventParams(obj types.Object) (map[string]string, error) { ret := map[string]string{} + if obj == nil { + return nil, errors.New("obj is nil") + } + if obj.Type() == nil { + return nil, errors.New("object is not a type: " + obj.String()) + } + if obj.Type().Underlying() == nil { + return nil, errors.New("underlying object is not a type: " + obj.Type().String()) + } if eventStruct := assume.T[*types.Struct](obj.Type().Underlying()); eventStruct != nil { for i := range eventStruct.NumFields() { if eventField := eventStruct.Field(i); eventField.Name() == "Params" { diff --git a/sesamy.schema.json b/sesamy.schema.json index 63e7b27..70b9c8a 100644 --- a/sesamy.schema.json +++ b/sesamy.schema.json @@ -436,14 +436,20 @@ "github.com.foomo.sesamy-cli.pkg.config.GoogleTagManager": { "properties": { "accountId": { - "type": "string" + "type": "string", + "description": "Google Tag Manager account id" }, "webContainer": { - "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerContainer" + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerContainer", + "description": "Google Tag Manager web container settings" }, "serverContainer": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerContainer", "description": "Google Tag Manager server container settings" + }, + "serverVariables": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerServerContainerVariables", + "description": "Google Tag Manager server container variables" } }, "additionalProperties": false, @@ -452,13 +458,30 @@ "github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerContainer": { "properties": { "tagId": { - "type": "string" + "type": "string", + "description": "The container tag id" }, "containerId": { - "type": "string" + "type": "string", + "description": "The container id" }, "workspaceId": { - "type": "string" + "type": "string", + "description": "The workspace id that should be used by the api" + } + }, + "additionalProperties": false, + "type": "object" + }, + "github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerServerContainerVariables": { + "properties": { + "eventData": { + "$ref": "#/$defs/[]string", + "description": "List of event data variables" + }, + "lookupTables": { + "$ref": "#/$defs/map[string]config.LookupTable", + "description": "Map of lookup table variables" } }, "additionalProperties": false, @@ -477,6 +500,20 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.LookupTable": { + "properties": { + "input": { + "type": "string", + "description": "Input source" + }, + "table": { + "$ref": "#/$defs/map[string]string", + "description": "Table mapping" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.MicrosoftAds": { "properties": { "enabled": { @@ -618,6 +655,12 @@ }, "type": "object" }, + "map[string]config.LookupTable": { + "additionalProperties": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.LookupTable" + }, + "type": "object" + }, "map[string]config.MicrosoftAdsConversionTag": { "additionalProperties": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.MicrosoftAdsConversionTag" diff --git a/test/tagmanager/clientserver_test.go b/test/tagmanager/clientserver_test.go index e558451..b488256 100644 --- a/test/tagmanager/clientserver_test.go +++ b/test/tagmanager/clientserver_test.go @@ -1,7 +1,6 @@ package tagmanager_test import ( - "context" "fmt" "log/slog" "os" @@ -23,7 +22,7 @@ func TestNewClient_Server(t *testing.T) { require.NoError(t, godotenv.Load("../../.env")) c, err := tagmanager.New( - context.TODO(), + t.Context(), slog.New(slog.NewTextHandler(os.Stdout, nil)), os.Getenv("TEST_ACCOUNT_ID"), config.GoogleTagManagerContainer{ diff --git a/test/tagmanager/clientweb_test.go b/test/tagmanager/clientweb_test.go index 1fee09e..828705d 100644 --- a/test/tagmanager/clientweb_test.go +++ b/test/tagmanager/clientweb_test.go @@ -1,7 +1,6 @@ package tagmanager_test import ( - "context" "fmt" "log/slog" "os" @@ -24,7 +23,7 @@ func TestNewClient_Web(t *testing.T) { require.NoError(t, godotenv.Load("../../.env")) c, err := tagmanager.New( - context.TODO(), + t.Context(), slog.New(slog.NewTextHandler(os.Stdout, nil)), os.Getenv("TEST_ACCOUNT_ID"), config.GoogleTagManagerContainer{ From 3dcf79d2663acc8243434568eee7b13cf565c6ce Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 24 Feb 2025 15:04:03 +0100 Subject: [PATCH 5/5] feat: update schema --- sesamy.schema.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sesamy.schema.json b/sesamy.schema.json index 70b9c8a..bb8c6b2 100644 --- a/sesamy.schema.json +++ b/sesamy.schema.json @@ -447,7 +447,7 @@ "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerContainer", "description": "Google Tag Manager server container settings" }, - "serverVariables": { + "serverContainerVariables": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleTagManagerServerContainerVariables", "description": "Google Tag Manager server container variables" } @@ -506,9 +506,13 @@ "type": "string", "description": "Input source" }, - "table": { + "keyTable": { "$ref": "#/$defs/map[string]string", - "description": "Table mapping" + "description": "Key value data map" + }, + "valueTable": { + "$ref": "#/$defs/map[string]string", + "description": "Vaule key data map" } }, "additionalProperties": false,