Skip to content

Commit ae20a46

Browse files
committed
implement fakedb
1 parent 3bc91b5 commit ae20a46

File tree

2 files changed

+153
-5
lines changed

2 files changed

+153
-5
lines changed

coderd/database/dbfake/dbfake.go

+151-3
Original file line numberDiff line numberDiff line change
@@ -1972,7 +1972,117 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
19721972
return nil, err
19731973
}
19741974

1975-
panic("not implemented")
1975+
q.mutex.RLock()
1976+
defer q.mutex.RUnlock()
1977+
1978+
type appKey struct {
1979+
AccessMethod string
1980+
SlugOrPort string
1981+
Slug string
1982+
DisplayName string
1983+
Icon string
1984+
}
1985+
type uniqueKey struct {
1986+
TemplateID uuid.UUID
1987+
UserID uuid.UUID
1988+
AgentID uuid.UUID
1989+
AppKey appKey
1990+
}
1991+
1992+
appUsageIntervalsByUserAgentApp := make(map[uniqueKey]map[time.Time]int64)
1993+
for _, s := range q.workspaceAppStats {
1994+
// (was.session_started_at >= ts.from_ AND was.session_started_at < ts.to_)
1995+
// OR (was.session_ended_at > ts.from_ AND was.session_ended_at < ts.to_)
1996+
// OR (was.session_started_at < ts.from_ AND was.session_ended_at >= ts.to_)
1997+
if !(((s.SessionStartedAt.After(arg.StartTime) || s.SessionStartedAt.Equal(arg.StartTime)) && s.SessionStartedAt.Before(arg.EndTime)) ||
1998+
(s.SessionEndedAt.After(arg.StartTime) && s.SessionEndedAt.Before(arg.EndTime)) ||
1999+
(s.SessionStartedAt.Before(arg.StartTime) && (s.SessionEndedAt.After(arg.EndTime) || s.SessionEndedAt.Equal(arg.EndTime)))) {
2000+
continue
2001+
}
2002+
2003+
w, err := q.getWorkspaceByIDNoLock(ctx, s.WorkspaceID)
2004+
if err != nil {
2005+
return nil, err
2006+
}
2007+
2008+
app, _ := q.getWorkspaceAppByAgentIDAndSlugNoLock(ctx, database.GetWorkspaceAppByAgentIDAndSlugParams{
2009+
AgentID: s.AgentID,
2010+
Slug: s.SlugOrPort,
2011+
})
2012+
2013+
key := uniqueKey{
2014+
TemplateID: w.TemplateID,
2015+
UserID: s.UserID,
2016+
AgentID: s.AgentID,
2017+
AppKey: appKey{
2018+
AccessMethod: s.AccessMethod,
2019+
SlugOrPort: s.SlugOrPort,
2020+
Slug: app.Slug,
2021+
DisplayName: app.DisplayName,
2022+
Icon: app.Icon,
2023+
},
2024+
}
2025+
if appUsageIntervalsByUserAgentApp[key] == nil {
2026+
appUsageIntervalsByUserAgentApp[key] = make(map[time.Time]int64)
2027+
}
2028+
2029+
t := s.SessionStartedAt.Truncate(5 * time.Minute)
2030+
if t.Before(arg.StartTime) {
2031+
t = arg.StartTime
2032+
}
2033+
for t.Before(s.SessionEndedAt) && t.Before(arg.EndTime) {
2034+
appUsageIntervalsByUserAgentApp[key][t] = 300 // 5 minutes.
2035+
t = t.Add(5 * time.Minute)
2036+
}
2037+
}
2038+
2039+
appUsageTemplateIDs := make(map[appKey]map[uuid.UUID]struct{})
2040+
appUsageUserIDs := make(map[appKey]map[uuid.UUID]struct{})
2041+
appUsage := make(map[appKey]int64)
2042+
for uniqueKey, usage := range appUsageIntervalsByUserAgentApp {
2043+
for _, seconds := range usage {
2044+
if appUsageTemplateIDs[uniqueKey.AppKey] == nil {
2045+
appUsageTemplateIDs[uniqueKey.AppKey] = make(map[uuid.UUID]struct{})
2046+
}
2047+
appUsageTemplateIDs[uniqueKey.AppKey][uniqueKey.TemplateID] = struct{}{}
2048+
if appUsageUserIDs[uniqueKey.AppKey] == nil {
2049+
appUsageUserIDs[uniqueKey.AppKey] = make(map[uuid.UUID]struct{})
2050+
}
2051+
appUsageUserIDs[uniqueKey.AppKey][uniqueKey.UserID] = struct{}{}
2052+
appUsage[uniqueKey.AppKey] += seconds
2053+
}
2054+
}
2055+
2056+
var rows []database.GetTemplateAppInsightsRow
2057+
for appKey, usage := range appUsage {
2058+
templateIDs := make([]uuid.UUID, 0, len(appUsageTemplateIDs[appKey]))
2059+
for templateID := range appUsageTemplateIDs[appKey] {
2060+
templateIDs = append(templateIDs, templateID)
2061+
}
2062+
slices.SortFunc(templateIDs, func(a, b uuid.UUID) int {
2063+
return slice.Ascending(a.String(), b.String())
2064+
})
2065+
activeUserIDs := make([]uuid.UUID, 0, len(appUsageUserIDs[appKey]))
2066+
for userID := range appUsageUserIDs[appKey] {
2067+
activeUserIDs = append(activeUserIDs, userID)
2068+
}
2069+
slices.SortFunc(activeUserIDs, func(a, b uuid.UUID) int {
2070+
return slice.Ascending(a.String(), b.String())
2071+
})
2072+
2073+
rows = append(rows, database.GetTemplateAppInsightsRow{
2074+
TemplateIDs: templateIDs,
2075+
ActiveUserIDs: activeUserIDs,
2076+
AccessMethod: appKey.AccessMethod,
2077+
SlugOrPort: appKey.SlugOrPort,
2078+
DisplayName: sql.NullString{String: appKey.DisplayName, Valid: appKey.DisplayName != ""},
2079+
Icon: sql.NullString{String: appKey.Icon, Valid: appKey.Icon != ""},
2080+
IsApp: appKey.Slug != "",
2081+
UsageSeconds: usage,
2082+
})
2083+
}
2084+
2085+
return rows, nil
19762086
}
19772087

19782088
func (q *FakeQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (database.GetTemplateAverageBuildTimeRow, error) {
@@ -2102,12 +2212,15 @@ func (q *FakeQuerier) GetTemplateDAUs(_ context.Context, arg database.GetTemplat
21022212
return rs, nil
21032213
}
21042214

2105-
func (q *FakeQuerier) GetTemplateDailyInsights(_ context.Context, arg database.GetTemplateDailyInsightsParams) ([]database.GetTemplateDailyInsightsRow, error) {
2215+
func (q *FakeQuerier) GetTemplateDailyInsights(ctx context.Context, arg database.GetTemplateDailyInsightsParams) ([]database.GetTemplateDailyInsightsRow, error) {
21062216
err := validateDatabaseType(arg)
21072217
if err != nil {
21082218
return nil, err
21092219
}
21102220

2221+
q.mutex.RLock()
2222+
defer q.mutex.RUnlock()
2223+
21112224
type dailyStat struct {
21122225
startTime, endTime time.Time
21132226
userSet map[uuid.UUID]struct{}
@@ -2142,6 +2255,37 @@ func (q *FakeQuerier) GetTemplateDailyInsights(_ context.Context, arg database.G
21422255
}
21432256
}
21442257

2258+
for _, s := range q.workspaceAppStats {
2259+
// (was.session_started_at >= ts.from_ AND was.session_started_at < ts.to_)
2260+
// OR (was.session_ended_at > ts.from_ AND was.session_ended_at < ts.to_)
2261+
// OR (was.session_started_at < ts.from_ AND was.session_ended_at >= ts.to_)
2262+
if !(((s.SessionStartedAt.After(arg.StartTime) || s.SessionStartedAt.Equal(arg.StartTime)) && s.SessionStartedAt.Before(arg.EndTime)) ||
2263+
(s.SessionEndedAt.After(arg.StartTime) && s.SessionEndedAt.Before(arg.EndTime)) ||
2264+
(s.SessionStartedAt.Before(arg.StartTime) && (s.SessionEndedAt.After(arg.EndTime) || s.SessionEndedAt.Equal(arg.EndTime)))) {
2265+
continue
2266+
}
2267+
2268+
for _, ds := range dailyStats {
2269+
// (was.session_started_at >= ts.from_ AND was.session_started_at < ts.to_)
2270+
// OR (was.session_ended_at > ts.from_ AND was.session_ended_at < ts.to_)
2271+
// OR (was.session_started_at < ts.from_ AND was.session_ended_at >= ts.to_)
2272+
if !(((s.SessionStartedAt.After(arg.StartTime) || s.SessionStartedAt.Equal(arg.StartTime)) && s.SessionStartedAt.Before(arg.EndTime)) ||
2273+
(s.SessionEndedAt.After(arg.StartTime) && s.SessionEndedAt.Before(arg.EndTime)) ||
2274+
(s.SessionStartedAt.Before(arg.StartTime) && (s.SessionEndedAt.After(arg.EndTime) || s.SessionEndedAt.Equal(arg.EndTime)))) {
2275+
continue
2276+
}
2277+
2278+
w, err := q.getWorkspaceByIDNoLock(ctx, s.WorkspaceID)
2279+
if err != nil {
2280+
return nil, err
2281+
}
2282+
2283+
ds.userSet[s.UserID] = struct{}{}
2284+
ds.templateIDSet[w.TemplateID] = struct{}{}
2285+
break
2286+
}
2287+
}
2288+
21452289
var result []database.GetTemplateDailyInsightsRow
21462290
for _, ds := range dailyStats {
21472291
templateIDs := make([]uuid.UUID, 0, len(ds.templateIDSet))
@@ -3089,14 +3233,18 @@ func (q *FakeQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.C
30893233
return agents, nil
30903234
}
30913235

3092-
func (q *FakeQuerier) GetWorkspaceAppByAgentIDAndSlug(_ context.Context, arg database.GetWorkspaceAppByAgentIDAndSlugParams) (database.WorkspaceApp, error) {
3236+
func (q *FakeQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg database.GetWorkspaceAppByAgentIDAndSlugParams) (database.WorkspaceApp, error) {
30933237
if err := validateDatabaseType(arg); err != nil {
30943238
return database.WorkspaceApp{}, err
30953239
}
30963240

30973241
q.mutex.RLock()
30983242
defer q.mutex.RUnlock()
30993243

3244+
return q.getWorkspaceAppByAgentIDAndSlugNoLock(ctx, arg)
3245+
}
3246+
3247+
func (q *FakeQuerier) getWorkspaceAppByAgentIDAndSlugNoLock(_ context.Context, arg database.GetWorkspaceAppByAgentIDAndSlugParams) (database.WorkspaceApp, error) {
31003248
for _, app := range q.workspaceApps {
31013249
if app.AgentID != arg.AgentID {
31023250
continue

coderd/insights_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ func TestTemplateInsights(t *testing.T) {
512512
}
513513
}
514514
// Check app usage.
515-
assert.Equal(t, gotApps, []codersdk.TemplateAppUsage{
515+
assert.Equal(t, []codersdk.TemplateAppUsage{
516516
{
517517
TemplateIDs: []uuid.UUID{template.ID},
518518
Type: codersdk.TemplateAppsTypeApp,
@@ -521,7 +521,7 @@ func TestTemplateInsights(t *testing.T) {
521521
Icon: testAppIcon,
522522
Seconds: 300 + 300 + 300, // Three times 5 minutes of usage (actually 1 + 1 + 5, but see TODO above).
523523
},
524-
}, "want app usage to match")
524+
}, gotApps, "want app usage to match")
525525

526526
// The full timeframe is <= 24h, so the interval matches exactly.
527527
require.Len(t, resp.IntervalReports, 1, "want one interval report")

0 commit comments

Comments
 (0)