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 }