mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Host Details Page: Query performance column (#1867)
This commit is contained in:
parent
9a0871a2f1
commit
36971b4cb8
10 changed files with 213 additions and 8 deletions
1
changes/1867-query-performance-ui
Normal file
1
changes/1867-query-performance-ui
Normal file
|
|
@ -0,0 +1 @@
|
|||
Renders query performance information on host details page pack section
|
||||
|
|
@ -45,6 +45,9 @@ describe(
|
|||
cy.get("input[disabled]").should("have.value", contents);
|
||||
});
|
||||
|
||||
// ensure load
|
||||
cy.wait(5000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
|
||||
// Wait until the host becomes available (usually immediate in local
|
||||
// testing, but may vary by environment).
|
||||
cy.waitUntil(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
interface IPillCellProps {
|
||||
value: [string, number];
|
||||
}
|
||||
|
||||
const generateClassTag = (rawValue: string): string => {
|
||||
return rawValue.replace(" ", "-").toLowerCase();
|
||||
};
|
||||
|
||||
const PillCell = (props: IPillCellProps): JSX.Element => {
|
||||
const { value } = props;
|
||||
|
||||
const pillClassName = classnames(
|
||||
"data-table__pill",
|
||||
`data-table__pill--${generateClassTag(value[0])}`
|
||||
);
|
||||
|
||||
const disable = () => {
|
||||
switch (value[0]) {
|
||||
case "Minimal":
|
||||
return false;
|
||||
case "Considerate":
|
||||
return false;
|
||||
case "Excessive":
|
||||
return false;
|
||||
case "Denylisted":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const tooltipText = () => {
|
||||
switch (value[0]) {
|
||||
case "Minimal":
|
||||
return (
|
||||
<>
|
||||
Running this query very <br />
|
||||
frequently has little to no <br /> impact on your device’s <br />
|
||||
performance.
|
||||
</>
|
||||
);
|
||||
case "Considerate":
|
||||
return (
|
||||
<>
|
||||
Running this query <br /> frequently can have a <br /> noticeable
|
||||
impact on your <br /> device’s performance.
|
||||
</>
|
||||
);
|
||||
case "Excessive":
|
||||
return (
|
||||
<>
|
||||
Running this query, even <br /> infrequently, can have a <br />
|
||||
significant impact on your <br /> device’s performance.
|
||||
</>
|
||||
);
|
||||
case "Denylisted":
|
||||
return (
|
||||
<>
|
||||
This query has been <br /> stopped from running <br /> because of
|
||||
excessive <br /> resource consumption.
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-tip data-for={value[1].toString()} data-tip-disable={disable()}>
|
||||
<span className={pillClassName}>{value[0]}</span>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={value[1].toString()}
|
||||
data-html
|
||||
>
|
||||
<span
|
||||
className={`tooltip ${generateClassTag(value[0])}__tooltip-text`}
|
||||
style={{ width: "196px" }}
|
||||
>
|
||||
{tooltipText()}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PillCell;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./PillCell";
|
||||
|
|
@ -149,6 +149,18 @@
|
|||
color: $core-fleet-black;
|
||||
}
|
||||
|
||||
&__pill {
|
||||
color: $core-fleet-black;
|
||||
font-weight: $bold;
|
||||
padding: 4px 12px;
|
||||
border-radius: 29px;
|
||||
|
||||
span {
|
||||
border-radius: 29px;
|
||||
background-color: $core-fleet-purple;
|
||||
}
|
||||
}
|
||||
|
||||
&__status {
|
||||
color: $core-fleet-blue;
|
||||
text-transform: capitalize;
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ export const humanQueryLastRun = (lastRun: string): string => {
|
|||
// Handles the case when a query has never been ran.
|
||||
// July 28, 2016 is the date of the initial commit to fleet/fleet.
|
||||
if (lastRun < "2016-07-28T00:00:00Z") {
|
||||
return "Never";
|
||||
return "Has not run";
|
||||
}
|
||||
|
||||
return moment(lastRun).fromNow();
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ export interface IQueryStats {
|
|||
description: string;
|
||||
pack_name: string;
|
||||
pack_id: number;
|
||||
average_memory?: number;
|
||||
denylisted?: boolean;
|
||||
executions?: number;
|
||||
average_memory: number;
|
||||
denylisted: boolean;
|
||||
executions: number;
|
||||
interval: number;
|
||||
last_executed: string;
|
||||
output_size?: number;
|
||||
system_time?: number;
|
||||
user_time?: number;
|
||||
system_time: number;
|
||||
user_time: number;
|
||||
wall_time?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import React from "react";
|
|||
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import PillCell from "components/TableContainer/DataTable/PillCell";
|
||||
import { IQueryStats } from "interfaces/query_stats";
|
||||
import { humanQueryLastRun, secondsToHms } from "fleet/helpers";
|
||||
import IconToolTip from "components/IconToolTip";
|
||||
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
|
|
@ -33,8 +35,30 @@ interface IDataColumn {
|
|||
interface IPackTable extends IQueryStats {
|
||||
frequency: string;
|
||||
last_run: string;
|
||||
performance: (string | number)[];
|
||||
}
|
||||
|
||||
const performanceIndicator = (scheduledQuery: IQueryStats): string => {
|
||||
if (scheduledQuery.executions === 0) {
|
||||
return "Undetermined";
|
||||
}
|
||||
if (scheduledQuery.denylisted === true) {
|
||||
return "Denylisted";
|
||||
}
|
||||
|
||||
const indicator =
|
||||
(scheduledQuery.user_time + scheduledQuery.system_time) /
|
||||
scheduledQuery.executions;
|
||||
|
||||
if (indicator < 2000) {
|
||||
return "Minimal";
|
||||
}
|
||||
if (indicator >= 2000 && indicator <= 4000) {
|
||||
return "Considerate";
|
||||
}
|
||||
return "Excessive";
|
||||
};
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
const generatePackTableHeaders = (): IDataColumn[] => {
|
||||
|
|
@ -55,11 +79,28 @@ const generatePackTableHeaders = (): IDataColumn[] => {
|
|||
},
|
||||
{
|
||||
title: "Last run",
|
||||
Header: "Last run",
|
||||
Header: () => {
|
||||
return (
|
||||
<>
|
||||
Last run
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={`The last time the query ran<br/>since the last time osquery <br/>started on this host.`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
disableSortBy: true,
|
||||
accessor: "last_run",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Performance impact",
|
||||
Header: "Performance impact",
|
||||
disableSortBy: true,
|
||||
accessor: "performance",
|
||||
Cell: (cellProps) => <PillCell value={cellProps.cell.value} />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -76,6 +117,12 @@ const enhancePackData = (query_stats: IQueryStats[]): IPackTable[] => {
|
|||
last_executed: query.last_executed,
|
||||
frequency: secondsToHms(query.interval),
|
||||
last_run: humanQueryLastRun(query.last_executed),
|
||||
performance: [performanceIndicator(query), query.scheduled_query_id],
|
||||
average_memory: query.average_memory,
|
||||
denylisted: query.denylisted,
|
||||
executions: query.executions,
|
||||
system_time: query.system_time,
|
||||
user_time: query.user_time,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -460,6 +460,36 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
width: 192px;
|
||||
}
|
||||
|
||||
.data-table__pill--undetermined {
|
||||
color: $ui-fleet-black-50;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.data-table__pill--denylisted {
|
||||
font-weight: 400;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.data-table__pill--minimal {
|
||||
background-color: $ui-vibrant-blue-10;
|
||||
}
|
||||
|
||||
.data-table__pill--considerate {
|
||||
background-color: $ui-vibrant-blue-25;
|
||||
}
|
||||
|
||||
.data-table__pill--excessive {
|
||||
background-color: $ui-vibrant-blue-50;
|
||||
}
|
||||
|
||||
.data-table__table {
|
||||
table-layout: fixed;
|
||||
|
||||
|
|
@ -467,7 +497,7 @@
|
|||
// Width for all columns except the "Query name" column
|
||||
// Width calculation adjusts for each row's horizontal padding
|
||||
th {
|
||||
width: calc(160px - 27px * 2);
|
||||
width: calc(200px - 27px * 2);
|
||||
}
|
||||
|
||||
// Width for the "Query name" column
|
||||
|
|
@ -482,6 +512,19 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.__react_component_tooltip {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-tooltip {
|
||||
display: inline;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-left: $pad-small;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ $ui-blue-gray: #dbe3e5;
|
|||
$ui-gray: #e3e3e3;
|
||||
$ui-light-grey: #fafafa;
|
||||
$ui-off-white: #f9fafc;
|
||||
$ui-vibrant-blue-50: rgba(106, 103, 254, 0.5);
|
||||
$ui-vibrant-blue-25: #d9d9fe;
|
||||
$ui-vibrant-blue-10: #f1f0ff;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue