Merge branch 'master' of github.com:elastic/kibana into upgrade/eslint-try2

This commit is contained in:
spalger 2016-12-09 21:14:32 -07:00
commit 1224b1829a
267 changed files with 4335 additions and 8842 deletions

View file

@ -133,7 +133,7 @@ to open *Management/Kibana/Saved Objects/Dashboards*.
[[sharing-dashboards]]
== Sharing a Dashboard
You can can share a direct link to a Kibana dashboard with another user,
You can either share a direct link to a Kibana dashboard with another user,
or embed the dashboard in a web page. Users must have Kibana access
to view embedded dashboards.

View file

@ -32,8 +32,8 @@ sophisticated date parsing APIs that Kibana uses to determine date information,
specify dates in the index pattern name.
+
. Click *Create* to add the index pattern. This first pattern is automatically configured as the default.
When you have more than one index pattern, you can designate which one to use as the default from
*Settings > Indices*.
When you have more than one index pattern, you can designate which one to use as the default by clicking
on the star icon above the index pattern title from *Management > Index Patterns*.
All done! Kibana is now connected to your Elasticsearch data. Kibana displays a read-only list of fields
configured for the matching index.

View file

@ -33,6 +33,7 @@ different series.
instructions.
<<metric-chart,Metric>>:: Display a single number.
<<pie-chart,Pie chart>>:: Display each source's contribution to a total.
<<tagcloud-chart,Tag cloud>>:: Display words as a cloud in which the size of the word correspond to its importance
<<tilemap,Tile map>>:: Associate the results of an aggregation with geographic
locations.
Timeseries:: Compute and combine data from multiple time series

View file

@ -1,5 +1,5 @@
[[tagcloud-chart]]
== Cloud Tag Charts
== Tag Clouds
A tag cloud visualization is a visual representation of text data, typically used to visualize free form text.
Tags are usually single words, and the importance of each tag is shown with font size or color.

View file

@ -53,9 +53,7 @@ describe('plugins/elasticsearch', function () {
expect(params.body.mappings.config.properties)
.to.have.property('buildNum');
expect(params.body.mappings.config.properties.buildNum)
.to.have.property('type', 'string');
expect(params.body.mappings.config.properties.buildNum)
.to.have.property('index', 'not_analyzed');
.to.have.property('type', 'keyword');
});
});

View file

@ -2,8 +2,7 @@ export const mappings = {
config: {
properties: {
buildNum: {
type: 'string',
index: 'not_analyzed'
type: 'keyword'
}
}
},

View file

@ -16,7 +16,6 @@ export default function HistogramVisType(Private) {
'effect on the series above it.',
params: {
defaults: {
shareYAxis: true,
addTooltip: true,
addLegend: true,
legendPosition: 'right',
@ -27,8 +26,7 @@ export default function HistogramVisType(Private) {
times: [],
addTimeMarker: false,
defaultYExtents: false,
setYExtents: false,
yAxis: {}
setYExtents: false
},
legendPositions: [{
value: 'left',

View file

@ -14,7 +14,6 @@ export default function HistogramVisType(Private) {
'exact numbers or percentages. If you are not sure which chart you need, you could do worse than to start here.',
params: {
defaults: {
shareYAxis: true,
addTooltip: true,
addLegend: true,
legendPosition: 'right',
@ -23,8 +22,7 @@ export default function HistogramVisType(Private) {
times: [],
addTimeMarker: false,
defaultYExtents: false,
setYExtents: false,
yAxis: {}
setYExtents: false
},
legendPositions: [{
value: 'left',

View file

@ -14,7 +14,6 @@ export default function HistogramVisType(Private) {
'Be careful with sparse sets as the connection between points can be misleading.',
params: {
defaults: {
shareYAxis: true,
addTooltip: true,
addLegend: true,
legendPosition: 'right',
@ -27,8 +26,7 @@ export default function HistogramVisType(Private) {
times: [],
addTimeMarker: false,
defaultYExtents: false,
setYExtents: false,
yAxis: {}
setYExtents: false
},
legendPositions: [{
value: 'left',

View file

@ -14,7 +14,6 @@ export default function HistogramVisType(Private) {
'Pro Tip: Pie charts are best used sparingly, and with no more than 7 slices per pie.',
params: {
defaults: {
shareYAxis: true,
addTooltip: true,
addLegend: true,
legendPosition: 'right',

View file

@ -2,12 +2,13 @@ import angular from 'angular';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import 'plugins/kibana/dashboard/services/_saved_dashboard';
import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../components/panel/lib/panel_state';
describe('dashboard panels', function () {
let $scope;
let $el;
const compile = (dashboard) => {
function compile(dashboard) {
ngMock.inject(($rootScope, $controller, $compile, $route) => {
$scope = $rootScope.$new();
$route.current = {
@ -19,12 +20,16 @@ describe('dashboard panels', function () {
$el = angular.element(`
<dashboard-app>
<dashboard-grid style="width: 600px; height: 600px;"></dashboard-grid>
</<dashboard-app>`);
</dashboard-app>`);
$compile($el)($scope);
$scope.$digest();
});
};
function findPanelWithVisualizationId(id) {
return $scope.state.panels.find((panel) => { return panel.id === id; });
}
beforeEach(() => {
ngMock.module('kibana');
});
@ -77,10 +82,30 @@ describe('dashboard panels', function () {
compile(dash);
});
expect($scope.state.panels.length).to.be(16);
const foo8Panel = $scope.state.panels.find(
(panel) => { return panel.id === 'foo8'; });
const foo8Panel = findPanelWithVisualizationId('foo8');
expect(foo8Panel).to.not.be(null);
expect(foo8Panel.row).to.be(8);
expect(foo8Panel.col).to.be(1);
});
it('initializes visualizations with the default size', function () {
ngMock.inject((SavedDashboard) => {
let dash = new SavedDashboard();
dash.init();
dash.panelsJSON = `[
{"col":3,"id":"foo1","row":1,"type":"visualization"},
{"col":5,"id":"foo2","row":1,"size_x":5,"size_y":9,"type":"visualization"}]`;
compile(dash);
});
expect($scope.state.panels.length).to.be(2);
const foo1Panel = findPanelWithVisualizationId('foo1');
expect(foo1Panel).to.not.be(null);
expect(foo1Panel.size_x).to.be(DEFAULT_PANEL_WIDTH);
expect(foo1Panel.size_y).to.be(DEFAULT_PANEL_HEIGHT);
const foo2Panel = findPanelWithVisualizationId('foo2');
expect(foo2Panel).to.not.be(null);
expect(foo2Panel.size_x).to.be(5);
expect(foo2Panel.size_y).to.be(9);
});
});

View file

@ -0,0 +1,35 @@
export const DEFAULT_PANEL_WIDTH = 3;
export const DEFAULT_PANEL_HEIGHT = 2;
/**
* Represents a panel on a grid. Keeps track of position in the grid and what visualization it
* contains.
*
* @typedef {Object} PanelState
* @property {number} id - Id of the visualization contained in the panel.
* @property {Element} $el - A reference to the gridster widget holding this panel. Used to
* update the size and column attributes. TODO: move out of panel state as this couples state to ui.
* @property {string} type - Type of the visualization in the panel.
* @property {number} panelId - Unique id to represent this panel in the grid.
* @property {number} size_x - Width of the panel.
* @property {number} size_y - Height of the panel.
* @property {number} col - Column index in the grid.
* @property {number} row - Row index in the grid.
*/
/**
* Creates and initializes a basic panel state.
* @param {number} id
* @param {string} type
* @param {number} panelId
* @return {PanelState}
*/
export function createPanelState(id, type, panelId) {
return {
size_x: DEFAULT_PANEL_WIDTH,
size_y: DEFAULT_PANEL_HEIGHT,
panelId: panelId,
type: type,
id: id
};
}

View file

@ -0,0 +1,45 @@
import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from 'plugins/kibana/dashboard/components/panel/lib/panel_state';
export class PanelUtils {
/**
* Fills in default parameters where not specified.
* @param {PanelState} panel
*/
static initializeDefaults(panel) {
panel.size_x = panel.size_x || DEFAULT_PANEL_WIDTH;
panel.size_y = panel.size_y || DEFAULT_PANEL_HEIGHT;
if (!panel.id) {
// In the interest of backwards comparability
if (panel.visId) {
panel.id = panel.visId;
panel.type = 'visualization';
delete panel.visId;
} else {
throw new Error('Missing object id on panel');
}
}
}
/**
* Ensures that the panel object has the latest size/pos info.
* @param {PanelState} panel
*/
static refreshSizeAndPosition(panel) {
const data = panel.$el.coords().grid;
panel.size_x = data.size_x;
panel.size_y = data.size_y;
panel.col = data.col;
panel.row = data.row;
}
/**
* $el is a circular structure because it contains a reference to it's parent panel,
* so it needs to be removed before it can be serialized (we also don't
* want it to show up in the url).
* @param {PanelState} panel
*/
static makeSerializeable(panel) {
delete panel.$el;
}
}

View file

@ -4,13 +4,13 @@
{{::savedObj.title}}
</span>
<div class="btn-group">
<a aria-label="Edit" ng-show="chrome.getVisible() && editUrl" ng-href="{{::editUrl}}">
<a aria-label="Edit" ng-show="!isFullScreenMode && editUrl" ng-href="{{::editUrl}}">
<i aria-hidden="true" class="fa fa-pencil"></i>
</a>
<a aria-label="Move" ng-show="chrome.getVisible()" class="panel-move">
<a aria-label="Move" ng-show="!isFullScreenMode" class="panel-move">
<i aria-hidden="true" class="fa fa-arrows"></i>
</a>
<a aria-label="Remove" ng-show="chrome.getVisible()" ng-click="remove()">
<a aria-label="Remove" ng-show="!isFullScreenMode" ng-click="remove()">
<i aria-hidden="true" class="fa fa-times"></i>
</a>
</div>
@ -26,7 +26,7 @@
ng-switch-when="visualization"
vis="savedObj.vis"
search-source="savedObj.searchSource"
show-spy-panel="chrome.getVisible()"
show-spy-panel="!isFullScreenMode"
ui-state="uiState"
render-counter
class="panel-content">

View file

@ -1,85 +0,0 @@
import _ from 'lodash';
import 'ui/visualize';
import 'ui/doc_table';
import { loadPanelProvider } from 'plugins/kibana/dashboard/components/panel/lib/load_panel';
import FilterManagerProvider from 'ui/filter_manager';
import uiModules from 'ui/modules';
import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html';
uiModules
.get('app/dashboard')
.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector) {
const loadPanel = Private(loadPanelProvider);
const filterManager = Private(FilterManagerProvider);
const services = require('plugins/kibana/management/saved_object_registry').all().map(function (serviceObj) {
const service = $injector.get(serviceObj.service);
return {
type: service.type,
name: serviceObj.service
};
});
const getPanelId = function (panel) {
return ['P', panel.panelIndex].join('-');
};
return {
restrict: 'E',
template: panelTemplate,
link: function ($scope) {
// using $scope inheritance, panels are available in AppState
const $state = $scope.state;
// receives $scope.panel from the dashboard grid directive, seems like should be isolate?
$scope.$watch('id', function () {
if (!$scope.panel.id || !$scope.panel.type) return;
loadPanel($scope.panel, $scope)
.then(function (panelConfig) {
// These could be done in loadPanel, putting them here to make them more explicit
$scope.savedObj = panelConfig.savedObj;
$scope.editUrl = panelConfig.editUrl;
$scope.$on('$destroy', function () {
panelConfig.savedObj.destroy();
$scope.parentUiState.removeChild(getPanelId(panelConfig.panel));
});
// create child ui state from the savedObj
const uiState = panelConfig.uiState || {};
$scope.uiState = $scope.parentUiState.createChild(getPanelId(panelConfig.panel), uiState, true);
const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef
if (panelSavedVis) {
panelSavedVis.setUiState($scope.uiState);
}
$scope.filter = function (field, value, operator) {
const index = $scope.savedObj.searchSource.get('index').id;
filterManager.add(field, value, operator, index);
};
})
.catch(function (e) {
$scope.error = e.message;
// If the savedObjectType matches the panel type, this means the object itself has been deleted,
// so we shouldn't even have an edit link. If they don't match, it means something else is wrong
// with the object (but the object still exists), so we link to the object editor instead.
const objectItselfDeleted = e.savedObjectType === $scope.panel.type;
if (objectItselfDeleted) return;
const type = $scope.panel.type;
const id = $scope.panel.id;
const service = _.find(services, { type: type });
if (!service) return;
$scope.editUrl = '#management/kibana/objects/' + service.name + '/' + id + '?notFound=' + e.savedObjectType;
});
});
$scope.remove = function () {
_.pull($state.panels, $scope.panel);
};
}
};
});

View file

@ -0,0 +1,104 @@
import _ from 'lodash';
import 'ui/visualize';
import 'ui/doc_table';
import { loadPanelProvider } from 'plugins/kibana/dashboard/components/panel/lib/load_panel';
import FilterManagerProvider from 'ui/filter_manager';
import uiModules from 'ui/modules';
import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html';
uiModules
.get('app/dashboard')
.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector) {
const loadPanel = Private(loadPanelProvider);
const filterManager = Private(FilterManagerProvider);
const services = require('plugins/kibana/management/saved_object_registry').all().map(function (serviceObj) {
const service = $injector.get(serviceObj.service);
return {
type: service.type,
name: serviceObj.service
};
});
/**
* Returns a unique id for storing the panel state in the persistent ui.
* @param {PanelState} panel
* @returns {string}
*/
const getPersistedStateId = function (panel) {
return `P-${panel.panelId}`;
};
return {
restrict: 'E',
template: panelTemplate,
scope: {
/**
* Whether or not the dashboard this panel is contained on is in 'full screen mode'.
* @type {boolean}
*/
isFullScreenMode: '=',
/**
* The parent's persisted state is used to create a child persisted state for the
* panel.
* @type {PersistedState}
*/
parentUiState: '=',
/**
* Contains information about this panel.
* @type {PanelState}
*/
panel: '=',
/**
* Handles removing this panel from the grid.
* @type {() => void}
*/
remove: '&'
},
link: function ($scope, element) {
if (!$scope.panel.id || !$scope.panel.type) return;
loadPanel($scope.panel, $scope)
.then(function (panelConfig) {
// These could be done in loadPanel, putting them here to make them more explicit
$scope.savedObj = panelConfig.savedObj;
$scope.editUrl = panelConfig.editUrl;
element.on('$destroy', function () {
panelConfig.savedObj.destroy();
$scope.parentUiState.removeChild(getPersistedStateId(panelConfig.panel));
$scope.$destroy();
});
// create child ui state from the savedObj
const uiState = panelConfig.uiState || {};
$scope.uiState = $scope.parentUiState.createChild(getPersistedStateId(panelConfig.panel), uiState, true);
const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef
if (panelSavedVis) {
panelSavedVis.setUiState($scope.uiState);
}
$scope.filter = function (field, value, operator) {
const index = $scope.savedObj.searchSource.get('index').id;
filterManager.add(field, value, operator, index);
};
})
.catch(function (e) {
$scope.error = e.message;
// If the savedObjectType matches the panel type, this means the object itself has been deleted,
// so we shouldn't even have an edit link. If they don't match, it means something else is wrong
// with the object (but the object still exists), so we link to the object editor instead.
const objectItselfDeleted = e.savedObjectType === $scope.panel.type;
if (objectItselfDeleted) return;
const type = $scope.panel.type;
const id = $scope.panel.id;
const service = _.find(services, { type: type });
if (!service) return;
$scope.editUrl = '#management/kibana/objects/' + service.name + '/' + id + '?notFound=' + e.savedObjectType;
});
}
};
});

View file

@ -3,6 +3,7 @@ import $ from 'jquery';
import Binder from 'ui/binder';
import 'gridster';
import uiModules from 'ui/modules';
import { PanelUtils } from 'plugins/kibana/dashboard/components/panel/lib/panel_utils';
const app = uiModules.get('app/dashboard');
@ -33,6 +34,24 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
// debounced layout function is safe to call as much as possible
const safeLayout = _.debounce(layout, 200);
$scope.removePanelFromState = (panelId) => {
_.remove($scope.state.panels, function (panel) {
return panel.panelId === panelId;
});
};
/**
* Removes the panel with the given id from the $scope.state.panels array. Does not
* remove the ui element from gridster - that is triggered by a watcher that is
* triggered on changes made to $scope.state.panels.
* @param panelId {number}
*/
$scope.getPanelByPanelId = (panelId) => {
return _.find($scope.state.panels, function (panel) {
return panel.panelId === panelId;
});
};
function init() {
$el.addClass('gridster');
@ -90,7 +109,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
};
// ensure that every panel can be serialized now that we are done
$state.panels.forEach(makePanelSerializeable);
$state.panels.forEach(PanelUtils.makeSerializeable);
// alert interested parties that we have finished processing changes to the panels
// TODO: change this from event based to calling a method on dashboardApp
@ -108,7 +127,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
panel.$el.stop();
removePanel(panel, true);
// not that we will, but lets be safe
makePanelSerializeable(panel);
PanelUtils.makeSerializeable(panel);
});
});
@ -121,81 +140,44 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
// return the panel object for an element.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ALWAYS CALL makePanelSerializeable AFTER YOU ARE DONE WITH IT
// ALWAYS CALL PanelUtils.makeSerializeable AFTER YOU ARE DONE WITH IT
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
function getPanelFor(el) {
const $panel = el.jquery ? el : $(el);
const panel = $panel.data('panel');
panel.$el = $panel;
panel.$scope = $panel.data('$scope');
return panel;
}
// since the $el and $scope are circular structures, they need to be
// removed from panel before it can be serialized (we also wouldn't
// want them to show up in the url)
function makePanelSerializeable(panel) {
delete panel.$el;
delete panel.$scope;
}
// tell gridster to remove the panel, and cleanup our metadata
function removePanel(panel, silent) {
// remove from grister 'silently' (don't reorganize after)
gridster.remove_widget(panel.$el, silent);
// destroy the scope
panel.$scope.$destroy();
panel.$el.removeData('panel');
panel.$el.removeData('$scope');
}
// tell gridster to add the panel, and create additional meatadata like $scope
function addPanel(panel) {
_.defaults(panel, {
size_x: 3,
size_y: 2
});
PanelUtils.initializeDefaults(panel);
// ignore panels that don't have vis id's
if (!panel.id) {
// In the interest of backwards compat
if (panel.visId) {
panel.id = panel.visId;
panel.type = 'visualization';
delete panel.visId;
} else {
throw new Error('missing object id on panel');
}
}
panel.$scope = $scope.$new();
panel.$scope.panel = panel;
panel.$scope.parentUiState = $scope.uiState;
panel.$el = $compile('<li><dashboard-panel></li>')(panel.$scope);
const panelHtml = `
<li>
<dashboard-panel remove="removePanelFromState(${panel.panelId})"
panel="getPanelByPanelId(${panel.panelId})"
is-full-screen-mode="!chrome.getVisible()"
parent-ui-state="uiState">
</li>`;
panel.$el = $compile(panelHtml)($scope);
// tell gridster to use the widget
gridster.add_widget(panel.$el, panel.size_x, panel.size_y, panel.col, panel.row);
// update size/col/etc.
refreshPanelStats(panel);
// Gridster may change the position of the widget when adding it, make sure the panel
// contains the latest info.
PanelUtils.refreshSizeAndPosition(panel);
// stash the panel and it's scope in the element's data
// stash the panel in the element's data
panel.$el.data('panel', panel);
panel.$el.data('$scope', panel.$scope);
}
// ensure that the panel object has the latest size/pos info
function refreshPanelStats(panel) {
const data = panel.$el.coords().grid;
panel.size_x = data.size_x;
panel.size_y = data.size_y;
panel.col = data.col;
panel.row = data.row;
}
// when gridster tell us it made a change, update each of the panel objects
@ -203,9 +185,8 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
// ensure that our panel objects keep their size in sync
gridster.$widgets.each(function (i, el) {
const panel = getPanelFor(el);
refreshPanelStats(panel);
panel.$scope.$broadcast('resize');
makePanelSerializeable(panel);
PanelUtils.refreshSizeAndPosition(panel);
PanelUtils.makeSerializeable(panel);
$scope.$root.$broadcast('change:vis');
});
}

View file

@ -7,7 +7,7 @@ import 'ui/notify';
import 'ui/typeahead';
import 'ui/share';
import 'plugins/kibana/dashboard/directives/grid';
import 'plugins/kibana/dashboard/components/panel/panel';
import 'plugins/kibana/dashboard/directives/dashboard_panel';
import 'plugins/kibana/dashboard/services/saved_dashboards';
import 'plugins/kibana/dashboard/styles/main.less';
import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter';
@ -17,6 +17,7 @@ import uiRoutes from 'ui/routes';
import uiModules from 'ui/modules';
import indexTemplate from 'plugins/kibana/dashboard/index.html';
import { savedDashboardRegister } from 'plugins/kibana/dashboard/services/saved_dashboard_register';
import { createPanelState } from 'plugins/kibana/dashboard/components/panel/lib/panel_state';
require('ui/saved_objects/saved_object_registry').register(savedDashboardRegister);
const app = uiModules.get('app/dashboard', [
@ -152,7 +153,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
docTitle.change(dash.title);
}
initPanelIndices();
initPanelIds();
// watch for state changes and update the appStatus.dirty value
stateMonitor = stateMonitorFactory.create($state, stateDefaults);
@ -171,24 +172,23 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
$scope.$emit('application.load');
}
function initPanelIndices() {
// find the largest panelIndex in all the panels
let maxIndex = getMaxPanelIndex();
function initPanelIds() {
// find the largest panelId in all the panels
let maxIndex = getMaxPanelId();
// ensure that all panels have a panelIndex
// ensure that all panels have a panelId
$scope.state.panels.forEach(function (panel) {
if (!panel.panelIndex) {
panel.panelIndex = maxIndex++;
if (!panel.panelId) {
panel.panelId = maxIndex++;
}
});
}
function getMaxPanelIndex() {
let index = $scope.state.panels.reduce(function (idx, panel) {
// if panel is missing an index, add one and increment the index
return Math.max(idx, panel.panelIndex || idx);
function getMaxPanelId() {
let maxId = $scope.state.panels.reduce(function (id, panel) {
return Math.max(id, panel.panelId || id);
}, 0);
return ++index;
return ++maxId;
}
function updateQueryOnRootSource() {
@ -272,12 +272,12 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
// called by the saved-object-finder when a user clicks a vis
$scope.addVis = function (hit) {
pendingVis++;
$state.panels.push({ id: hit.id, type: 'visualization', panelIndex: getMaxPanelIndex() });
$state.panels.push(createPanelState(hit.id, 'visualization', getMaxPanelId()));
};
$scope.addSearch = function (hit) {
pendingVis++;
$state.panels.push({ id: hit.id, type: 'search', panelIndex: getMaxPanelIndex() });
$state.panels.push(createPanelState(hit.id, 'search', getMaxPanelId()));
};
// Setup configurable values for config directive, after objects are initialized

View file

@ -46,20 +46,20 @@ module.factory('SavedDashboard', function (courier, config) {
// if type:dashboard has no mapping, we push this mapping into ES
SavedDashboard.mapping = {
title: 'string',
title: 'text',
hits: 'integer',
description: 'string',
panelsJSON: 'string',
optionsJSON: 'string',
uiStateJSON: 'string',
description: 'text',
panelsJSON: 'text',
optionsJSON: 'text',
uiStateJSON: 'text',
version: 'integer',
timeRestore: 'boolean',
timeTo: 'string',
timeFrom: 'string',
timeTo: 'keyword',
timeFrom: 'keyword',
refreshInterval: {
type: 'object',
properties: {
display: {type: 'string'},
display: {type: 'keyword'},
pause: { type: 'boolean'},
section: { type: 'integer'},
value: { type: 'integer'}

View file

@ -31,11 +31,11 @@ module.factory('SavedSearch', function (courier) {
SavedSearch.type = 'search';
SavedSearch.mapping = {
title: 'string',
description: 'string',
title: 'text',
description: 'text',
hits: 'integer',
columns: 'string',
sort: 'string',
columns: 'keyword',
sort: 'keyword',
version: 'integer'
};

View file

@ -1,50 +0,0 @@
import uiModules from 'ui/modules';
import jsondiffpatch from '@bigfunger/jsondiffpatch';
import '../styles/_output_preview.less';
import outputPreviewTemplate from '../views/output_preview.html';
const htmlFormat = jsondiffpatch.formatters.html.format;
const app = uiModules.get('kibana');
app.directive('outputPreview', function () {
return {
restrict: 'E',
template: outputPreviewTemplate,
scope: {
oldObject: '=',
newObject: '=',
error: '='
},
link: function ($scope, $el) {
const div = $el.find('.visual')[0];
$scope.diffpatch = jsondiffpatch.create({
arrays: {
detectMove: false
},
textDiff: {
minLength: 120
}
});
$scope.updateUi = function () {
let left = $scope.oldObject;
let right = $scope.newObject;
let delta = $scope.diffpatch.diff(left, right);
if (!delta || $scope.error) delta = {};
div.innerHTML = htmlFormat(delta, left);
};
},
controller: function ($scope, debounce) {
$scope.collapsed = false;
const updateOutput = debounce(function () {
$scope.updateUi();
}, 200);
$scope.$watch('oldObject', updateOutput);
$scope.$watch('newObject', updateOutput);
}
};
});

View file

@ -1,20 +0,0 @@
import uiModules from 'ui/modules';
import '../styles/_pipeline_output.less';
import pipelineOutputTemplate from '../views/pipeline_output.html';
const app = uiModules.get('kibana');
app.directive('pipelineOutput', function () {
return {
restrict: 'E',
template: pipelineOutputTemplate,
scope: {
pipeline: '=',
samples: '=',
sample: '='
},
controller: function ($scope) {
$scope.collapsed = true;
}
};
});

View file

@ -1,94 +0,0 @@
import uiModules from 'ui/modules';
import _ from 'lodash';
import Pipeline from '../lib/pipeline';
import angular from 'angular';
import * as ProcessorTypes from '../processors/view_models';
import IngestProvider from 'ui/ingest';
import '../styles/_pipeline_setup.less';
import './pipeline_output';
import './source_data';
import './processor_ui_container';
import '../processors';
import pipelineSetupTemplate from '../views/pipeline_setup.html';
const app = uiModules.get('kibana');
function buildProcessorTypeList(enabledProcessorTypeIds) {
return _(ProcessorTypes)
.map(Type => {
const instance = new Type();
return {
typeId: instance.typeId,
title: instance.title,
Type
};
})
.compact()
.filter((processorType) => enabledProcessorTypeIds.includes(processorType.typeId))
.sortBy('title')
.value();
}
app.directive('pipelineSetup', function () {
return {
restrict: 'E',
template: pipelineSetupTemplate,
scope: {
samples: '=',
pipeline: '='
},
controller: function ($scope, debounce, Private, Notifier) {
const ingest = Private(IngestProvider);
const notify = new Notifier({ location: `Ingest Pipeline Setup` });
$scope.sample = {};
//determines which processors are available on the cluster
ingest.getProcessors()
.then((enabledProcessorTypeIds) => {
$scope.processorTypes = buildProcessorTypeList(enabledProcessorTypeIds);
})
.catch(notify.error);
const pipeline = new Pipeline();
// Loads pre-existing pipeline which will exist if the user returns from
// a later step in the wizard
if ($scope.pipeline) {
pipeline.load($scope.pipeline);
$scope.sample = $scope.pipeline.input;
}
$scope.pipeline = pipeline;
//initiates the simulate call if the pipeline is dirty
const simulatePipeline = debounce((event, message) => {
if (pipeline.processors.length === 0) {
pipeline.updateOutput();
return;
}
return ingest.simulate(pipeline.model)
.then((results) => { pipeline.applySimulateResults(results); })
.catch(notify.error);
}, 200);
$scope.$watchCollection('pipeline.processors', (newVal, oldVal) => {
pipeline.updateParents();
});
$scope.$watch('sample', (newVal) => {
pipeline.input = $scope.sample;
pipeline.updateParents();
});
$scope.$watch('processorType', (newVal) => {
if (!newVal) return;
pipeline.add(newVal.Type);
$scope.processorType = '';
});
$scope.$watch('pipeline.dirty', simulatePipeline);
$scope.expandContext = 1;
}
};
});

View file

@ -1,34 +0,0 @@
import uiModules from 'ui/modules';
import _ from 'lodash';
import '../styles/_processor_ui_container.less';
import './output_preview';
import './processor_ui_container_header';
import template from '../views/processor_ui_container.html';
const app = uiModules.get('kibana');
app.directive('processorUiContainer', function ($compile) {
return {
restrict: 'E',
scope: {
pipeline: '=',
processor: '='
},
template: template,
link: function ($scope, $el) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
const $container = $el.find('.processor-ui-content');
const typeId = processor.typeId;
const newScope = $scope.$new();
newScope.pipeline = pipeline;
newScope.processor = processor;
const template = `<processor-ui-${typeId}></processor-ui-${typeId}>`;
const $innerEl = $compile(template)(newScope);
$innerEl.appendTo($container);
}
};
});

View file

@ -1,17 +0,0 @@
import uiModules from 'ui/modules';
import '../styles/_processor_ui_container_header.less';
import processorUiContainerHeaderTemplate from '../views/processor_ui_container_header.html';
const app = uiModules.get('kibana');
app.directive('processorUiContainerHeader', function () {
return {
restrict: 'E',
scope: {
processor: '=',
field: '=',
pipeline: '='
},
template: processorUiContainerHeaderTemplate
};
});

View file

@ -1,45 +0,0 @@
import uiModules from 'ui/modules';
import angular from 'angular';
import '../styles/_source_data.less';
import sourceDataTemplate from '../views/source_data.html';
const app = uiModules.get('kibana');
app.directive('sourceData', function () {
return {
restrict: 'E',
scope: {
samples: '=',
sample: '=',
disabled: '='
},
template: sourceDataTemplate,
controller: function ($scope) {
const samples = $scope.samples;
if (samples.length > 0) {
$scope.selectedSample = samples[0];
}
$scope.$watch('selectedSample', (newValue) => {
//the added complexity of this directive is to strip out the properties
//that angular adds to array objects that are bound via ng-options
$scope.sample = angular.copy(newValue);
});
$scope.previousLine = function () {
let currentIndex = samples.indexOf($scope.selectedSample);
if (currentIndex <= 0) currentIndex = samples.length;
$scope.selectedSample = samples[currentIndex - 1];
};
$scope.nextLine = function () {
let currentIndex = samples.indexOf($scope.selectedSample);
if (currentIndex >= samples.length - 1) currentIndex = -1;
$scope.selectedSample = samples[currentIndex + 1];
};
}
};
});

View file

@ -1 +0,0 @@
import './directives/pipeline_setup';

View file

@ -1,74 +0,0 @@
import expect from 'expect.js';
import sinon from 'sinon';
import createMultiSelectModel from '../create_multi_select_model';
describe('createMultiSelectModel', function () {
it('should throw an error if the first argument is not an array', () => {
expect(createMultiSelectModel).withArgs('foo', []).to.throwError();
expect(createMultiSelectModel).withArgs(1234, []).to.throwError();
expect(createMultiSelectModel).withArgs(undefined, []).to.throwError();
expect(createMultiSelectModel).withArgs(null, []).to.throwError();
expect(createMultiSelectModel).withArgs([], []).to.not.throwError();
});
it('should throw an error if the second argument is not an array', () => {
expect(createMultiSelectModel).withArgs([], 'foo').to.throwError();
expect(createMultiSelectModel).withArgs([], 1234).to.throwError();
expect(createMultiSelectModel).withArgs([], undefined).to.throwError();
expect(createMultiSelectModel).withArgs([], null).to.throwError();
expect(createMultiSelectModel).withArgs([], []).to.not.throwError();
});
it('should output an array with an item for each passed in', () => {
const items = [ 'foo', 'bar', 'baz' ];
const expected = [
{ title: 'foo', selected: false },
{ title: 'bar', selected: false },
{ title: 'baz', selected: false }
];
const actual = createMultiSelectModel(items, []);
expect(actual).to.eql(expected);
});
it('should set the selected property in the output', () => {
const items = [ 'foo', 'bar', 'baz' ];
const selectedItems = [ 'bar', 'baz' ];
const expected = [
{ title: 'foo', selected: false },
{ title: 'bar', selected: true },
{ title: 'baz', selected: true }
];
const actual = createMultiSelectModel(items, selectedItems);
expect(actual).to.eql(expected);
});
it('should trim values when comparing for selected', () => {
const items = [ 'foo', 'bar', 'baz' ];
const selectedItems = [ ' bar ', ' baz ' ];
const expected = [
{ title: 'foo', selected: false },
{ title: 'bar', selected: true },
{ title: 'baz', selected: true }
];
const actual = createMultiSelectModel(items, selectedItems);
expect(actual).to.eql(expected);
});
it('should be case insensitive when comparing for selected', () => {
const items = [ 'foo', 'bar', 'baz' ];
const selectedItems = [ ' Bar ', ' BAZ ' ];
const expected = [
{ title: 'foo', selected: false },
{ title: 'bar', selected: true },
{ title: 'baz', selected: true }
];
const actual = createMultiSelectModel(items, selectedItems);
expect(actual).to.eql(expected);
});
});

View file

@ -1,86 +0,0 @@
import expect from 'expect.js';
import sinon from 'sinon';
import keysDeep from '../keys_deep';
describe('keys deep', function () {
it('should list first level properties', function () {
let object = {
property1: 'value1',
property2: 'value2'
};
let expected = [
'property1',
'property2'
];
const keys = keysDeep(object);
expect(keys).to.eql(expected);
});
it('should list nested properties', function () {
let object = {
property1: 'value1',
property2: 'value2',
property3: {
subProperty1: 'value1.1'
}
};
let expected = [
'property1',
'property2',
'property3.subProperty1',
'property3'
];
const keys = keysDeep(object);
expect(keys).to.eql(expected);
});
it('should recursivly list nested properties', function () {
let object = {
property1: 'value1',
property2: 'value2',
property3: {
subProperty1: 'value1.1',
subProperty2: {
prop1: 'value1.2.1',
prop2: 'value2.2.2'
},
subProperty3: 'value1.3'
}
};
let expected = [
'property1',
'property2',
'property3.subProperty1',
'property3.subProperty2.prop1',
'property3.subProperty2.prop2',
'property3.subProperty2',
'property3.subProperty3',
'property3'
];
const keys = keysDeep(object);
expect(keys).to.eql(expected);
});
it('should list array properties, but not contents', function () {
let object = {
property1: 'value1',
property2: [ 'item1', 'item2' ]
};
let expected = [
'property1',
'property2'
];
const keys = keysDeep(object);
expect(keys).to.eql(expected);
});
});

View file

@ -1,480 +0,0 @@
import _ from 'lodash';
import expect from 'expect.js';
import sinon from 'sinon';
import Pipeline from '../pipeline';
import * as processorTypes from '../../processors/view_models';
describe('processor pipeline', function () {
function getProcessorIds(pipeline) {
return pipeline.processors.map(p => p.processorId);
}
describe('model', function () {
it('should only contain the clean data properties', function () {
const pipeline = new Pipeline();
const actual = pipeline.model;
const expectedKeys = [ 'input', 'processors' ];
expect(_.keys(actual)).to.eql(expectedKeys);
});
it('should access the model property of each processor', function () {
const pipeline = new Pipeline();
pipeline.input = { foo: 'bar' };
pipeline.add(processorTypes.Set);
const actual = pipeline.model;
const expected = {
input: pipeline.input,
processors: [ pipeline.processors[0].model ]
};
expect(actual).to.eql(expected);
});
});
describe('load', function () {
it('should remove existing processors from the pipeline', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const oldProcessors = [ pipeline.processors[0], pipeline.processors[1], pipeline.processors[2] ];
const newPipeline = new Pipeline();
newPipeline.add(processorTypes.Set);
newPipeline.add(processorTypes.Set);
newPipeline.add(processorTypes.Set);
pipeline.load(newPipeline);
expect(_.find(pipeline.processors, oldProcessors[0])).to.be(undefined);
expect(_.find(pipeline.processors, oldProcessors[1])).to.be(undefined);
expect(_.find(pipeline.processors, oldProcessors[2])).to.be(undefined);
});
it('should call addExisting for each of the imported processors', function () {
const pipeline = new Pipeline();
sinon.stub(pipeline, 'addExisting');
const newPipeline = new Pipeline();
newPipeline.add(processorTypes.Set);
newPipeline.add(processorTypes.Set);
newPipeline.add(processorTypes.Set);
pipeline.load(newPipeline);
expect(pipeline.addExisting.calledWith(newPipeline.processors[0])).to.be(true);
expect(pipeline.addExisting.calledWith(newPipeline.processors[1])).to.be(true);
expect(pipeline.addExisting.calledWith(newPipeline.processors[2])).to.be(true);
});
});
describe('remove', function () {
it('remove the specified processor from the processors collection', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
pipeline.remove(pipeline.processors[1]);
expect(pipeline.processors[0].processorId).to.be(processorIds[0]);
expect(pipeline.processors[1].processorId).to.be(processorIds[2]);
});
});
describe('add', function () {
it('should append new items to the processors collection', function () {
const pipeline = new Pipeline();
expect(pipeline.processors.length).to.be(0);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
expect(pipeline.processors.length).to.be(3);
});
it('should append assign each new processor a unique processorId', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const ids = pipeline.processors.map((p) => { return p.processorId; });
expect(_.uniq(ids).length).to.be(3);
});
it('added processors should be an instance of the type supplied', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
expect(pipeline.processors[0] instanceof processorTypes.Set).to.be(true);
expect(pipeline.processors[1] instanceof processorTypes.Set).to.be(true);
expect(pipeline.processors[2] instanceof processorTypes.Set).to.be(true);
});
});
describe('addExisting', function () {
it('should append new items to the processors collection', function () {
const pipeline = new Pipeline();
expect(pipeline.processors.length).to.be(0);
const testProcessor = new processorTypes.Set('foo');
pipeline.addExisting(testProcessor);
expect(pipeline.processors.length).to.be(1);
});
it('should instantiate an object of the same class as the object passed in', function () {
const pipeline = new Pipeline();
const testProcessor = new processorTypes.Set('foo');
pipeline.addExisting(testProcessor);
expect(pipeline.processors[0] instanceof processorTypes.Set).to.be(true);
});
it('the object added should be a different instance than the object passed in', function () {
const pipeline = new Pipeline();
const testProcessor = new processorTypes.Set('foo');
pipeline.addExisting(testProcessor);
expect(pipeline.processors[0]).to.not.be(testProcessor);
});
it('the object added should have the same property values as the object passed in (except id)', function () {
const pipeline = new Pipeline();
const testProcessor = new processorTypes.Set('foo');
testProcessor.foo = 'bar';
testProcessor.bar = 'baz';
pipeline.addExisting(testProcessor);
expect(pipeline.processors[0].foo).to.be('bar');
expect(pipeline.processors[0].bar).to.be('baz');
expect(pipeline.processors[0].processorId).to.not.be('foo');
});
});
describe('moveUp', function () {
it('should be able to move an item up in the array', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[1];
pipeline.moveUp(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[1]);
expect(pipeline.processors[1].processorId).to.be(processorIds[0]);
expect(pipeline.processors[2].processorId).to.be(processorIds[2]);
});
it('should be able to move the same item move than once', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[2];
pipeline.moveUp(target);
pipeline.moveUp(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[2]);
expect(pipeline.processors[1].processorId).to.be(processorIds[0]);
expect(pipeline.processors[2].processorId).to.be(processorIds[1]);
});
it('should not move the selected item past the top', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[2];
pipeline.moveUp(target);
pipeline.moveUp(target);
pipeline.moveUp(target);
pipeline.moveUp(target);
pipeline.moveUp(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[2]);
expect(pipeline.processors[1].processorId).to.be(processorIds[0]);
expect(pipeline.processors[2].processorId).to.be(processorIds[1]);
});
it('should not allow the top item to be moved up', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[0];
pipeline.moveUp(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[0]);
expect(pipeline.processors[1].processorId).to.be(processorIds[1]);
expect(pipeline.processors[2].processorId).to.be(processorIds[2]);
});
});
describe('moveDown', function () {
it('should be able to move an item down in the array', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[1];
pipeline.moveDown(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[0]);
expect(pipeline.processors[1].processorId).to.be(processorIds[2]);
expect(pipeline.processors[2].processorId).to.be(processorIds[1]);
});
it('should be able to move the same item move than once', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[0];
pipeline.moveDown(target);
pipeline.moveDown(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[1]);
expect(pipeline.processors[1].processorId).to.be(processorIds[2]);
expect(pipeline.processors[2].processorId).to.be(processorIds[0]);
});
it('should not move the selected item past the bottom', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[0];
pipeline.moveDown(target);
pipeline.moveDown(target);
pipeline.moveDown(target);
pipeline.moveDown(target);
pipeline.moveDown(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[1]);
expect(pipeline.processors[1].processorId).to.be(processorIds[2]);
expect(pipeline.processors[2].processorId).to.be(processorIds[0]);
});
it('should not allow the bottom item to be moved down', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const target = pipeline.processors[2];
pipeline.moveDown(target);
expect(pipeline.processors[0].processorId).to.be(processorIds[0]);
expect(pipeline.processors[1].processorId).to.be(processorIds[1]);
expect(pipeline.processors[2].processorId).to.be(processorIds[2]);
});
});
describe('updateParents', function () {
it('should set the first processors parent to pipeline.input', function () {
const pipeline = new Pipeline();
pipeline.input = { foo: 'bar' };
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.processors.forEach(p => sinon.stub(p, 'setParent'));
pipeline.updateParents();
expect(pipeline.processors[0].setParent.calledWith(pipeline.input)).to.be(true);
});
it('should set non-first processors parent to previous processor', function () {
const pipeline = new Pipeline();
pipeline.input = { foo: 'bar' };
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.processors.forEach(p => sinon.stub(p, 'setParent'));
pipeline.updateParents();
expect(pipeline.processors[1].setParent.calledWith(pipeline.processors[0])).to.be(true);
expect(pipeline.processors[2].setParent.calledWith(pipeline.processors[1])).to.be(true);
expect(pipeline.processors[3].setParent.calledWith(pipeline.processors[2])).to.be(true);
});
it('should set pipeline.dirty', function () {
const pipeline = new Pipeline();
pipeline.updateParents();
expect(pipeline.dirty).to.be(true);
});
});
describe('getProcessorById', function () {
it('should return a processor when suppied its id', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
const processorIds = getProcessorIds(pipeline);
const actual = pipeline.getProcessorById(processorIds[2]);
const expected = pipeline.processors[2];
expect(actual).to.be(expected);
});
it('should throw an error if given an unknown id', function () {
const pipeline = new Pipeline();
expect(pipeline.getProcessorById).withArgs('foo').to.throwError();
});
});
describe('updateOutput', function () {
it('should set the output to input if first processor has error', function () {
const pipeline = new Pipeline();
pipeline.input = { bar: 'baz' };
pipeline.add(processorTypes.Set);
pipeline.processors[0].outputObject = { field1: 'value1' };
pipeline.processors[0].error = {}; //define an error
pipeline.updateOutput();
expect(pipeline.output).to.be(pipeline.input);
});
it('should set the output to the processor before the error on a compile error', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.processors[0].outputObject = { field1: 'value1' };
pipeline.processors[1].outputObject = { field1: 'value2' };
pipeline.processors[2].outputObject = { field1: 'value3' };
pipeline.updateOutput();
expect(pipeline.output).to.eql({ field1: 'value3' });
pipeline.processors[1].error = { compile: true }; //define a compile error
pipeline.processors[0].locked = true; //all other processors get locked.
pipeline.processors[2].locked = true; //all other processors get locked.
pipeline.updateOutput();
expect(pipeline.output).to.eql({ field1: 'value1' });
});
it('should set the output to the last processor with valid output if a processor has an error', function () {
const pipeline = new Pipeline();
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.add(processorTypes.Set);
pipeline.processors[0].outputObject = { field1: 'value1' };
pipeline.processors[1].outputObject = { field1: 'value2' };
pipeline.processors[2].outputObject = { field1: 'value3' };
pipeline.updateOutput();
expect(pipeline.output).to.eql({ field1: 'value3' });
pipeline.processors[2].error = {}; //define an error
pipeline.updateOutput();
expect(pipeline.output).to.eql({ field1: 'value2' });
pipeline.processors[1].error = {}; //define an error
pipeline.processors[2].error = undefined; //if processor[1] has an error,
pipeline.processors[2].locked = true; //subsequent processors will be locked.
pipeline.updateOutput();
expect(pipeline.output).to.eql({ field1: 'value1' });
});
it('should set output to be last processor output if processors exist', function () {
const pipeline = new Pipeline();
pipeline.input = { bar: 'baz' };
pipeline.add(processorTypes.Set);
const expected = { foo: 'bar' };
pipeline.processors[0].outputObject = expected;
pipeline.updateOutput();
expect(pipeline.output).to.be(expected);
});
it('should set output to be equal to input if no processors exist', function () {
const pipeline = new Pipeline();
pipeline.input = { bar: 'baz' };
pipeline.updateOutput();
expect(pipeline.output).to.be(pipeline.input);
});
it('should set pipeline.dirty', function () {
const pipeline = new Pipeline();
pipeline.updateParents();
expect(pipeline.dirty).to.be(true);
pipeline.updateOutput();
expect(pipeline.dirty).to.be(false);
});
});
// describe('applySimulateResults', function () { });
});

View file

@ -1,21 +0,0 @@
import _ from 'lodash';
export default function selectableArray(items, selectedItems) {
if (!_.isArray(items)) throw new Error('First argument must be an array');
if (!_.isArray(selectedItems)) throw new Error('Second argument must be an array');
return items.map((item) => {
const selected = _.find(selectedItems, (selectedItem) => {
return cleanItem(selectedItem) === cleanItem(item);
});
return {
title: item,
selected: !_.isUndefined(selected)
};
});
};
function cleanItem(item) {
return _.trim(item).toUpperCase();
}

View file

@ -1,21 +0,0 @@
import _ from 'lodash';
export default function keysDeep(object, base) {
let result = [];
let delimitedBase = base ? base + '.' : '';
_.forEach(object, (value, key) => {
var fullKey = delimitedBase + key;
if (_.isPlainObject(value)) {
result = result.concat(keysDeep(value, fullKey));
} else {
result.push(fullKey);
}
});
if (base) {
result.push(base);
}
return result;
};

View file

@ -1,176 +0,0 @@
import _ from 'lodash';
function updateProcessorOutputs(pipeline, simulateResults) {
simulateResults.forEach((result) => {
const processor = pipeline.getProcessorById(result.processorId);
processor.outputObject = _.get(result, 'output');
processor.error = _.get(result, 'error');
});
}
//Updates the error state of the pipeline and its processors
//If a pipeline compile error is returned, lock all processors but the error
//If a pipeline data error is returned, lock all processors after the error
function updateErrorState(pipeline) {
pipeline.hasCompileError = _.some(pipeline.processors, (processor) => {
return _.get(processor, 'error.compile');
});
_.forEach(pipeline.processors, processor => {
processor.locked = false;
});
const errorIndex = _.findIndex(pipeline.processors, 'error');
if (errorIndex === -1) return;
_.forEach(pipeline.processors, (processor, index) => {
if (pipeline.hasCompileError && index !== errorIndex) {
processor.locked = true;
}
if (!pipeline.hasCompileError && index > errorIndex) {
processor.locked = true;
}
});
}
function updateProcessorInputs(pipeline) {
pipeline.processors.forEach((processor) => {
//we don't want to change the inputObject if the parent processor
//is in error because that can cause us to lose state.
if (!_.get(processor, 'parent.error')) {
//the parent property of the first processor is set to the pipeline.input.
//In all other cases it is set to processor[index-1]
if (!processor.parent.processorId) {
processor.inputObject = _.cloneDeep(processor.parent);
} else {
processor.inputObject = _.cloneDeep(processor.parent.outputObject);
}
}
});
}
export default class Pipeline {
constructor() {
this.processors = [];
this.processorCounter = 0;
this.input = {};
this.output = undefined;
this.dirty = false;
this.hasCompileError = false;
}
get model() {
const pipeline = {
input: this.input,
processors: _.map(this.processors, processor => processor.model)
};
return pipeline;
}
setDirty() {
this.dirty = true;
}
load(pipeline) {
this.processors = [];
pipeline.processors.forEach((processor) => {
this.addExisting(processor);
});
}
remove(processor) {
const processors = this.processors;
const index = processors.indexOf(processor);
processors.splice(index, 1);
}
moveUp(processor) {
const processors = this.processors;
const index = processors.indexOf(processor);
if (index === 0) return;
const temp = processors[index - 1];
processors[index - 1] = processors[index];
processors[index] = temp;
}
moveDown(processor) {
const processors = this.processors;
const index = processors.indexOf(processor);
if (index === processors.length - 1) return;
const temp = processors[index + 1];
processors[index + 1] = processors[index];
processors[index] = temp;
}
addExisting(existingProcessor) {
const Type = existingProcessor.constructor;
const newProcessor = this.add(Type);
_.assign(newProcessor, _.omit(existingProcessor, 'processorId'));
return newProcessor;
}
add(ProcessorType) {
const processors = this.processors;
this.processorCounter += 1;
const processorId = `processor_${this.processorCounter}`;
const newProcessor = new ProcessorType(processorId);
processors.push(newProcessor);
return newProcessor;
}
updateParents() {
const processors = this.processors;
processors.forEach((processor, index) => {
let newParent;
if (index === 0) {
newParent = this.input;
} else {
newParent = processors[index - 1];
}
processor.setParent(newParent);
});
this.dirty = true;
}
getProcessorById(processorId) {
const result = _.find(this.processors, { processorId });
if (!result) {
throw new Error(`Could not find processor by id [${processorId}]`);
}
return result;
}
updateOutput() {
const processors = this.processors;
const errorIndex = _.findIndex(processors, 'error');
const goodProcessor = errorIndex === -1 ? _.last(processors) : processors[errorIndex - 1];
this.output = goodProcessor ? goodProcessor.outputObject : this.input;
this.dirty = false;
}
// Updates the state of the pipeline and processors with the results
// from an ingest simulate call.
applySimulateResults(simulateResults) {
updateProcessorOutputs(this, simulateResults);
updateErrorState(this);
updateProcessorInputs(this);
this.updateOutput();
}
}

View file

@ -1,38 +0,0 @@
import uiModules from 'ui/modules';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiAppend', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function processorUiChanged() {
pipeline.setDirty();
}
function splitValues(delimitedList) {
return delimitedList.split('\n');
}
function joinValues(valueArray) {
return valueArray.join('\n');
}
function updateValues() {
processor.values = splitValues($scope.values);
}
$scope.values = joinValues(processor.values);
$scope.$watch('values', updateValues);
$scope.$watch('processor.targetField', processorUiChanged);
$scope.$watchCollection('processor.values', processorUiChanged);
}
};
});

View file

@ -1,8 +0,0 @@
<div class="form-group">
<label>Target Field:</label>
<input type="text" class="form-control" ng-model="processor.targetField">
</div>
<div class="form-group">
<label>Values:</label><span> (line delimited)</span>
<textarea ng-model="values" class="form-control"></textarea>
</div>

View file

@ -1,23 +0,0 @@
import Processor from '../base/view_model';
export class Append extends Processor {
constructor(processorId) {
super(processorId, 'append', 'Append');
this.targetField = '';
this.values = [];
}
get description() {
const target = this.targetField || '?';
return `[${target}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
targetField: this.targetField || '',
values: this.values || []
};
}
};

View file

@ -1,23 +0,0 @@
export default class Processor {
constructor(processorId, typeId, title) {
if (!typeId || !title) {
throw new Error('Cannot instantiate the base Processor class.');
}
this.processorId = processorId;
this.title = title;
this.typeId = typeId;
this.collapsed = false;
this.parent = undefined;
this.inputObject = undefined;
this.outputObject = undefined;
this.error = undefined;
}
setParent(newParent) {
const oldParent = this.parent;
this.parent = newParent;
return (oldParent !== this.parent);
}
}

View file

@ -1,43 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiConvert', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.types = ['auto', 'number', 'string', 'boolean'];
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.type', processorUiChanged);
$scope.$watch('processor.targetField', processorUiChanged);
}
};
});

View file

@ -1,24 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Type:</label>
<select
class="form-control"
ng-options="type as type for type in types"
ng-model="processor.type">
</select>
</div>
<div class="form-group">
<label>Target Field:</label>
<input type="text" class="form-control" ng-model="processor.targetField">
</div>

View file

@ -1,28 +0,0 @@
import _ from 'lodash';
import Processor from '../base/view_model';
export class Convert extends Processor {
constructor(processorId) {
super(processorId, 'convert', 'Convert');
this.sourceField = '';
this.targetField = '';
this.type = 'auto';
}
get description() {
const source = this.sourceField || '?';
const type = this.type || '?';
const target = this.targetField ? ` -> [${this.targetField}]` : '';
return `[${source}] to ${type}${target}`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
targetField: this.targetField || '',
type: this.type || 'auto'
};
}
};

View file

@ -1,58 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import createMultiSelectModel from '../../lib/create_multi_select_model';
import template from './view.html';
import './styles.less';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiDate', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope, debounce) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
const updateFormats = debounce(() => {
processor.formats = _($scope.formats)
.filter('selected')
.map('title')
.value();
$scope.customFormatSelected = _.includes(processor.formats, 'Custom');
processorUiChanged();
}, 200);
$scope.updateFormats = updateFormats;
$scope.formats = createMultiSelectModel(['ISO8601', 'UNIX', 'UNIX_MS', 'TAI64N', 'Custom'], processor.formats);
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.customFormat', updateFormats);
$scope.$watch('processor.targetField', processorUiChanged);
$scope.$watch('processor.timezone', processorUiChanged);
$scope.$watch('processor.locale', processorUiChanged);
}
};
});

View file

@ -1,5 +0,0 @@
processor-ui-date {
.custom-date-format {
display: flex;
}
}

View file

@ -1,74 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Target Field:</label>
<input type="text" class="form-control" ng-model="processor.targetField">
</div>
<div class="form-group">
<label>Formats:</label>
<div ng-repeat="format in formats">
<input
type="checkbox"
id="format_{{processor.processorId}}_{{$index}}"
ng-model="format.selected"
ng-click="updateFormats()" />
<label for="format_{{processor.processorId}}_{{$index}}">
{{format.title}}
<a
aria-label="Custom Date Format Help"
tooltip="Custom Date Format Help"
tooltip-append-to-body="true"
href="http://www.joda.org/joda-time/key_format.html"
target="_blank"
ng-show="format.title === 'Custom'">
<i aria-hidden="true" class="fa fa-question-circle"></i>
</a>
</label>
</div>
<div
class="custom-date-format"
ng-show="customFormatSelected">
<input
type="text"
class="form-control"
ng-model="processor.customFormat">
</div>
</div>
<div class="form-group">
<label>
Timezone:
<a
aria-label="Timezone Help"
tooltip="Timezone Help"
tooltip-append-to-body="true"
href="http://joda-time.sourceforge.net/timezones.html"
target="_blank">
<i aria-hidden="true" class="fa fa-question-circle"></i>
</a>
</label>
<input type="text" class="form-control" ng-model="processor.timezone"></div>
</div>
<div class="form-group">
<label>
Locale:
<a
aria-label="Locale Help"
tooltip="Locale Help"
tooltip-append-to-body="true"
href="https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html"
target="_blank">
<i aria-hidden="true" class="fa fa-question-circle"></i>
</a>
</label>
<input type="text" class="form-control" ng-model="processor.locale"></div>
</div>

View file

@ -1,32 +0,0 @@
import Processor from '../base/view_model';
export class Date extends Processor {
constructor(processorId) {
super(processorId, 'date', 'Date');
this.sourceField = '';
this.targetField = '@timestamp';
this.formats = [];
this.timezone = 'Etc/UTC';
this.locale = 'ENGLISH';
this.customFormat = '';
}
get description() {
const source = this.sourceField || '?';
const target = this.targetField || '?';
return `[${source}] -> [${target}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
targetField: this.targetField || '',
formats: this.formats || [],
timezone: this.timezone || '',
locale: this.locale || '',
customFormat: this.customFormat || ''
};
}
};

View file

@ -1,61 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
import './styles.less';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiGeoip', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
function splitValues(delimitedList) {
return delimitedList.split('\n');
}
function joinValues(valueArray) {
return valueArray.join('\n');
}
function updateDatabaseFields() {
const fieldsString = $scope.databaseFields.replace(/,/g, '\n');
processor.databaseFields = _(splitValues(fieldsString)).map(_.trim).compact().value();
$scope.databaseFields = joinValues(processor.databaseFields);
}
$scope.databaseFields = joinValues(processor.databaseFields);
$scope.$watch('databaseFields', updateDatabaseFields);
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.targetField', processorUiChanged);
$scope.$watch('processor.databaseFile', processorUiChanged);
$scope.$watchCollection('processor.databaseFields', processorUiChanged);
}
};
});

View file

@ -1,13 +0,0 @@
processor-ui-geoip {
.advanced-section {
margin-top: 15px;
}
.advanced-section-heading {
.btn {
background-color: transparent;
color: black;
border: transparent;
}
}
}

View file

@ -1,42 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Target Field:</label>
<input type="text" class="form-control" ng-model="processor.targetField">
</div>
<div class="advanced-section">
<div class="form-group advanced-section-heading">
<button
ng-click="processor.advancedExpanded = !processor.advancedExpanded"
type="button"
class="btn btn-default btn-xs processor-ui-container-header-toggle">
<i
aria-hidden="true"
ng-class="{ 'fa-caret-down': processor.advancedExpanded, 'fa-caret-right': !processor.advancedExpanded }"
class="fa">
</i>
</button>
<label ng-click="processor.advancedExpanded = !processor.advancedExpanded">Advanced Settings</label>
</div>
<div ng-show="processor.advancedExpanded">
<div class="form-group">
<label>Database File:</label>
<input type="text" class="form-control" ng-model="processor.databaseFile">
</div>
<div class="form-group">
<label>Data Fields:</label><span> (line delimited)</span>
<textarea ng-model="databaseFields" class="form-control"></textarea>
</div>
</div>
</div>

View file

@ -1,28 +0,0 @@
import Processor from '../base/view_model';
export class GeoIp extends Processor {
constructor(processorId) {
super(processorId, 'geoip', 'Geo IP');
this.sourceField = '';
this.targetField = '';
this.databaseFile = '';
this.databaseFields = [];
}
get description() {
const source = this.sourceField || '?';
const target = this.targetField || '?';
return `[${source}] -> [${target}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
targetField: this.targetField || '',
databaseFile: this.databaseFile || '',
databaseFields: this.databaseFields || []
};
}
};

View file

@ -1,40 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiGrok', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.pattern', processorUiChanged);
}
};
});

View file

@ -1,16 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Pattern:</label>
<input type="text" class="form-control" ng-model="processor.pattern">
</div>

View file

@ -1,30 +0,0 @@
import _ from 'lodash';
import keysDeep from '../../lib/keys_deep';
import Processor from '../base/view_model';
export class Grok extends Processor {
constructor(processorId) {
super(processorId, 'grok', 'Grok');
this.sourceField = '';
this.pattern = '';
}
get description() {
const inputKeys = keysDeep(this.inputObject);
const outputKeys = keysDeep(this.outputObject);
const addedKeys = _.difference(outputKeys, inputKeys);
const added = addedKeys.sort().map(field => `[${field}]`).join(', ');
const source = this.sourceField || '?';
return `[${source}] -> ${added}`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
pattern: this.pattern || ''
};
}
};

View file

@ -1,41 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiGsub', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.pattern', processorUiChanged);
$scope.$watch('processor.replacement', processorUiChanged);
}
};
});

View file

@ -1,20 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Pattern:</label>
<input type="text" class="form-control" ng-model="processor.pattern">
</div>
<div class="form-group">
<label>Replacement:</label>
<input type="text" class="form-control" ng-trim="false" ng-model="processor.replacement">
</div>

View file

@ -1,25 +0,0 @@
import Processor from '../base/view_model';
export class Gsub extends Processor {
constructor(processorId) {
super(processorId, 'gsub', 'Gsub');
this.sourceField = '';
this.pattern = '';
this.replacement = '';
}
get description() {
const source = this.sourceField || '?';
return `[${source}] - /${this.pattern}/ -> '${this.replacement}'`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
pattern: this.pattern || '',
replacement: this.replacement || ''
};
}
};

View file

@ -1,14 +0,0 @@
import './append/directive';
import './convert/directive';
import './date/directive';
import './geoip/directive';
import './grok/directive';
import './gsub/directive';
import './join/directive';
import './lowercase/directive';
import './remove/directive';
import './rename/directive';
import './set/directive';
import './split/directive';
import './trim/directive';
import './uppercase/directive';

View file

@ -1,41 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiJoin', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
const allKeys = keysDeep(processor.inputObject);
$scope.fields = _.filter(allKeys, (key) => { return _.isArray(_.get(processor.inputObject, key)); });
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.separator', processorUiChanged);
}
};
});

View file

@ -1,16 +0,0 @@
<div class="form-group">
<label>Array Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Separator:</label>
<input type="text" class="form-control" ng-trim="false" ng-model="processor.separator">
</div>

View file

@ -1,24 +0,0 @@
import Processor from '../base/view_model';
export class Join extends Processor {
constructor(processorId) {
super(processorId, 'join', 'Join');
this.sourceField = '';
this.separator = '';
}
get description() {
const source = this.sourceField || '?';
const separator = this.separator ? ` on '${this.separator}'` : '';
return `[${source}]${separator}`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
separator: this.separator || ''
};
}
};

View file

@ -1,39 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiLowercase', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
const allKeys = keysDeep(processor.inputObject);
$scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); });
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
}
};
});

View file

@ -1,12 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>

View file

@ -1,21 +0,0 @@
import Processor from '../base/view_model';
export class Lowercase extends Processor {
constructor(processorId) {
super(processorId, 'lowercase', 'Lowercase');
this.sourceField = '';
}
get description() {
const source = this.sourceField || '?';
return `[${source}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || ''
};
}
};

View file

@ -1,38 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiRemove', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
}
};
});

View file

@ -1,12 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>

View file

@ -1,21 +0,0 @@
import Processor from '../base/view_model';
export class Remove extends Processor {
constructor(processorId) {
super(processorId, 'remove', 'Remove');
this.sourceField = '';
}
get description() {
const source = this.sourceField || '?';
return `[${source}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || ''
};
}
};

View file

@ -1,40 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiRename', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
$scope.fields = keysDeep(processor.inputObject);
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.targetField', processorUiChanged);
}
};
});

View file

@ -1,16 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Target Field:</label>
<input type="text" class="form-control" ng-model="processor.targetField">
</div>

View file

@ -1,24 +0,0 @@
import Processor from '../base/view_model';
export class Rename extends Processor {
constructor(processorId) {
super(processorId, 'rename', 'Rename');
this.sourceField = '';
this.targetField = '';
}
get description() {
const source = this.sourceField || '?';
const target = this.targetField || '?';
return `[${source}] -> [${target}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
targetField: this.targetField || ''
};
}
};

View file

@ -1,23 +0,0 @@
import uiModules from 'ui/modules';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiSet', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.targetField', processorUiChanged);
$scope.$watch('processor.value', processorUiChanged);
}
};
});

View file

@ -1,8 +0,0 @@
<div class="form-group">
<label>Target Field:</label>
<input type="text" class="form-control" ng-model="processor.targetField">
</div>
<div class="form-group">
<label>Value:</label>
<input type="text" class="form-control" ng-trim="false" ng-model="processor.value">
</div>

View file

@ -1,23 +0,0 @@
import Processor from '../base/view_model';
export class Set extends Processor {
constructor(processorId) {
super(processorId, 'set', 'Set');
this.targetField = '';
this.value = '';
}
get description() {
const target = this.targetField || '?';
return `[${target}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
targetField: this.targetField || '',
value: this.value || ''
};
}
};

View file

@ -1,41 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiSplit', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
const allKeys = keysDeep(processor.inputObject);
$scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); });
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
$scope.$watch('processor.separator', processorUiChanged);
}
};
});

View file

@ -1,16 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>
<div class="form-group">
<label>Separator:</label>
<input type="text" class="form-control" ng-trim="false" ng-model="processor.separator">
</div>

View file

@ -1,24 +0,0 @@
import Processor from '../base/view_model';
export class Split extends Processor {
constructor(processorId) {
super(processorId, 'split', 'Split');
this.sourceField = '';
this.separator = '';
}
get description() {
const source = this.sourceField || '?';
const separator = this.separator || '?';
return `[${source}] on '${separator}'`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || '',
separator: this.separator || ''
};
}
};

View file

@ -1,39 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiTrim', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
const allKeys = keysDeep(processor.inputObject);
$scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); });
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
}
};
});

View file

@ -1,12 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>

View file

@ -1,21 +0,0 @@
import Processor from '../base/view_model';
export class Trim extends Processor {
constructor(processorId) {
super(processorId, 'trim', 'Trim');
this.sourceField = '';
}
get description() {
const source = this.sourceField || '?';
return `[${source}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || ''
};
}
};

View file

@ -1,39 +0,0 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import keysDeep from '../../lib/keys_deep';
import template from './view.html';
const app = uiModules.get('kibana');
//scope.processor, scope.pipeline are attached by the process_container.
app.directive('processorUiUppercase', function () {
return {
restrict: 'E',
template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function consumeNewInputObject() {
const allKeys = keysDeep(processor.inputObject);
$scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); });
refreshFieldData();
}
function refreshFieldData() {
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
}
function processorUiChanged() {
pipeline.setDirty();
}
$scope.$watch('processor.inputObject', consumeNewInputObject);
$scope.$watch('processor.sourceField', () => {
refreshFieldData();
processorUiChanged();
});
}
};
});

View file

@ -1,12 +0,0 @@
<div class="form-group">
<label>Field:</label>
<select
class="form-control"
ng-options="field as field for field in fields"
ng-model="processor.sourceField">
</select>
</div>
<div class="form-group">
<label>Field Data:</label>
<pre>{{ fieldData }}</pre>
</div>

View file

@ -1,21 +0,0 @@
import Processor from '../base/view_model';
export class Uppercase extends Processor {
constructor(processorId) {
super(processorId, 'uppercase', 'Uppercase');
this.sourceField = '';
}
get description() {
const source = this.sourceField || '?';
return `[${source}]`;
}
get model() {
return {
processorId: this.processorId,
typeId: this.typeId,
sourceField: this.sourceField || ''
};
}
};

View file

@ -1,14 +0,0 @@
export { Append } from './append/view_model';
export { Convert } from './convert/view_model';
export { Date } from './date/view_model';
export { GeoIp } from './geoip/view_model';
export { Grok } from './grok/view_model';
export { Gsub } from './gsub/view_model';
export { Join } from './join/view_model';
export { Lowercase } from './lowercase/view_model';
export { Remove } from './remove/view_model';
export { Rename } from './rename/view_model';
export { Set } from './set/view_model';
export { Split } from './split/view_model';
export { Trim } from './trim/view_model';
export { Uppercase } from './uppercase/view_model';

View file

@ -1,28 +0,0 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
output-preview {
.visual {
border: none;
background-color: @settings-filebeat-wizard-panel-bg;
border-radius: 0;
overflow-x: auto;
}
.visual.collapsed {
max-height: 125px;
overflow-y: auto;
}
pre {
background-color: transparent;
border: none;
}
.hide-unchanged {
.jsondiffpatch-unchanged {
display: none;
}
}
}

View file

@ -1,22 +0,0 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
pipeline-output {
flex: 1 1 auto;
display: flex;
flex-direction: column;
.header-line {
display: flex;
label {
width: 100%;
}
}
pre {
min-height: 450px;
flex: 1 1 1px;
}
}

View file

@ -1,74 +0,0 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
pipeline-setup {
.main-panels {
display: flex;
margin-bottom: 10px;
.left-panel {
.flex-parent(1, 1, 1px);
width: 50%;
&>label {
margin-bottom: 2px;
}
}
.center-panel {
.flex-parent(0, 0, auto, column);
justify-content: center;
.buttons {
.flex-parent(0, 0, auto, column);
}
}
.right-panel {
.flex-parent(1, 1, 1px, column);
width: 50%;
}
.pipeline {
min-height: 450px;
background-color: @settings-filebeat-wizard-panel-bg;
}
}
label {
margin-bottom: 0px;
}
ul.pipeline-container {
list-style-type: none;
padding: 0px;
margin-bottom: 0px;
&>li {
padding: 1px;
}
}
.add-processor {
padding:10px;
margin-bottom: 10px;
}
.add-processor-dropdown {
display: flex;
justify-content: flex-start;
align-items: center;
select.form-control {
background-color: @settings-filebeat-wizard-processor-select-bg;
border: none;
width: auto;
margin-right: 5px;
}
}
textarea.form-control {
min-height: 150px;
}
}

View file

@ -1,42 +0,0 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
processor-ui-container {
display: block;
margin-bottom: 1px;
border-bottom: 2px solid;
border-color: white;
.processor-ui-container-body {
display: block;
overflow: hidden;
position: relative;
.overlay {
display: none;
position: absolute;
top: -5000px;
left: -5000px;
width: 10000px;
height: 10000px;
background-color: @settings-filebeat-wizard-processor-container-overlay-bg;
}
&.locked {
.overlay {
display: block;
}
}
}
.processor-ui-container-body-content {
padding: 10px;
background-color: white;
}
label {
font-weight: normal;
}
}

View file

@ -1,47 +0,0 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
processor-ui-container-header {
.processor-ui-container-header {
display: flex;
align-items: center;
flex: 1 0 auto;
background-color: @settings-filebeat-wizard-panel-bg;
border: none;
padding: 10px;
button {
width: 22px;
border-radius: 4px;
}
}
.processor-ui-container-header-toggle {
flex: 0 0 auto;
margin-right: 5px;
}
.processor-ui-container-header-title {
flex: 1 1 auto;
.ellipsis();
font-weight: bold;
.processor-title {
width: 100%;
}
.processor-description {
font-weight: normal;
}
.processor-description.danger {
font-weight: bold;
color: @brand-danger;
}
}
.processor-ui-container-header-controls {
flex: 0 0 auto;
}
}

View file

@ -1,17 +0,0 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
source-data {
flex: 1 0 auto;
display: flex;
height: 22px;
button {
flex: 0 0 auto;
width: 22px;
position: relative;
top: -5px;
margin-left: 5px;
}
}

View file

@ -1,24 +0,0 @@
<div class="form-group">
<label>Processor Changes:</label>
<a
style="float: right"
ng-click="collapsed = true"
ng-hide="collapsed">collapse</a>
<a
style="float: right"
ng-click="collapsed = false"
ng-show="collapsed">expand</a>
<span style="float: right">&nbsp;/&nbsp;</span>
<a
style="float: right"
ng-click="showAll = false"
ng-show="showAll">only show changes</a>
<a
style="float: right"
ng-click="showAll = true"
ng-hide="showAll">show all</a>
<div
class="visual"
ng-class="{'hide-unchanged': !showAll, collapsed: collapsed}"></div>
</div>

View file

@ -1,18 +0,0 @@
<div class="header-line">
<label>
Pipeline Output
<a
aria-label="The pipeline output shows the result of the defined pipeline using the sample records supplied in the previous step."
tooltip="The pipeline output shows the result of the defined pipeline using the sample records supplied in the previous step."
tooltip-append-to-body="true"
target="_blank">
<i aria-hidden="true" class="fa fa-question-circle"></i>
</a>
</label>
<source-data
sample="sample"
samples="samples"
disabled="pipeline.hasCompileError">
</source-data>
</div>
<pre class="output">{{ pipeline.output | json }}</pre>

View file

@ -1,74 +0,0 @@
<h2>
<em>Let's build a pipeline!</em> Ingest pipelines are an easy way to modify documents before they're indexed in Elasticsearch. They're composed of processors which can change your data in many ways. Create a pipeline below while cycling through your samples to see its effect on your data.
</h2>
<div class="main-panels">
<div
ng-hide="expandContext < 1"
class="left-panel">
<label>
Processor Pipeline
<a
aria-label="A pipeline is a definition of a series of processors that are to be executed in the same order as they are declared."
tooltip="A pipeline is a definition of a series of processors that are to be executed in the same order as they are declared."
tooltip-append-to-body="true"
target="_blank">
<i aria-hidden="true" class="fa fa-question-circle"></i>
</a>
</label>
<div class="pipeline">
<ul
class="pipeline-container"
ng-show="pipeline.processors.length > 0">
<li ng-repeat="processor in pipeline.processors track by processor.processorId">
<processor-ui-container pipeline="pipeline" processor="processor"></processor-ui-container>
</li>
</ul>
<div class="add-processor">
<div
class="form-group"
ng-hide="pipeline.processors.length > 0">
<label>
Your pipeline is currently empty. Add a processor to get started!
</label>
</div>
<div class="add-processor-dropdown">
<select
class="form-control"
ng-options="processorType.title for processorType in processorTypes"
ng-model="processorType"
ng-disabled="pipeline.hasCompileError">
<option value="">Select a Processor...</option>
</select>
</div>
</div>
</div>
</div>
<div class="center-panel">
<div class="buttons">
<button
aria-label="{{expandContext > 1 ? 'Expand Right Panel' : 'Collapse Left Panel'}}"
tooltip="{{expandContext > 1 ? 'Expand Right Panel' : 'Collapse Left Panel'}}"
ng-click="expandContext = expandContext - 1"
ng-disabled="expandContext < 1"
type="button"
class="btn btn-primary btn-xs collapser">
<i aria-hidden="true" class="fa fa-chevron-circle-left"></i>
</button>
<button
aria-label="{{expandContext < 1 ? 'Expand Left Panel' : 'Collapse Right Panel'}}"
tooltip="{{expandContext < 1 ? 'Expand Left Panel' : 'Collapse Right Panel'}}"
ng-click="expandContext = expandContext + 1"
ng-disabled="expandContext > 1"
type="button"
class="btn btn-primary btn-xs collapser">
<i aria-hidden="true" class="fa fa-chevron-circle-right"></i>
</button>
</div>
</div>
<div
ng-hide="expandContext > 1"
class="right-panel">
<pipeline-output pipeline="pipeline" samples="samples" sample="sample"></pipeline-output>
</div>
</div>

View file

@ -1,25 +0,0 @@
<processor-ui-container-header
processor="processor"
field="sourceField"
pipeline="pipeline">
</processor-ui-container-header>
<div
class="processor-ui-container-body"
ng-class="{locked: processor.locked}">
<div
class="processor-ui-container-body-content"
ng-hide="processor.collapsed">
<div
ng-show="processor.error"
class="alert alert-danger">
{{processor.error.message}}
</div>
<div class="processor-ui-content"></div>
<output-preview
new-object="processor.outputObject"
old-object="processor.inputObject"
error="processor.error">
</output-preview>
</div>
<div class="overlay"></div>
</div>

View file

@ -1,61 +0,0 @@
<div class="processor-ui-container-header">
<button
aria-label="{{ processor.collapsed ? 'Expand Processor' : 'Collapse Processor' }}"
tooltip="{{ processor.collapsed ? 'Expand Processor' : 'Collapse Processor' }}"
tooltip-append-to-body="true"
ng-click="processor.collapsed = !processor.collapsed"
type="button"
class="btn btn-primary btn-xs processor-ui-container-header-toggle">
<i aria-hidden="true" ng-class="{ 'fa-caret-down': !processor.collapsed, 'fa-caret-right': processor.collapsed }" class="fa"></i>
</button>
<div class="processor-ui-container-header-title">
<span class="processor-title">
{{processor.title}}
</span>
<span class="processor-description">
- {{ processor.description }}
</span>
<!-- error -->
<span ng-if="processor.error" class="processor-description danger">
- Error
</span>
</div>
<div class="processor-ui-container-header-controls btn-group">
<button
aria-label="Increase Priority"
tooltip="Increase Priority"
tooltip-append-to-body="true"
ng-click="pipeline.moveUp(processor)"
type="button"
class="btn btn-xs btn-primary"
ng-disabled="pipeline.hasCompileError">
<i aria-hidden="true" class="fa fa-caret-up"></i>
</button>
<button
aria-label="Decrease Priority"
tooltip="Decrease Priority"
tooltip-append-to-body="true"
ng-click="pipeline.moveDown(processor)"
type="button"
class="btn btn-xs btn-primary"
ng-disabled="pipeline.hasCompileError">
<i aria-hidden="true" class="fa fa-caret-down"></i>
</button>
<button
aria-label="Remove Processor"
tooltip="Remove Processor"
tooltip-append-to-body="true"
ng-click="pipeline.remove(processor)"
type="button"
class="btn btn-xs btn-danger"
ng-disabled="pipeline.hasCompileError && !processor.error">
<i aria-hidden="true" class="fa fa-times"></i>
</button>
</div>
</div>

View file

@ -1,20 +0,0 @@
<button
aria-label="Previous Sample"
tooltip="Previous Sample"
tooltip-append-to-body="true"
ng-click="previousLine()"
type="button"
class="btn btn-xs btn-primary"
ng-disabled="disabled">
<i aria-hidden="true" class="fa fa-caret-left"></i>
</button>
<button
aria-label="Next Sample"
tooltip="Next Sample"
tooltip-append-to-body="true"
ng-click="nextLine()"
type="button"
class="btn btn-xs btn-primary"
ng-disabled="disabled">
<i aria-hidden="true" class="fa fa-caret-right"></i>
</button>

View file

@ -19,6 +19,10 @@ kbn-management-objects-view {
min-height: 70px; /* 1 */
}
.tab-account {
background-color: @kibanaGray6;
}
.tab-management {
background-color: @kibanaGray6;
}

View file

@ -53,11 +53,11 @@ uiModules
SavedVis.type = 'visualization';
SavedVis.mapping = {
title: 'string',
title: 'text',
visState: 'json',
uiStateJSON: 'string',
description: 'string',
savedSearchId: 'string',
uiStateJSON: 'text',
description: 'text',
savedSearchId: 'keyword',
version: 'integer'
};

View file

@ -1,87 +0,0 @@
import expect from 'expect.js';
import _ from 'lodash';
import ingestSimulateApiKibanaToEsConverter from '../../converters/ingest_simulate_api_kibana_to_es_converter';
describe('ingestSimulateApiKibanaToEsConverter', function () {
it('populates the docs._source section and converts known processors', function () {
function buildSamplePipeline(input) {
return {
processors: [ { processor_id: 'processor1', type_id: 'set', target_field: 'bar', value: 'foo' } ],
input: input
};
}
function buildExpected(input) {
return {
pipeline : {
processors: [{
set: {
field: 'bar',
tag: 'processor1',
value: 'foo'
}
}]
},
'docs' : [
{ '_source': input }
]
};
}
let expected;
let actual;
expected = buildExpected(undefined);
actual = ingestSimulateApiKibanaToEsConverter(buildSamplePipeline(undefined));
expect(actual).to.eql(expected);
expected = buildExpected('foo');
actual = ingestSimulateApiKibanaToEsConverter(buildSamplePipeline('foo'));
expect(actual).to.eql(expected);
expected = buildExpected({ foo: 'bar' });
actual = ingestSimulateApiKibanaToEsConverter(buildSamplePipeline({ foo: 'bar' }));
expect(actual).to.eql(expected);
});
it('handles multiple processors', function () {
const pipeline = {
processors: [
{ processor_id: 'processor1', type_id: 'set', target_field: 'bar', value: 'foo' },
{ processor_id: 'processor2', type_id: 'set', target_field: 'bar', value: 'foo' },
],
input: {}
};
const expected = {
'pipeline': {
'processors': [
{
set: {
field: 'bar',
tag: 'processor1',
value: 'foo'
}
},
{
set: {
field: 'bar',
tag: 'processor2',
value: 'foo'
}
}
]
},
'docs': [
{'_source': {}}
]
};
const actual = ingestSimulateApiKibanaToEsConverter(pipeline);
expect(actual).to.eql(expected);
});
});

View file

@ -1,100 +0,0 @@
import initDefaultFieldProps from '../init_default_field_props';
import expect from 'expect.js';
import _ from 'lodash';
let fields;
const testData = [
{
'name': 'ip',
'type': 'ip'
}, {
'name': '@timestamp',
'type': 'date'
}, {
'name': 'agent',
'type': 'string'
}, {
'name': 'bytes',
'type': 'number'
},
{
'name': 'geo.coordinates',
'type': 'geo_point'
}
];
describe('initDefaultFieldProps', function () {
beforeEach(function () {
fields = _.cloneDeep(testData);
});
it('should throw an error if no argument is passed or the argument is not an array', function () {
expect(initDefaultFieldProps).to.throwException(/requires an array argument/);
expect(initDefaultFieldProps).withArgs({}).to.throwException(/requires an array argument/);
});
it('should set the same defaults for everything but strings', function () {
const results = initDefaultFieldProps(fields);
_.forEach(results, function (field) {
if (field.type !== 'string') {
expect(field).to.have.property('indexed', true);
expect(field).to.have.property('analyzed', false);
expect(field).to.have.property('doc_values', true);
expect(field).to.have.property('scripted', false);
expect(field).to.have.property('count', 0);
}
});
});
it('should make string fields analyzed', function () {
const results = initDefaultFieldProps(fields);
_.forEach(results, function (field) {
if (field.type === 'string' && !_.contains(field.name, 'keyword')) {
expect(field).to.have.property('indexed', true);
expect(field).to.have.property('analyzed', true);
expect(field).to.have.property('doc_values', false);
expect(field).to.have.property('scripted', false);
expect(field).to.have.property('count', 0);
}
});
});
it('should create an extra raw non-analyzed field for strings', function () {
const results = initDefaultFieldProps(fields);
const rawField = _.find(results, function (field) {
return _.contains(field.name, 'keyword');
});
expect(rawField).to.have.property('indexed', true);
expect(rawField).to.have.property('analyzed', false);
expect(rawField).to.have.property('doc_values', true);
expect(rawField).to.have.property('scripted', false);
expect(rawField).to.have.property('count', 0);
});
it('should apply some overrides to metafields', function () {
const results = initDefaultFieldProps([{name: '_source'}, {name: '_timestamp'}]);
const expected = [
{
name: '_source',
indexed: false,
analyzed: false,
doc_values: false,
count: 0,
scripted: false,
type: '_source'
},
{
name: '_timestamp',
indexed: true,
analyzed: false,
doc_values: false,
count: 0,
scripted: false,
type: 'date'
}
];
expect(_.isEqual(expected, results)).to.be.ok();
});
});

View file

@ -1,90 +0,0 @@
import processESIngestProcessorsResponse from '../process_es_ingest_processors_response';
import expect from 'expect.js';
import _ from 'lodash';
describe('processESIngestSimulateResponse', function () {
it('should return a list of strings indicating the enabled processors', function () {
const response = {
nodes: {
node_foo: {
ingest: {
processors: [
{ type: 'proc_foo' },
{ type: 'proc_bar' }
]
}
}
}
};
const expected = [ 'proc_foo', 'proc_bar' ];
const actual = processESIngestProcessorsResponse(response);
expect(_.isEqual(actual, expected)).to.be.ok();
});
it('should return a unique list of processors', function () {
const response = {
nodes: {
node_foo: {
ingest: {
processors: [
{ type: 'proc_foo' },
{ type: 'proc_bar' }
]
}
},
node_bar: {
ingest: {
processors: [
{ type: 'proc_foo' },
{ type: 'proc_bar' }
]
}
}
}
};
const expected = [ 'proc_foo', 'proc_bar' ];
const actual = processESIngestProcessorsResponse(response);
expect(_.isEqual(actual, expected)).to.be.ok();
});
it('should combine the available processors from all nodes', function () {
const response = {
nodes: {
node_foo: {
ingest: {
processors: [
{ type: 'proc_foo' }
]
}
},
node_bar: {
ingest: {
processors: [
{ type: 'proc_bar' }
]
}
}
}
};
const expected = [ 'proc_foo', 'proc_bar' ];
const actual = processESIngestProcessorsResponse(response);
expect(_.isEqual(actual, expected)).to.be.ok();
});
it('should return an empty array for unexpected response', function () {
expect(_.isEqual(processESIngestProcessorsResponse({ nodes: {}}), [])).to.be.ok();
expect(_.isEqual(processESIngestProcessorsResponse({}), [])).to.be.ok();
expect(_.isEqual(processESIngestProcessorsResponse(undefined), [])).to.be.ok();
expect(_.isEqual(processESIngestProcessorsResponse(null), [])).to.be.ok();
expect(_.isEqual(processESIngestProcessorsResponse(''), [])).to.be.ok();
expect(_.isEqual(processESIngestProcessorsResponse(1), [])).to.be.ok();
});
});

View file

@ -1,19 +0,0 @@
import processESIngestSimulateError from '../process_es_ingest_simulate_error';
import expect from 'expect.js';
import _ from 'lodash';
describe('processESIngestSimulateError', function () {
it('result will be returned for processor that threw the error', function () {
const error = _.set({}, 'body.error.root_cause[0].reason', 'foobar');
_.set(error, 'body.error.root_cause[0].header.processor_tag', 'processor1');
const expected = [
{ processorId: 'processor1', error: { compile: true, message: 'foobar' } }
];
const actual = processESIngestSimulateError(error);
expect(_.isEqual(actual, expected)).to.be.ok();
});
});

View file

@ -1,73 +0,0 @@
import processESIngestSimulateResponse from '../process_es_ingest_simulate_response';
import expect from 'expect.js';
import _ from 'lodash';
describe('processESIngestSimulateResponse', function () {
it('each processor that receives a result will contain response info', function () {
const response = {
docs: [ { processor_results: [
{ tag: 'processor1', doc: { _source: 'new_foo' }, error: undefined },
{ tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined },
{ tag: 'processor3', doc: { _source: 'new_baz' }, error: undefined }
] } ]
};
const expected = [
{ processorId: 'processor1', output: 'new_foo', error: undefined },
{ processorId: 'processor2', output: 'new_bar', error: undefined },
{ processorId: 'processor3', output: 'new_baz', error: undefined }
];
const actual = processESIngestSimulateResponse(response);
expect(actual).to.eql(expected);
});
describe('processors that return an error object', function () {
it('will be the root_cause reason if one exists', function () {
const response = {
docs: [ { processor_results: [
{ tag: 'processor1', doc: { _source: 'new_foo' }, error: undefined },
{
tag: 'processor2',
doc: 'dummy',
error: { root_cause: [ { reason: 'something bad happened', type: 'general exception' } ] }
}
] } ]
};
const expected = [
{ processorId: 'processor1', output: 'new_foo', error: undefined },
{ processorId: 'processor2', output: undefined, error: { compile: false, message: 'something bad happened'} }
];
const actual = processESIngestSimulateResponse(response);
expect(actual).to.eql(expected);
});
it('will be the root_cause type if reason does not exists', function () {
const response = {
docs: [ { processor_results: [
{ tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined },
{
tag: 'processor3',
doc: 'dummy',
error: { root_cause: [ { type: 'something bad happened' } ] }
}
] } ]
};
const expected = [
{ processorId: 'processor2', output: 'new_bar', error: undefined },
{ processorId: 'processor3', output: undefined, error: { compile: false, message: 'something bad happened'} }
];
const actual = processESIngestSimulateResponse(response);
expect(actual).to.eql(expected);
});
});
});

View file

@ -1,10 +0,0 @@
import _ from 'lodash';
import * as ingestProcessorApiKibanaToEsConverters from '../processors/converters';
export default function ingestPipelineApiKibanaToEsConverter(pipelineApiDocument) {
return {
processors: _.map(pipelineApiDocument, (processor) => {
return ingestProcessorApiKibanaToEsConverters[processor.type_id](processor);
})
};
}

Some files were not shown because too many files have changed in this diff Show more