- Notifications
You must be signed in to change notification settings - Fork1k
chore: move usage types to new package#19103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// Package usagetypes contains the types for usage events. These are kept in | ||
// their own package to avoid importing any real code from coderd. | ||
// | ||
// Imports in this package should be limited to the standard library and the | ||
// following packages ONLY: | ||
// - github.com/google/uuid | ||
// - golang.org/x/xerrors | ||
// | ||
// This package is imported by the Tallyman codebase. | ||
package usagetypes | ||
// Please read the package documentation before adding imports. | ||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"strings" | ||
"golang.org/x/xerrors" | ||
) | ||
// UsageEventType is an enum of all usage event types. It mirrors the database | ||
// type `usage_event_type`. | ||
typeUsageEventTypestring | ||
const ( | ||
UsageEventTypeDCManagedAgentsV1UsageEventType="dc_managed_agents_v1" | ||
) | ||
func (eUsageEventType)Valid()bool { | ||
switche { | ||
caseUsageEventTypeDCManagedAgentsV1: | ||
returntrue | ||
default: | ||
returnfalse | ||
} | ||
} | ||
func (eUsageEventType)IsDiscrete()bool { | ||
returne.Valid()&&strings.HasPrefix(string(e),"dc_") | ||
} | ||
func (eUsageEventType)IsHeartbeat()bool { | ||
returne.Valid()&&strings.HasPrefix(string(e),"hb_") | ||
} | ||
// ParseEvent parses the raw event data into the specified Go type. It fails if | ||
// there is any unknown fields or extra data after the event. The returned event | ||
// is validated. | ||
funcParseEvent[TEvent](data json.RawMessage) (T,error) { | ||
dec:=json.NewDecoder(bytes.NewReader(data)) | ||
dec.DisallowUnknownFields() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. 👍 | ||
vareventT | ||
err:=dec.Decode(&event) | ||
iferr!=nil { | ||
returnevent,xerrors.Errorf("unmarshal %T event: %w",event,err) | ||
} | ||
ifdec.More() { | ||
returnevent,xerrors.Errorf("extra data after %T event",event) | ||
} | ||
err=event.Valid() | ||
iferr!=nil { | ||
returnevent,xerrors.Errorf("invalid %T event: %w",event,err) | ||
} | ||
returnevent,nil | ||
} | ||
// ParseEventWithType parses the raw event data into the specified Go type. It | ||
// fails if there is any unknown fields or extra data after the event. The | ||
// returned event is validated. | ||
funcParseEventWithType(eventTypeUsageEventType,data json.RawMessage) (Event,error) { | ||
switcheventType { | ||
caseUsageEventTypeDCManagedAgentsV1: | ||
returnParseEvent[DCManagedAgentsV1](data) | ||
default: | ||
returnnil,xerrors.Errorf("unknown event type: %s",eventType) | ||
} | ||
} | ||
// Event is a usage event that can be collected by the usage collector. | ||
// | ||
// Note that the following event types should not be updated once they are | ||
// merged into the product. Please consult Dean before making any changes. | ||
// | ||
// This type cannot be implemented outside of this package as it this package | ||
// is the source of truth for the coder/tallyman repo. | ||
typeEventinterface { | ||
usageEvent()// to prevent external types from implementing this interface | ||
EventType()UsageEventType | ||
Valid()error | ||
Fields()map[string]any// fields to be marshaled and sent to tallyman/Metronome | ||
} | ||
// DiscreteEvent is a usage event that is collected as a discrete event. | ||
typeDiscreteEventinterface { | ||
Event | ||
discreteUsageEvent()// marker method, also prevents external types from implementing this interface | ||
} | ||
// DCManagedAgentsV1 is a discrete usage event for the number of managed agents. | ||
// This event is sent in the following situations: | ||
// - Once on first startup after usage tracking is added to the product with | ||
// the count of all existing managed agents (count=N) | ||
// - A new managed agent is created (count=1) | ||
typeDCManagedAgentsV1struct { | ||
Countuint64`json:"count"` | ||
} | ||
var_DiscreteEvent=DCManagedAgentsV1{} | ||
func (DCManagedAgentsV1)usageEvent() {} | ||
func (DCManagedAgentsV1)discreteUsageEvent() {} | ||
func (DCManagedAgentsV1)EventType()UsageEventType { | ||
returnUsageEventTypeDCManagedAgentsV1 | ||
} | ||
func (eDCManagedAgentsV1)Valid()error { | ||
ife.Count==0 { | ||
returnxerrors.New("count must be greater than 0") | ||
} | ||
returnnil | ||
} | ||
func (eDCManagedAgentsV1)Fields()map[string]any { | ||
returnmap[string]any{ | ||
"count":e.Count, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package usagetypes_test | ||
import ( | ||
"testing" | ||
"github.com/stretchr/testify/require" | ||
"github.com/coder/coder/v2/coderd/usage/usagetypes" | ||
) | ||
funcTestParseEvent(t*testing.T) { | ||
t.Parallel() | ||
t.Run("ExtraFields",func(t*testing.T) { | ||
t.Parallel() | ||
_,err:= usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": 1, "extra": "field"}`)) | ||
require.ErrorContains(t,err,"unmarshal usagetypes.DCManagedAgentsV1 event") | ||
}) | ||
t.Run("ExtraData",func(t*testing.T) { | ||
t.Parallel() | ||
_,err:= usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": 1}{"count": 2}`)) | ||
require.ErrorContains(t,err,"extra data after usagetypes.DCManagedAgentsV1 event") | ||
}) | ||
t.Run("DCManagedAgentsV1",func(t*testing.T) { | ||
t.Parallel() | ||
event,err:= usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": 1}`)) | ||
require.NoError(t,err) | ||
require.Equal(t, usagetypes.DCManagedAgentsV1{Count:1},event) | ||
require.Equal(t,map[string]any{"count":uint64(1)},event.Fields()) | ||
_,err= usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": "invalid"}`)) | ||
require.ErrorContains(t,err,"unmarshal usagetypes.DCManagedAgentsV1 event") | ||
_,err= usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{}`)) | ||
require.ErrorContains(t,err,"invalid usagetypes.DCManagedAgentsV1 event: count must be greater than 0") | ||
}) | ||
} | ||
funcTestParseEventWithType(t*testing.T) { | ||
t.Parallel() | ||
t.Run("UnknownEvent",func(t*testing.T) { | ||
t.Parallel() | ||
_,err:=usagetypes.ParseEventWithType(usagetypes.UsageEventType("fake"), []byte(`{}`)) | ||
require.ErrorContains(t,err,"unknown event type: fake") | ||
}) | ||
t.Run("DCManagedAgentsV1",func(t*testing.T) { | ||
t.Parallel() | ||
eventType:=usagetypes.UsageEventTypeDCManagedAgentsV1 | ||
event,err:=usagetypes.ParseEventWithType(eventType, []byte(`{"count": 1}`)) | ||
require.NoError(t,err) | ||
require.Equal(t, usagetypes.DCManagedAgentsV1{Count:1},event) | ||
require.Equal(t,eventType,event.EventType()) | ||
require.Equal(t,map[string]any{"count":uint64(1)},event.Fields()) | ||
}) | ||
} |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.