update UI to react 18 (#17471)

This commit is contained in:
Gabriel Hernandez 2024-03-13 19:09:16 +00:00 committed by GitHub
parent 8be1d4766f
commit 3c2e4b8f4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 438 additions and 532 deletions

View File

@ -94,6 +94,7 @@ const DEFAULT_HOST_MOCK: IHost = {
software: [],
users: [],
policies: [],
device_mapping: [],
};
const createMockHost = (overrides?: Partial<IHost>): IHost => {

View File

@ -1,6 +1,10 @@
import React, { useContext, useEffect, useState } from "react";
import React, { FC, ReactNode, useContext, useEffect, useState } from "react";
import { AxiosResponse } from "axios";
import { QueryClient, QueryClientProvider } from "react-query";
import {
QueryClient,
QueryClientProvider,
QueryClientProviderProps,
} from "react-query";
import page_titles from "router/page_titles";
import TableProvider from "context/table";
@ -35,6 +39,14 @@ interface IAppProps {
};
}
// We create a CustomQueryClientProvider that takes the same props as the original
// QueryClientProvider but adds the children prop as a ReactNode. This children
// prop is not required explicitly in React 18. We do it this way to avoid
// having to update the react-query package version and typings for now.
// When we upgrade React Query we should be able to remove this.
type ICustomQueryClientProviderProps = React.PropsWithChildren<QueryClientProviderProps>;
const CustomQueryClientProvider: FC<ICustomQueryClientProviderProps> = QueryClientProvider;
const baseClass = "app";
const App = ({ children, location }: IAppProps): JSX.Element => {
@ -170,7 +182,7 @@ const App = ({ children, location }: IAppProps): JSX.Element => {
return isLoading ? (
<Spinner />
) : (
<QueryClientProvider client={queryClient}>
<CustomQueryClientProvider client={queryClient}>
<TableProvider>
<QueryProvider>
<PolicyProvider>
@ -185,7 +197,7 @@ const App = ({ children, location }: IAppProps): JSX.Element => {
</PolicyProvider>
</QueryProvider>
</TableProvider>
</QueryClientProvider>
</CustomQueryClientProvider>
);
};

View File

@ -316,8 +316,8 @@ const SelectTargets = ({
}
};
const handleRowRemove = (row: Row) => {
const removedHost = row.original as IHost;
const handleRowRemove = (row: Row<IHost>) => {
const removedHost = row.original;
setTargetedHosts((prevHosts) =>
prevHosts.filter((h) => h.id !== removedHost.id)
);

View File

@ -20,7 +20,7 @@ interface ITargetsInputProps {
targetedHosts: IHost[];
setSearchText: (value: string) => void;
handleRowSelect: (value: Row) => void;
handleRowRemove: (value: Row) => void;
handleRowRemove: (value: Row<IHost>) => void;
}
const baseClass = "targets-input";

View File

@ -1,9 +1,9 @@
/* eslint-disable react/prop-types */
import React from "react";
import { Row } from "react-table";
import { Column, Row } from "react-table";
import { IDataColumn } from "interfaces/datatable_config";
import { IStringCellProps } from "interfaces/datatable_config";
import { IHost } from "interfaces/host";
import TextCell from "components/TableContainer/DataTable/TextCell";
@ -11,26 +11,20 @@ import LiveQueryIssueCell from "components/TableContainer/DataTable/LiveQueryIss
import StatusIndicator from "components/StatusIndicator";
import Icon from "components/Icon/Icon";
interface ICellProps {
cell: {
value: string;
};
row: {
original: IHost;
};
}
type ITargestInputhostTableConfig = Column<IHost>;
type ITableStringCellProps = IStringCellProps<IHost>;
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
export const generateTableHeaders = (
handleRowRemove?: (value: Row) => void
): IDataColumn[] => {
handleRowRemove?: (value: Row<IHost>) => void
): ITargestInputhostTableConfig[] => {
const deleteHeader = handleRowRemove
? [
{
id: "delete",
Header: "",
Cell: (cellProps: { row: Row }): JSX.Element => (
Cell: (cellProps: ITableStringCellProps) => (
<div onClick={() => handleRowRemove(cellProps.row)}>
<Icon name="close-filled" />
</div>
@ -42,10 +36,9 @@ export const generateTableHeaders = (
return [
{
title: "Host",
Header: "Host",
accessor: "display_name",
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: ITableStringCellProps) => {
return (
<LiveQueryIssueCell
displayName={cellProps.cell.value}
@ -59,32 +52,27 @@ export const generateTableHeaders = (
// TODO: Consider removing status column from selected hosts table because
// status info is not refreshed once a target has been selected
{
title: "Status",
Header: "Status",
disableSortBy: true,
accessor: "status",
Cell: (cellProps) => <StatusIndicator value={cellProps.cell.value} />,
},
{
title: "Private IP address",
Header: "Private IP address",
accessor: "primary_ip",
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
},
{
title: "MAC address",
Header: "MAC address",
accessor: "primary_mac",
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
},
{
title: "OS",
Header: "OS",
accessor: "os_version",
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
},
{
title: "Osquery",
Header: "Osquery",
accessor: "osquery_version",
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,

View File

@ -24,7 +24,7 @@ function useActionCallback(
callbackFn: (targetIds: number[]) => void | undefined
) {
return useCallback(
(targetIds) => {
(targetIds: any) => {
callbackFn(targetIds);
},
[callbackFn]

View File

@ -316,7 +316,7 @@ const DataTable = ({
}, [toggleAllPagesSelected, toggleAllRowsSelected]);
const onSelectRowClick = useCallback(
(row) => {
(row: any) => {
if (disableMultiRowSelect) {
row.toggleRowSelected();
onSelectSingleRow && onSelectSingleRow(row);

View File

@ -3,9 +3,7 @@ import { FilterProps, TableInstance } from "react-table";
import SearchField from "components/forms/fields/SearchField";
const DefaultColumnFilter = ({
column,
}: FilterProps<TableInstance>): JSX.Element => {
const DefaultColumnFilter = <T extends object>({ column }: FilterProps<T>) => {
const { setFilter } = column;
// Remove last_fetched filter per design as it is confusing to filter by a non-displayed date-string

View File

@ -1,4 +1,4 @@
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
// used for babel polyfills.
import "core-js/stable";
@ -11,6 +11,6 @@ import "./index.scss";
if (typeof window !== "undefined") {
const { document } = global;
const app = document.getElementById("app");
ReactDOM.render(routes, app);
const root = createRoot(app);
root.render(routes);
}

View File

@ -33,7 +33,7 @@ export interface ICampaign {
};
id: number;
query_id: number;
query_results: unknown[];
query_results: Record<string, unknown>[];
status: string;
totals: {
count: number;

View File

@ -1,4 +1,8 @@
import { Column } from "react-table";
/** This file contains reusable types that can be used when creating new tables
* using react-table.
*/
import { CellProps, Column, HeaderProps } from "react-table";
export type IDataColumn = Column & {
title?: string;
@ -8,3 +12,42 @@ export type IDataColumn = Column & {
preFilteredRows?: any;
setFilter?: any;
};
/**
* Interface for a column configuration in a table. This is a wrapper around the `Column` type from
* `react-table`.
* @param T - The type of the data in the column
* @example
* ```ts
* type IHostTableColumnConfig = IColumnConfig<IHost>;
*/
export type IColumnConfig<T extends object> = Column<T>;
/**
* Interface for a cell with a string value. This is a wrapper around the
* `CellProps` type from `react-table`.
*/
export type IStringCellProps<T extends object> = CellProps<T, string>;
/**
* Interface for a cell with a number value. This is a wrapper around the
* `CellProps` type from `react-table`.
*/
export type INumberCellProps<T extends object> = CellProps<T, number>;
/**
*
*/
export type IBoolCellProps<T extends object> = CellProps<T, boolean>;
/**
* Interface for a cell with a value that is an array of objects. This is a
* wrapper around the `HeaderProps` type from `react-table`.
*/
export type IHeaderProps<T extends object> = HeaderProps<T>;
/**
* The typing for web socket data is loose as we are getting the data is
* not typed and is not guaranteed to have the same shape every time.
* */
export type IWebSocketData = Record<string, unknown>;

View File

@ -246,6 +246,11 @@ export interface IHostEncrpytionKeyResponse {
};
}
export interface IHostIssues {
total_issues_count: number;
failing_policies_count: number;
}
export interface IHost {
created_at: string;
updated_at: string;
@ -293,10 +298,7 @@ export interface IHost {
labels: ILabel[];
packs: IPack[];
software: ISoftware[];
issues: {
total_issues_count: number;
failing_policies_count: number;
};
issues: IHostIssues;
status: HostStatus;
display_text: string;
display_name: string;
@ -310,6 +312,7 @@ export interface IHost {
geolocation?: IGeoLocation;
batteries?: IBattery[];
disk_encryption_enabled?: boolean;
device_mapping: IDeviceUser[] | null;
}
/*

View File

@ -486,7 +486,7 @@ const TAGGED_TEMPLATES = {
const activityType = lowerCase(activity.type).replace(" saved", "");
return !entityName ? (
return !entityName || typeof entityName !== "string" ? (
`${activityType}.`
) : (
<>

View File

@ -4,7 +4,7 @@
*/
import React from "react";
import { Column } from "react-table";
import { CellProps, Column, HeaderProps } from "react-table";
import { InjectedRouter } from "react-router";
import { buildQueryStringFromParams } from "utilities/url";
@ -22,40 +22,21 @@ import LinkCell from "components/TableContainer/DataTable/LinkCell";
import VulnerabilitiesCell from "pages/SoftwarePage/components/VulnerabilitiesCell";
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
import {
INumberCellProps,
IStringCellProps,
} from "interfaces/datatable_config";
interface ICellProps {
cell: {
value: number | string | ISoftwareVulnerability[];
};
row: {
original: IOperatingSystemVersion;
};
}
type ITableColumnConfig = Column<IOperatingSystemVersion>;
interface IStringCellProps extends ICellProps {
cell: {
value: string;
};
}
interface INumberCellProps extends ICellProps {
cell: {
value: number;
};
}
interface IVulnCellProps extends ICellProps {
cell: {
value: ISoftwareVulnerability[];
};
}
interface IHeaderProps {
column: {
title: string;
isSortedDesc: boolean;
};
}
type INameCellProps = IStringCellProps<IOperatingSystemVersion>;
type IVersionCellProps = IStringCellProps<IOperatingSystemVersion>;
type IVulnCellProps = CellProps<
IOperatingSystemVersion,
ISoftwareVulnerability[]
>;
type IHostCountCellProps = INumberCellProps<IOperatingSystemVersion>;
type IHostHeaderProps = HeaderProps<IOperatingSystemVersion>;
interface IOSTableConfigOptions {
includeName?: boolean;
@ -67,12 +48,12 @@ const generateDefaultTableHeaders = (
teamId?: number,
router?: InjectedRouter,
configOptions?: IOSTableConfigOptions
): Column[] => [
): ITableColumnConfig[] => [
{
Header: "Name",
disableSortBy: true,
accessor: "name_only",
Cell: (cellProps: IStringCellProps) => {
Cell: (cellProps: INameCellProps) => {
if (!configOptions?.includeIcon) {
return (
<TextCell
@ -116,7 +97,7 @@ const generateDefaultTableHeaders = (
Header: "Version",
disableSortBy: true,
accessor: "version",
Cell: (cellProps: IStringCellProps) => (
Cell: (cellProps: IVersionCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
@ -124,7 +105,7 @@ const generateDefaultTableHeaders = (
Header: "Vulnerabilities",
disableSortBy: true,
accessor: "vulnerabilities",
Cell: (cellProps: IVulnCellProps): JSX.Element => {
Cell: (cellProps: IVulnCellProps) => {
const platform = cellProps.row.original.platform;
if (platform !== "darwin" && platform !== "windows") {
return <TextCell value="Not supported" greyed />;
@ -133,16 +114,17 @@ const generateDefaultTableHeaders = (
},
},
{
Header: (cellProps: IHeaderProps): JSX.Element => (
Header: (cellProps: IHostHeaderProps) => (
<HeaderCell
value="Hosts"
disableSortBy={false}
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
disableSortBy: false,
accessor: "hosts_count",
Cell: (cellProps: INumberCellProps): JSX.Element => {
Cell: (cellProps: IHostCountCellProps) => {
const { hosts_count, os_version_id } = cellProps.row.original;
return (
<span className="hosts-cell__wrapper">
@ -168,7 +150,7 @@ const generateTableHeaders = (
teamId?: number,
router?: InjectedRouter,
configOptions?: IOSTableConfigOptions
): Column[] => {
): ITableColumnConfig[] => {
let tableConfig = generateDefaultTableHeaders(teamId, router, configOptions);
if (!configOptions?.includeName) {

View File

@ -1,5 +1,5 @@
import React from "react";
import { Column } from "react-table";
import { CellProps, Column } from "react-table";
import { InjectedRouter } from "react-router";
import {
@ -8,55 +8,33 @@ import {
formatSoftwareType,
} from "interfaces/software";
import PATHS from "router/paths";
import { buildQueryStringFromParams } from "utilities/url";
import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import TextCell from "components/TableContainer/DataTable/TextCell";
import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import VersionCell from "../../components/VersionCell";
import VulnerabilitiesCell from "../../components/VulnerabilitiesCell";
import SoftwareIcon from "../../components/icons/SoftwareIcon";
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
interface ICellProps {
cell: {
value: number | string | ISoftwareTitleVersion[];
};
row: {
original: ISoftwareTitle;
};
}
interface IStringCellProps extends ICellProps {
cell: {
value: string;
};
}
interface IVersionCellProps extends ICellProps {
cell: {
value: ISoftwareTitleVersion[];
};
}
type ISoftwareTitlesTableConfig = Column<ISoftwareTitle>;
type ITableStringCellProps = IStringCellProps<ISoftwareTitle>;
type IVersionsCellProps = CellProps<ISoftwareTitle, ISoftwareTitle["versions"]>;
type IVulnerabilitiesCellProps = IVersionsCellProps;
type IHostCountCellProps = CellProps<
ISoftwareTitle,
ISoftwareTitle["hosts_count"]
>;
type IViewAllHostsLinkProps = CellProps<ISoftwareTitle>;
interface INumberCellProps extends ICellProps {
cell: {
value: number;
};
}
interface IVulnCellProps extends ICellProps {
cell: {
value: ISoftwareTitleVersion[];
};
}
interface IHeaderProps {
column: {
title: string;
isSortedDesc: boolean;
};
}
type ITableHeaderProps = IHeaderProps<ISoftwareTitle>;
const getVulnerabilities = (versions: ISoftwareTitleVersion[]) => {
if (!versions) {
@ -77,19 +55,15 @@ const getVulnerabilities = (versions: ISoftwareTitleVersion[]) => {
const generateTableHeaders = (
router: InjectedRouter,
teamId?: number
): Column[] => {
const softwareTableHeaders = [
): ISoftwareTitlesTableConfig[] => {
const softwareTableHeaders: ISoftwareTitlesTableConfig[] = [
{
title: "Name",
Header: (cellProps: IHeaderProps): JSX.Element => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
Header: (cellProps: ITableHeaderProps) => (
<HeaderCell value="Name" isSortedDesc={cellProps.column.isSortedDesc} />
),
disableSortBy: false,
accessor: "name",
Cell: (cellProps: IStringCellProps): JSX.Element => {
Cell: (cellProps: ITableStringCellProps) => {
const { id, name, source } = cellProps.row.original;
const teamQueryParam = buildQueryStringFromParams({ team_id: teamId });
@ -120,20 +94,18 @@ const generateTableHeaders = (
sortType: "caseInsensitive",
},
{
title: "Version",
Header: "Version",
disableSortBy: true,
accessor: "versions",
Cell: (cellProps: IVersionCellProps): JSX.Element => (
Cell: (cellProps: IVersionsCellProps) => (
<VersionCell versions={cellProps.cell.value} />
),
},
{
title: "Type",
Header: "Type",
disableSortBy: true,
accessor: "source",
Cell: (cellProps: ICellProps): JSX.Element => (
Cell: (cellProps: ITableStringCellProps) => (
<TextCell value={formatSoftwareType(cellProps.row.original)} />
),
},
@ -144,11 +116,9 @@ const generateTableHeaders = (
// With the versions data, we can sum up the vulnerabilities to get the
// total number of vulnerabilities for the software title
{
title: "Vulnerabilities",
Header: "Vulnerabilities",
disableSortBy: true,
accessor: "vulnerabilities",
Cell: (cellProps: IVulnCellProps): JSX.Element => {
Cell: (cellProps: IVulnerabilitiesCellProps) => {
const vulnerabilities = getVulnerabilities(
cellProps.row.original.versions ?? []
);
@ -156,26 +126,23 @@ const generateTableHeaders = (
},
},
{
title: "Hosts",
Header: (cellProps: IHeaderProps): JSX.Element => (
Header: (cellProps: ITableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Hosts"
disableSortBy={false}
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
disableSortBy: false,
accessor: "hosts_count",
Cell: (cellProps: INumberCellProps): JSX.Element => (
Cell: (cellProps: IHostCountCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "",
Header: "",
accessor: "linkToFilteredHosts",
disableSortBy: true,
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: IViewAllHostsLinkProps) => {
return (
<ViewAllHostsLink
queryParams={{

View File

@ -1,5 +1,5 @@
import React from "react";
import { Column } from "react-table";
import { CellProps, Column } from "react-table";
import { InjectedRouter } from "react-router";
import { buildQueryStringFromParams } from "utilities/url";
@ -8,6 +8,7 @@ import {
ISoftwareVersion,
ISoftwareVulnerability,
} from "interfaces/software";
import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
import PATHS from "router/paths";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
@ -19,60 +20,29 @@ import SoftwareIcon from "../../components/icons/SoftwareIcon";
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
interface ICellProps {
cell: {
value: number | string | ISoftwareVulnerability[];
};
row: {
original: ISoftwareVersion;
};
}
interface IStringCellProps extends ICellProps {
cell: {
value: string;
};
}
interface IVersionCellProps extends ICellProps {
cell: {
value: string;
};
}
type ISoftwareVersionsTableConfig = Column<ISoftwareVersion>;
type ITableStringCellProps = IStringCellProps<ISoftwareVersion>;
type IVulnerabilitiesCellProps = CellProps<
ISoftwareVersion,
ISoftwareVulnerability[] | null
>;
type IHostCountCellProps = CellProps<ISoftwareVersion, number | undefined>;
interface INumberCellProps extends ICellProps {
cell: {
value: number;
};
}
interface IVulnCellProps extends ICellProps {
cell: {
value: ISoftwareVulnerability[];
};
}
interface IHeaderProps {
column: {
title: string;
isSortedDesc: boolean;
};
}
type ITableHeaderProps = IHeaderProps<ISoftwareVersion>;
const generateTableHeaders = (
router: InjectedRouter,
teamId?: number
): Column[] => {
const softwareTableHeaders = [
): ISoftwareVersionsTableConfig[] => {
const softwareTableHeaders: ISoftwareVersionsTableConfig[] = [
{
title: "Name",
Header: (cellProps: IHeaderProps): JSX.Element => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
Header: (cellProps: ITableHeaderProps) => (
<HeaderCell value="Name" isSortedDesc={cellProps.column.isSortedDesc} />
),
disableSortBy: false,
accessor: "name",
Cell: (cellProps: IStringCellProps): JSX.Element => {
Cell: (cellProps: ITableStringCellProps) => {
const { id, name, source } = cellProps.row.original;
const teamQueryParam = buildQueryStringFromParams({
@ -105,53 +75,47 @@ const generateTableHeaders = (
sortType: "caseInsensitive",
},
{
title: "Version",
Header: "Version",
disableSortBy: true,
accessor: "version",
Cell: (cellProps: IVersionCellProps): JSX.Element => (
Cell: (cellProps: ITableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Type",
Header: "Type",
disableSortBy: true,
accessor: "source",
Cell: (cellProps: ICellProps): JSX.Element => (
Cell: (cellProps: ITableStringCellProps) => (
<TextCell value={formatSoftwareType(cellProps.row.original)} />
),
},
{
title: "Vulnerabilities",
Header: "Vulnerabilities",
disableSortBy: true,
accessor: "vulnerabilities",
Cell: (cellProps: IVulnCellProps): JSX.Element => (
Cell: (cellProps: IVulnerabilitiesCellProps) => (
<VulnerabilitiesCell vulnerabilities={cellProps.cell.value} />
),
},
{
title: "Hosts",
Header: (cellProps: IHeaderProps): JSX.Element => (
Header: (cellProps: ITableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Hosts"
disableSortBy={false}
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
disableSortBy: false,
accessor: "hosts_count",
Cell: (cellProps: INumberCellProps): JSX.Element => (
Cell: (cellProps: IHostCountCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "",
Header: "",
accessor: "linkToFilteredHosts",
disableSortBy: true,
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: ITableStringCellProps) => {
return (
<>
{cellProps.row.original && (

View File

@ -33,7 +33,7 @@ const SoftwareVulnOSVersions = ({
);
const onSelectSingleRow = useCallback(
({ original: { os_version_id } }) => {
({ original: { os_version_id } }: any) => {
if (!os_version_id) {
return;
}

View File

@ -11,30 +11,27 @@ import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
import TextCell from "components/TableContainer/DataTable/TextCell";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import { InjectedRouter } from "react-router";
import {
INumberCellProps,
IStringCellProps,
} from "interfaces/datatable_config";
interface ICellProps {
row: {
original: IVulnerabilityOSVersion;
};
}
type ISWVulnTableColumnConfig = Column<IVulnerabilityOSVersion>;
interface INumberCellProps extends ICellProps {
cell: {
value: number;
};
}
type ITableStringCellProps = IStringCellProps<IVulnerabilityOSVersion>;
type ITableNumberCellProps = INumberCellProps<IVulnerabilityOSVersion>;
const generateColumnConfigs = (
isPremiumTier: boolean,
router: InjectedRouter,
teamIdForApi?: number
): Column[] => {
const configs = [
): ISWVulnTableColumnConfig[] => {
const configs: ISWVulnTableColumnConfig[] = [
{
Header: "Name",
disableSortBy: true,
accessor: "name_only",
Cell: ({ row }: ICellProps) => {
Cell: ({ row }: ITableStringCellProps) => {
const { name, os_version_id, platform } = row.original;
const endpoint = PATHS.SOFTWARE_OS_DETAILS(os_version_id);
// since No Teams not supported on this page, falsiness of 0 is okay
@ -65,7 +62,9 @@ const generateColumnConfigs = (
Header: "Version",
disableSortBy: true,
accessor: "version",
Cell: ({ cell }: INumberCellProps) => <TextCell value={cell.value} />,
Cell: ({ cell }: ITableStringCellProps) => (
<TextCell value={cell.value} />
),
},
{
Header: () => (
@ -75,13 +74,15 @@ const generateColumnConfigs = (
),
disableSortBy: true,
accessor: "resolved_in_version",
Cell: ({ cell }: INumberCellProps) => <TextCell value={cell.value} />,
Cell: ({ cell }: ITableStringCellProps) => (
<TextCell value={cell.value} />
),
},
{
Header: "Hosts",
disableSortBy: true,
accessor: "hosts_count",
Cell: ({ row }: ICellProps) => {
Cell: ({ row }: ITableNumberCellProps) => {
const { hosts_count, os_version_id } = row.original;
return (
<>

View File

@ -35,7 +35,7 @@ const SoftwareVulnSoftwareVersions = ({
);
const onSelectSingleRow = useCallback(
({ original: { id: software_version_id } }) => {
({ original: { id: software_version_id } }: any) => {
if (!software_version_id) {
return;
}

View File

@ -1,7 +1,8 @@
import React from "react";
import { Column } from "react-table";
import { CellProps, Column } from "react-table";
import { IVulnerabilitySoftware } from "interfaces/software";
import { IStringCellProps } from "interfaces/datatable_config";
import PATHS from "router/paths";
@ -11,32 +12,25 @@ import ViewAllHostsLink from "components/ViewAllHostsLink";
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
import { InjectedRouter } from "react-router";
interface ICellProps {
cell: {
value: number | string | IVulnerabilitySoftware;
};
row: {
original: IVulnerabilitySoftware;
};
}
type SwVulnTableColumnConfig = Column<IVulnerabilitySoftware>;
interface IStringCellProps extends ICellProps {
cell: {
value: string;
};
}
type ITableStringCellProps = IStringCellProps<IVulnerabilitySoftware>;
type IHostCountCellProps = CellProps<
IVulnerabilitySoftware,
number | undefined
>;
const generateColumnConfigs = (
isPremiumTier: boolean,
router: InjectedRouter,
teamIdForApi?: number
): Column[] => {
const configs = [
): SwVulnTableColumnConfig[] => {
const configs: SwVulnTableColumnConfig[] = [
{
Header: "Name",
disableSortBy: true,
accessor: "name",
Cell: ({ row }: ICellProps) => {
Cell: ({ row }: ITableStringCellProps) => {
const { name, id } = row.original;
const endpoint = PATHS.SOFTWARE_VERSION_DETAILS(id.toString());
// since No Teams not supported on this page, falsiness of 0 is okay
@ -67,7 +61,9 @@ const generateColumnConfigs = (
Header: "Version",
disableSortBy: true,
accessor: "version",
Cell: ({ cell }: IStringCellProps) => <TextCell value={cell.value} />,
Cell: ({ cell }: ITableStringCellProps) => (
<TextCell value={cell.value} />
),
},
{
Header: () => (
@ -77,13 +73,15 @@ const generateColumnConfigs = (
),
disableSortBy: true,
accessor: "resolved_in_version",
Cell: ({ cell }: IStringCellProps) => <TextCell value={cell.value} />,
Cell: ({ cell }: ITableStringCellProps) => (
<TextCell value={cell.value} />
),
},
{
Header: "Hosts",
disableSortBy: true,
accessor: "hosts_count",
Cell: ({ row }: ICellProps) => {
Cell: ({ row }: IHostCountCellProps) => {
const { hosts_count, id } = row.original;
return (
<>

View File

@ -8,7 +8,7 @@ import ReactTooltip from "react-tooltip";
const baseClass = "version-cell";
const generateText = (versions: ISoftwareTitleVersion[]) => {
const generateText = (versions: ISoftwareTitleVersion[] | null) => {
if (!versions) {
return <TextCell value="Unavailable" greyed />;
}
@ -40,7 +40,7 @@ const generateTooltip = (
};
interface IVersionCellProps {
versions: ISoftwareTitleVersion[];
versions: ISoftwareTitleVersion[] | null;
}
const VersionCell = ({ versions }: IVersionCellProps) => {

View File

@ -27,7 +27,7 @@ const AddUsersModal = ({
const [selectedUsers, setSelectedUsers] = useState([]);
const onChangeDropdown = useCallback(
(values) => {
(values: any) => {
setSelectedUsers(values);
},
[setSelectedUsers]

View File

@ -70,7 +70,7 @@ const AutocompleteDropdown = ({
// We disable any filtering client side as the server filters the results
// for us.
const filterOptions = useCallback((options) => {
const filterOptions = useCallback((options: any) => {
return options;
}, []);

View File

@ -41,7 +41,7 @@ const CreateTeamModal = ({
);
const onFormSubmit = useCallback(
(evt) => {
(evt: any) => {
evt.preventDefault();
onSubmit({
name,

View File

@ -2,7 +2,7 @@
// disable this rule as it was throwing an error in Header and Cell component
// definitions for the selection row for some reason when we dont really need it.
import React from "react";
import { Column } from "react-table";
import { CellProps, Column } from "react-table";
import ReactTooltip from "react-tooltip";
import { IDeviceUser, IHost } from "interfaces/host";
@ -25,60 +25,30 @@ import {
hostTeamName,
} from "utilities/helpers";
import { COLORS } from "styles/var/colors";
import { IDataColumn } from "interfaces/datatable_config";
import {
IHeaderProps,
IStringCellProps,
INumberCellProps,
} from "interfaces/datatable_config";
import PATHS from "router/paths";
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
import getHostStatusTooltipText from "../helpers";
interface IGetToggleAllRowsSelectedProps {
checked: boolean;
indeterminate: boolean;
title: string;
onChange: () => void;
style: { cursor: string };
}
type IHostTableColumnConfig = Column<IHost> & {
// This is used to prevent these columns from being hidden. This will be
// used in EditColumnsModal to prevent these columns from being hidden.
disableHidden?: boolean;
// We add title in the column config to be able to use it in the EditColumnsModal
// as well
title?: string;
};
interface IRow {
original: IHost;
getToggleRowSelectedProps: () => IGetToggleAllRowsSelectedProps;
toggleRowSelected: () => void;
}
interface IHeaderProps {
column: {
title: string;
isSortedDesc: boolean;
};
getToggleAllRowsSelectedProps: () => IGetToggleAllRowsSelectedProps;
toggleAllRowsSelected: () => void;
rows: IRow[];
}
interface ICellProps {
cell: {
value: string;
};
row: IRow;
}
interface INumberCellProps {
cell: {
value: number;
};
row: {
original: IHost;
getToggleRowSelectedProps: () => IGetToggleAllRowsSelectedProps;
toggleRowSelected: () => void;
};
}
interface IDeviceUserCellProps {
cell: {
value: IDeviceUser[];
};
row: {
original: IHost;
};
}
type IHostTableHeaderProps = IHeaderProps<IHost>;
type IHostTableStringCellProps = IStringCellProps<IHost>;
type IHostTableNumberCellProps = INumberCellProps<IHost>;
type ISelectionCellProps = CellProps<IHost>;
type IIssuesCellProps = CellProps<IHost, IHost["issues"]>;
type IDeviceUserCellProps = CellProps<IHost, IHost["device_mapping"]>;
const condenseDeviceUsers = (users: IDeviceUser[]): string[] => {
if (!users?.length) {
@ -117,13 +87,13 @@ const lastSeenTime = (status: string, seenTime: string): string => {
return "Online";
};
const allHostTableHeaders: IDataColumn[] = [
const allHostTableHeaders: IHostTableColumnConfig[] = [
// We are using React Table useRowSelect functionality for the selection header.
// More information on its API can be found here
// https://react-table.tanstack.com/docs/api/useRowSelect
{
id: "selection",
Header: (cellProps: IHeaderProps): JSX.Element => {
Header: (cellProps: IHostTableHeaderProps) => {
const props = cellProps.getToggleAllRowsSelectedProps();
const checkboxProps = {
value: props.checked,
@ -132,7 +102,7 @@ const allHostTableHeaders: IDataColumn[] = [
};
return <Checkbox {...checkboxProps} />;
},
Cell: (cellProps: ICellProps): JSX.Element => {
Cell: (cellProps: ISelectionCellProps) => {
const props = cellProps.row.getToggleRowSelectedProps();
const checkboxProps = {
value: props.checked,
@ -143,15 +113,11 @@ const allHostTableHeaders: IDataColumn[] = [
disableHidden: true,
},
{
title: "Host",
Header: (cellProps: IHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell value="Hosts" isSortedDesc={cellProps.column.isSortedDesc} />
),
accessor: "display_name",
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: IHostTableStringCellProps) => {
if (
// if the host is pending, we want to disable the link to host details
cellProps.row.original.mdm.enrollment_status === "Pending" &&
@ -202,49 +168,50 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Hostname",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Hostname"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "hostname",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Computer name",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Computer name"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "computer_name",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Team",
Header: (cellProps: IHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell value="Team" isSortedDesc={cellProps.column.isSortedDesc} />
),
accessor: "team_name",
Cell: (cellProps: ICellProps) => (
Cell: (cellProps) => (
<TextCell value={cellProps.cell.value} formatter={hostTeamName} />
),
},
{
title: "Status",
Header: (cellProps: IHeaderProps): JSX.Element => {
Header: (cellProps: IHostTableHeaderProps) => {
const titleWithToolTip = (
<TooltipWrapper
tipContent={
<>
Online hosts will respond to a live query. Offline hosts wont
respond to a live query because they may be shut down, asleep, or
not connected to the internet.
Online hosts will respond to a live query. Offline hosts
won&apos;t respond to a live query because they may be shut down,
asleep, or not connected to the internet.
</>
}
className="status-header"
@ -261,7 +228,7 @@ const allHostTableHeaders: IDataColumn[] = [
},
disableSortBy: true,
accessor: "status",
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: IHostTableStringCellProps) => {
const value = cellProps.cell.value;
const tooltip = {
tooltipText: getHostStatusTooltipText(value),
@ -274,7 +241,7 @@ const allHostTableHeaders: IDataColumn[] = [
Header: "Issues",
disableSortBy: true,
accessor: "issues",
Cell: (cellProps: ICellProps) => (
Cell: (cellProps: IIssuesCellProps) => (
<IssueCell
issues={cellProps.row.original.issues}
rowId={cellProps.row.original.id}
@ -283,14 +250,14 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Disk space available",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Disk space available"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "gigs_disk_space_available",
Cell: (cellProps: INumberCellProps) => {
Cell: (cellProps: IHostTableNumberCellProps) => {
const {
id,
platform,
@ -312,32 +279,36 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Operating system",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Operating system"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "os_version",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Osquery",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Osquery"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "osquery_version",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Used by",
Header: "Used by",
disableSortBy: true,
accessor: "device_mapping",
Cell: (cellProps: IDeviceUserCellProps): JSX.Element => {
Cell: (cellProps: IDeviceUserCellProps) => {
const numUsers = cellProps.cell.value?.length || 0;
const users = condenseDeviceUsers(cellProps.cell.value || []);
if (users.length) {
@ -372,18 +343,20 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Private IP address",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Private IP address"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "primary_ip",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "MDM status",
Header: (): JSX.Element => {
Header: () => {
const titleWithToolTip = (
<TooltipWrapper
tipContent={
@ -400,13 +373,13 @@ const allHostTableHeaders: IDataColumn[] = [
return <HeaderCell value={titleWithToolTip} disableSortBy />;
},
disableSortBy: true,
accessor: "mdm.enrollment_status",
accessor: (originalRow) => originalRow.mdm.enrollment_status,
id: "mdm_enrollment_status",
Cell: HostMdmStatusCell,
},
{
title: "MDM server URL",
Header: (): JSX.Element => {
Header: () => {
const titleWithToolTip = (
<TooltipWrapper
tipContent={
@ -423,9 +396,9 @@ const allHostTableHeaders: IDataColumn[] = [
return <HeaderCell value={titleWithToolTip} disableSortBy />;
},
disableSortBy: true,
accessor: "mdm.server_url",
accessor: (originalRow) => originalRow.mdm.server_url,
id: "mdm_server_url",
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: IHostTableStringCellProps) => {
if (cellProps.row.original.platform === "chrome") {
return NotSupported;
}
@ -437,7 +410,7 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Public IP address",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={
<TooltipWrapper tipContent="The IP address the host uses to connect to Fleet.">
@ -448,7 +421,7 @@ const allHostTableHeaders: IDataColumn[] = [
/>
),
accessor: "public_ip",
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: IHostTableStringCellProps) => {
return (
<TextCell value={cellProps.cell.value ?? DEFAULT_EMPTY_CELL_VALUE} />
);
@ -456,7 +429,7 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Last fetched",
Header: (cellProps: IHeaderProps): JSX.Element => {
Header: (cellProps: IHostTableHeaderProps) => {
const titleWithToolTip = (
<TooltipWrapper
tipContent={
@ -477,7 +450,7 @@ const allHostTableHeaders: IDataColumn[] = [
);
},
accessor: "detail_updated_at",
Cell: (cellProps: ICellProps) => (
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell
value={{ timeString: cellProps.cell.value }}
formatter={HumanTimeDiffWithFleetLaunchCutoff}
@ -486,7 +459,7 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "Last seen",
Header: (cellProps: IHeaderProps): JSX.Element => {
Header: (cellProps: IHostTableHeaderProps) => {
const titleWithToolTip = (
<TooltipWrapper
tipContent={
@ -507,7 +480,7 @@ const allHostTableHeaders: IDataColumn[] = [
);
},
accessor: "seen_time",
Cell: (cellProps: ICellProps) => (
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell
value={{ timeString: cellProps.cell.value }}
formatter={HumanTimeDiffWithFleetLaunchCutoff}
@ -516,27 +489,24 @@ const allHostTableHeaders: IDataColumn[] = [
},
{
title: "UUID",
Header: (cellProps: IHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell value="UUID" isSortedDesc={cellProps.column.isSortedDesc} />
),
accessor: "uuid",
Cell: (cellProps: ICellProps) => (
Cell: (cellProps: IHostTableStringCellProps) => (
<TooltipTruncatedTextCell value={cellProps.cell.value} />
),
},
{
title: "Last restarted",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Last restarted"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "last_restarted_at",
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: IHostTableStringCellProps) => {
const { platform, last_restarted_at } = cellProps.row.original;
if (platform === "chrome") {
@ -557,53 +527,58 @@ const allHostTableHeaders: IDataColumn[] = [
Header: "CPU",
disableSortBy: true,
accessor: "cpu_type",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "RAM",
Header: (cellProps: IHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell value="RAM" isSortedDesc={cellProps.column.isSortedDesc} />
),
accessor: "memory",
Cell: (cellProps: ICellProps) => (
Cell: (cellProps: IHostTableNumberCellProps) => (
<TextCell value={cellProps.cell.value} formatter={humanHostMemory} />
),
},
{
title: "MAC address",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="MAC address"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "primary_mac",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Serial number",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Serial number"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "hardware_serial",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
{
title: "Hardware model",
Header: (cellProps: IHeaderProps) => (
Header: (cellProps: IHostTableHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
value="Hardware model"
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
accessor: "hardware_model",
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
},
];
@ -635,9 +610,9 @@ const generateAvailableTableHeaders = ({
}: {
isFreeTier: boolean | undefined;
isOnlyObserver: boolean | undefined;
}): IDataColumn[] => {
}): IHostTableColumnConfig[] => {
return allHostTableHeaders.reduce(
(columns: Column[], currentColumn: Column) => {
(columns: Column<IHost>[], currentColumn: Column<IHost>) => {
// skip over column headers that are not shown in free observer tier
if (isFreeTier && isOnlyObserver) {
if (
@ -650,8 +625,8 @@ const generateAvailableTableHeaders = ({
} else if (isFreeTier) {
if (
currentColumn.accessor === "team_name" ||
currentColumn.accessor === "mdm_server_url" ||
currentColumn.accessor === "mdm_enrollment_status"
currentColumn.id === "mdm_server_url" ||
currentColumn.id === "mdm_enrollment_status"
) {
return columns;
}
@ -681,7 +656,7 @@ const generateVisibleTableColumns = ({
hiddenColumns: string[];
isFreeTier: boolean | undefined;
isOnlyObserver: boolean | undefined;
}): IDataColumn[] => {
}): IHostTableColumnConfig[] => {
// remove columns set as hidden by the user.
return generateAvailableTableHeaders({ isFreeTier, isOnlyObserver }).filter(
(column) => {

View File

@ -164,7 +164,7 @@ const HQRTable = ({
customControl={renderTableButtons}
setExportRows={setFilteredResults}
emptyComponent={() => null}
defaultSortHeader={columnConfigs[0].title}
defaultSortHeader={columnConfigs[0].id}
defaultSortDirection="asc"
/>
)}

View File

@ -1,60 +1,53 @@
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import {
IHeaderProps,
IStringCellProps,
IWebSocketData,
} from "interfaces/datatable_config";
import React from "react";
import {
CellProps,
ColumnInstance,
ColumnInterface,
HeaderProps,
TableInstance,
} from "react-table";
import { CellProps, Column } from "react-table";
import {
getUniqueColumnNamesFromRows,
humanHostLastSeen,
internallyTruncateText,
} from "utilities/helpers";
type IHeaderProps = HeaderProps<TableInstance> & {
column: ColumnInstance & IDataColumn;
};
type IHQRTTableColumn = Column<IWebSocketData>;
type ITableHeaderProps = IHeaderProps<IWebSocketData>;
type ITableStringCellProps = CellProps<IWebSocketData, string | unknown>;
type ICellProps = CellProps<TableInstance>;
interface IDataColumn extends ColumnInterface {
title?: string;
accessor: string;
}
const generateColumnConfigs = (rows: Record<string, string>[]) =>
const generateColumnConfigs = (rows: IWebSocketData[]): IHQRTTableColumn[] =>
// casting necessary because of loose typing of below method
// see note there for more details
(getUniqueColumnNamesFromRows(rows) as string[]).map((colName) => {
getUniqueColumnNamesFromRows(rows).map<IHQRTTableColumn>((colName) => {
return {
id: colName,
title: colName,
Header: (headerProps: IHeaderProps) => (
Header: (headerProps: ITableHeaderProps) => (
<HeaderCell
value={
// Sentence case last fetched
headerProps.column.title === "last_fetched"
headerProps.column.id === "last_fetched"
? "Last fetched"
: headerProps.column.title || headerProps.column.id
: headerProps.column.id || headerProps.column.id
}
isSortedDesc={headerProps.column.isSortedDesc}
/>
),
accessor: colName,
Cell: (cellProps: ICellProps) => {
Cell: (cellProps: ITableStringCellProps) => {
if (typeof cellProps?.cell?.value !== "string") return null;
// Sorts chronologically by date, but UI displays readable last fetched
if (cellProps.column.id === "last_fetched") {
return humanHostLastSeen(cellProps?.cell?.value);
return <>{humanHostLastSeen(cellProps?.cell?.value)}</>;
}
// truncate columns longer than 300 characters
const val = cellProps?.cell?.value;
return !!val?.length && val.length > 300
? internallyTruncateText(val)
: val ?? null;
: <>val</> ?? null;
},
Filter: DefaultColumnFilter, // Component hides filter for last_fetched
filterType: "text",

View File

@ -48,7 +48,7 @@ const PacksTable = ({
}, [packs, searchString, setFilteredPacks]);
const onQueryChange = useCallback(
(queryData) => {
(queryData: any) => {
const { searchQuery } = queryData;
setSearchString(searchQuery);
},

View File

@ -354,7 +354,7 @@ const ManageQueriesPage = ({
};
const onSaveQueryAutomations = useCallback(
async (newAutomatedQueryIds) => {
async (newAutomatedQueryIds: any) => {
setIsUpdatingAutomations(true);
// Query ids added to turn on automations

View File

@ -14,13 +14,13 @@ describe("QueryReport", () => {
{
host_id: 1,
host_name: "host1",
last_fetched: "2021-01-01",
last_fetched: "2020-01-01",
columns: { col1: "value1", col2: "value2" },
},
{
host_id: 2,
host_name: "host2",
last_fetched: "2021-01-01",
last_fetched: "2020-01-01",
columns: { col1: "value3", col2: "value4" },
},
],

View File

@ -3,14 +3,7 @@
// definitions for the selection row for some reason when we dont really need it.
import React from "react";
import {
CellProps,
Column,
ColumnInstance,
ColumnInterface,
HeaderProps,
TableInstance,
} from "react-table";
import { CellProps, Column } from "react-table";
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
@ -20,19 +13,13 @@ import {
humanHostLastSeen,
internallyTruncateText,
} from "utilities/helpers";
import { IHeaderProps, IWebSocketData } from "interfaces/datatable_config";
type IHeaderProps = HeaderProps<TableInstance> & {
column: ColumnInstance & IDataColumn;
};
type IQueryReportTableColumnConfig = Column<IWebSocketData>;
type ITableHeaderProps = IHeaderProps<IWebSocketData>;
type ITableCellProps = CellProps<IWebSocketData, string | unknown>;
type ICellProps = CellProps<TableInstance>;
interface IDataColumn extends ColumnInterface {
title?: string;
accessor: string;
}
const _unshiftHostname = (headers: IDataColumn[]) => {
const _unshiftHostname = (headers: IQueryReportTableColumnConfig[]) => {
const newHeaders = [...headers];
const displayNameIndex = headers.findIndex(
(h) => h.id === "host_display_name"
@ -41,7 +28,7 @@ const _unshiftHostname = (headers: IDataColumn[]) => {
// remove hostname header from headers
const [displayNameHeader] = newHeaders.splice(displayNameIndex, 1);
// reformat title and insert at start of headers array
newHeaders.unshift({ ...displayNameHeader, title: "Host" });
newHeaders.unshift({ ...displayNameHeader, id: "Host" });
}
// TODO: Remove after v5 when host_hostname is removed rom API response.
const hostNameIndex = headers.findIndex((h) => h.id === "host_hostname");
@ -52,45 +39,50 @@ const _unshiftHostname = (headers: IDataColumn[]) => {
return newHeaders;
};
const generateReportColumnConfigsFromResults = (results: any[]): Column[] => {
const generateReportColumnConfigsFromResults = (
results: IWebSocketData[]
): IQueryReportTableColumnConfig[] => {
/* Results include an array of objects, each representing a table row
Each key value pair in an object represents a column name and value
To create headers, use JS set to create an array of all unique column names */
const uniqueColumnNames = getUniqueColumnNamesFromRows(results);
const columnConfigs = uniqueColumnNames.map((key) => {
return {
id: key as string,
title: key as string,
Header: (headerProps: IHeaderProps) => (
<HeaderCell
value={
// Sentence case last fetched
headerProps.column.title === "last_fetched"
? "Last fetched"
: headerProps.column.title || headerProps.column.id
const columnConfigs = uniqueColumnNames.map<IQueryReportTableColumnConfig>(
(key) => {
return {
id: key,
Header: (headerProps: ITableHeaderProps) => (
<HeaderCell
value={
// Sentence case last fetched
headerProps.column.id === "last_fetched"
? "Last fetched"
: headerProps.column.id
}
isSortedDesc={headerProps.column.isSortedDesc}
/>
),
accessor: key,
Cell: (cellProps: ITableCellProps) => {
if (typeof cellProps.cell.value !== "string") return null;
// Sorts chronologically by date, but UI displays readable last fetched
if (cellProps.column.id === "last_fetched") {
return <>{humanHostLastSeen(cellProps?.cell?.value)}</>;
}
isSortedDesc={headerProps.column.isSortedDesc}
/>
),
accessor: key as string,
Cell: (cellProps: ICellProps) => {
// Sorts chronologically by date, but UI displays readable last fetched
if (cellProps.column.id === "last_fetched") {
return humanHostLastSeen(cellProps?.cell?.value);
}
// truncate columns longer than 300 characters
const val = cellProps?.cell?.value;
return !!val?.length && val.length > 300
? internallyTruncateText(val)
: val ?? null;
},
Filter: DefaultColumnFilter, // Component hides filter for last_fetched
filterType: "text",
disableSortBy: false,
sortType: "caseInsensitive",
};
});
// truncate columns longer than 300 characters
const val = cellProps?.cell?.value;
return !!val?.length && val.length > 300
? internallyTruncateText(val)
: <>{val}</> ?? null;
},
Filter: DefaultColumnFilter, // Component hides filter for last_fetched
filterType: "text",
disableSortBy: false,
sortType: "caseInsensitive",
};
}
);
return _unshiftHostname(columnConfigs);
};

View File

@ -10,7 +10,7 @@ import {
generateCSVFilename,
generateCSVQueryResults,
} from "utilities/generate_csv";
import { ICampaign } from "interfaces/campaign";
import { ICampaign, ICampaignError } from "interfaces/campaign";
import { ITarget } from "interfaces/target";
import Button from "components/buttons/Button";
@ -67,7 +67,9 @@ const QueryResults = ({
const [resultsColumnConfigs, setResultsColumnConfigs] = useState<Column[]>(
[]
);
const [errorColumnConfigs, setErrorColumnConfigs] = useState<Column[]>([]);
const [errorColumnConfigs, setErrorColumnConfigs] = useState<
Column<ICampaignError>[]
>([]);
const [queryResultsForTableRender, setQueryResultsForTableRender] = useState(
queryResults
);

View File

@ -3,14 +3,7 @@
// definitions for the selection row for some reason when we dont really need it.
import React from "react";
import {
CellProps,
Column,
ColumnInstance,
ColumnInterface,
HeaderProps,
TableInstance,
} from "react-table";
import { CellProps, Column, HeaderProps } from "react-table";
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
@ -19,18 +12,7 @@ import {
internallyTruncateText,
} from "utilities/helpers";
type IHeaderProps = HeaderProps<TableInstance> & {
column: ColumnInstance & IDataColumn;
};
type ICellProps = CellProps<TableInstance>;
interface IDataColumn extends ColumnInterface {
title?: string;
accessor: string;
}
const _unshiftHostname = (columns: IDataColumn[]) => {
const _unshiftHostname = <T extends object>(columns: Column<T>[]) => {
const newHeaders = [...columns];
const displayNameIndex = columns.findIndex(
(h) => h.id === "host_display_name"
@ -39,7 +21,7 @@ const _unshiftHostname = (columns: IDataColumn[]) => {
// remove hostname header from headers
const [displayNameHeader] = newHeaders.splice(displayNameIndex, 1);
// reformat title and insert at start of headers array
newHeaders.unshift({ ...displayNameHeader, title: "Host" });
newHeaders.unshift({ ...displayNameHeader, id: "Host" });
}
// TODO: Remove after v5 when host_hostname is removed rom API response.
const hostNameIndex = columns.findIndex((h) => h.id === "host_hostname");
@ -50,24 +32,23 @@ const _unshiftHostname = (columns: IDataColumn[]) => {
return newHeaders;
};
const generateColumnConfigsFromRows = (
const generateColumnConfigsFromRows = <T extends Record<keyof T, unknown>>(
// TODO - narrow typing down this entire chain of logic
// typed as any[] to accomodate loose typing of websocket API
results: any[] // {col:val, ...} for each row of query results
): Column[] => {
results: T[] // {col:val, ...} for each row of query results
): Column<T>[] => {
const uniqueColumnNames = getUniqueColumnNamesFromRows(results);
const columnsConfigs = uniqueColumnNames.map((colName) => {
const columnsConfigs = uniqueColumnNames.map<Column<T>>((colName) => {
return {
id: colName as string,
title: colName as string,
Header: (headerProps: IHeaderProps) => (
Header: (headerProps: HeaderProps<T>) => (
<HeaderCell
value={headerProps.column.title || headerProps.column.id}
value={headerProps.column.id}
isSortedDesc={headerProps.column.isSortedDesc}
/>
),
accessor: colName as string,
Cell: (cellProps: ICellProps) => {
accessor: colName,
Cell: (cellProps: CellProps<T>) => {
const val = cellProps?.cell?.value;
return !!val?.length && val.length > 300
? internallyTruncateText(val)

View File

@ -70,8 +70,8 @@ interface IWrapperComponentProps {
}
const createWrapperComponent = (
CurrentWrapper: React.FC<any>, // TODO: types
WrapperComponent: React.FC<any>, // TODO: types
CurrentWrapper: React.FC<React.PropsWithChildren<any>>, // TODO: types
WrapperComponent: React.FC<React.PropsWithChildren<any>>, // TODO: types
props: IWrapperComponentProps
) => {
return ({ children }: IChildrenProp) => (

View File

@ -9,7 +9,7 @@ interface IResult {
distributed_query_execution_id: number;
error: string | null;
host: IHost;
rows: unknown[];
rows: Record<string, unknown>[];
};
}

View File

@ -15,10 +15,10 @@ export const generateCSVFilename = (descriptor: string) => {
};
// Live query results, live query errors, and query report
export const generateCSVQueryResults = (
export const generateCSVQueryResults = <T extends object>(
rows: Row[],
filename: string,
tableHeaders: Column[] | string[],
tableHeaders: Column<T>[] | string[],
omitHostDisplayName?: boolean
) => {
return new global.window.File(

View File

@ -858,14 +858,18 @@ export const internallyTruncateText = (
original: string,
prefixLength = 280,
suffixLength = 10
) => (
): JSX.Element => (
<>
{original.slice(0, prefixLength)}...
{original.slice(original.length - suffixLength)} <em>(truncated)</em>
</>
);
export const getUniqueColumnNamesFromRows = (rows: any[]) =>
export const getUniqueColumnNamesFromRows = <
T extends Record<keyof T, unknown>
>(
rows: T[]
) =>
// rows of type {col:val, col:val, ...}[]
// cannot type more narrowly due to loose typing of websocket API and use of this function
// by QueryResultsTableConfig, where results come from that API
@ -873,11 +877,10 @@ export const getUniqueColumnNamesFromRows = (rows: any[]) =>
Array.from(
rows.reduce(
(accOuter, row) =>
Object.keys(row).reduce(
(accInner, colNameInRow) => accInner.add(colNameInRow),
accOuter
),
new Set()
Object.keys(row).reduce((accInner, colNameInRow) => {
return accInner.add(colNameInRow as keyof T);
}, accOuter),
new Set<keyof T>()
)
);

View File

@ -37,10 +37,10 @@
"prop-types": "15.8.1",
"proxy-middleware": "0.15.0",
"rc-pagination": "1.16.3",
"react": "16.14.0",
"react": "18.2.0",
"react-accessible-accordion": "3.3.5",
"react-ace": "9.3.0",
"react-dom": "16.14.0",
"react-dom": "18.2.0",
"react-error-boundary": "3.1.4",
"react-markdown": "8.0.3",
"react-query": "3.34.16",
@ -106,7 +106,8 @@
"@types/mocha": "2.2.48",
"@types/node": "14.18.12",
"@types/prop-types": "15.7.4",
"@types/react": "17.0.40",
"@types/react": "18.2.63",
"@types/react-dom": "18.2.0",
"@types/react-router": "3.0.28",
"@types/react-select": "1.3.0",
"@types/react-table": "7.7.7",

View File

@ -5203,9 +5203,16 @@
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-dom@*":
version "17.0.13"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.13.tgz"
integrity sha512-wEP+B8hzvy6ORDv1QBhcQia4j6ea4SFIBttHYpXKPFZRviBvknq0FRh3VrIxeXUmsPkwuXVZrVGG7KUVONmXCQ==
version "18.2.19"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.19.tgz#b84b7c30c635a6c26c6a6dfbb599b2da9788be58"
integrity sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==
dependencies:
"@types/react" "*"
"@types/react-dom@18.2.0":
version "18.2.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.0.tgz#374f28074bb117f56f58c4f3f71753bebb545156"
integrity sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==
dependencies:
"@types/react" "*"
@ -5226,7 +5233,7 @@
"@types/react-table@7.7.7":
version "7.7.7"
resolved "https://registry.npmjs.org/@types/react-table/-/react-table-7.7.7.tgz"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.7.tgz#503be1ce2e06857c11b281629f539358d32671f9"
integrity sha512-3l2TP4detx9n5Jt44XhdH7Ku6PYwz6kB83P8W+YcBMUkIHtiw2gsCCcq9c4wyCIcdSwcTlWZ9WqH4PF7Yfbprg==
dependencies:
"@types/react" "*"
@ -5252,10 +5259,10 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@17.0.40":
version "17.0.40"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz"
integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==
"@types/react@*", "@types/react@18.2.63":
version "18.2.63"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.63.tgz#4637c56146ad90f96d0583171edab953f7e6fe57"
integrity sha512-ppaqODhs15PYL2nGUOaOu2RSCCB4Difu4UFrP4I3NHLloXC/ESQzQMi9nvjfT1+rudd0d2L3fQPJxRSey+rGlQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
@ -15074,15 +15081,13 @@ react-docgen@^5.0.0:
node-dir "^0.1.10"
strip-indent "^3.0.0"
react-dom@16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.19.1"
scheduler "^0.23.0"
react-element-to-jsx-string@^15.0.0:
version "15.0.0"
@ -15283,7 +15288,7 @@ react-style-singleton@^2.2.1:
react-table@7.7.0:
version "7.7.0"
resolved "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==
react-tabs@3.2.3:
@ -15320,14 +15325,12 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
read-pkg-up@^7.0.1:
version "7.0.1"
@ -15882,13 +15885,12 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"
scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@2.7.0:
version "2.7.0"