diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 027099af88..308bc7099f 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -23,6 +23,19 @@ import ( var hostSearchColumns = []string{"hostname", "computer_name", "uuid", "hardware_serial", "primary_ip"} +// Fixme: We should not make implementation details of the database schema part of the API. +var defaultHostColumnTableAliases = map[string]string{ + "created_at": "h.created_at", + "updated_at": "h.updated_at", +} + +func defaultHostColumnTableAlias(s string) string { + if newCol, ok := defaultHostColumnTableAliases[s]; ok { + return newCol + } + return s +} + // NewHost creates a new host on the datastore. // // Currently only used for testing. @@ -571,6 +584,8 @@ func (ds *Datastore) ListHosts(ctx context.Context, filter fleet.TeamFilter, opt } func (ds *Datastore) applyHostFilters(opt fleet.HostListOptions, sql string, filter fleet.TeamFilter, params []interface{}) (string, []interface{}) { + opt.OrderKey = defaultHostColumnTableAlias(opt.OrderKey) + deviceMappingJoin := `LEFT JOIN ( SELECT host_id, diff --git a/server/datastore/mysql/hosts_test.go b/server/datastore/mysql/hosts_test.go index 8cfc039895..1084159d32 100644 --- a/server/datastore/mysql/hosts_test.go +++ b/server/datastore/mysql/hosts_test.go @@ -129,7 +129,7 @@ func TestHosts(t *testing.T) { {"FailingPoliciesCount", testFailingPoliciesCount}, {"SetOrUpdateHostDisksSpace", testHostsSetOrUpdateHostDisksSpace}, {"HostIDsByOSID", testHostIDsByOSID}, - {"TestHostDisplayName", testHostDisplayName}, + {"TestHostOrder", testHostOrder}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { @@ -5372,8 +5372,8 @@ func testHostsSetOrUpdateHostDisksSpace(t *testing.T, ds *Datastore) { require.Equal(t, 6.0, h.PercentDiskSpaceAvailable) } -// testHostDisplayName tests listing a host sorted by display name. -func testHostDisplayName(t *testing.T, ds *Datastore) { +// testHostOrder tests listing a host sorted by different keys. +func testHostOrder(t *testing.T, ds *Datastore) { ctx := context.Background() _, err := ds.NewHost(ctx, &fleet.Host{ID: 1, OsqueryHostID: "1", Hostname: "0001", NodeKey: "1"}) require.NoError(t, err) @@ -5381,17 +5381,41 @@ func testHostDisplayName(t *testing.T, ds *Datastore) { require.NoError(t, err) _, err = ds.NewHost(ctx, &fleet.Host{ID: 3, OsqueryHostID: "3", Hostname: "0003", NodeKey: "3"}) require.NoError(t, err) + chk := func(hosts []*fleet.Host, expect ...string) { + require.Len(t, hosts, len(expect)) + for i, h := range hosts { + assert.Equal(t, expect[i], h.DisplayName()) + } + } hosts, err := ds.ListHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{ ListOptions: fleet.ListOptions{ OrderKey: "display_name", }, }) require.NoError(t, err) - expect := []string{"0001", "0003", "0004"} - require.Len(t, hosts, len(expect)) - for i, h := range hosts { - assert.Equal(t, expect[i], h.DisplayName()) - } + chk(hosts, "0001", "0003", "0004") + + ds.writer.Exec(`UPDATE hosts SET created_at = created_at + id`) + + hosts, err = ds.ListHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{ + ListOptions: fleet.ListOptions{ + OrderKey: "created_at", + After: "2010-10-22T20:22:03Z", + OrderDirection: fleet.OrderAscending, + }, + }) + require.NoError(t, err) + chk(hosts, "0001", "0004", "0003") + + hosts, err = ds.ListHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{ + ListOptions: fleet.ListOptions{ + OrderKey: "created_at", + After: "2180-10-22T20:22:03Z", + OrderDirection: fleet.OrderDescending, + }, + }) + require.NoError(t, err) + chk(hosts, "0003", "0004", "0001") } func testHostIDsByOSID(t *testing.T, ds *Datastore) {