go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/analytics/module.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package analytics 16 17 import ( 18 "context" 19 "flag" 20 "html/template" 21 22 "go.chromium.org/luci/common/errors" 23 24 "go.chromium.org/luci/server/module" 25 ) 26 27 // ModuleName can be used to refer to this module when declaring dependencies. 28 var ModuleName = module.RegisterName("go.chromium.org/luci/server/analytics") 29 30 // ModuleOptions contain configuration of the analytics server module. 31 type ModuleOptions struct { 32 // Google Analytics measurement ID to use like "G-XXXXXX". 33 // 34 // Default is empty, meaning Google analytics integration is disabled. 35 AnalyticsID string 36 } 37 38 // Register registers the command line flags. 39 func (o *ModuleOptions) Register(f *flag.FlagSet) { 40 f.StringVar( 41 &o.AnalyticsID, 42 "analytics-id", 43 o.AnalyticsID, 44 `Google analytics measurement ID in "G-XXXXXX" format.`, 45 ) 46 } 47 48 // NewModuleFromFlags initializes options through command line flags. 49 // 50 // Calling this function registers flags in flag.CommandLine. They are usually 51 // parsed in server.Main(...). 52 func NewModuleFromFlags() module.Module { 53 opts := &ModuleOptions{} 54 opts.Register(flag.CommandLine) 55 return &analyticsModule{opts: opts} 56 } 57 58 // analyticsModule implements module.Module. 59 type analyticsModule struct { 60 opts *ModuleOptions 61 } 62 63 // Name is part of module.Module interface. 64 func (*analyticsModule) Name() module.Name { 65 return ModuleName 66 } 67 68 // Dependencies is part of module.Module interface. 69 func (*analyticsModule) Dependencies() []module.Dependency { 70 return nil 71 } 72 73 var ctxKey = "go.chromium.org/luci/server/analytics/ctxKey" 74 75 // Initialize is part of module.Module interface. 76 func (m *analyticsModule) Initialize(ctx context.Context, host module.Host, opts module.HostOptions) (context.Context, error) { 77 switch { 78 case m.opts.AnalyticsID == "": 79 return ctx, nil 80 case rGA4Allowed.MatchString(m.opts.AnalyticsID): 81 return context.WithValue(ctx, &ctxKey, makeGTagSnippet(m.opts.AnalyticsID)), nil 82 default: 83 return ctx, errors.Reason("given --analytics-id %q is not a measurement ID (like G-XXXXXX)", m.opts.AnalyticsID).Err() 84 } 85 } 86 87 // Snippet returns the analytics snippet to be inserted into the page's source as is. 88 // 89 // Depending on the kind of the AnalyticsID configured (see module's Options): 90 // 91 // - format G-XXXXXX-YY 92 // documented at https://developers.google.com/analytics/devguides/collection/gtagjs#install_the_global_site_tag 93 // will embed loading of "gtag.js" 94 // 95 // If the AnalyticsID isn't configured, this will return an empty template 96 // so that it's safe to insert its return value into a page's source regardless. 97 func Snippet(ctx context.Context) template.HTML { 98 v, _ := ctx.Value(&ctxKey).(template.HTML) 99 return v 100 }