kibana/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx

526 lines
14 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { get } from 'lodash/fp';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import {
EuiBasicTable,
EuiButtonEmpty,
EuiCodeBlock,
EuiButtonIcon,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
TypedLensByValueInput,
PersistedIndexPatternLayer,
PieVisualizationState,
} from '../../../lens/public';
import { FilterStateStore } from '../../../../../src/plugins/data/common';
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
import { PlatformIcons } from './queries/platforms';
import { OsqueryManagerPackagePolicyInputStream } from '../../common/types';
export enum ViewResultsActionButtonType {
icon = 'icon',
button = 'button',
}
interface ViewResultsInDiscoverActionProps {
actionId: string;
buttonType: ViewResultsActionButtonType;
endDate?: string;
startDate?: string;
}
function getLensAttributes(actionId: string): TypedLensByValueInput['attributes'] {
const dataLayer: PersistedIndexPatternLayer = {
columnOrder: ['8690befd-fd69-4246-af4a-dd485d2a3b38', 'ed999e9d-204c-465b-897f-fe1a125b39ed'],
columns: {
'8690befd-fd69-4246-af4a-dd485d2a3b38': {
sourceField: 'type',
isBucketed: true,
dataType: 'string',
scale: 'ordinal',
operationType: 'terms',
label: 'Top values of type',
params: {
otherBucket: true,
size: 5,
missingBucket: false,
orderBy: {
columnId: 'ed999e9d-204c-465b-897f-fe1a125b39ed',
type: 'column',
},
orderDirection: 'desc',
},
},
'ed999e9d-204c-465b-897f-fe1a125b39ed': {
sourceField: 'Records',
isBucketed: false,
dataType: 'number',
scale: 'ratio',
operationType: 'count',
label: 'Count of records',
},
},
incompleteColumns: {},
};
const xyConfig: PieVisualizationState = {
shape: 'pie',
layers: [
{
legendDisplay: 'default',
nestedLegend: false,
layerId: 'layer1',
metric: 'ed999e9d-204c-465b-897f-fe1a125b39ed',
numberDisplay: 'percent',
groups: ['8690befd-fd69-4246-af4a-dd485d2a3b38'],
categoryDisplay: 'default',
},
],
};
return {
visualizationType: 'lnsPie',
title: `Action ${actionId} results`,
references: [
{
id: 'logs-*',
name: 'indexpattern-datasource-current-indexpattern',
type: 'index-pattern',
},
{
id: 'logs-*',
name: 'indexpattern-datasource-layer-layer1',
type: 'index-pattern',
},
{
name: 'filter-index-pattern-0',
id: 'logs-*',
type: 'index-pattern',
},
],
state: {
datasourceStates: {
indexpattern: {
layers: {
layer1: dataLayer,
},
},
},
filters: [
{
$state: { store: FilterStateStore.APP_STATE },
meta: {
indexRefName: 'filter-index-pattern-0',
negate: false,
alias: null,
disabled: false,
params: {
query: actionId,
},
type: 'phrase',
key: 'action_id',
},
query: {
match_phrase: {
action_id: actionId,
},
},
},
],
query: { language: 'kuery', query: '' },
visualization: xyConfig,
},
};
}
const ViewResultsInLensActionComponent: React.FC<ViewResultsInDiscoverActionProps> = ({
actionId,
buttonType,
endDate,
startDate,
}) => {
const lensService = useKibana().services.lens;
const handleClick = useCallback(
(event) => {
const openInNewWindow = !(!isModifiedEvent(event) && isLeftClickEvent(event));
event.preventDefault();
lensService?.navigateToPrefilledEditor(
{
id: '',
timeRange: {
from: startDate ?? 'now-1d',
to: endDate ?? 'now',
mode: startDate || endDate ? 'absolute' : 'relative',
},
attributes: getLensAttributes(actionId),
},
openInNewWindow
);
},
[actionId, endDate, lensService, startDate]
);
if (buttonType === ViewResultsActionButtonType.button) {
return (
<EuiButtonEmpty
size="xs"
iconType="lensApp"
onClick={handleClick}
disabled={!lensService?.canUseEditor()}
>
<FormattedMessage
id="xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel"
defaultMessage="View results in Lens"
/>
</EuiButtonEmpty>
);
}
return (
<EuiToolTip
content={i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
{
defaultMessage: 'View results in Lens',
}
)}
>
<EuiButtonIcon
iconType="lensApp"
disabled={!lensService?.canUseEditor()}
onClick={handleClick}
aria-label={i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
{
defaultMessage: 'View results in Lens',
}
)}
/>
</EuiToolTip>
);
};
export const ViewResultsInLensAction = React.memo(ViewResultsInLensActionComponent);
const ViewResultsInDiscoverActionComponent: React.FC<ViewResultsInDiscoverActionProps> = ({
actionId,
buttonType,
endDate,
startDate,
}) => {
const urlGenerator = useKibana().services.discover?.urlGenerator;
const [discoverUrl, setDiscoverUrl] = useState<string>('');
useEffect(() => {
const getDiscoverUrl = async () => {
if (!urlGenerator?.createUrl) return;
const newUrl = await urlGenerator.createUrl({
indexPatternId: 'logs-*',
filters: [
{
meta: {
index: 'logs-*',
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'action_id',
params: { query: actionId },
},
query: { match_phrase: { action_id: actionId } },
$state: { store: FilterStateStore.APP_STATE },
},
],
refreshInterval: {
pause: true,
value: 0,
},
timeRange:
startDate && endDate
? {
to: endDate,
from: startDate,
mode: 'absolute',
}
: {
to: 'now',
from: 'now-15m',
mode: 'relative',
},
});
setDiscoverUrl(newUrl);
};
getDiscoverUrl();
}, [actionId, endDate, startDate, urlGenerator]);
if (buttonType === ViewResultsActionButtonType.button) {
return (
<EuiButtonEmpty size="xs" iconType="discoverApp" href={discoverUrl}>
<FormattedMessage
id="xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel"
defaultMessage="View results in Discover"
/>
</EuiButtonEmpty>
);
}
return (
<EuiToolTip
content={i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
{
defaultMessage: 'View results in Discover',
}
)}
>
<EuiButtonIcon
iconType="discoverApp"
href={discoverUrl}
aria-label={i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
{
defaultMessage: 'View results in Discover',
}
)}
/>
</EuiToolTip>
);
};
export const ViewResultsInDiscoverAction = React.memo(ViewResultsInDiscoverActionComponent);
interface ScheduledQueryGroupQueriesTableProps {
data: OsqueryManagerPackagePolicyInputStream[];
editMode?: boolean;
onDeleteClick?: (item: OsqueryManagerPackagePolicyInputStream) => void;
onEditClick?: (item: OsqueryManagerPackagePolicyInputStream) => void;
selectedItems?: OsqueryManagerPackagePolicyInputStream[];
setSelectedItems?: (selection: OsqueryManagerPackagePolicyInputStream[]) => void;
}
const ScheduledQueryGroupQueriesTableComponent: React.FC<ScheduledQueryGroupQueriesTableProps> = ({
data,
editMode = false,
onDeleteClick,
onEditClick,
selectedItems,
setSelectedItems,
}) => {
const renderDeleteAction = useCallback(
(item: OsqueryManagerPackagePolicyInputStream) => (
<EuiButtonIcon
color="danger"
// @ts-expect-error update types
// eslint-disable-next-line react/jsx-no-bind, react-perf/jsx-no-new-function-as-prop
onClick={() => onDeleteClick(item)}
iconType="trash"
aria-label={i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.deleteActionAriaLabel',
{
defaultMessage: 'Delete {queryName}',
values: {
queryName: item.vars?.id.value,
},
}
)}
/>
),
[onDeleteClick]
);
const renderEditAction = useCallback(
(item: OsqueryManagerPackagePolicyInputStream) => (
<EuiButtonIcon
color="primary"
// @ts-expect-error update types
// eslint-disable-next-line react/jsx-no-bind, react-perf/jsx-no-new-function-as-prop
onClick={() => onEditClick(item)}
iconType="pencil"
aria-label={i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.editActionAriaLabel',
{
defaultMessage: 'Edit {queryName}',
values: {
queryName: item.vars?.id.value,
},
}
)}
/>
),
[onEditClick]
);
const renderQueryColumn = useCallback(
(query: string) => (
<EuiCodeBlock language="sql" fontSize="s" paddingSize="none" transparentBackground>
{query}
</EuiCodeBlock>
),
[]
);
const renderPlatformColumn = useCallback(
(platform: string) => <PlatformIcons platform={platform} />,
[]
);
const renderVersionColumn = useCallback(
(version: string) =>
version
? `${version}`
: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel', {
defaultMessage: 'ALL',
}),
[]
);
const renderDiscoverResultsAction = useCallback(
(item) => (
<ViewResultsInDiscoverAction
actionId={item.vars?.id.value}
buttonType={ViewResultsActionButtonType.icon}
/>
),
[]
);
const renderLensResultsAction = useCallback(
(item) => (
<ViewResultsInLensAction
actionId={item.vars?.id.value}
buttonType={ViewResultsActionButtonType.icon}
/>
),
[]
);
const columns = useMemo(
() => [
{
field: 'vars.id.value',
name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.idColumnTitle', {
defaultMessage: 'ID',
}),
width: '20%',
},
{
field: 'vars.interval.value',
name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.intervalColumnTitle', {
defaultMessage: 'Interval (s)',
}),
width: '100px',
},
{
field: 'vars.query.value',
name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.queryColumnTitle', {
defaultMessage: 'Query',
}),
render: renderQueryColumn,
},
{
field: 'vars.platform.value',
name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.platformColumnTitle', {
defaultMessage: 'Platform',
}),
render: renderPlatformColumn,
},
{
field: 'vars.version.value',
name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.versionColumnTitle', {
defaultMessage: 'Min Osquery version',
}),
render: renderVersionColumn,
},
{
name: editMode
? i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.actionsColumnTitle', {
defaultMessage: 'Actions',
})
: i18n.translate(
'xpack.osquery.scheduledQueryGroup.queriesTable.viewResultsColumnTitle',
{
defaultMessage: 'View results',
}
),
width: '120px',
actions: editMode
? [
{
render: renderEditAction,
},
{
render: renderDeleteAction,
},
]
: [
{
render: renderDiscoverResultsAction,
},
{
render: renderLensResultsAction,
},
],
},
],
[
editMode,
renderDeleteAction,
renderDiscoverResultsAction,
renderEditAction,
renderLensResultsAction,
renderPlatformColumn,
renderQueryColumn,
renderVersionColumn,
]
);
const sorting = useMemo(
() => ({
sort: {
field: 'vars.id.value' as keyof OsqueryManagerPackagePolicyInputStream,
direction: 'asc' as const,
},
}),
[]
);
const itemId = useCallback(
(item: OsqueryManagerPackagePolicyInputStream) => get('vars.id.value', item),
[]
);
const selection = useMemo(
() => ({
onSelectionChange: setSelectedItems,
initialSelected: selectedItems,
}),
[selectedItems, setSelectedItems]
);
return (
<EuiBasicTable<OsqueryManagerPackagePolicyInputStream>
items={data}
itemId={itemId}
columns={columns}
sorting={sorting}
selection={editMode ? selection : undefined}
isSelectable={editMode}
/>
);
};
export const ScheduledQueryGroupQueriesTable = React.memo(ScheduledQueryGroupQueriesTableComponent);