github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/shared/metrics/models.go (about)

     1  // Copyright 2017 The WPT Dashboard Project. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package metrics
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"reflect"
    11  	"time"
    12  
    13  	"cloud.google.com/go/datastore"
    14  	"github.com/web-platform-tests/wpt.fyi/shared"
    15  )
    16  
    17  // SubTest models a single test within a WPT test file.
    18  type SubTest struct {
    19  	Name    string  `json:"name"`
    20  	Status  string  `json:"status"`
    21  	Message *string `json:"message"`
    22  }
    23  
    24  // TestResults captures the results of running the tests in a WPT test file.
    25  type TestResults struct {
    26  	Test     string    `json:"test"`
    27  	Status   string    `json:"status"`
    28  	Message  *string   `json:"message"`
    29  	Subtests []SubTest `json:"subtests"`
    30  }
    31  
    32  // RunInfo is an alias of ProductAtRevision with a custom marshaler to produce
    33  // the "run_info" object in wptreport.json.
    34  type RunInfo struct {
    35  	shared.ProductAtRevision
    36  }
    37  
    38  // MarshalJSON is the custom JSON marshaler that produces field names matching
    39  // the "run_info" object in wptreport.json.
    40  func (r RunInfo) MarshalJSON() ([]byte, error) {
    41  	m := map[string]string{
    42  		"revision":        r.FullRevisionHash,
    43  		"product":         r.BrowserName,
    44  		"browser_version": r.BrowserVersion,
    45  		"os":              r.OSName,
    46  	}
    47  	// Optional field:
    48  	if r.OSVersion != "" {
    49  		m["os_version"] = r.OSVersion
    50  	}
    51  	return json.Marshal(m)
    52  }
    53  
    54  // TestResultsReport models the `wpt run` results report JSON file format.
    55  type TestResultsReport struct {
    56  	Results []*TestResults `json:"results"`
    57  	RunInfo RunInfo        `json:"run_info,omitempty"`
    58  }
    59  
    60  // TestRunsMetadata is a struct for metadata derived from a group of TestRun entities.
    61  type TestRunsMetadata struct {
    62  	// TestRuns are the TestRun entities, loaded from the TestRunIDs
    63  	TestRuns   shared.TestRuns   `json:"test_runs,omitempty" datastore:"-"`
    64  	TestRunIDs shared.TestRunIDs `json:"-"`
    65  	StartTime  time.Time         `json:"start_time"`
    66  	EndTime    time.Time         `json:"end_time"`
    67  	DataURL    string            `json:"url"`
    68  }
    69  
    70  // Load is part of the datastore.PropertyLoadSaver interface.
    71  // We use it to reset all time to UTC and trim their monotonic clock.
    72  func (t *TestRunsMetadata) Load(ps []datastore.Property) error {
    73  	if err := datastore.LoadStruct(t, ps); err != nil {
    74  		return err
    75  	}
    76  	t.StartTime = t.StartTime.UTC().Round(0)
    77  	t.EndTime = t.EndTime.UTC().Round(0)
    78  	return nil
    79  }
    80  
    81  // Save is part of the datastore.PropertyLoadSaver interface.
    82  // Delegate to the default behaviour.
    83  func (t *TestRunsMetadata) Save() ([]datastore.Property, error) {
    84  	return datastore.SaveStruct(t)
    85  }
    86  
    87  // LoadTestRuns fetches the TestRun entities for the PassRateMetadata's TestRunIDs.
    88  func (t *TestRunsMetadata) LoadTestRuns(ctx context.Context) (err error) {
    89  	ds := shared.NewAppEngineDatastore(ctx, false)
    90  	t.TestRuns, err = t.TestRunIDs.LoadTestRuns(ds)
    91  	return err
    92  }
    93  
    94  // TODO(lukebjerring): Remove TestRunLegacy when old format migrated.
    95  
    96  // TestRunLegacy is a copy of the TestRun struct, before the `Labels` field
    97  // was added (which causes an array of array and breaks datastore).
    98  type TestRunLegacy struct {
    99  	ID int64 `json:"id" datastore:"-"`
   100  
   101  	shared.ProductAtRevision
   102  
   103  	// URL for summary of results, which is derived from raw results.
   104  	ResultsURL string `json:"results_url"`
   105  
   106  	// Time when the test run metadata was first created.
   107  	CreatedAt time.Time `json:"created_at"`
   108  
   109  	// Time when the test run started.
   110  	TimeStart time.Time `json:"time_start"`
   111  
   112  	// Time when the test run ended.
   113  	TimeEnd time.Time `json:"time_end"`
   114  
   115  	// URL for raw results JSON object. Resembles the JSON output of the
   116  	// wpt report tool.
   117  	RawResultsURL string `json:"raw_results_url"`
   118  
   119  	// Legacy format's Labels are (necessarily) ignored by datastore.
   120  	Labels []string `datastore:"-" json:"labels"`
   121  }
   122  
   123  // Load is part of the datastore.PropertyLoadSaver interface.
   124  // We use it to reset all time to UTC and trim their monotonic clock.
   125  func (r *TestRunLegacy) Load(ps []datastore.Property) error {
   126  	if err := datastore.LoadStruct(r, ps); err != nil {
   127  		return err
   128  	}
   129  	r.CreatedAt = r.CreatedAt.UTC().Round(0)
   130  	r.TimeStart = r.TimeStart.UTC().Round(0)
   131  	r.TimeEnd = r.TimeEnd.UTC().Round(0)
   132  	return nil
   133  }
   134  
   135  // Save is part of the datastore.PropertyLoadSaver interface.
   136  // Delegate to the default behaviour.
   137  func (r *TestRunLegacy) Save() ([]datastore.Property, error) {
   138  	return datastore.SaveStruct(r)
   139  }
   140  
   141  // ConvertRuns converts TestRuns into the legacy format.
   142  func ConvertRuns(runs shared.TestRuns) (converted []TestRunLegacy, err error) {
   143  	if serialized, err := json.Marshal(runs); err != nil {
   144  		return nil, err
   145  	} else if err = json.Unmarshal(serialized, &converted); err != nil {
   146  		return nil, err
   147  	}
   148  	return converted, nil
   149  }
   150  
   151  // TODO(lukebjerring): Remove TestRunsMetadataLegacy when old format migrated.
   152  
   153  // TestRunsMetadataLegacy is a struct for loading legacy TestRunMetadata entities,
   154  // which may have nested TestRun entities.
   155  type TestRunsMetadataLegacy struct {
   156  	TestRuns   []TestRunLegacy   `json:"test_runs"`
   157  	TestRunIDs shared.TestRunIDs `json:"-"`
   158  	StartTime  time.Time         `json:"start_time"`
   159  	EndTime    time.Time         `json:"end_time"`
   160  	DataURL    string            `json:"url"`
   161  }
   162  
   163  // LoadTestRuns fetches the TestRun entities for the PassRateMetadata's TestRunIDs.
   164  func (t *TestRunsMetadataLegacy) LoadTestRuns(ctx context.Context) (err error) {
   165  	if len(t.TestRuns) == 0 {
   166  		ds := shared.NewAppEngineDatastore(ctx, false)
   167  		newRuns, err := t.TestRunIDs.LoadTestRuns(ds)
   168  		if err != nil {
   169  			return err
   170  		}
   171  		t.TestRuns, err = ConvertRuns(newRuns)
   172  	}
   173  	return err
   174  }
   175  
   176  // Load is part of the datastore.PropertyLoadSaver interface.
   177  // We use it to reset all time to UTC and trim their monotonic clock.
   178  func (t *TestRunsMetadataLegacy) Load(ps []datastore.Property) error {
   179  	if err := datastore.LoadStruct(t, ps); err != nil {
   180  		return err
   181  	}
   182  	t.StartTime = t.StartTime.UTC().Round(0)
   183  	t.EndTime = t.EndTime.UTC().Round(0)
   184  	return nil
   185  }
   186  
   187  // Save is part of the datastore.PropertyLoadSaver interface.
   188  // Delegate to the default behaviour.
   189  func (t *TestRunsMetadataLegacy) Save() ([]datastore.Property, error) {
   190  	return datastore.SaveStruct(t)
   191  }
   192  
   193  // PassRateMetadata constitutes metadata capturing:
   194  // - When metric run was performed;
   195  // - What test runs are part of the metric run;
   196  // - Where the metric run results reside (a URL).
   197  type PassRateMetadata struct {
   198  	TestRunsMetadata
   199  }
   200  
   201  // TODO(lukebjerring): Remove PassRateMetadataLegacy when old format migrated.
   202  
   203  // PassRateMetadataLegacy is a struct for storing a PassRateMetadata entry in the
   204  // datastore, avoiding nested arrays. PassRateMetadata is the legacy format, used for
   205  // loading the entity, for backward compatibility.
   206  type PassRateMetadataLegacy struct {
   207  	TestRunsMetadataLegacy
   208  }
   209  
   210  // GetDatastoreKindName gets the full (namespaced) data type name for the given
   211  // interface (whether a pointer or not).
   212  func GetDatastoreKindName(data interface{}) string {
   213  	dataType := reflect.TypeOf(data)
   214  	for dataType.Kind() == reflect.Ptr {
   215  		dataType = reflect.Indirect(reflect.ValueOf(
   216  			data)).Type()
   217  	}
   218  	// This package was originally in another repo. We need to hard
   219  	// code the original repo name here to avoid changing the Kind.
   220  	return "github.com.web-platform-tests.results-analysis.metrics." +
   221  		dataType.Name()
   222  }