github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/dsref/version_info.go (about) 1 package dsref 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "github.com/qri-io/dataset" 10 ) 11 12 // VersionInfo is an aggregation of fields from a dataset version for caching & 13 // listing purposes. VersionInfos are typically used when showing a list of 14 // datasets or a list of dataset versions ("qri list"). Fields on VersionInfo 15 // are focused on being the minimum set of values required to drive user 16 // interfaces that list datasets. 17 // 18 // VersionInfos can also describe dataset versions that are being created or 19 // failed to create. In these cases the calculated VersionInfo.Path value must 20 // always equal the empty string. 21 // 22 // If any fields are added to this struct, keep it in sync with: 23 // dscache/def.fbs dscache 24 // dscache/fill_info.go func fillInfoForDatasets 25 // repo/ref/convert.go func ConvertToVersionInfo 26 // If you are considering making major changes to VersionInfo, read this 27 // synopsis first: 28 // https://github.com/qri-io/qri/pull/1641#issuecomment-778521313 29 type VersionInfo struct { 30 // 31 // Key as a stable identifier 32 // 33 // InitID is derived from the logbook for the dataset 34 InitID string `json:"initID,omitempty"` 35 // 36 // Fields from dsref.Ref 37 // 38 // Username of dataset owner 39 Username string `json:"username,omitempty"` 40 // ProfileID of dataset owner 41 ProfileID string `json:"profileID,omitempty"` 42 // Unique name reference for this dataset 43 Name string `json:"name,omitempty"` 44 // Content-addressed path for this dataset 45 Path string `json:"path,omitempty"` 46 // 47 // State about the dataset that can change 48 // 49 // If true, this dataset has published versions 50 Published bool `json:"published,omitempty"` 51 // If true, this reference doesn't exist locally. Only makes sense if path is set, as this 52 // flag refers to specific versions, not to entire dataset histories. 53 Foreign bool `json:"foreign,omitempty"` 54 // 55 // Meta fields 56 // 57 // Title from the meta structure 58 MetaTitle string `json:"metaTitle,omitempty"` 59 // List of themes from the meta structure, comma-separated list 60 ThemeList string `json:"themeList,omitempty"` 61 // 62 // Structure fields 63 // 64 // Size of the body in bytes 65 BodySize int `json:"bodySize,omitempty"` 66 // Num of rows in the body 67 BodyRows int `json:"bodyRows,omitempty"` 68 // Format of the body, such as "csv" or "json" 69 BodyFormat string `json:"bodyFormat,omitempty"` 70 // Number of errors from the structure 71 NumErrors int `json:"numErrors,omitempty"` 72 // 73 // Commit fields 74 // 75 // Timestamp field from the commit 76 CommitTime time.Time `json:"commitTime,omitempty"` 77 // Title field from the commit 78 CommitTitle string `json:"commitTitle,omitempty"` 79 // Message field from the commit 80 CommitMessage string `json:"commitMessage,omitempty"` 81 // 82 // 83 // Workflow fields 84 // 85 WorkflowID string `json:"workflowID,omitempty"` 86 WorkflowTriggerDescription string `json:"workflowtriggerDescription,omitempty"` 87 // 88 // Run Fields 89 // 90 // RunID is derived from from either the Commit.RunID, field or the runID of a 91 // failed run. In the latter case the Path value will be empty 92 RunID string `json:"runID,omitempty"` 93 // RunStatus is a string version of the run.Status enumeration. This value 94 // will always be one of: 95 // ""|"waiting"|"running"|"succeeded"|"failed"|"unchanged"|"skipped" 96 // RunStatus is not stored on a dataset version, and instead must come from 97 // either run state or a cache of run state 98 // it's of type string to follow the "plain old data" pattern 99 RunStatus string `json:"runStatus,omitempty"` 100 // RunDuration is how long the run took/has currently taken in nanoseconds 101 // default value of 0 means no duration data is available. 102 // RunDuration is not stored on a dataset version, and instead must come from 103 // either run state or logbook 104 RunDuration int64 `json:"runDuration,omitempty"` 105 // RunStart is the start time of the run. It is not stored on a dataset version 106 // and instead must come from either run state or logbook 107 RunStart *time.Time `json:"runStart,omitempty"` 108 // 109 // 110 // Aggregate Fields 111 // TODO (ramfox): These fields are only temporarily living on `VersionInfo`. 112 // They are needed by the frontend to display "details" about the head of 113 // of the dataset. When we get more user feedback and settle what info 114 // users want about their datasets, these fields may move to a new struct 115 // store, or subsystem. 116 // These fields are not derived from any `dataset.Dataset` fields. 117 // These fields should only be used in the `collection` package. 118 // 119 // RunCount is the number of times this dataset's transform has been run 120 RunCount int `json:"runCount,omitempty"` 121 // CommitCount is the number of commits in this dataset's history 122 CommitCount int `json:"commitCount,omitempty"` 123 // DownloadCount is the number of times this dataset has been directly 124 // downloaded from this Qri node 125 DownloadCount int `json:"downloadCount,omitempty"` 126 // FollowerCount is the number of followers this dataset has on this Qri node 127 FollowerCount int `json:"followerCount,omitempty"` 128 // OpenIssueCount is the number of open issues this dataset has on this 129 // Qri node 130 OpenIssueCount int `json:"openIssueCount,omitempty"` 131 } 132 133 // NewVersionInfoFromRef creates a sparse-populated VersionInfo from a dsref.Ref 134 func NewVersionInfoFromRef(ref Ref) VersionInfo { 135 return VersionInfo{ 136 InitID: ref.InitID, 137 Username: ref.Username, 138 ProfileID: ref.ProfileID, 139 Name: ref.Name, 140 Path: ref.Path, 141 } 142 } 143 144 // SimpleRef returns a simple dsref.Ref 145 func (v VersionInfo) SimpleRef() Ref { 146 return Ref{ 147 InitID: v.InitID, 148 Username: v.Username, 149 ProfileID: v.ProfileID, 150 Name: v.Name, 151 Path: v.Path, 152 } 153 } 154 155 // Alias returns the alias components of a Ref as a string 156 func (v *VersionInfo) Alias() string { 157 s := v.Username 158 if v.Name != "" { 159 s += "/" + v.Name 160 } 161 return s 162 } 163 164 // ConvertDatasetToVersionInfo assigns values form a dataset to a VersionInfo 165 // This function is a shim while we work on building up dscache as a store of 166 // VersionInfo. 167 // 168 // Deprecated: Don't use this function for new code. Instead reference a 169 // VersionInfo that is stored somewhere, or write a function that builds a 170 // VersionInfo without needing a dataset 171 func ConvertDatasetToVersionInfo(ds *dataset.Dataset) VersionInfo { 172 vi := VersionInfo{ 173 InitID: ds.ID, 174 Username: ds.Peername, 175 ProfileID: ds.ProfileID, 176 Name: ds.Name, 177 Path: ds.Path, 178 } 179 if ds.Commit != nil { 180 vi.CommitTime = ds.Commit.Timestamp 181 vi.CommitTitle = ds.Commit.Title 182 vi.CommitMessage = ds.Commit.Message 183 vi.RunID = ds.Commit.RunID 184 } 185 if ds.Meta != nil { 186 vi.MetaTitle = ds.Meta.Title 187 if ds.Meta.Theme != nil { 188 vi.ThemeList = strings.Join(ds.Meta.Theme, ",") 189 } 190 } 191 192 if ds.Structure != nil { 193 vi.BodyFormat = ds.Structure.Format 194 vi.BodySize = ds.Structure.Length 195 vi.BodyRows = ds.Structure.Entries 196 vi.NumErrors = ds.Structure.ErrCount 197 } 198 199 return vi 200 } 201 202 // ConvertVersionInfoToDataset builds up a dataset from all the relevant 203 // VersionInfo fields. 204 // 205 // Deprecated: This function is needed only for supporting Search functionality. 206 // Do not add new callers if possible. 207 func ConvertVersionInfoToDataset(info *VersionInfo) *dataset.Dataset { 208 return &dataset.Dataset{ 209 Peername: info.Username, 210 ProfileID: info.ProfileID, 211 Name: info.Name, 212 Path: info.Path, 213 Commit: &dataset.Commit{ 214 Timestamp: info.CommitTime, 215 Title: info.CommitTitle, 216 Message: info.CommitMessage, 217 RunID: info.RunID, 218 }, 219 Meta: &dataset.Meta{ 220 Title: info.MetaTitle, 221 }, 222 Structure: &dataset.Structure{ 223 Format: info.BodyFormat, 224 Length: info.BodySize, 225 Entries: info.BodyRows, 226 ErrCount: info.NumErrors, 227 }, 228 } 229 } 230 231 type lessFunc func(a, b *VersionInfo) bool 232 233 func newLessFunc(key string) (lessFunc, error) { 234 switch key { 235 case "name": 236 return func(a, b *VersionInfo) bool { 237 return (a.Username < b.Username || a.Name < b.Name) 238 }, nil 239 case "size": 240 return func(a, b *VersionInfo) bool { return a.BodySize < b.BodySize }, nil 241 } 242 243 return nil, fmt.Errorf("unrecognized sorting key %q", key) 244 } 245 246 // VersionInfoAggregator sorts slices of VersionInfos according to a provided 247 // string configuration. Call its Sort method to sort the data 248 // TODO(b5): add support for filtering 249 type VersionInfoAggregator struct { 250 infos []VersionInfo 251 less []lessFunc 252 } 253 254 // Sort sorts the argument slice according to the less functions passed to OrderedBy. 255 func (agg *VersionInfoAggregator) Sort(infos []VersionInfo) { 256 agg.infos = infos 257 sort.Sort(agg) 258 } 259 260 // NewVersionInfoAggregator returns a Sorter that sorts using the less functions 261 // in order. 262 func NewVersionInfoAggregator(orderBy []string) (*VersionInfoAggregator, error) { 263 less := []lessFunc{} 264 for _, o := range orderBy { 265 fn, err := newLessFunc(o) 266 if err != nil { 267 return nil, err 268 } 269 less = append(less, fn) 270 } 271 272 return &VersionInfoAggregator{ 273 less: less, 274 }, nil 275 } 276 277 // Len is part of sort.Interface. 278 func (agg *VersionInfoAggregator) Len() int { 279 return len(agg.infos) 280 } 281 282 // Swap is part of sort.Interface. 283 func (agg *VersionInfoAggregator) Swap(i, j int) { 284 agg.infos[i], agg.infos[j] = agg.infos[j], agg.infos[i] 285 } 286 287 func (agg *VersionInfoAggregator) Less(i, j int) bool { 288 p, q := &agg.infos[i], &agg.infos[j] 289 // Try all but the last comparison. 290 var k int 291 for k = 0; k < len(agg.less)-1; k++ { 292 less := agg.less[k] 293 switch { 294 case less(p, q): 295 // p < q, so we have a decision. 296 return true 297 case less(q, p): 298 // p > q, so we have a decision. 299 return false 300 } 301 // p == q; try the next comparison. 302 } 303 // All comparisons to here said "equal", so just return whatever 304 // the final comparison reports. 305 return agg.less[k](p, q) 306 }