Host Details Page: Query performance column (#1867)

This commit is contained in:
RachelElysia 2021-09-01 18:08:20 -04:00 committed by GitHub
parent 9a0871a2f1
commit 36971b4cb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 213 additions and 8 deletions

View file

@ -0,0 +1 @@
Renders query performance information on host details page pack section

View file

@ -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(

View file

@ -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 devices <br />
performance.
</>
);
case "Considerate":
return (
<>
Running this query <br /> frequently can have a <br /> noticeable
impact on your <br /> devices performance.
</>
);
case "Excessive":
return (
<>
Running this query, even <br /> infrequently, can have a <br />
significant impact on your <br /> devices 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;

View file

@ -0,0 +1 @@
export { default } from "./PillCell";

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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,
};
});
};

View file

@ -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;
}
}
}

View file

@ -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;