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  }