fleet/frontend/pages/queries/QueryPage/components/QueryResults/QueryResults.tsx
Martavis Parker 384c987389
Removed all traces of Redux from the app! (#5287)
* clean up routes and useless components

* component clean up

* removed redux from routes

* rename file

* moved useDeepEffect hook with others

* removed redux, fleet, app_constants dirs; added types to utilities

* style cleanup

* typo fix

* removed unused ts-ignore comments

* removed redux packages!!!

* formatting

* fixed typing for simple search function

* updated frontend readme
2022-04-22 09:45:35 -07:00

322 lines
9.3 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import classnames from "classnames";
import { format } from "date-fns";
import FileSaver from "file-saver";
import { filter, get } from "lodash";
import convertToCSV from "utilities/convert_to_csv";
import { ICampaign } from "interfaces/campaign";
import { ITarget } from "interfaces/target";
import Button from "components/buttons/Button";
import Spinner from "components/Spinner";
import TableContainer from "components/TableContainer";
import TabsWrapper from "components/TabsWrapper";
import TooltipWrapper from "components/TooltipWrapper";
import ShowQueryModal from "./ShowQueryModal";
import DownloadIcon from "../../../../../../assets/images/icon-download-12x12@2x.png";
import EyeIcon from "../../../../../../assets/images/icon-eye-16x16@2x.png";
import resultsTableHeaders from "./QueryResultsTableConfig";
interface IQueryResultsProps {
campaign: ICampaign;
isQueryFinished: boolean;
onRunQuery: (evt: React.MouseEvent<HTMLButtonElement>) => void;
onStopQuery: (evt: React.MouseEvent<HTMLButtonElement>) => void;
setSelectedTargets: (value: ITarget[]) => void;
goToQueryEditor: () => void;
targetsTotalCount: number;
}
const baseClass = "query-results";
const CSV_QUERY_TITLE = "Query Results";
const PAGE_TITLES = {
RUNNING: "Querying selected hosts",
FINISHED: "Query finished",
};
const NAV_TITLES = {
RESULTS: "Results",
ERRORS: "Errors",
};
const QueryResults = ({
campaign,
isQueryFinished,
onRunQuery,
onStopQuery,
setSelectedTargets,
goToQueryEditor,
targetsTotalCount,
}: IQueryResultsProps): JSX.Element => {
const { hosts_count: hostsCount, query_results: queryResults, errors } =
campaign || {};
const totalRowsCount = get(campaign, ["query_results", "length"], 0);
const [pageTitle, setPageTitle] = useState<string>(PAGE_TITLES.RUNNING);
const [navTabIndex, setNavTabIndex] = useState(0);
const [
targetsRespondedPercent,
setTargetsRespondedPercent,
] = useState<number>(0);
const [showQueryModal, setShowQueryModal] = useState<boolean>(false);
useEffect(() => {
const calculatePercent =
targetsTotalCount !== 0
? Math.round((campaign.hosts_count.total / targetsTotalCount) * 100)
: 0;
setTargetsRespondedPercent(calculatePercent);
}, [campaign]);
useEffect(() => {
if (isQueryFinished) {
setPageTitle(PAGE_TITLES.FINISHED);
} else {
setPageTitle(PAGE_TITLES.RUNNING);
}
}, [isQueryFinished]);
const onExportQueryResults = (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
if (queryResults) {
const csv = convertToCSV(queryResults, (fields: string[]) => {
const result = filter(fields, (f) => f !== "host_hostname");
result.unshift("host_hostname");
return result;
});
const formattedTime = format(new Date(), "MM-dd-yy hh-mm-ss");
const filename = `${CSV_QUERY_TITLE} (${formattedTime}).csv`;
const file = new global.window.File([csv], filename, {
type: "text/csv",
});
FileSaver.saveAs(file);
}
};
const onExportErrorsResults = (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
if (errors) {
const csv = convertToCSV(errors, (fields: string[]) => {
const result = filter(fields, (f) => f !== "host_hostname");
result.unshift("host_hostname");
return result;
});
const formattedTime = format(new Date(), "MM-dd-yy hh-mm-ss");
const filename = `${CSV_QUERY_TITLE} Errors (${formattedTime}).csv`;
const file = new global.window.File([csv], filename, {
type: "text/csv",
});
FileSaver.saveAs(file);
}
};
const onShowQueryModal = () => {
setShowQueryModal(!showQueryModal);
};
const onQueryDone = () => {
setSelectedTargets([]);
goToQueryEditor();
};
const renderNoResults = () => {
return (
<p className="no-results-message">
Your live query returned no results.
<span>
Expecting to see results? Check to see if the hosts you targeted
reported &ldquo;Online&rdquo; or check out the &ldquo;Errors&rdquo;
table.
</span>
</p>
);
};
const renderTable = (tableData: unknown[]) => {
return (
<TableContainer
columns={resultsTableHeaders(tableData || [])}
data={tableData || []}
emptyComponent={renderNoResults}
isLoading={false}
disableCount
isClientSidePagination
showMarkAllPages={false}
isAllPagesSelected={false}
resultsTitle={"hosts"}
/>
);
};
const renderResultsTable = () => {
const emptyResults = !queryResults || !queryResults.length;
const hasNoResultsYet = !isQueryFinished && emptyResults;
const finishedWithNoResults =
isQueryFinished && (!hostsCount.successful || emptyResults);
if (hasNoResultsYet) {
return <Spinner />;
}
if (finishedWithNoResults) {
return renderNoResults();
}
return (
<div className={`${baseClass}__results-table-container`}>
<div className={`${baseClass}__results-table-header`}>
<span className={`${baseClass}__results-count`}>
{totalRowsCount} result{totalRowsCount !== 1 && "s"}
</span>
<div className={`${baseClass}__results-cta`}>
<Button
className={`${baseClass}__show-query-btn`}
onClick={onShowQueryModal}
variant="text-link"
>
<>
Show query <img alt="Show query" src={EyeIcon} />
</>
</Button>
<Button
className={`${baseClass}__export-btn`}
onClick={onExportQueryResults}
variant="text-link"
>
<>
Export results <img alt="Export results" src={DownloadIcon} />
</>
</Button>
</div>
</div>
{renderTable(queryResults)}
</div>
);
};
const renderErrorsTable = () => {
return (
<div className={`${baseClass}__error-table-container`}>
<div className={`${baseClass}__errors-table-header`}>
{errors && (
<span className={`${baseClass}__error-count`}>
{errors.length} error{errors.length !== 1 && "s"}
</span>
)}
<div className={`${baseClass}__errors-cta`}>
<Button
className={`${baseClass}__show-query-btn`}
onClick={onShowQueryModal}
variant="text-link"
>
<>
Show query <img alt="Show query" src={EyeIcon} />
</>
</Button>
<Button
className={`${baseClass}__export-btn`}
onClick={onExportErrorsResults}
variant="text-link"
>
<>
Export errors <img alt="" src={DownloadIcon} />
</>
</Button>
</div>
</div>
<div className={`${baseClass}__error-table-wrapper`}>
{renderTable(errors)}
</div>
</div>
);
};
const renderFinishedButtons = () => (
<div className={`${baseClass}__btn-wrapper`}>
<Button
className={`${baseClass}__done-btn`}
onClick={onQueryDone}
variant="brand"
>
Done
</Button>
<Button
className={`${baseClass}__run-btn`}
onClick={onRunQuery}
variant="blue-green"
>
Run again
</Button>
</div>
);
const renderStopQueryButton = () => (
<div className={`${baseClass}__btn-wrapper`}>
<Button
className={`${baseClass}__stop-btn`}
onClick={onStopQuery}
variant="alert"
>
<>
<Spinner isInButton />
Stop
</>
</Button>
</div>
);
const firstTabClass = classnames("react-tabs__tab", "no-count", {
"errors-empty": !errors || errors?.length === 0,
});
return (
<div className={baseClass}>
<div className={`${baseClass}__wrapper`}>
<h1>{pageTitle}</h1>
<div className={`${baseClass}__text-wrapper`}>
<span>{targetsTotalCount}</span>&nbsp;hosts targeted&nbsp; (
{targetsRespondedPercent}%&nbsp;
<TooltipWrapper
tipContent={`
Hosts that respond may<br /> return results, errors, or <br />no results`}
>
responded
</TooltipWrapper>
)
</div>
</div>
{isQueryFinished ? renderFinishedButtons() : renderStopQueryButton()}
<TabsWrapper>
<Tabs selectedIndex={navTabIndex} onSelect={(i) => setNavTabIndex(i)}>
<TabList>
<Tab className={firstTabClass}>{NAV_TITLES.RESULTS}</Tab>
<Tab disabled={!errors?.length}>
<span>
{errors?.length > 0 && (
<span className="count">{errors.length}</span>
)}
{NAV_TITLES.ERRORS}
</span>
</Tab>
</TabList>
<TabPanel>{renderResultsTable()}</TabPanel>
<TabPanel>{renderErrorsTable()}</TabPanel>
</Tabs>
</TabsWrapper>
{showQueryModal && <ShowQueryModal onCancel={onShowQueryModal} />}
</div>
);
};
export default QueryResults;