diff --git a/x-pack/plugins/ml/index.js b/x-pack/plugins/ml/index.js
index ce511ad3f7d84..68971bd91cd2c 100644
--- a/x-pack/plugins/ml/index.js
+++ b/x-pack/plugins/ml/index.js
@@ -24,6 +24,7 @@ import { filtersRoutes } from './server/routes/filters';
import { resultsServiceRoutes } from './server/routes/results_service';
import { jobServiceRoutes } from './server/routes/job_service';
import { jobAuditMessagesRoutes } from './server/routes/job_audit_messages';
+import { fileDataVisualizerRoutes } from './server/routes/file_data_visualizer';
export const ml = (kibana) => {
return new kibana.Plugin({
@@ -38,9 +39,10 @@ export const ml = (kibana) => {
description: 'Machine Learning for the Elastic Stack',
icon: 'plugins/ml/ml.svg',
main: 'plugins/ml/app',
+ styleSheetPath: `${__dirname}/public/index.scss`,
},
hacks: ['plugins/ml/hacks/toggle_app_link_in_nav'],
- home: ['plugins/ml/register_feature']
+ home: ['plugins/ml/register_feature'],
},
@@ -72,7 +74,8 @@ export const ml = (kibana) => {
const config = server.config();
return {
kbnIndex: config.get('kibana.index'),
- esServerUrl: config.get('elasticsearch.url')
+ esServerUrl: config.get('elasticsearch.url'),
+ maxPayloadBytes: config.get('server.maxPayloadBytes'),
};
});
@@ -90,6 +93,7 @@ export const ml = (kibana) => {
resultsServiceRoutes(server, commonRouteConfig);
jobServiceRoutes(server, commonRouteConfig);
jobAuditMessagesRoutes(server, commonRouteConfig);
+ fileDataVisualizerRoutes(server, commonRouteConfig);
}
});
diff --git a/x-pack/plugins/ml/public/app.js b/x-pack/plugins/ml/public/app.js
index ad76c56cfdf19..1af1b28286b98 100644
--- a/x-pack/plugins/ml/public/app.js
+++ b/x-pack/plugins/ml/public/app.js
@@ -32,6 +32,7 @@ import 'plugins/ml/components/confirm_modal';
import 'plugins/ml/components/nav_menu';
import 'plugins/ml/components/loading_indicator';
import 'plugins/ml/settings';
+import 'plugins/ml/file_datavisualizer';
import uiRoutes from 'ui/routes';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/_file_datavisualizer.scss b/x-pack/plugins/ml/public/file_datavisualizer/_file_datavisualizer.scss
new file mode 100644
index 0000000000000..4e3f6c009ece3
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/_file_datavisualizer.scss
@@ -0,0 +1,5 @@
+@import 'components/index';
+
+.file-datavisualizer-container {
+ padding: 20px;
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/_index.scss
new file mode 100644
index 0000000000000..9e91116dfcf62
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/_index.scss
@@ -0,0 +1 @@
+@import 'file_datavisualizer';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/_index.scss
new file mode 100644
index 0000000000000..eef52ad53a14f
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/_index.scss
@@ -0,0 +1,4 @@
+@import 'file_datavisualizer_view/index';
+@import 'results_view/index';
+@import 'summary/index';
+@import 'fields_stats/index';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_field_stats_card.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_field_stats_card.scss
new file mode 100644
index 0000000000000..66d1ec2c16c1d
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_field_stats_card.scss
@@ -0,0 +1,13 @@
+.card-container {
+ display: inline-grid;
+ padding: 0px 10px 10px 0px;
+}
+
+.ml-field-data-card {
+ height: 408px;
+
+ .card-contents {
+ height: 378px;
+ line-height: 21px;
+ }
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_fields_stats.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_fields_stats.scss
new file mode 100644
index 0000000000000..ac04fad0ef3e3
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_fields_stats.scss
@@ -0,0 +1,8 @@
+.fields-stats {
+ padding: 10px;
+}
+.field {
+ margin-bottom: 10px;
+}
+
+
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_index.scss
new file mode 100644
index 0000000000000..fe6a232f016a3
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/_index.scss
@@ -0,0 +1,2 @@
+@import 'fields_stats';
+@import 'field_stats_card';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/field_stats_card.js b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/field_stats_card.js
new file mode 100644
index 0000000000000..b5430326dadd5
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/field_stats_card.js
@@ -0,0 +1,96 @@
+/*
+ * 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 from 'react';
+
+import {
+ EuiSpacer,
+
+} from '@elastic/eui';
+
+import { FieldTypeIcon } from '../../../components/field_type_icon';
+
+export function FieldStatsCard({ field }) {
+
+ const percent = Math.round(field.percent * 100) / 100;
+
+ let type = field.type;
+ if (type === 'double' || type === 'long') {
+ type = 'number';
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {field.count} document{(field.count > 1) ? 's' : ''} ({percent}%)
+
+
+ {field.cardinality} distinct value{(field.cardinality > 1) ? 's' : ''}
+
+
+ {
+ (field.mean_value) &&
+
+
+
+
{field.min_value}
+
{field.median_value}
+
{field.max_value}
+
+
+ }
+
+
+ {
+ (field.top_hits) &&
+
+
+
+
+
+
top values
+ {field.top_hits.map((h) => {
+ const pcnt = Math.round(((h.count / field.count) * 100) * 100) / 100;
+ return (
+
+
{h.value}
+
+
{pcnt}%
+
+ );
+ }
+ )}
+
+
+ }
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/fields_stats.js b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/fields_stats.js
new file mode 100644
index 0000000000000..5fca0a6886877
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/fields_stats.js
@@ -0,0 +1,77 @@
+/*
+ * 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, {
+ Component,
+} from 'react';
+
+import { FieldStatsCard } from './field_stats_card';
+
+export class FieldsStats extends Component {
+ constructor(props) {
+ super(props);
+
+ this.fields = createFields(this.props.results);
+ console.log(this.fields);
+ }
+
+ render() {
+ return (
+
+ {
+ this.fields.map(f => (
+
+ ))
+ }
+
+ );
+ }
+}
+
+function createFields(results) {
+ const {
+ mappings,
+ field_stats: fieldStats,
+ num_messages_analyzed: numMessagesAnalyzed,
+ timestamp_field: timestampField,
+ } = results;
+
+ let fields = [];
+
+ if (mappings && fieldStats) {
+ fields = Object.keys(fieldStats).map((fName) => {
+ const field = { name: fName };
+ const f = fieldStats[fName];
+ const m = mappings[fName];
+
+ // sometimes the timestamp field is not in the mappings, and so our
+ // collection of fields will be missing a time field with a type of date
+ if (fName === timestampField && field.type === undefined) {
+ field.type = 'date';
+ }
+
+ if (f !== undefined) {
+ Object.assign(field, f);
+ }
+
+ if (m !== undefined) {
+ field.type = m.type;
+ if (m.format !== undefined) {
+ field.format = m.format;
+ }
+ }
+
+ field.percent = ((field.count / numMessagesAnalyzed) * 100);
+
+ return field;
+ });
+ }
+ return fields;
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/index.js b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/index.js
new file mode 100644
index 0000000000000..f880ae210c841
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/fields_stats/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 { FieldsStats } from './fields_stats';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/_file_contents.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/_file_contents.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/_index.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/file_contents.js b/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/file_contents.js
new file mode 100644
index 0000000000000..b937907d144d4
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/file_contents.js
@@ -0,0 +1,47 @@
+/*
+ * 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 from 'react';
+
+import {
+ EuiTitle,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import { MLJobEditor, EDITOR_MODE } from '../../../jobs/jobs_list/components/ml_job_editor';
+
+export function FileContents({ data, format, numberOfLines }) {
+ let mode = EDITOR_MODE.TEXT;
+ if (format === EDITOR_MODE.JSON) {
+ mode = EDITOR_MODE.JSON;
+ }
+
+ const formattedData = limitByNumberOfLines(data, numberOfLines);
+
+ return (
+
+
+ File contents
+
+
+ First {numberOfLines} line{(numberOfLines > 1) ? 's' : ''}
+
+
+
+
+
+ );
+}
+
+function limitByNumberOfLines(data, numberOfLines) {
+ return data.split('\n').slice(0, numberOfLines).join('\n');
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/index.js b/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/index.js
new file mode 100644
index 0000000000000..439e1fd32ff17
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_contents/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 { FileContents } from './file_contents';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/_file_datavisualizer_view.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/_file_datavisualizer_view.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/_index.scss
new file mode 100644
index 0000000000000..957ca0eec24d3
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/_index.scss
@@ -0,0 +1 @@
+@import 'file_datavisualizer_view'
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/file_datavisualizer_view.js
new file mode 100644
index 0000000000000..c35273e932a1c
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/file_datavisualizer_view.js
@@ -0,0 +1,190 @@
+/*
+ * 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, {
+ Component,
+} from 'react';
+
+import {
+ EuiFilePicker,
+ EuiSpacer,
+ EuiLoadingSpinner,
+} from '@elastic/eui';
+
+import { ml } from '../../../services/ml_api_service';
+import { ResultsView } from '../results_view';
+import { FileCouldNotBeRead, FileTooLarge } from './file_error_callouts';
+
+
+export class FileDataVisualizerView extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ files: {},
+ fileContents: '',
+ fileSize: 0,
+ fileTooLarge: false,
+ fileCouldNotBeRead: false,
+ serverErrorMessage: '',
+ loading: false,
+ loaded: false,
+ results: undefined,
+ };
+
+ this.maxPayloadBytes = this.props.maxPayloadBytes;
+ }
+
+ onChange = (files) => {
+ this.setState({
+ files,
+ loading: (files.length > 0),
+ loaded: false,
+ fileContents: '',
+ fileSize: 0,
+ fileTooLarge: false,
+ fileCouldNotBeRead: false,
+ serverErrorMessage: '',
+ results: undefined,
+ }, () => {
+ if (files.length) {
+ this.analyzeFile(files[0]);
+ }
+ });
+
+ };
+
+ async analyzeFile(file) {
+ if (file.size < this.maxPayloadBytes) {
+ let data = null;
+ try {
+ const fileContents = await readFile(file);
+ data = fileContents.data;
+ this.setState({
+ fileContents: data,
+ fileSize: file.size,
+ });
+
+ } catch (error) {
+ console.error(error);
+ this.setState({
+ loaded: false,
+ loading: false,
+ fileCouldNotBeRead: true,
+ });
+ }
+
+ if (data !== null) {
+ try {
+ const resp = await ml.analyzeFile(data);
+ this.setState({
+ results: resp.results,
+ loaded: true,
+ loading: false,
+ });
+ } catch (error) {
+ console.error(error);
+ const msg = (error.message || '').split('::')[0];
+ this.setState({
+ results: undefined,
+ loaded: false,
+ loading: false,
+ fileCouldNotBeRead: true,
+ serverErrorMessage: msg,
+ });
+ }
+ } else {
+ this.setState({
+ loaded: false,
+ loading: false,
+ fileCouldNotBeRead: true,
+ });
+ }
+ } else {
+ this.setState({
+ loaded: false,
+ loading: false,
+ fileTooLarge: true,
+ fileSize: file.size,
+ });
+ }
+ }
+
+ render() {
+ const {
+ loading,
+ loaded,
+ results,
+ fileContents,
+ fileSize,
+ fileTooLarge,
+ fileCouldNotBeRead,
+ serverErrorMessage,
+ } = this.state;
+
+ return (
+
+
+ this.onChange(files)}
+ />
+
+
+
+
+ {(loading) &&
+
+
+
+ }
+
+ {(fileTooLarge) &&
+
+ }
+
+ {(fileCouldNotBeRead) &&
+
+ }
+
+ {(loaded) &&
+
+ }
+
+ );
+ }
+}
+
+function readFile(file) {
+ return new Promise((resolve, reject) => {
+
+ if (file && file.size) {
+ const reader = new FileReader();
+ reader.readAsText(file);
+
+ reader.onload = (() => {
+ return () => {
+ const data = reader.result;
+ if (data === '') {
+ reject();
+ } else {
+ resolve({ data });
+ }
+ };
+ })(file);
+ } else {
+ reject();
+ }
+ });
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/file_error_callouts.js b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/file_error_callouts.js
new file mode 100644
index 0000000000000..244309040f403
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/file_error_callouts.js
@@ -0,0 +1,40 @@
+/*
+ * 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 from 'react';
+
+import {
+ EuiCallOut,
+} from '@elastic/eui';
+
+export function FileTooLarge({ fileSize, maxFileSize }) {
+ return (
+
+
+ The size of the file you selected for upload is {fileSize} which exceeds the maximum of {maxFileSize} permitted by Kibana
+
+
+ );
+}
+
+export function FileCouldNotBeRead({ error }) {
+ return (
+
+ {
+ (error !== undefined) &&
+ {error}
+ }
+
+ );
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/index.js b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/index.js
new file mode 100644
index 0000000000000..cc9714d2b4c45
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 { FileDataVisualizerView } from './file_datavisualizer_view';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/_index.scss
new file mode 100644
index 0000000000000..af3737cf017de
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/_index.scss
@@ -0,0 +1 @@
+@import 'results_view'
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/_results_view.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/_results_view.scss
new file mode 100644
index 0000000000000..6f5c2826515e6
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/_results_view.scss
@@ -0,0 +1,10 @@
+.results {
+ .euiDescriptionList{
+ dd, dt {
+ margin-top: 5px;
+ }
+ dd:nth-child(1), dt:nth-child(1), {
+ margin-top: 0px;
+ }
+ }
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/index.js b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/index.js
new file mode 100644
index 0000000000000..7dfaa3c440d22
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 { ResultsView } from './results_view';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/results_view.js b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/results_view.js
new file mode 100644
index 0000000000000..0d00063104bfc
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/results_view/results_view.js
@@ -0,0 +1,69 @@
+/*
+ * 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, {
+ Component,
+} from 'react';
+
+import {
+ EuiHorizontalRule,
+ EuiTabbedContent,
+} from '@elastic/eui';
+
+import { FileContents } from '../file_contents';
+import { Summary } from '../summary';
+import { FieldsStats } from '../fields_stats';
+
+export class ResultsView extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+
+ render() {
+ const {
+ data,
+ results
+ } = this.props;
+
+ console.log(results);
+
+ const tabs = [
+ {
+ id: 'file-stats',
+ name: 'File stats',
+ content: ,
+ }
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+ { }}
+ />
+
+
+ );
+ }
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/summary/_index.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/summary/_index.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/summary/_summary.scss b/x-pack/plugins/ml/public/file_datavisualizer/components/summary/_summary.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/summary/index.js b/x-pack/plugins/ml/public/file_datavisualizer/components/summary/index.js
new file mode 100644
index 0000000000000..cdb96978f4cb6
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/summary/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 { Summary } from './summary';
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/summary/summary.js b/x-pack/plugins/ml/public/file_datavisualizer/components/summary/summary.js
new file mode 100644
index 0000000000000..81bc30e88c505
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/components/summary/summary.js
@@ -0,0 +1,80 @@
+/*
+ * 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 from 'react';
+
+import {
+ EuiDescriptionList,
+ EuiTitle,
+} from '@elastic/eui';
+
+export function Summary({ results }) {
+ const items = createDisplayItems(results);
+
+ return (
+
+
+ Summary
+
+
+
+ );
+}
+
+function createDisplayItems(results) {
+ const items = [
+ {
+ title: 'Number of lines analyzed',
+ description: results.num_lines_analyzed,
+ },
+ {
+ title: 'Charset',
+ description: results.charset,
+ }
+ ];
+
+ if (results.format !== undefined) {
+ items.push({
+ title: 'Format',
+ description: results.format,
+ });
+
+ if (results.format === 'delimited') {
+ items.push({
+ title: 'Delimiter',
+ description: results.delimiter,
+ });
+
+ items.push({
+ title: 'Has header row',
+ description: `${results.has_header_row}`,
+ });
+
+ }
+ }
+
+ if (results.timestamp_field !== undefined) {
+ items.push({
+ title: 'Time field',
+ description: results.timestamp_field,
+ });
+ }
+
+ if (results.timestamp_formats !== undefined) {
+ const s = (results.timestamp_formats.length > 1) ? 's' : '';
+ items.push({
+ title: `Time format${s}`,
+ description: results.timestamp_formats.join(', '),
+ });
+ }
+
+ return items;
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js b/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js
new file mode 100644
index 0000000000000..922a2a896c54e
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer.js
@@ -0,0 +1,18 @@
+/*
+ * 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 { FileDataVisualizerView } from './components/file_datavisualizer_view';
+
+import React from 'react';
+
+export function FileDataVisualizerPage({ maxPayloadBytes }) {
+ return (
+
+
+
+ );
+}
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js b/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js
new file mode 100644
index 0000000000000..516e592c0c193
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js
@@ -0,0 +1,44 @@
+/*
+ * 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 'ngreact';
+
+import { uiModules } from 'ui/modules';
+const module = uiModules.get('apps/ml', ['react']);
+
+import { checkLicense } from 'plugins/ml/license/check_license';
+import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
+import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
+import { loadNewJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
+import { initPromise } from 'plugins/ml/util/promise';
+
+import uiRoutes from 'ui/routes';
+
+const template = '';
+
+uiRoutes
+ .when('/filedatavisualizer/?', {
+ template,
+ resolve: {
+ CheckLicense: checkLicense,
+ privileges: checkGetJobsPrivilege,
+ mlNodeCount: getMlNodeCount,
+ loadNewJobDefaults,
+ initPromise: initPromise(true)
+ }
+ });
+
+
+
+import { FileDataVisualizerPage } from './file_datavisualizer';
+
+module.directive('fileDatavisualizerPage', function ($injector) {
+ const maxPayloadBytes = $injector.get('maxPayloadBytes');
+ const reactDirective = $injector.get('reactDirective');
+
+ return reactDirective(FileDataVisualizerPage, undefined, { restrict: 'E' }, { maxPayloadBytes });
+});
diff --git a/x-pack/plugins/ml/public/file_datavisualizer/index.js b/x-pack/plugins/ml/public/file_datavisualizer/index.js
new file mode 100644
index 0000000000000..cd7a23430202a
--- /dev/null
+++ b/x-pack/plugins/ml/public/file_datavisualizer/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 './file_datavisualizer_directive';
diff --git a/x-pack/plugins/ml/public/index.scss b/x-pack/plugins/ml/public/index.scss
new file mode 100644
index 0000000000000..549b8a413fbaa
--- /dev/null
+++ b/x-pack/plugins/ml/public/index.scss
@@ -0,0 +1 @@
+@import 'file_datavisualizer/index';
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/index.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/index.js
index eda2b66187c1d..dc1bb83bf3f4d 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/index.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/index.js
@@ -5,4 +5,4 @@
*/
-export { MLJobEditor } from './ml_job_editor';
+export { MLJobEditor, EDITOR_MODE } from './ml_job_editor';
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js
index 9157408175444..840f39b9b4e67 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js
@@ -12,7 +12,16 @@ import {
EuiCodeEditor
} from '@elastic/eui';
-export function MLJobEditor({ value, height = '500px', width = '100%', mode = 'json', readOnly = false, onChange = () => {} }) {
+export const EDITOR_MODE = { TEXT: 'text', JSON: 'json' };
+
+export function MLJobEditor({
+ value,
+ height = '500px',
+ width = '100%',
+ mode = EDITOR_MODE.JSON,
+ readOnly = false,
+ onChange = () => {}
+}) {
return (
{
method: 'GET'
});
+ ml.fileStructure = ca({
+ urls: [
+ {
+ fmt: '/_xpack/ml/find_file_structure',
+ }
+ ],
+ needBody: true,
+ method: 'POST'
+ });
+
};
diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.js b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.js
new file mode 100644
index 0000000000000..85042c76dae67
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.js
@@ -0,0 +1,79 @@
+/*
+ * 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 Boom from 'boom';
+import fs from 'fs';
+import os from 'os';
+const util = require('util');
+// const readFile = util.promisify(fs.readFile);
+const readdir = util.promisify(fs.readdir);
+const writeFile = util.promisify(fs.writeFile);
+
+export function fileDataVisualizerProvider(callWithRequest) {
+ async function analyzeFile(data) {
+ let cached = false;
+ let results = [];
+ try {
+ results = await callWithRequest('ml.fileStructure', { body: data });
+ cached = await cacheData(data);
+ } catch (error) {
+ throw Boom.badRequest(error);
+ }
+ return {
+ cached,
+ results,
+ };
+ }
+
+ async function cacheData(data) {
+ const outputPath = `${os.tmpdir()}/kibana-ml`;
+ const tempFile = 'es-ml-tempFile';
+ const tempFilePath = `${outputPath}/${tempFile}`;
+
+ try {
+ createOutputDir(outputPath);
+ await deleteOutputFiles(outputPath);
+ await writeFile(tempFilePath, data);
+ return true;
+ } catch (error) {
+ return false;
+ }
+ }
+
+ function createOutputDir(dir) {
+ if (fs.existsSync(dir) === false) {
+ fs.mkdirSync(dir);
+ }
+ }
+
+ async function deleteOutputFiles(outputPath) {
+ const files = await listDirs(outputPath);
+ files.forEach((f) => {
+ fs.unlinkSync(`${outputPath}/${f}`);
+ });
+ }
+
+ async function listDirs(dirName) {
+ const dirs = [];
+ return new Promise((resolve, reject) => {
+ readdir(dirName)
+ .then((fileNames) => {
+ fileNames.forEach((fileName) => {
+ dirs.push(fileName);
+ });
+ resolve(dirs);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+ }
+
+ return {
+ analyzeFile
+ };
+}
diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/index.js b/x-pack/plugins/ml/server/models/file_data_visualizer/index.js
new file mode 100644
index 0000000000000..6185ba1930bbf
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/file_data_visualizer/index.js
@@ -0,0 +1,8 @@
+/*
+ * 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 { fileDataVisualizerProvider } from './file_data_visualizer';
diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.js b/x-pack/plugins/ml/server/routes/file_data_visualizer.js
new file mode 100644
index 0000000000000..8b711320577b5
--- /dev/null
+++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.js
@@ -0,0 +1,32 @@
+/*
+ * 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 { callWithRequestFactory } from '../client/call_with_request_factory';
+import { wrapError } from '../client/errors';
+import { fileDataVisualizerProvider } from '../models/file_data_visualizer';
+
+function analyzeFiles(callWithRequest, data) {
+ const { analyzeFile } = fileDataVisualizerProvider(callWithRequest);
+ return analyzeFile(data);
+}
+
+export function fileDataVisualizerRoutes(server, commonRouteConfig) {
+ server.route({
+ method: 'POST',
+ path: '/api/ml/file_data_visualizer/analyze_file',
+ handler(request, reply) {
+ const callWithRequest = callWithRequestFactory(server, request);
+ const data = request.payload;
+ return analyzeFiles(callWithRequest, data)
+ .then(resp => reply(resp))
+ .catch(resp => reply(wrapError(resp)));
+ },
+ config: {
+ ...commonRouteConfig
+ }
+ });
+}