mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Wizard] Creates a new CSV Add Data Wizard
This commit is contained in:
parent
5918737d86
commit
57c391aa4e
17 changed files with 587 additions and 10 deletions
|
@ -132,6 +132,7 @@
|
|||
"moment": "2.10.6",
|
||||
"moment-timezone": "0.4.1",
|
||||
"node-uuid": "1.4.7",
|
||||
"papaparse": "4.1.2",
|
||||
"raw-loader": "0.5.1",
|
||||
"request": "2.61.0",
|
||||
"rimraf": "2.4.3",
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<file-upload ng-if="!wizard.file" on-locate="wizard.file = file" upload-selector="button.upload">
|
||||
<h2><em>Pick a CSV file to get started.</em>
|
||||
Please follow the instructions below.
|
||||
</h2>
|
||||
|
||||
<div class="upload-wizard-file-upload-container">
|
||||
<div class="upload-instructions">Drop your file here</div>
|
||||
<div class="upload-instructions-separator">or</div>
|
||||
<button class="btn btn-primary btn-lg controls upload" ng-click>
|
||||
Select File
|
||||
</button>
|
||||
<div>Maximum upload file size: 1 GB</div>
|
||||
</div>
|
||||
</file-upload>
|
||||
|
||||
<div class="upload-wizard-file-preview-container" ng-if="wizard.file">
|
||||
<h2><em>Review the sample below.</em>
|
||||
Click next if it looks like we parsed your file correctly.
|
||||
</h2>
|
||||
|
||||
<div ng-if="!!wizard.formattedErrors.length" class="alert alert-warning parse-error">
|
||||
<ul>
|
||||
<li ng-repeat="error in wizard.formattedErrors">{{ error }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="advanced-options form-inline">
|
||||
<span class="form-group">
|
||||
<label>Delimiter</label>
|
||||
<select ng-model="wizard.parseOptions.delimiter"
|
||||
ng-options="option.value as option.label for option in wizard.delimiterOptions"
|
||||
class="form-control">
|
||||
</select>
|
||||
</span>
|
||||
<span class="form-group">
|
||||
<label>Filename:</label>
|
||||
{{ wizard.file.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="preview">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="col in wizard.columns track by $index">
|
||||
<span title="{{ col }}">{{ col | limitTo:12 }}{{ col.length > 12 ? '...' : '' }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="row in wizard.rows">
|
||||
<td ng-repeat="cell in row track by $index">{{ cell }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,79 @@
|
|||
import _ from 'lodash';
|
||||
import Papa from 'papaparse';
|
||||
import modules from 'ui/modules';
|
||||
import template from './parse_csv_step.html';
|
||||
import './styles/_add_data_parse_csv_step.less';
|
||||
|
||||
modules.get('apps/settings')
|
||||
.directive('parseCsvStep', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
scope: {
|
||||
file: '=',
|
||||
parseOptions: '=',
|
||||
samples: '='
|
||||
},
|
||||
bindToController: true,
|
||||
controllerAs: 'wizard',
|
||||
controller: function ($scope) {
|
||||
|
||||
this.delimiterOptions = [
|
||||
{
|
||||
label: 'comma',
|
||||
value: ','
|
||||
},
|
||||
{
|
||||
label: 'tab',
|
||||
value: '\t'
|
||||
},
|
||||
{
|
||||
label: 'space',
|
||||
value: ' '
|
||||
},
|
||||
{
|
||||
label: 'semicolon',
|
||||
value: ';'
|
||||
},
|
||||
{
|
||||
label: 'pipe',
|
||||
value: '|'
|
||||
}
|
||||
];
|
||||
|
||||
this.parse = () => {
|
||||
if (!this.file) return;
|
||||
|
||||
const config = _.assign(
|
||||
{
|
||||
header: true,
|
||||
preview: 10,
|
||||
dynamicTyping: true,
|
||||
complete: (results) => {
|
||||
$scope.$apply(() => {
|
||||
this.formattedErrors = _.map(results.errors, (error) => {
|
||||
return `${error.type} at row ${error.row} - ${error.message}`;
|
||||
});
|
||||
this.columns = results.meta.fields;
|
||||
this.rows = _.map(results.data, _.values);
|
||||
this.samples = results.data;
|
||||
this.parseOptions = _.defaults({}, this.parseOptions, {delimiter: results.meta.delimiter});
|
||||
});
|
||||
}
|
||||
},
|
||||
this.parseOptions
|
||||
);
|
||||
|
||||
Papa.parse(this.file, config);
|
||||
};
|
||||
|
||||
$scope.$watch('wizard.parseOptions', this.parse, true);
|
||||
$scope.$watch('wizard.file', () => {
|
||||
delete this.formattedErrors;
|
||||
this.parse();
|
||||
});
|
||||
|
||||
this.parse();
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
@import (reference) "../../../styles/_add_data_wizard";
|
||||
|
||||
.upload-wizard-file-upload-container {
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: @settings-add-data-wizard-form-control-bg;
|
||||
border: @settings-add-data-wizard-parse-csv-container-border 1px dashed;
|
||||
text-align: center;
|
||||
|
||||
.upload-instructions {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.upload-instructions-separator {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
button.upload {
|
||||
align-self: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-wizard-file-preview-container {
|
||||
.preview {
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
border: @settings-add-data-wizard-parse-csv-container-border 1px solid;
|
||||
|
||||
table {
|
||||
margin-bottom: 0;
|
||||
|
||||
.table-striped()
|
||||
}
|
||||
}
|
||||
|
||||
.parse-error {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.advanced-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 15px;
|
||||
|
||||
label {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<div ng-if="!uploadStep.created && !uploadStep.formattedErrors.length">
|
||||
<h2><em>Sit back, relax, we'll take it from here.</em></h2>
|
||||
|
||||
<div class="loading-message well">
|
||||
We're loading your data now. This may take some time if you selected a large file.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="uploadStep.created || !!uploadStep.formattedErrors.length" class="bulk-results">
|
||||
<h2><em>Upload complete.</em> Let's take a look:</h2>
|
||||
|
||||
<div ng-if="uploadStep.created" class="alert alert-success">
|
||||
Created <strong>{{ uploadStep.created }}</strong> documents!<br/>
|
||||
</div>
|
||||
<div class="alert alert-warning" ng-if="!!uploadStep.formattedErrors.length">
|
||||
We encountered errors while indexing your data
|
||||
<ul>
|
||||
<li ng-repeat="error in uploadStep.formattedErrors">{{ error }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,44 @@
|
|||
import modules from 'ui/modules';
|
||||
import template from './upload_data_step.html';
|
||||
import _ from 'lodash';
|
||||
import IngestProvider from 'ui/ingest';
|
||||
|
||||
modules.get('apps/settings')
|
||||
.directive('uploadDataStep', function () {
|
||||
return {
|
||||
template: template,
|
||||
scope: {
|
||||
results: '='
|
||||
},
|
||||
bindToController: true,
|
||||
controllerAs: 'uploadStep',
|
||||
controller: function ($scope, $http, Notifier, $window, Private) {
|
||||
const ingest = Private(IngestProvider);
|
||||
const notify = new Notifier({
|
||||
location: 'Add Data'
|
||||
});
|
||||
|
||||
const usePipeline = !_.isEmpty(_.get(this.results, 'pipeline.processors'));
|
||||
ingest.uploadCSV(this.results.file, this.results.indexPattern.id, this.results.parseOptions.delimiter, usePipeline)
|
||||
.then(
|
||||
(res) => {
|
||||
this.created = 0;
|
||||
this.formattedErrors = [];
|
||||
_.forEach(res.data, (response) => {
|
||||
this.created += response.created;
|
||||
this.formattedErrors = this.formattedErrors.concat(_.map(_.get(response, 'errors.index'), (doc) => {
|
||||
return `${doc._id.split('-', 1)[0].replace('L', 'Line ').trim()}: ${doc.error.type} - ${doc.error.reason}`;
|
||||
}));
|
||||
if (!_.isEmpty(_.get(response, 'errors.other'))) {
|
||||
this.formattedErrors = this.formattedErrors.concat(response.errors.other);
|
||||
}
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
notify.error(err);
|
||||
$window.scrollTo(0, 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -20,6 +20,13 @@
|
|||
<div>
|
||||
Pick this option if you have log file data you'd like to send to Elasticsearch.
|
||||
</div>
|
||||
|
||||
<h4>
|
||||
<a href="#/settings/indices/create/upload">Upload</a>
|
||||
</h4>
|
||||
<div>
|
||||
Got CSVs? Upload them here. No pain, all gain.
|
||||
</div>
|
||||
</div>
|
||||
</kbn-settings-indices>
|
||||
</kbn-settings-app>
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'plugins/kibana/settings/sections/indices/_create';
|
|||
import 'plugins/kibana/settings/sections/indices/_edit';
|
||||
import 'plugins/kibana/settings/sections/indices/_field_editor';
|
||||
import 'plugins/kibana/settings/sections/indices/filebeat/index';
|
||||
import 'plugins/kibana/settings/sections/indices/upload/index';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import uiModules from 'ui/modules';
|
||||
import indexTemplate from 'plugins/kibana/settings/sections/indices/index.html';
|
||||
|
|
|
@ -52,6 +52,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 6px 35px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<div class="wizard-container">
|
||||
<div class="wizard-step-headings" ng-class="{complete: wizard.complete}">
|
||||
<span ng-class="{active: wizard.currentStep === 0}"
|
||||
class="wizard-step-heading"
|
||||
ng-click="wizard.setCurrentStep(0)">
|
||||
1. Select
|
||||
</span>
|
||||
<span ng-class="{active: wizard.currentStep === 1, aheadActive: wizard.currentStep < 1}"
|
||||
class="wizard-step-heading"
|
||||
ng-click="wizard.currentStep < 1 || wizard.setCurrentStep(1)">
|
||||
2. Parse
|
||||
</span>
|
||||
<span ng-class="{active: wizard.currentStep === 2, aheadActive: wizard.currentStep < 2}"
|
||||
class="wizard-step-heading"
|
||||
ng-click="wizard.currentStep < 2 || wizard.setCurrentStep(2)">
|
||||
3. Review
|
||||
</span>
|
||||
<span ng-class="{active: wizard.currentStep === 3, aheadActive: wizard.currentStep < 3}"
|
||||
class="wizard-step-heading"
|
||||
ng-click="wizard.currentStep < 3 || wizard.setCurrentStep(3)">
|
||||
4. Upload
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div ng-switch="wizard.currentStep">
|
||||
<div ng-switch-when="0">
|
||||
<parse-csv-step file="wizard.stepResults.file" parse-options="wizard.stepResults.parseOptions" samples="wizard.stepResults.samples"></parse-csv-step>
|
||||
<div class="wizard-nav-buttons">
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
ng-disabled="!wizard.stepResults.file"
|
||||
ng-click="wizard.stepResults = undefined">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
ng-disabled="!wizard.stepResults.samples"
|
||||
ng-click="wizard.nextStep()">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-switch-when="1">
|
||||
<pipeline-setup
|
||||
pipeline="wizard.stepResults.pipeline"
|
||||
samples="wizard.stepResults.samples">
|
||||
</pipeline-setup>
|
||||
|
||||
<div class="wizard-nav-buttons">
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
ng-click="wizard.prevStep()">
|
||||
Prev
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
ng-click="wizard.nextStep()">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-switch-when="2">
|
||||
<pattern-review-step
|
||||
index-pattern="wizard.stepResults.indexPattern"
|
||||
pipeline="wizard.stepResults.pipeline"
|
||||
sample-doc="wizard.stepResults.pipeline.output">
|
||||
</pattern-review-step>
|
||||
|
||||
<div class="wizard-nav-buttons">
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
ng-click="wizard.prevStep()">
|
||||
Prev
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
ng-disabled="!wizard.stepResults.indexPattern || !wizard.stepResults.indexPattern.id"
|
||||
ng-click="wizard.save()">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-switch-when="3">
|
||||
<upload-data-step results="wizard.stepResults"></upload-data-step>
|
||||
|
||||
<div class="wizard-nav-buttons">
|
||||
<div></div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
ng-click="wizard.nextStep()">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,99 @@
|
|||
import modules from 'ui/modules';
|
||||
import template from 'plugins/kibana/settings/sections/indices/upload/directives/upload_wizard.html';
|
||||
import IngestProvider from 'ui/ingest';
|
||||
import 'plugins/kibana/settings/sections/indices/add_data_steps/pattern_review_step';
|
||||
import 'plugins/kibana/settings/sections/indices/add_data_steps/parse_csv_step';
|
||||
import 'plugins/kibana/settings/sections/indices/add_data_steps/pipeline_setup';
|
||||
import 'plugins/kibana/settings/sections/indices/add_data_steps/upload_data_step';
|
||||
|
||||
modules.get('apps/settings')
|
||||
.directive('uploadWizard', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
scope: {},
|
||||
bindToController: true,
|
||||
controllerAs: 'wizard',
|
||||
controller: function ($scope, AppState, safeConfirm, kbnUrl, $http, Notifier, $window, config, Private) {
|
||||
const ingest = Private(IngestProvider);
|
||||
const $state = this.state = new AppState();
|
||||
|
||||
var notify = new Notifier({
|
||||
location: 'Add Data'
|
||||
});
|
||||
|
||||
var totalSteps = 4;
|
||||
this.stepResults = {};
|
||||
|
||||
this.setCurrentStep = (step) => {
|
||||
if (!this.complete) {
|
||||
$state.currentStep = step;
|
||||
$state.save();
|
||||
}
|
||||
};
|
||||
this.setCurrentStep(0);
|
||||
|
||||
this.nextStep = () => {
|
||||
if ($state.currentStep + 1 < totalSteps) {
|
||||
this.setCurrentStep($state.currentStep + 1);
|
||||
}
|
||||
else if ($state.currentStep + 1 === totalSteps) {
|
||||
kbnUrl.change('/discover');
|
||||
}
|
||||
};
|
||||
|
||||
this.prevStep = () => {
|
||||
if ($state.currentStep > 0) {
|
||||
this.setCurrentStep($state.currentStep - 1);
|
||||
}
|
||||
};
|
||||
|
||||
this.save = () => {
|
||||
const processors = this.stepResults.pipeline.processors.map(processor => processor.model);
|
||||
return ingest.save(this.stepResults.indexPattern, processors)
|
||||
.then(
|
||||
() => {
|
||||
this.nextStep();
|
||||
},
|
||||
(err) => {
|
||||
notify.error(err);
|
||||
$window.scrollTo(0,0);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.$watch('wizard.state.currentStep', (newValue, oldValue) => {
|
||||
if (this.complete) {
|
||||
$state.currentStep = totalSteps - 1;
|
||||
$state.save();
|
||||
return;
|
||||
}
|
||||
if (newValue + 1 === totalSteps) {
|
||||
this.complete = true;
|
||||
}
|
||||
if (newValue < oldValue) {
|
||||
return safeConfirm('Going back will reset any changes you\'ve made to this step, do you want to continue?')
|
||||
.then(
|
||||
() => {
|
||||
if ($state.currentStep < 1) {
|
||||
delete this.stepResults.pipeline;
|
||||
}
|
||||
if ($state.currentStep < 2) {
|
||||
delete this.stepResults.indexPattern;
|
||||
}
|
||||
this.currentStep = newValue;
|
||||
},
|
||||
() => {
|
||||
$state.currentStep = oldValue;
|
||||
$state.save();
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.currentStep = newValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<kbn-settings-app section="Upload a CSV">
|
||||
<upload-wizard />
|
||||
</kbn-settings-app>
|
|
@ -0,0 +1,7 @@
|
|||
import routes from 'ui/routes';
|
||||
import template from 'plugins/kibana/settings/sections/indices/upload/index.html';
|
||||
import './directives/upload_wizard';
|
||||
|
||||
routes.when('/settings/indices/create/upload', {
|
||||
template: template
|
||||
});
|
|
@ -204,4 +204,3 @@ kbn-settings-indices {
|
|||
.kbn-settings-indices-create {
|
||||
.time-and-pattern > div {}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Sets the default index if there isn\'t one already', function () {
|
||||
$httpBackend
|
||||
.when('POST', '../api/kibana/ingest')
|
||||
.when('POST', '/api/kibana/ingest')
|
||||
.respond('ok');
|
||||
|
||||
expect(config.get('defaultIndex')).to.be(null);
|
||||
|
@ -38,7 +38,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Returns error from ingest API if there is one', function (done) {
|
||||
$httpBackend
|
||||
.expectPOST('../api/kibana/ingest')
|
||||
.expectPOST('/api/kibana/ingest')
|
||||
.respond(400);
|
||||
|
||||
ingest.save({id: 'foo'})
|
||||
|
@ -57,7 +57,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Broadcasts an ingest:updated event on the rootScope upon succesful save', function () {
|
||||
$httpBackend
|
||||
.when('POST', '../api/kibana/ingest')
|
||||
.when('POST', '/api/kibana/ingest')
|
||||
.respond('ok');
|
||||
|
||||
ingest.save({id: 'foo'});
|
||||
|
@ -75,7 +75,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Calls the DELETE endpoint of the ingest API with the given id', function () {
|
||||
$httpBackend
|
||||
.expectDELETE('../api/kibana/ingest/foo')
|
||||
.expectDELETE('/api/kibana/ingest/foo')
|
||||
.respond('ok');
|
||||
|
||||
ingest.delete('foo');
|
||||
|
@ -84,7 +84,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Returns error from ingest API if there is one', function (done) {
|
||||
$httpBackend
|
||||
.expectDELETE('../api/kibana/ingest/foo')
|
||||
.expectDELETE('/api/kibana/ingest/foo')
|
||||
.respond(404);
|
||||
|
||||
ingest.delete('foo')
|
||||
|
@ -103,7 +103,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Broadcasts an ingest:updated event on the rootScope upon succesful save', function () {
|
||||
$httpBackend
|
||||
.when('DELETE', '../api/kibana/ingest/foo')
|
||||
.when('DELETE', '/api/kibana/ingest/foo')
|
||||
.respond('ok');
|
||||
|
||||
ingest.delete('foo');
|
||||
|
@ -114,11 +114,53 @@ describe('Ingest Service', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('uploadCSV', function () {
|
||||
it('throws an error if file and index pattern are not provided', function () {
|
||||
expect(ingest.uploadCSV).to.throwException(/file is required/);
|
||||
expect(ingest.uploadCSV).withArgs('foo').to.throwException(/index pattern is required/);
|
||||
});
|
||||
|
||||
it('POSTs to the kibana _data endpoint with the correct params and the file attached as multipart/form-data', function () {
|
||||
$httpBackend
|
||||
.expectPOST('/api/kibana/foo/_data?csv_delimiter=;&pipeline=true', function (data) {
|
||||
// The assertions we can do here are limited because of poor browser support for FormData methods
|
||||
return data instanceof FormData;
|
||||
})
|
||||
.respond('ok');
|
||||
|
||||
const file = new Blob(['foo,bar'], {type : 'text/csv'});
|
||||
|
||||
ingest.uploadCSV(file, 'foo', ';', true);
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('Returns error from the data API if there is one', function (done) {
|
||||
$httpBackend
|
||||
.expectPOST('/api/kibana/foo/_data?csv_delimiter=;&pipeline=true')
|
||||
.respond(404);
|
||||
|
||||
const file = new Blob(['foo,bar'], {type : 'text/csv'});
|
||||
|
||||
ingest.uploadCSV(file, 'foo', ';', true)
|
||||
.then(
|
||||
() => {
|
||||
throw new Error('expected an error response');
|
||||
},
|
||||
(error) => {
|
||||
expect(error.status).to.be(404);
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProcessors', () => {
|
||||
|
||||
it('Calls the processors GET endpoint of the ingest API', function () {
|
||||
$httpBackend
|
||||
.expectGET('../api/kibana/ingest/processors')
|
||||
.expectGET('/api/kibana/ingest/processors')
|
||||
.respond('ok');
|
||||
|
||||
ingest.getProcessors();
|
||||
|
@ -127,7 +169,7 @@ describe('Ingest Service', function () {
|
|||
|
||||
it('Throws user-friendly error when there is an error in the request', function (done) {
|
||||
$httpBackend
|
||||
.when('GET', '../api/kibana/ingest/processors')
|
||||
.when('GET', '/api/kibana/ingest/processors')
|
||||
.respond(404);
|
||||
|
||||
ingest.getProcessors()
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../plugins/kibana/common/lib/case_conversion';
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
export default function IngestProvider($rootScope, $http, config, $q) {
|
||||
|
||||
const ingestAPIPrefix = '../api/kibana/ingest';
|
||||
const ingestAPIPrefix = chrome.addBasePath('/api/kibana/ingest');
|
||||
|
||||
this.save = function (indexPattern, pipeline) {
|
||||
if (_.isEmpty(indexPattern)) {
|
||||
|
@ -71,4 +72,30 @@ export default function IngestProvider($rootScope, $http, config, $q) {
|
|||
});
|
||||
};
|
||||
|
||||
this.uploadCSV = function (file, indexPattern, delimiter, pipeline) {
|
||||
if (_.isUndefined(file)) {
|
||||
throw new Error('file is required');
|
||||
}
|
||||
if (_.isUndefined(indexPattern)) {
|
||||
throw new Error('index pattern is required');
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('csv', file);
|
||||
|
||||
const params = {};
|
||||
if (!_.isUndefined(delimiter)) {
|
||||
params.csv_delimiter = delimiter;
|
||||
}
|
||||
if (!_.isUndefined(pipeline)) {
|
||||
params.pipeline = pipeline;
|
||||
}
|
||||
|
||||
return $http.post(chrome.addBasePath(`/api/kibana/${indexPattern}/_data`), formData, {
|
||||
params: params,
|
||||
transformRequest: angular.identity,
|
||||
headers: {'Content-Type': undefined}
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -138,6 +138,9 @@
|
|||
|
||||
@settings-filebeat-wizard-processor-container-overlay-bg: fade(#000, 10%);
|
||||
|
||||
// Settings - Add Data Wizard - Parse CSV
|
||||
@settings-add-data-wizard-parse-csv-container-border: @kibanaBlue3;
|
||||
|
||||
// Visualize ===================================================================
|
||||
@visualize-show-spy-border: @gray-lighter;
|
||||
@visualize-show-spy-bg: @white;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue