diff --git a/changes/15458-host-search-pending-display-name b/changes/15458-host-search-pending-display-name new file mode 100644 index 0000000000..1509015b20 --- /dev/null +++ b/changes/15458-host-search-pending-display-name @@ -0,0 +1 @@ +- Added ability to query by host display name via list hosts endpoint. \ No newline at end of file diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 6985364c35..0f7220e794 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -26,6 +26,8 @@ var ( wildCardableHostSearchColumns = []string{"hostname", "computer_name"} ) +// TODO: should host search columns include display_name (requires join to host_display_names)? + // 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", @@ -1010,9 +1012,12 @@ func (ds *Datastore) applyHostFilters( } displayNameJoin := "" - if opt.ListOptions.OrderKey == "display_name" { + if opt.ListOptions.OrderKey == "display_name" || opt.MatchQuery != "" { displayNameJoin = ` JOIN host_display_names hdn ON h.id = hdn.host_id ` } + if opt.MatchQuery != "" { + displayNameJoin = ` LEFT ` + displayNameJoin + } lowDiskSpaceFilter := "TRUE" if opt.LowDiskSpaceFilter != nil { @@ -1077,7 +1082,7 @@ func (ds *Datastore) applyHostFilters( sqlStmt, params = filterHostsByMDMBootstrapPackageStatus(sqlStmt, opt, params) sqlStmt, params = filterHostsByOS(sqlStmt, opt, params) - sqlStmt, params, _ = hostSearchLike(sqlStmt, params, opt.MatchQuery, hostSearchColumns...) + sqlStmt, params, _ = hostSearchLike(sqlStmt, params, opt.MatchQuery, append(hostSearchColumns, "display_name")...) sqlStmt, params = appendListOptionsWithCursorToSQL(sqlStmt, params, &opt.ListOptions) return sqlStmt, params, nil @@ -2357,6 +2362,7 @@ func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, m // to get all the additional data for hosts that match the search criteria by host_id matchingHosts := "SELECT id FROM hosts WHERE TRUE" var args []interface{} + // TODO: should search columns include display_name (requires join to host_display_names)? searchHostsQuery, args, matchesEmail := hostSearchLike(matchingHosts, args, matchQuery, hostSearchColumns...) // if matchQuery is "email like" then don't bother with the additional wildcard searching if !matchesEmail && len(matchQuery) > 2 && hasNonASCIIRegex(matchQuery) { diff --git a/server/datastore/mysql/labels.go b/server/datastore/mysql/labels.go index 9bc4c77c09..d2cf21737d 100644 --- a/server/datastore/mysql/labels.go +++ b/server/datastore/mysql/labels.go @@ -595,6 +595,7 @@ func (ds *Datastore) applyHostLabelFilters(ctx context.Context, filter fleet.Tea } else if opt.OSSettingsDiskEncryptionFilter.IsValid() { query, params = ds.filterHostsByOSSettingsDiskEncryptionStatus(query, opt, params, enableDiskEncryption) } + // TODO: should search columns include display_name (requires join to host_display_names)? query, params = searchLike(query, params, opt.MatchQuery, hostSearchColumns...) query, params = appendListOptionsWithCursorToSQL(query, params, &opt.ListOptions) diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 3a798546d1..848905087c 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -2033,6 +2033,15 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() { s.DoJSON("GET", "/api/latest/fleet/hosts?mdm_enrollment_status=pending", nil, http.StatusOK, &listHostsRes) require.Len(t, listHostsRes.Hosts, len(devices)) + // searching by display name works + listHostsRes = listHostsResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts?query=%s", url.QueryEscape("MacBook Mini")), nil, http.StatusOK, &listHostsRes) + require.Len(t, listHostsRes.Hosts, 3) + for _, host := range listHostsRes.Hosts { + require.Equal(t, "MacBook Mini", host.HardwareModel) + require.Equal(t, host.DisplayName, fmt.Sprintf("MacBook Mini (%s)", host.HardwareSerial)) + } + s.pushProvider.PushFunc = func(pushes []*mdm.Push) (map[string]*push.Response, error) { return map[string]*push.Response{}, nil }