Skip to content

Commit

Permalink
Make ArchiveTrace button auto-configurable (#4913)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?
- backend part of #4874

## Description of the changes
The button to archive a trace is now configured based on the state of
the QueryService in addition to the UI configuration. It is now
possible to request features from the QueryService to inject them
into the UI.

Related UI change jaegertracing/jaeger-ui#1944

## How was this change tested?
All corresponding tests have been updated.

## Checklist
- [X] I have read

https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md
- [X] I have signed all commits
- [X] I have added unit tests for the new functionality
- [X] I have run lint and test steps successfully

---------

Signed-off-by: Antonin Barthelemy <antobarth@gmail.com>

---------

Signed-off-by: Barthelemy Antonin <antobarth@gmail.com>
Signed-off-by: Yuri Shkuro <github@ysh.us>
Co-authored-by: Yuri Shkuro <github@ysh.us>
Co-authored-by: Yuri Shkuro <yurishkuro@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 7, 2023
1 parent 5a28824 commit c17b575
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 57 deletions.
10 changes: 6 additions & 4 deletions cmd/query/app/fixture/index.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<base href="/" data-inject-target="BASE_URL"/>
<title>Test Page</title>
<!--
<meta charset="UTF-8">
<base href="/" data-inject-target="BASE_URL" />
<title>Test Page</title>
<!--
// JAEGER_CONFIG_JS
// the line above may be replaced by user-provided JS file that should define a UIConfig function.
JAEGER_CONFIG=DEFAULT_CONFIG;
JAEGER_STORAGE_CAPABILITIES=DEFAULT_STORAGE_CAPABILITIES;
JAEGER_VERSION=DEFAULT_VERSION;
-->

</html>
19 changes: 19 additions & 0 deletions cmd/query/app/querysvc/query_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ type QueryServiceOptions struct {
Adjuster adjuster.Adjuster
}

// StorageCapabilities is a feature flag for query service
type StorageCapabilities struct {
ArchiveStorage bool `json:"archiveStorage"`
// SupportRegex bool
// SupportTagFilter bool
}

// QueryService contains span utils required by the query-service.
type QueryService struct {
spanReader spanstore.Reader
Expand Down Expand Up @@ -122,6 +129,13 @@ func (qs QueryService) GetDependencies(ctx context.Context, endTs time.Time, loo
return qs.dependencyReader.GetDependencies(ctx, endTs, lookback)
}

// GetCapabilities returns the features supported by the query service.
func (qs QueryService) GetCapabilities() StorageCapabilities {
return StorageCapabilities{
ArchiveStorage: qs.options.hasArchiveStorage(),
}
}

// InitArchiveStorage tries to initialize archive storage reader/writer if storage factory supports them.
func (opts *QueryServiceOptions) InitArchiveStorage(storageFactory storage.Factory, logger *zap.Logger) bool {
archiveFactory, ok := storageFactory.(storage.ArchiveFactory)
Expand Down Expand Up @@ -151,3 +165,8 @@ func (opts *QueryServiceOptions) InitArchiveStorage(storageFactory storage.Facto
opts.ArchiveSpanWriter = writer
return true
}

// hasArchiveStorage returns true if archive storage reader/writer are initialized.
func (opts *QueryServiceOptions) hasArchiveStorage() bool {
return opts.ArchiveSpanReader != nil && opts.ArchiveSpanWriter != nil
}
18 changes: 18 additions & 0 deletions cmd/query/app/querysvc/query_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,24 @@ func TestGetDependencies(t *testing.T) {
assert.Equal(t, expectedDependencies, actualDependencies)
}

// Test QueryService.GetCapacities()
func TestGetCapabilities(t *testing.T) {
tqs := initializeTestService()
expectedStorageCapabilities := StorageCapabilities{
ArchiveStorage: false,
}
assert.Equal(t, expectedStorageCapabilities, tqs.queryService.GetCapabilities())
}

func TestGetCapabilitiesWithSupportsArchive(t *testing.T) {
tqs := initializeTestService(withArchiveSpanReader(), withArchiveSpanWriter())

expectedStorageCapabilities := StorageCapabilities{
ArchiveStorage: true,
}
assert.Equal(t, expectedStorageCapabilities, tqs.queryService.GetCapabilities())
}

type fakeStorageFactory1 struct{}

type fakeStorageFactory2 struct {
Expand Down
2 changes: 1 addition & 1 deletion cmd/query/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func createHTTPServer(querySvc *querysvc.QueryService, metricsQuerySvc querysvc.
}

apiHandler.RegisterRoutes(r)
RegisterStaticHandler(r, logger, queryOpts)
RegisterStaticHandler(r, logger, queryOpts, querySvc.GetCapabilities())
var handler http.Handler = r
handler = additionalHeadersHandler(handler, queryOpts.AdditionalHeaders)
if queryOpts.BearerTokenPropagation {
Expand Down
62 changes: 35 additions & 27 deletions cmd/query/app/static_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,29 @@ import (
"github.com/gorilla/mux"
"go.uber.org/zap"

"github.com/jaegertracing/jaeger/cmd/query/app/querysvc"
"github.com/jaegertracing/jaeger/cmd/query/app/ui"
"github.com/jaegertracing/jaeger/pkg/fswatcher"
"github.com/jaegertracing/jaeger/pkg/version"
)

var (
// The following patterns are searched and replaced in the index.html as a way of customizing the UI.
configPattern = regexp.MustCompile("JAEGER_CONFIG *= *DEFAULT_CONFIG;")
configJsPattern = regexp.MustCompile(`(?im)^\s*\/\/\s*JAEGER_CONFIG_JS.*\n.*`)
versionPattern = regexp.MustCompile("JAEGER_VERSION *= *DEFAULT_VERSION;")
basePathPattern = regexp.MustCompile(`<base href="/"`) // Note: tag is not closed
configPattern = regexp.MustCompile("JAEGER_CONFIG *= *DEFAULT_CONFIG;")
configJsPattern = regexp.MustCompile(`(?im)^\s*\/\/\s*JAEGER_CONFIG_JS.*\n.*`)
versionPattern = regexp.MustCompile("JAEGER_VERSION *= *DEFAULT_VERSION;")
compabilityPattern = regexp.MustCompile("JAEGER_STORAGE_CAPABILITIES *= *DEFAULT_STORAGE_CAPABILITIES;")
basePathPattern = regexp.MustCompile(`<base href="/"`) // Note: tag is not closed
)

// RegisterStaticHandler adds handler for static assets to the router.
func RegisterStaticHandler(r *mux.Router, logger *zap.Logger, qOpts *QueryOptions) {
func RegisterStaticHandler(r *mux.Router, logger *zap.Logger, qOpts *QueryOptions, qCapabilities querysvc.StorageCapabilities) {
staticHandler, err := NewStaticAssetsHandler(qOpts.StaticAssets.Path, StaticAssetsHandlerOptions{
BasePath: qOpts.BasePath,
UIConfigPath: qOpts.UIConfig,
Logger: logger,
LogAccess: qOpts.StaticAssets.LogAccess,
BasePath: qOpts.BasePath,
UIConfigPath: qOpts.UIConfig,
StorageCapabilities: qCapabilities,
Logger: logger,
LogAccess: qOpts.StaticAssets.LogAccess,
})
if err != nil {
logger.Panic("Could not create static assets handler", zap.Error(err))
Expand All @@ -68,10 +71,11 @@ type StaticAssetsHandler struct {

// StaticAssetsHandlerOptions defines options for NewStaticAssetsHandler
type StaticAssetsHandlerOptions struct {
BasePath string
UIConfigPath string
LogAccess bool
Logger *zap.Logger
BasePath string
UIConfigPath string
LogAccess bool
StorageCapabilities querysvc.StorageCapabilities
Logger *zap.Logger
}

type loadedConfig struct {
Expand All @@ -90,16 +94,16 @@ func NewStaticAssetsHandler(staticAssetsRoot string, options StaticAssetsHandler
options.Logger = zap.NewNop()
}

indexHTML, err := loadAndEnrichIndexHTML(assetsFS.Open, options)
if err != nil {
return nil, err
}

h := &StaticAssetsHandler{
options: options,
assetsFS: assetsFS,
}

indexHTML, err := h.loadAndEnrichIndexHTML(assetsFS.Open)
if err != nil {
return nil, err
}

options.Logger.Info("Using UI configuration", zap.String("path", options.UIConfigPath))
watcher, err := fswatcher.New([]string{options.UIConfigPath}, h.reloadUIConfig, h.options.Logger)
if err != nil {
Expand All @@ -112,38 +116,42 @@ func NewStaticAssetsHandler(staticAssetsRoot string, options StaticAssetsHandler
return h, nil
}

func loadAndEnrichIndexHTML(open func(string) (http.File, error), options StaticAssetsHandlerOptions) ([]byte, error) {
func (sH *StaticAssetsHandler) loadAndEnrichIndexHTML(open func(string) (http.File, error)) ([]byte, error) {
indexBytes, err := loadIndexHTML(open)
if err != nil {
return nil, fmt.Errorf("cannot load index.html: %w", err)
}
// replace UI config
if configObject, err := loadUIConfig(options.UIConfigPath); err != nil {
if configObject, err := loadUIConfig(sH.options.UIConfigPath); err != nil {
return nil, err
} else if configObject != nil {
indexBytes = configObject.regexp.ReplaceAll(indexBytes, configObject.config)
}
// replace storage capabilities
capabilitiesJSON, _ := json.Marshal(sH.options.StorageCapabilities)
capabilitiesString := fmt.Sprintf("JAEGER_STORAGE_CAPABILITIES = %s;", string(capabilitiesJSON))
indexBytes = compabilityPattern.ReplaceAll(indexBytes, []byte(capabilitiesString))
// replace Jaeger version
versionJSON, _ := json.Marshal(version.Get())
versionString := fmt.Sprintf("JAEGER_VERSION = %s;", string(versionJSON))
indexBytes = versionPattern.ReplaceAll(indexBytes, []byte(versionString))
// replace base path
if options.BasePath == "" {
options.BasePath = "/"
if sH.options.BasePath == "" {
sH.options.BasePath = "/"
}
if options.BasePath != "/" {
if !strings.HasPrefix(options.BasePath, "/") || strings.HasSuffix(options.BasePath, "/") {
return nil, fmt.Errorf("invalid base path '%s'. Must start but not end with a slash '/', e.g. '/jaeger/ui'", options.BasePath)
if sH.options.BasePath != "/" {
if !strings.HasPrefix(sH.options.BasePath, "/") || strings.HasSuffix(sH.options.BasePath, "/") {
return nil, fmt.Errorf("invalid base path '%s'. Must start but not end with a slash '/', e.g. '/jaeger/ui'", sH.options.BasePath)
}
indexBytes = basePathPattern.ReplaceAll(indexBytes, []byte(fmt.Sprintf(`<base href="%s/"`, options.BasePath)))
indexBytes = basePathPattern.ReplaceAll(indexBytes, []byte(fmt.Sprintf(`<base href="%s/"`, sH.options.BasePath)))
}

return indexBytes, nil
}

func (sH *StaticAssetsHandler) reloadUIConfig() {
sH.options.Logger.Info("reloading UI config", zap.String("filename", sH.options.UIConfigPath))
content, err := loadAndEnrichIndexHTML(sH.assetsFS.Open, sH.options)
content, err := sH.loadAndEnrichIndexHTML(sH.assetsFS.Open)
if err != nil {
sH.options.Logger.Error("error while reloading the UI config", zap.Error(err))
}
Expand Down
63 changes: 38 additions & 25 deletions cmd/query/app/static_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zaptest/observer"

"github.com/jaegertracing/jaeger/cmd/query/app/querysvc"
"github.com/jaegertracing/jaeger/pkg/testutils"
)

Expand All @@ -58,6 +59,7 @@ func TestRegisterStaticHandlerPanic(t *testing.T) {
},
},
},
querysvc.StorageCapabilities{ArchiveStorage: false},
)
})
assert.Contains(t, buf.String(), "Could not create static assets handler")
Expand All @@ -66,36 +68,44 @@ func TestRegisterStaticHandlerPanic(t *testing.T) {

func TestRegisterStaticHandler(t *testing.T) {
testCases := []struct {
basePath string // input to the test
subroute bool // should we create a subroute?
baseURL string // expected URL prefix
logAccess bool
expectedBaseHTML string // substring to match in the home page
UIConfigPath string // path to UI config
expectedUIConfig string // expected UI config
basePath string // input to the test
subroute bool // should we create a subroute?
baseURL string // expected URL prefix
archiveStorage bool // archive storage enabled?
logAccess bool
expectedBaseHTML string // substring to match in the home page
UIConfigPath string // path to UI config
expectedUIConfig string // expected UI config
expectedStorageCapabilities string // expected storage capabilities
}{
{
basePath: "",
baseURL: "/",
expectedBaseHTML: `<base href="/"`,
logAccess: true,
UIConfigPath: "",
expectedUIConfig: "JAEGER_CONFIG=DEFAULT_CONFIG;",
basePath: "",
baseURL: "/",
expectedBaseHTML: `<base href="/"`,
archiveStorage: false,
logAccess: true,
UIConfigPath: "",
expectedUIConfig: "JAEGER_CONFIG=DEFAULT_CONFIG;",
expectedStorageCapabilities: `JAEGER_STORAGE_CAPABILITIES = {"archiveStorage":false};`,
},
{
basePath: "/",
baseURL: "/",
expectedBaseHTML: `<base href="/"`,
UIConfigPath: "fixture/ui-config.json",
expectedUIConfig: `JAEGER_CONFIG = {"x":"y"};`,
basePath: "/",
baseURL: "/",
archiveStorage: false,
expectedBaseHTML: `<base href="/"`,
UIConfigPath: "fixture/ui-config.json",
expectedUIConfig: `JAEGER_CONFIG = {"x":"y"};`,
expectedStorageCapabilities: `JAEGER_STORAGE_CAPABILITIES = {"archiveStorage":false};`,
},
{
basePath: "/jaeger",
baseURL: "/jaeger/",
expectedBaseHTML: `<base href="/jaeger/"`,
subroute: true,
UIConfigPath: "fixture/ui-config.js",
expectedUIConfig: "function UIConfig(){",
basePath: "/jaeger",
baseURL: "/jaeger/",
expectedBaseHTML: `<base href="/jaeger/"`,
subroute: true,
archiveStorage: true,
UIConfigPath: "fixture/ui-config.js",
expectedUIConfig: "function UIConfig(){",
expectedStorageCapabilities: `JAEGER_STORAGE_CAPABILITIES = {"archiveStorage":true};`,
},
}
httpClient = &http.Client{
Expand All @@ -117,7 +127,9 @@ func TestRegisterStaticHandler(t *testing.T) {
BasePath: testCase.basePath,
UIConfig: testCase.UIConfigPath,
},
})
},
querysvc.StorageCapabilities{ArchiveStorage: testCase.archiveStorage},
)

server := httptest.NewServer(r)
defer server.Close()
Expand All @@ -136,6 +148,7 @@ func TestRegisterStaticHandler(t *testing.T) {

html := httpGet("") // get home page
assert.Contains(t, html, testCase.expectedUIConfig, "actual: %v", html)
assert.Contains(t, html, testCase.expectedStorageCapabilities, "actual: %v", html)
assert.Contains(t, html, `JAEGER_VERSION = {"gitCommit":"","gitVersion":"","buildDate":""};`, "actual: %v", html)
assert.Contains(t, html, testCase.expectedBaseHTML, "actual: %v", html)

Expand Down

0 comments on commit c17b575

Please sign in to comment.