Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Adding filebeat config to file dataviz #58152

Merged
31 changes: 31 additions & 0 deletions x-pack/legacy/plugins/ml/common/types/file_datavisualizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export interface FindFileStructureResponse {
charset: string;
has_header_row: boolean;
has_byte_order_marker: boolean;
format: string;
field_stats: {
[fieldName: string]: {
count: number;
cardinality: number;
top_hits: Array<{ count: number; value: any }>;
};
};
sample_start: string;
num_messages_analyzed: number;
mappings: {
[fieldName: string]: {
type: string;
};
};
quote: string;
delimiter: string;
need_client_timezone: boolean;
num_lines_analyzed: number;
column_names: string[];
}
4 changes: 4 additions & 0 deletions x-pack/legacy/plugins/ml/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'ace';
import { AppMountParameters, CoreStart } from 'kibana/public';

import { DataPublicPluginStart } from 'src/plugins/data/public';
import { SecurityPluginSetup } from '../../../../../plugins/security/public';

import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { setDependencyCache, clearCache } from './util/dependency_cache';
Expand All @@ -20,6 +21,7 @@ import { MlRouter } from './routing';

export interface MlDependencies extends AppMountParameters {
data: DataPublicPluginStart;
security: SecurityPluginSetup;
__LEGACY: {
XSRF: string;
APP_URL: string;
Expand Down Expand Up @@ -49,6 +51,7 @@ const App: FC<AppProps> = ({ coreStart, deps }) => {
APP_URL: deps.__LEGACY.APP_URL,
application: coreStart.application,
http: coreStart.http,
security: deps.security,
});
deps.onAppLeave(actions => {
clearCache();
Expand All @@ -64,6 +67,7 @@ const App: FC<AppProps> = ({ coreStart, deps }) => {
const services = {
appName: 'ML',
data: deps.data,
security: deps.security,
...coreStart,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
useKibana,
KibanaReactContextValue,
} from '../../../../../../../../src/plugins/kibana_react/public';
import { SecurityPluginSetup } from '../../../../../../../plugins/security/public';

interface StartPlugins {
data: DataPublicPluginStart;
security: SecurityPluginSetup;
}
export type StartServices = CoreStart & StartPlugins;
// eslint-disable-next-line react-hooks/rules-of-hooks
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer';

export function createFilebeatConfig(
index: string,
results: FindFileStructureResponse,
ingestPipelineId: string,
username: string | null
) {
return [
'filebeat.inputs:',
'- type: log',
' paths:',
" - '<add path to your files here>'",
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
...getEncoding(results),
...getExcludeLines(results),
...getMultiline(results),
'',
...getProcessors(results),
'output.elasticsearch:',
' hosts: ["<es_url>"]',
...getUserDetails(username),
` index: "${index}"`,
` pipeline: "${ingestPipelineId}"`,
'',
'setup:',
' template.enabled: false',
' ilm.enabled: false',
].join('\n');
jgowdyelastic marked this conversation as resolved.
Show resolved Hide resolved
}

function getEncoding(results: any) {
return results.charset !== 'UTF-8' ? [` encoding: ${results.charset}`] : [];
}

function getExcludeLines(results: any) {
return results.exclude_lines_pattern !== undefined
? [` exclude_lines: ['${results.exclude_lines_pattern.replace(/'/g, "''")}']`]
: [];
}

function getMultiline(results: any) {
return results.multiline_start_pattern !== undefined
? [
' multiline:',
` pattern: '${results.multiline_start_pattern.replace(/'/g, "''")}'`,
' match: after',
' negate: true',
]
: [];
}

function getProcessors(results: any) {
return results.need_client_timezone === true ? ['processors:', '- add_locale: ~', ''] : [];
}

function getUserDetails(username: string | null) {
return username !== null ? [` username: "${username}"`, ' password: "<password>"'] : [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FC, useState, useEffect } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlyout,
EuiFlyoutFooter,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiButtonEmpty,
EuiTitle,
EuiFlyoutBody,
EuiSpacer,
EuiCodeBlock,
EuiCode,
EuiCopy,
} from '@elastic/eui';
import { createFilebeatConfig } from './filebeat_config';
import { useMlKibana } from '../../../../contexts/kibana';
import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer';

export enum EDITOR_MODE {
HIDDEN,
READONLY,
EDITABLE,
}
interface Props {
index: string;
results: FindFileStructureResponse;
indexPatternId: string;
ingestPipelineId: string;
closeFlyout(): void;
}
export const FilebeatConfigFlyout: FC<Props> = ({
index,
results,
indexPatternId,
ingestPipelineId,
closeFlyout,
}) => {
const [fileBeatConfig, setFileBeatConfig] = useState('');
const [username, setUsername] = useState<string | null>(null);
const {
services: { security },
} = useMlKibana();

useEffect(() => {
security.authc.getCurrentUser().then(user => {
setUsername(user.username === undefined ? null : user.username);
});
}, []);

useEffect(() => {
const config = createFilebeatConfig(index, results, ingestPipelineId, username);
setFileBeatConfig(config);
}, [username]);

return (
<EuiFlyout onClose={closeFlyout} hideCloseButton size={'m'}>
<EuiFlyoutBody>
<EuiFlexGroup>
<Contents value={fileBeatConfig} username={username} index={index} />
</EuiFlexGroup>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={closeFlyout} flush="left">
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton"
defaultMessage="Close"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={fileBeatConfig}>
{copy => (
<EuiButton onClick={copy}>
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton"
defaultMessage="Copy to clipboard"
/>
</EuiButton>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
);
};

const Contents: FC<{
value: string;
index: string;
username: string | null;
}> = ({ value, index, username }) => {
return (
<EuiFlexItem>
<EuiTitle size="s">
<h5>
<FormattedMessage
id="xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTitle"
defaultMessage="Filebeat configuration"
/>
</h5>
</EuiTitle>
<EuiSpacer size="s" />
<p>
<FormattedMessage
id="xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTopText1"
defaultMessage="Additional data can be uploaded to the {index} index using Filebeat."
values={{ index: <EuiCode>{index}</EuiCode> }}
/>
</p>
<p>
<FormattedMessage
id="xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTopText2"
defaultMessage="Modify {filebeatYml} to set the connection information:"
values={{ filebeatYml: <EuiCode>filebeat.yml</EuiCode> }}
/>
</p>

<EuiSpacer size="s" />

<EuiCodeBlock language="bash">{value}</EuiCodeBlock>

<EuiSpacer size="s" />
<p>
{username === null ? (
<FormattedMessage
id="xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigBottomTextNoUsername"
defaultMessage="Where {esUrl} is the URL of Elasticsearch."
values={{
esUrl: <EuiCode>{'<es_url>'}</EuiCode>,
}}
/>
) : (
<FormattedMessage
id="xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigBottomText"
defaultMessage="Where {password} is the password of the {user} user, {esUrl} is the URL of Elasticsearch."
values={{
user: <EuiCode>{username}</EuiCode>,
password: <EuiCode>{'<password>'}</EuiCode>,
esUrl: <EuiCode>{'<es_url>'}</EuiCode>,
}}
/>
)}
</p>
</EuiFlexItem>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { FilebeatConfigFlyout } from './filebeat_config_flyout';
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { i18n } from '@kbn/i18n';
import { importerFactory } from './importer';
import { ResultsLinks } from '../results_links';
import { FilebeatConfigFlyout } from '../filebeat_config_flyout';
import { ImportProgress, IMPORT_STATUS } from '../import_progress';
import { ImportErrors } from '../import_errors';
import { ImportSummary } from '../import_summary';
Expand Down Expand Up @@ -64,6 +65,7 @@ const DEFAULT_STATE = {
indexNameError: '',
indexPatternNameError: '',
timeFieldName: undefined,
isFilebeatFlyoutVisible: false,
};

export class ImportView extends Component {
Expand Down Expand Up @@ -384,6 +386,16 @@ export class ImportView extends Component {
});
};

showFilebeatFlyout = () => {
this.setState({ isFilebeatFlyoutVisible: true });
this.props.hideBottomBar();
};

closeFilebeatFlyout = () => {
this.setState({ isFilebeatFlyoutVisible: false });
this.props.showBottomBar();
};

async loadIndexNames() {
const indices = await ml.getIndices();
const indexNames = indices.map(i => i.name);
Expand Down Expand Up @@ -424,6 +436,7 @@ export class ImportView extends Component {
indexNameError,
indexPatternNameError,
timeFieldName,
isFilebeatFlyoutVisible,
} = this.state;

const createPipeline = pipelineString !== '';
Expand Down Expand Up @@ -549,7 +562,18 @@ export class ImportView extends Component {
indexPatternId={indexPatternId}
timeFieldName={timeFieldName}
createIndexPattern={createIndexPattern}
showFilebeatFlyout={this.showFilebeatFlyout}
/>

{isFilebeatFlyoutVisible && (
<FilebeatConfigFlyout
index={index}
results={this.props.results}
indexPatternId={indexPatternId}
ingestPipelineId={ingestPipelineId}
closeFlyout={this.closeFilebeatFlyout}
/>
)}
</React.Fragment>
)}
</EuiPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ export class MessageImporter extends Importer {
if (this.multilineStartRegex === null || line.match(this.multilineStartRegex) !== null) {
this.addMessage(data, message);
message = '';
} else if (data.length === 0) {
// discard everything before the first line that is considered the first line of a message
// as it could be left over partial data from a spilt or rolled over log,
// or could be a blank line after the header in a csv file
return '';
} else {
message += '\n';
}
Expand Down
Loading