cleanup of manage host page utilities (#7055)

* colocate LabelForm with ManageHostPage

* add url utility to create query string from params object
This commit is contained in:
Gabriel Hernandez 2022-08-05 14:27:10 +01:00 committed by GitHub
parent 4ae4eabf31
commit aa60f3a54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 174 additions and 115 deletions

View file

@ -49,7 +49,6 @@ import Button from "components/buttons/Button";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import HostSidePanel from "components/side_panels/HostSidePanel";
import LabelForm from "components/forms/LabelForm";
import QuerySidePanel from "components/side_panels/QuerySidePanel";
import TableContainer from "components/TableContainer";
import TableDataError from "components/DataError";
@ -73,10 +72,10 @@ import {
DEFAULT_SORT_HEADER,
DEFAULT_SORT_DIRECTION,
HOST_SELECT_STATUSES,
isAcceptableStatus,
getNextLocationPath,
} from "./helpers";
} from "./constants";
import { isAcceptableStatus, getNextLocationPath } from "./helpers";
import LabelForm from "../components/LabelForm";
import DeleteSecretModal from "../../../components/DeleteSecretModal";
import SecretEditorModal from "../../../components/SecretEditorModal";
import AddHostsModal from "../../../components/AddHostsModal";

View file

@ -0,0 +1,34 @@
export const NEW_LABEL_HASH = "#new_label";
export const EDIT_LABEL_HASH = "#edit_label";
export const ALL_HOSTS_LABEL = "all-hosts";
export const LABEL_SLUG_PREFIX = "labels/";
export const DEFAULT_SORT_HEADER = "hostname";
export const DEFAULT_SORT_DIRECTION = "asc";
export const HOST_SELECT_STATUSES = [
{
disabled: false,
label: "All hosts",
value: ALL_HOSTS_LABEL,
helpText: "All hosts that have been enrolled to Fleet.",
},
{
disabled: false,
label: "Online hosts",
value: "online",
helpText: "Hosts that have recently checked in to Fleet.",
},
{
disabled: false,
label: "Offline hosts",
value: "offline",
helpText: "Hosts that have not checked in to Fleet recently.",
},
{
disabled: false,
label: "New hosts",
value: "new",
helpText: "Hosts that have been enrolled to Fleet in the last 24 hours.",
},
];

View file

@ -1,4 +1,5 @@
import { isString, isPlainObject, isEmpty, reduce, trim, union } from "lodash";
import { isEmpty, reduce, trim, union } from "lodash";
import { buildQueryStringFromParams } from "utilities/url";
interface ILocationParams {
pathPrefix?: string;
@ -7,40 +8,7 @@ interface ILocationParams {
queryParams?: { [key: string]: string | number };
}
export const NEW_LABEL_HASH = "#new_label";
export const EDIT_LABEL_HASH = "#edit_label";
export const ALL_HOSTS_LABEL = "all-hosts";
export const LABEL_SLUG_PREFIX = "labels/";
export const DEFAULT_SORT_HEADER = "hostname";
export const DEFAULT_SORT_DIRECTION = "asc";
export const HOST_SELECT_STATUSES = [
{
disabled: false,
label: "All hosts",
value: ALL_HOSTS_LABEL,
helpText: "All hosts that have been enrolled to Fleet.",
},
{
disabled: false,
label: "Online hosts",
value: "online",
helpText: "Hosts that have recently checked in to Fleet.",
},
{
disabled: false,
label: "Offline hosts",
value: "offline",
helpText: "Hosts that have not checked in to Fleet recently.",
},
{
disabled: false,
label: "New hosts",
value: "new",
helpText: "Hosts that have been enrolled to Fleet in the last 24 hours.",
},
];
type RouteParams = Record<string, string>;
export const isAcceptableStatus = (filter: string): boolean => {
return filter === "new" || filter === "online" || filter === "offline";
@ -61,43 +29,31 @@ export const isValidPemCertificate = (cert: string): boolean => {
return regexPemHeader.test(cert) && regexPemFooter.test(cert);
};
const createRouteString = (routeTemplate: string, routeParams: RouteParams) => {
let routeString = "";
if (!isEmpty(routeParams)) {
routeString = reduce(
routeParams,
(string, value, key) => {
return string.replace(`:${key}`, encodeURIComponent(value));
},
routeTemplate
);
}
return routeString;
};
export const getNextLocationPath = ({
pathPrefix = "",
routeTemplate = "",
routeParams = {},
queryParams = {},
}: ILocationParams): string => {
const pathPrefixFinal = isString(pathPrefix) ? pathPrefix : "";
const routeTemplateFinal = (isString(routeTemplate) && routeTemplate) || "";
const routeParamsFinal = isPlainObject(routeParams) ? routeParams : {};
const queryParamsFinal = isPlainObject(queryParams) ? queryParams : {};
let routeString = "";
if (!isEmpty(routeParamsFinal)) {
routeString = reduce(
routeParamsFinal,
(string, value, key) => {
return string.replace(`:${key}`, encodeURIComponent(value));
},
routeTemplateFinal
);
}
let queryString = "";
if (!isEmpty(queryParamsFinal)) {
queryString = reduce(
queryParamsFinal,
(arr: string[], value, key) => {
key && arr.push(`${key}=${encodeURIComponent(value)}`);
return arr;
},
[]
).join("&");
}
const routeString = createRouteString(routeTemplate, routeParams);
const queryString = buildQueryStringFromParams(queryParams);
const nextLocation = union(
trim(pathPrefixFinal, "/").split("/"),
trim(pathPrefix, "/").split("/"),
routeString.split("/")
).join("/");

View file

@ -1,16 +1,13 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import sendRequest from "services";
import endpoints from "utilities/endpoints";
import { buildQueryStringFromParams } from "utilities/url";
export default {
loadAll: (teamId?: number) => {
const { MACADMINS } = endpoints;
let path = MACADMINS;
if (teamId) {
path += `?team_id=${teamId}`;
}
const queryString = buildQueryStringFromParams({ team_id: teamId });
const path = `${MACADMINS}?${queryString}`;
return sendRequest("GET", path);
},
};

View file

@ -3,13 +3,14 @@ import sendRequest from "services";
import endpoints from "utilities/endpoints";
import { IOperatingSystemVersion } from "interfaces/operating_system";
import { IOsqueryPlatform } from "interfaces/platform";
import { buildQueryStringFromParams } from "utilities/url";
export interface IOperatingSystemsResponse {
counts_updated_at: string;
os_versions: IOperatingSystemVersion[];
}
interface IGetOperatingSystemProps {
interface IGetVersionParams {
platform: IOsqueryPlatform;
teamId?: number;
}
@ -18,18 +19,11 @@ export default {
getVersions: async ({
platform,
teamId,
}: IGetOperatingSystemProps): Promise<IOperatingSystemsResponse> => {
}: IGetVersionParams): Promise<IOperatingSystemsResponse> => {
const { OS_VERSIONS } = endpoints;
let path = OS_VERSIONS;
const queryParams = [`platform=${platform}`];
if (teamId) {
queryParams.push(`team_id=${teamId}`);
}
const queryString = `?${queryParams.join("&")}`;
path += queryString;
const queryParams = { platform, team_id: teamId };
const queryString = buildQueryStringFromParams(queryParams);
const path = `${OS_VERSIONS}?${queryString}`;
try {
return sendRequest("GET", path);

View file

@ -1,8 +1,9 @@
import { snakeCase } from "lodash";
import { snakeCase, reduce } from "lodash";
import sendRequest from "services";
import endpoints from "utilities/endpoints";
import { ISoftware } from "interfaces/software";
import { buildQueryStringFromParams, QueryParams } from "utilities/url";
interface IGetSoftwareProps {
page?: number;
@ -32,19 +33,15 @@ type ISoftwareParams = Partial<IGetSoftwareProps>;
const ORDER_KEY = "name";
const ORDER_DIRECTION = "asc";
const buildQueryStringFromParams = (params: ISoftwareParams) => {
const filteredParams = Object.entries(params).filter(
([key, value]) => !!value
const convertParamsToSnakeCase = (params: ISoftwareParams) => {
return reduce<typeof params, QueryParams>(
params,
(result, val, key) => {
result[snakeCase(key)] = val;
return result;
},
{}
);
if (!filteredParams.length) {
return "";
}
return `?${filteredParams
.map(
([key, value]) =>
`${encodeURIComponent(snakeCase(key))}=${encodeURIComponent(value)}`
)
.join("&")}`;
};
export default {
@ -58,21 +55,19 @@ export default {
teamId,
}: ISoftwareParams): Promise<ISoftwareResponse> => {
const { SOFTWARE } = endpoints;
const pagination = perPage ? `page=${page}&per_page=${perPage}` : "";
const sort = `order_key=${orderKey}&order_direction=${orderDir}`;
let path = `${SOFTWARE}?${pagination}&${sort}`;
const queryParams = {
page,
perPage,
orderKey,
orderDirection: orderDir,
teamId,
query,
vulnerable,
};
if (teamId) {
path += `&team_id=${teamId}`;
}
if (query) {
path += `&query=${encodeURIComponent(query)}`;
}
if (vulnerable) {
path += `&vulnerable=${vulnerable}`;
}
const snakeCaseParams = convertParamsToSnakeCase(queryParams);
const queryString = buildQueryStringFromParams(snakeCaseParams);
const path = `${SOFTWARE}?${queryString}`;
try {
return sendRequest("GET", path);
@ -84,9 +79,10 @@ export default {
count: async (params: ISoftwareParams): Promise<ISoftwareCountResponse> => {
const { SOFTWARE } = endpoints;
const path = `${SOFTWARE}/count`;
const queryString = buildQueryStringFromParams(params);
const snakeCaseParams = convertParamsToSnakeCase(params);
const queryString = buildQueryStringFromParams(snakeCaseParams);
return sendRequest("GET", path.concat(queryString));
return sendRequest("GET", path.concat(`?${queryString}`));
},
getSoftwareById: async (

View file

@ -0,0 +1,41 @@
import { isEmpty, reduce, omitBy, Dictionary } from "lodash";
type QueryValues = string | number | boolean | undefined | null;
export type QueryParams = Record<string, QueryValues>;
type FilteredQueryValues = string | number | boolean;
type FilteredQueryParams = Record<string, FilteredQueryValues>;
const reduceQueryParams = (
params: string[],
value: FilteredQueryValues,
key: string
) => {
key && params.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
return params;
};
const filterEmptyParams = (queryParams: QueryParams) => {
return omitBy(
queryParams,
(value) => value === undefined || value === "" || value === null
) as Dictionary<FilteredQueryValues>;
};
/**
* creates a query string from a query params object. If a value is undefined, null,
* or an empty string on the queryParams object, that key-value pair will be
* excluded from the query string.
*/
export const buildQueryStringFromParams = (queryParams: QueryParams) => {
const filteredParams = filterEmptyParams(queryParams);
let queryString = "";
if (!isEmpty(queryParams)) {
queryString = reduce<FilteredQueryParams, string[]>(
filteredParams,
reduceQueryParams,
[]
).join("&");
}
return queryString;
};

View file

@ -0,0 +1,42 @@
import { buildQueryStringFromParams } from ".";
describe("url utilites", () => {
it("creates a query string from a params object", () => {
const params = {
query: "test",
page: 1,
order: "asc",
isNew: true,
};
expect(buildQueryStringFromParams(params)).toBe(
"query=test&page=1&order=asc&isNew=true"
);
});
it("filters out undefined values", () => {
const params = {
query: undefined,
page: 1,
order: "asc",
};
expect(buildQueryStringFromParams(params)).toBe("page=1&order=asc");
});
it("filters out empty string values", () => {
const params = {
query: "",
page: 1,
order: "asc",
};
expect(buildQueryStringFromParams(params)).toBe("page=1&order=asc");
});
it("filters out null values", () => {
const params = {
query: null,
page: 1,
order: "asc",
};
expect(buildQueryStringFromParams(params)).toBe("page=1&order=asc");
});
});