Show host display name in query results. (#15173)

Query report now shows the host display name instead of hostname.
#14763 

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Victor Lyuboslavsky
2023-11-16 14:26:57 -06:00
committed by GitHub
parent bd68426b03
commit 36e12d02e3
6 changed files with 45 additions and 22 deletions
@@ -0,0 +1 @@
Query report now shows the host display name instead of hostname.
+2 -1
View File
@@ -91,7 +91,8 @@ func (ds *Datastore) OverwriteQueryResultRows(ctx context.Context, rows []*fleet
// (to avoid having to left join hosts).
func (ds *Datastore) QueryResultRows(ctx context.Context, queryID uint) ([]*fleet.ScheduledQueryResultRow, error) {
selectStmt := `
SELECT qr.query_id, qr.host_id, COALESCE(h.hostname, '') as hostname, qr.last_fetched, qr.data
SELECT qr.query_id, qr.host_id, qr.last_fetched, qr.data,
h.hostname, h.computer_name, h.hardware_model, h.hardware_serial
FROM query_results qr
LEFT JOIN hosts h ON (qr.host_id=h.id)
WHERE query_id = ?
+12 -8
View File
@@ -632,23 +632,27 @@ func (h *Host) IsEligibleForBitLockerEncryption() bool {
(needsEncryption || encryptedWithoutKey)
}
// DisplayName returns ComputerName if it isn't empty. Otherwise, it returns Hostname if it isn't
// HostDisplayName returns ComputerName if it isn't empty. Otherwise, it returns Hostname if it isn't
// empty. If Hostname is empty and both HardwareSerial and HardwareModel are not empty, it returns a
// composite string with HardwareModel and HardwareSerial. If all else fails, it returns an empty
// string.
func (h *Host) DisplayName() string {
func HostDisplayName(ComputerName string, Hostname string, HardwareModel string, HardwareSerial string) string {
switch {
case h.ComputerName != "":
return h.ComputerName
case h.Hostname != "":
return h.Hostname
case h.HardwareModel != "" && h.HardwareSerial != "":
return fmt.Sprintf("%s (%s)", h.HardwareModel, h.HardwareSerial)
case ComputerName != "":
return ComputerName
case Hostname != "":
return Hostname
case HardwareModel != "" && HardwareSerial != "":
return fmt.Sprintf("%s (%s)", HardwareModel, HardwareSerial)
default:
return ""
}
}
func (h *Host) DisplayName() string {
return HostDisplayName(h.ComputerName, h.Hostname, h.HardwareModel, h.HardwareSerial)
}
type HostIssues struct {
TotalIssuesCount int `json:"total_issues_count" db:"total_issues_count" csv:"issues"` // when exporting in CSV, we want that value as the "issues" column
FailingPoliciesCount int `json:"failing_policies_count" db:"failing_policies_count" csv:"-"`
+18 -3
View File
@@ -1,6 +1,7 @@
package fleet
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
@@ -380,7 +381,7 @@ func MapQueryReportResultsToRows(rows []*ScheduledQueryResultRow) ([]HostQueryRe
}
results = append(results, HostQueryResultRow{
HostID: row.HostID,
Hostname: row.Hostname,
Hostname: row.HostDisplayName(),
LastFetched: row.LastFetched,
Columns: columns,
})
@@ -421,11 +422,25 @@ type ScheduledQueryResultRow struct {
QueryID uint `db:"query_id"`
// HostID is the unique identifier of the host.
HostID uint `db:"host_id"`
// Hostname is the host's hostname.
Hostname string `db:"hostname"`
// Hostname is the host's hostname. NullString is used in case host does not exist.
Hostname sql.NullString `db:"hostname"`
// ComputerName is the host's computer_name.
ComputerName sql.NullString `db:"computer_name"`
// HardwareModel is the host's hardware_model.
HardwareModel sql.NullString `db:"hardware_model"`
// HardwareSerial is the host's hardware_serial.
HardwareSerial sql.NullString `db:"hardware_serial"`
// Data holds a single result row. It holds a map where the map keys
// are column names and map values are the values.
Data json.RawMessage `db:"data"`
// LastFetched is the time this result was received.
LastFetched time.Time `db:"last_fetched"`
}
func (s *ScheduledQueryResultRow) HostDisplayName() string {
// If host does not exist, all values below default to empty string
return HostDisplayName(
s.ComputerName.String, s.Hostname.String,
s.HardwareModel.String, s.HardwareSerial.String,
)
}
+7 -6
View File
@@ -1,6 +1,7 @@
package fleet
import (
"database/sql"
"encoding/json"
"testing"
"time"
@@ -228,7 +229,7 @@ func TestMapQueryReportResultRows(t *testing.T) {
rows: []*ScheduledQueryResultRow{
{
HostID: 1,
Hostname: "macOS host",
Hostname: sql.NullString{String: "macOS host", Valid: true},
LastFetched: macOSUSBDevicesLastFetched,
Data: json.RawMessage(`{
"class": "9",
@@ -247,7 +248,7 @@ func TestMapQueryReportResultRows(t *testing.T) {
},
{
HostID: 1,
Hostname: "macOS host",
Hostname: sql.NullString{String: "macOS host", Valid: true},
LastFetched: macOSUSBDevicesLastFetched,
Data: json.RawMessage(`{
"class": "9",
@@ -266,7 +267,7 @@ func TestMapQueryReportResultRows(t *testing.T) {
},
{
HostID: 2,
Hostname: "ubuntu host",
Hostname: sql.NullString{String: "ubuntu host", Valid: true},
LastFetched: ubuntuUSBDevicesLastFetched,
Data: json.RawMessage(`{
"class": "9",
@@ -350,7 +351,7 @@ func TestMapQueryReportResultRows(t *testing.T) {
rows: []*ScheduledQueryResultRow{
{
HostID: 1,
Hostname: "macOS host",
Hostname: sql.NullString{String: "macOS host", Valid: true},
LastFetched: macOSOsqueryInfoLastFetched,
Data: json.RawMessage(`{
"build_distro": "10.14",
@@ -396,7 +397,7 @@ func TestMapQueryReportResultRows(t *testing.T) {
rows: []*ScheduledQueryResultRow{
{
HostID: 3,
Hostname: "bar",
Hostname: sql.NullString{String: "bar", Valid: true},
LastFetched: time.Now(),
Data: json.RawMessage(`invalid JSON`),
},
@@ -408,7 +409,7 @@ func TestMapQueryReportResultRows(t *testing.T) {
rows: []*ScheduledQueryResultRow{
{
HostID: 3,
Hostname: "bar",
Hostname: sql.NullString{String: "bar", Valid: true},
LastFetched: time.Now(),
Data: json.RawMessage(`{"foobar": 1}`),
},
+5 -4
View File
@@ -8163,6 +8163,7 @@ func (s *integrationTestSuite) TestQueryReports() {
SeenTime: time.Now(),
NodeKey: ptr.String("2"),
UUID: "2",
ComputerName: "Foo Local2",
Hostname: "foo.local2",
OsqueryHostID: ptr.String("2"),
PrimaryIP: "192.168.1.2",
@@ -8339,7 +8340,7 @@ func (s *integrationTestSuite) TestQueryReports() {
return gqrr.Results[i].Columns["usb_port"] < gqrr.Results[j].Columns["usb_port"]
})
require.Equal(t, host2Team1.ID, gqrr.Results[0].HostID)
require.Equal(t, host2Team1.Hostname, gqrr.Results[0].Hostname)
require.Equal(t, host2Team1.DisplayName(), gqrr.Results[0].Hostname)
require.NotZero(t, gqrr.Results[0].LastFetched)
require.Equal(t, map[string]string{
"class": "239",
@@ -8356,7 +8357,7 @@ func (s *integrationTestSuite) TestQueryReports() {
"version": "0.19",
}, gqrr.Results[0].Columns)
require.Equal(t, host2Team1.ID, gqrr.Results[1].HostID)
require.Equal(t, host2Team1.Hostname, gqrr.Results[1].Hostname)
require.Equal(t, host2Team1.DisplayName(), gqrr.Results[1].Hostname)
require.NotZero(t, gqrr.Results[1].LastFetched)
require.Equal(t, map[string]string{
"class": "0",
@@ -8383,7 +8384,7 @@ func (s *integrationTestSuite) TestQueryReports() {
return gqrr.Results[i].Columns["version"] > gqrr.Results[j].Columns["version"]
})
require.Equal(t, host1Global.ID, gqrr.Results[0].HostID)
require.Equal(t, host1Global.Hostname, gqrr.Results[0].Hostname)
require.Equal(t, host1Global.DisplayName(), gqrr.Results[0].Hostname)
require.NotZero(t, gqrr.Results[0].LastFetched)
require.Equal(t, map[string]string{
"build_distro": "centos7",
@@ -8400,7 +8401,7 @@ func (s *integrationTestSuite) TestQueryReports() {
"watcher": "3570",
}, gqrr.Results[0].Columns)
require.Equal(t, host2Team1.ID, gqrr.Results[1].HostID)
require.Equal(t, host2Team1.Hostname, gqrr.Results[1].Hostname)
require.Equal(t, host2Team1.DisplayName(), gqrr.Results[1].Hostname)
require.NotZero(t, gqrr.Results[1].LastFetched)
require.Equal(t, map[string]string{
"build_distro": "10.14",