Add AI features, maintenance window, and Fleet Desktop to usage statistics Story #19693 (#21131)

Additional statistics as described in
https://github.com/fleetdm/fleet/pull/20091 :

`aiFeaturesDisabled`:
Whether server_settings.ai_features_disabled is set to true in the
config.

`maintenanceWindowsEnabled`:
Whether at least one team has
integrations.google_calendar.enable_calendar_events set to true

`maintenanceWindowsConfigured`:
Maintenance windows are considered "configured" if:
configuration has value set for integrations.google_calendar[0].domain
configuration has value set for
integrations.google_calendar[0].api_key_json

`numHostsFleetDesktopEnabled`:
The number of hosts with Fleet desktop installed.
This commit is contained in:
Sharon Katz
2024-08-12 15:12:28 -04:00
committed by GitHub
parent 9c45f85e83
commit 8fdfb9a004
7 changed files with 71 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
- Added additional statistics items for AI features, maintenance window, and Fleet Desktop
+5 -1
View File
@@ -116,6 +116,10 @@ func TestMaybeSendStatistics(t *testing.T) {
HostsEnrolledByOsqueryVersion: []fleet.HostsCountByOsqueryVersion{},
StoredErrors: []byte(`[]`),
Organization: "Fleet",
AIFeaturesDisabled: true,
MaintenanceWindowsEnabled: true,
MaintenanceWindowsConfigured: true,
NumHostsFleetDesktopEnabled: 1984,
}, true, nil
}
recorded := false
@@ -134,7 +138,7 @@ func TestMaybeSendStatistics(t *testing.T) {
require.NoError(t, err)
assert.True(t, recorded)
require.True(t, cleanedup)
assert.Equal(t, `{"anonymousIdentifier":"ident","fleetVersion":"1.2.3","licenseTier":"premium","organization":"Fleet","numHostsEnrolled":999,"numUsers":99,"numSoftwareVersions":100,"numHostSoftwares":101,"numSoftwareTitles":102,"numHostSoftwareInstalledPaths":103,"numSoftwareCPEs":104,"numSoftwareCVEs":105,"numTeams":9,"numPolicies":0,"numLabels":3,"softwareInventoryEnabled":true,"vulnDetectionEnabled":true,"systemUsersEnabled":true,"hostsStatusWebHookEnabled":true,"mdmMacOsEnabled":false,"hostExpiryEnabled":false,"mdmWindowsEnabled":false,"liveQueryDisabled":false,"numWeeklyActiveUsers":111,"numWeeklyPolicyViolationDaysActual":0,"numWeeklyPolicyViolationDaysPossible":0,"hostsEnrolledByOperatingSystem":{"linux":[{"version":"1.2.3","numEnrolled":22}]},"hostsEnrolledByOrbitVersion":[],"hostsEnrolledByOsqueryVersion":[],"storedErrors":[],"numHostsNotResponding":0}`, requestBody)
assert.Equal(t, `{"anonymousIdentifier":"ident","fleetVersion":"1.2.3","licenseTier":"premium","organization":"Fleet","numHostsEnrolled":999,"numUsers":99,"numSoftwareVersions":100,"numHostSoftwares":101,"numSoftwareTitles":102,"numHostSoftwareInstalledPaths":103,"numSoftwareCPEs":104,"numSoftwareCVEs":105,"numTeams":9,"numPolicies":0,"numLabels":3,"softwareInventoryEnabled":true,"vulnDetectionEnabled":true,"systemUsersEnabled":true,"hostsStatusWebHookEnabled":true,"mdmMacOsEnabled":false,"hostExpiryEnabled":false,"mdmWindowsEnabled":false,"liveQueryDisabled":false,"numWeeklyActiveUsers":111,"numWeeklyPolicyViolationDaysActual":0,"numWeeklyPolicyViolationDaysPossible":0,"hostsEnrolledByOperatingSystem":{"linux":[{"version":"1.2.3","numEnrolled":22}]},"hostsEnrolledByOrbitVersion":[],"hostsEnrolledByOsqueryVersion":[],"storedErrors":[],"numHostsNotResponding":0,"aiFeaturesDisabled":true,"maintenanceWindowsEnabled":true,"maintenanceWindowsConfigured":true,"numHostsFleetDesktopEnabled":1984}`, requestBody)
}
func TestMaybeSendStatisticsSkipsSendingIfNotNeeded(t *testing.T) {
+5 -1
View File
@@ -102,7 +102,11 @@ Below is the JSON payload that is sent to Fleet Device Management Inc:
},
...
],
"numHostsNotResponding": 9
"numHostsNotResponding": 9,
"aiFeaturesDisabled": true,
"maintenanceWindowsEnabled": true,
"maintenanceWindowsConfigured": true,
"numHostsFleetDesktopEnabled": 999
}
```
+12
View File
@@ -5035,6 +5035,18 @@ func amountHostsByOsqueryVersionDB(ctx context.Context, db sqlx.QueryerContext)
return counts, nil
}
func numHostsFleetDesktopEnabledDB(ctx context.Context, db sqlx.QueryerContext) (int, error) {
var count int
const stmt = `
SELECT count(*) FROM host_orbit_info WHERE desktop_version IS NOT NULL
`
if err := sqlx.GetContext(ctx, db, &count, stmt); err != nil {
return 0, err
}
return count, nil
}
func (ds *Datastore) GetMatchingHostSerials(ctx context.Context, serials []string) (map[string]*fleet.Host, error) {
result := map[string]*fleet.Host{}
if len(serials) == 0 {
+22
View File
@@ -10,6 +10,7 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/version"
"github.com/go-kit/log/level"
"github.com/jmoiron/sqlx"
@@ -98,6 +99,10 @@ func (ds *Datastore) ShouldSendStatistics(ctx context.Context, frequency time.Du
if err != nil {
return ctxerr.Wrap(ctx, err, "amount hosts by osquery version")
}
numHostsFleetDesktopEnabled, err := numHostsFleetDesktopEnabledDB(ctx, ds.reader(ctx))
if err != nil {
return ctxerr.Wrap(ctx, err, "number of hosts with Fleet desktop installed")
}
stats.NumHostsEnrolled = amountEnrolledHosts
stats.NumUsers = amountUsers
@@ -130,6 +135,23 @@ func (ds *Datastore) ShouldSendStatistics(ctx context.Context, frequency time.Du
if lic != nil && lic.IsPremium() {
stats.Organization = lic.Organization
}
stats.AIFeaturesDisabled = appConfig.ServerSettings.AIFeaturesDisabled
stats.MaintenanceWindowsConfigured = len(appConfig.Integrations.GoogleCalendar) > 0 && appConfig.Integrations.GoogleCalendar[0].Domain != "" && len(appConfig.Integrations.GoogleCalendar[0].ApiKey) > 0
stats.MaintenanceWindowsEnabled = false
teams, err := ds.ListTeams(ctx, fleet.TeamFilter{User: &fleet.User{
GlobalRole: ptr.String(fleet.RoleAdmin),
}}, fleet.ListOptions{})
if err != nil {
return ctxerr.Wrap(ctx, err, "list teams")
}
for _, team := range teams {
if team.Config.Integrations.GoogleCalendar != nil && team.Config.Integrations.GoogleCalendar.Enable {
stats.MaintenanceWindowsEnabled = true
break
}
}
stats.NumHostsFleetDesktopEnabled = numHostsFleetDesktopEnabled
return nil
}
+16
View File
@@ -87,6 +87,10 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) {
assert.Equal(t, false, stats.HostExpiryEnabled)
assert.Equal(t, false, stats.MDMWindowsEnabled)
assert.Equal(t, false, stats.LiveQueryDisabled)
assert.Equal(t, false, stats.AIFeaturesDisabled)
assert.Equal(t, false, stats.MaintenanceWindowsEnabled)
assert.Equal(t, false, stats.MaintenanceWindowsConfigured)
assert.Equal(t, 0, stats.NumHostsFleetDesktopEnabled)
firstIdentifier := stats.AnonymousIdentifier
@@ -227,6 +231,10 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) {
assert.Equal(t, `[{"count":10,"loc":["a","b","c"]}]`, string(stats.StoredErrors))
assert.Equal(t, []fleet.HostsCountByOsqueryVersion{{OsqueryVersion: "4.9.0", NumHosts: 1}}, stats.HostsEnrolledByOsqueryVersion)
assert.Equal(t, []fleet.HostsCountByOrbitVersion{{OrbitVersion: "1.1.0", NumHosts: 1}}, stats.HostsEnrolledByOrbitVersion)
assert.Equal(t, false, stats.AIFeaturesDisabled)
assert.Equal(t, false, stats.MaintenanceWindowsEnabled)
assert.Equal(t, false, stats.MaintenanceWindowsConfigured)
assert.Equal(t, 1, stats.NumHostsFleetDesktopEnabled)
err = ds.RecordStatisticsSent(ctx)
require.NoError(t, err)
@@ -332,6 +340,10 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) {
{Version: "", NumEnrolled: 1},
}, stats.HostsEnrolledByOperatingSystem[""])
assert.Equal(t, `[{"count":10,"loc":["a","b","c"]}]`, string(stats.StoredErrors))
assert.Equal(t, false, stats.AIFeaturesDisabled)
assert.Equal(t, false, stats.MaintenanceWindowsEnabled)
assert.Equal(t, false, stats.MaintenanceWindowsConfigured)
assert.Equal(t, 1, stats.NumHostsFleetDesktopEnabled)
// Create multiple new sessions for a single user
_, err = ds.NewSession(ctx, u1.ID, "session_key2")
@@ -366,6 +378,10 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) {
assert.Equal(t, 0, stats.NumWeeklyPolicyViolationDaysActual)
assert.Equal(t, 0, stats.NumWeeklyPolicyViolationDaysPossible)
assert.Equal(t, `[{"count":10,"loc":["a","b","c"]}]`, string(stats.StoredErrors))
assert.Equal(t, false, stats.AIFeaturesDisabled)
assert.Equal(t, false, stats.MaintenanceWindowsEnabled)
assert.Equal(t, false, stats.MaintenanceWindowsConfigured)
assert.Equal(t, 1, stats.NumHostsFleetDesktopEnabled)
// Add host to test hosts not responding stats
_, err = ds.NewHost(ctx, &fleet.Host{
+10
View File
@@ -47,6 +47,16 @@ type StatisticsPayload struct {
StoredErrors json.RawMessage `json:"storedErrors"`
// NumHostsNotResponding is a count of hosts that connect to Fleet successfully but fail to submit results for distributed queries.
NumHostsNotResponding int `json:"numHostsNotResponding"`
// Whether server_settings.ai_features_disabled is set to true in the config.
AIFeaturesDisabled bool `json:"aiFeaturesDisabled"`
// Whether at least one team has integrations.google_calendar.enable_calendar_events set to true
MaintenanceWindowsEnabled bool `json:"maintenanceWindowsEnabled"`
// Maintenance windows are considered "configured" if:
// configuration has value set for integrations.google_calendar[0].domain
// configuration has value set for integrations.google_calendar[0].api_key_json
MaintenanceWindowsConfigured bool `json:"maintenanceWindowsConfigured"`
// The number of hosts with Fleet desktop installed.
NumHostsFleetDesktopEnabled int `json:"numHostsFleetDesktopEnabled"`
}
type HostsCountByOrbitVersion struct {