mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Upgrade to eslint 4 (#14862)
* [eslint] upgrade to 4.10.0
* [eslint-config-kibana] limit jest config to jest test files
* [ui_framework] remove trailing comma from rest-spreads
* [dashboard/tests] tag jest helpers with .test.js suffix
* explicitly import expect.js where used
* [eslint] apply auto-fixes
* [eslint] manually add/wrap some parens for compliance
* [npm] point to local packages for testing/review
* [jest] remove .test extension from jest helpers
* [ui_framework] fix trailing comma removal from 3bc661a1c8
* [packages] upgrade eslint packages
This commit is contained in:
parent
94b2b324aa
commit
5cddc10077
380 changed files with 8880 additions and 8855 deletions
18
package.json
18
package.json
|
@ -214,11 +214,11 @@
|
|||
"yauzl": "2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elastic/eslint-config-kibana": "0.13.0",
|
||||
"@elastic/eslint-config-kibana": "0.14.0",
|
||||
"@elastic/eslint-import-resolver-kibana": "0.9.0",
|
||||
"@elastic/eslint-plugin-kibana-custom": "1.0.3",
|
||||
"@elastic/eslint-plugin-kibana-custom": "1.1.0",
|
||||
"angular-mocks": "1.4.7",
|
||||
"babel-eslint": "7.2.3",
|
||||
"babel-eslint": "8.0.2",
|
||||
"backport": "1.1.1",
|
||||
"chai": "3.5.0",
|
||||
"chalk": "2.0.1",
|
||||
|
@ -229,13 +229,13 @@
|
|||
"classnames": "2.2.5",
|
||||
"enzyme": "2.9.1",
|
||||
"enzyme-to-json": "1.4.5",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-babel": "4.1.1",
|
||||
"eslint-plugin-import": "2.3.0",
|
||||
"eslint-plugin-jest": "21.0.0",
|
||||
"eslint-plugin-mocha": "4.9.0",
|
||||
"eslint": "4.10.0",
|
||||
"eslint-plugin-babel": "4.1.2",
|
||||
"eslint-plugin-import": "2.8.0",
|
||||
"eslint-plugin-jest": "21.2.0",
|
||||
"eslint-plugin-mocha": "4.11.0",
|
||||
"eslint-plugin-prefer-object-spread": "1.2.1",
|
||||
"eslint-plugin-react": "7.1.0",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"event-stream": "3.3.2",
|
||||
"expect.js": "0.3.1",
|
||||
"faker": "1.1.0",
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
'jest',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.test.js'],
|
||||
plugins: [
|
||||
'jest',
|
||||
],
|
||||
|
||||
env: {
|
||||
'jest/globals': true,
|
||||
},
|
||||
env: {
|
||||
'jest/globals': true,
|
||||
},
|
||||
|
||||
rules: {
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
'jest/no-identical-title': 'error',
|
||||
},
|
||||
rules: {
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
'jest/no-identical-title': 'error',
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@elastic/eslint-config-kibana",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"description": "The eslint config used by the kibana team",
|
||||
"main": ".eslintrc.js",
|
||||
"scripts": {
|
||||
|
@ -18,7 +18,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/elastic/eslint-config-kibana#readme",
|
||||
"peerDependencies": {
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-eslint": "^8.0.0",
|
||||
"eslint": "^4.1.0",
|
||||
"eslint-plugin-babel": "^4.1.1",
|
||||
"eslint-plugin-import": "^2.6.0",
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
module.exports.rules = {
|
||||
'no-default-export': context => ({
|
||||
ExportDefaultDeclaration: (node) => {
|
||||
context.report(node, 'Default exports not allowed.');
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-default-export': {
|
||||
meta: {
|
||||
schema: []
|
||||
},
|
||||
create: context => ({
|
||||
ExportDefaultDeclaration: (node) => {
|
||||
context.report(node, 'Default exports not allowed.');
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@elastic/eslint-plugin-kibana-custom",
|
||||
"version": "1.0.3",
|
||||
"version": "1.1.0",
|
||||
"description": "Custom ESLint rules for Kibana",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -7,29 +7,29 @@ const argv = process.env.kbnWorkerArgv ? JSON.parse(process.env.kbnWorkerArgv) :
|
|||
const program = new Command('bin/kibana');
|
||||
|
||||
program
|
||||
.version(pkg.version)
|
||||
.description(
|
||||
'Kibana is an open source (Apache Licensed), browser ' +
|
||||
.version(pkg.version)
|
||||
.description(
|
||||
'Kibana is an open source (Apache Licensed), browser ' +
|
||||
'based analytics and search dashboard for Elasticsearch.'
|
||||
);
|
||||
);
|
||||
|
||||
// attach commands
|
||||
serveCommand(program);
|
||||
|
||||
program
|
||||
.command('help <command>')
|
||||
.description('Get the help for a specific command')
|
||||
.action(function (cmdName) {
|
||||
const cmd = _.find(program.commands, { _name: cmdName });
|
||||
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
||||
cmd.help();
|
||||
});
|
||||
.command('help <command>')
|
||||
.description('Get the help for a specific command')
|
||||
.action(function (cmdName) {
|
||||
const cmd = _.find(program.commands, { _name: cmdName });
|
||||
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
||||
cmd.help();
|
||||
});
|
||||
|
||||
program
|
||||
.command('*', null, { noHelp: true })
|
||||
.action(function (cmd) {
|
||||
program.error(`unknown command ${cmd}`);
|
||||
});
|
||||
.command('*', null, { noHelp: true })
|
||||
.action(function (cmd) {
|
||||
program.error(`unknown command ${cmd}`);
|
||||
});
|
||||
|
||||
// check for no command name
|
||||
const subCommand = argv[2] && !String(argv[2][0]).match(/^-|^\.|\//);
|
||||
|
|
|
@ -82,8 +82,8 @@ export default class BasePathProxy {
|
|||
});
|
||||
}
|
||||
})
|
||||
.return(undefined)
|
||||
.nodeify(reply);
|
||||
.return(undefined)
|
||||
.nodeify(reply);
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ Command.prototype.error = function (err) {
|
|||
if (err && err.message) err = err.message;
|
||||
|
||||
console.log(
|
||||
`
|
||||
`
|
||||
${red(' ERROR ')} ${err}
|
||||
|
||||
${help(this, ' ')}
|
||||
|
@ -20,7 +20,7 @@ ${help(this, ' ')}
|
|||
|
||||
Command.prototype.defaultHelp = function () {
|
||||
console.log(
|
||||
`
|
||||
`
|
||||
${help(this, ' ')}
|
||||
|
||||
`
|
||||
|
|
|
@ -13,7 +13,7 @@ export default function help(command, spaces) {
|
|||
const cmdDef = !defCmd ? '' : `=${defCmd._name}`;
|
||||
|
||||
return (
|
||||
`
|
||||
`
|
||||
Usage: ${command._name} [command${cmdDef}] [options]
|
||||
|
||||
${desc}
|
||||
|
@ -58,7 +58,7 @@ function cmdHelp(cmd) {
|
|||
if (!cmd) return '';
|
||||
return (
|
||||
|
||||
`
|
||||
`
|
||||
"${cmd._name}" Options:
|
||||
|
||||
${indent(cmd.optionHelp(), 2)}
|
||||
|
|
|
@ -79,109 +79,109 @@ export default function (program) {
|
|||
const command = program.command('serve');
|
||||
|
||||
command
|
||||
.description('Run the kibana server')
|
||||
.collectUnknownOptions()
|
||||
.option('-e, --elasticsearch <uri>', 'Elasticsearch instance')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'Path to the config file, can be changed with the CONFIG_PATH environment variable as well. ' +
|
||||
.description('Run the kibana server')
|
||||
.collectUnknownOptions()
|
||||
.option('-e, --elasticsearch <uri>', 'Elasticsearch instance')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'Path to the config file, can be changed with the CONFIG_PATH environment variable as well. ' +
|
||||
'Use multiple --config args to include multiple config files.',
|
||||
configPathCollector,
|
||||
[ getConfig() ]
|
||||
)
|
||||
.option('-p, --port <port>', 'The port to bind to', parseInt)
|
||||
.option('-q, --quiet', 'Prevent all logging except errors')
|
||||
.option('-Q, --silent', 'Prevent all logging')
|
||||
.option('--verbose', 'Turns on verbose logging')
|
||||
.option('-H, --host <host>', 'The host to bind to')
|
||||
.option('-l, --log-file <path>', 'The file to log to')
|
||||
.option(
|
||||
'--plugin-dir <path>',
|
||||
'A path to scan for plugins, this can be specified multiple ' +
|
||||
configPathCollector,
|
||||
[ getConfig() ]
|
||||
)
|
||||
.option('-p, --port <port>', 'The port to bind to', parseInt)
|
||||
.option('-q, --quiet', 'Prevent all logging except errors')
|
||||
.option('-Q, --silent', 'Prevent all logging')
|
||||
.option('--verbose', 'Turns on verbose logging')
|
||||
.option('-H, --host <host>', 'The host to bind to')
|
||||
.option('-l, --log-file <path>', 'The file to log to')
|
||||
.option(
|
||||
'--plugin-dir <path>',
|
||||
'A path to scan for plugins, this can be specified multiple ' +
|
||||
'times to specify multiple directories',
|
||||
pluginDirCollector,
|
||||
[
|
||||
fromRoot('plugins'),
|
||||
fromRoot('src/core_plugins')
|
||||
]
|
||||
)
|
||||
.option(
|
||||
'--plugin-path <path>',
|
||||
'A path to a plugin which should be included by the server, ' +
|
||||
pluginDirCollector,
|
||||
[
|
||||
fromRoot('plugins'),
|
||||
fromRoot('src/core_plugins')
|
||||
]
|
||||
)
|
||||
.option(
|
||||
'--plugin-path <path>',
|
||||
'A path to a plugin which should be included by the server, ' +
|
||||
'this can be specified multiple times to specify multiple paths',
|
||||
pluginPathCollector,
|
||||
[]
|
||||
)
|
||||
.option('--plugins <path>', 'an alias for --plugin-dir', pluginDirCollector);
|
||||
pluginPathCollector,
|
||||
[]
|
||||
)
|
||||
.option('--plugins <path>', 'an alias for --plugin-dir', pluginDirCollector);
|
||||
|
||||
if (canCluster) {
|
||||
command
|
||||
.option('--dev', 'Run the server with development mode defaults')
|
||||
.option('--ssl', 'Run the dev server using HTTPS')
|
||||
.option('--no-base-path', 'Don\'t put a proxy in front of the dev server, which adds a random basePath')
|
||||
.option('--no-watch', 'Prevents automatic restarts of the server in --dev mode');
|
||||
.option('--dev', 'Run the server with development mode defaults')
|
||||
.option('--ssl', 'Run the dev server using HTTPS')
|
||||
.option('--no-base-path', 'Don\'t put a proxy in front of the dev server, which adds a random basePath')
|
||||
.option('--no-watch', 'Prevents automatic restarts of the server in --dev mode');
|
||||
}
|
||||
|
||||
command
|
||||
.action(async function (opts) {
|
||||
if (opts.dev) {
|
||||
try {
|
||||
const kbnDevConfig = fromRoot('config/kibana.dev.yml');
|
||||
if (statSync(kbnDevConfig).isFile()) {
|
||||
opts.config.push(kbnDevConfig);
|
||||
}
|
||||
} catch (err) {
|
||||
.action(async function (opts) {
|
||||
if (opts.dev) {
|
||||
try {
|
||||
const kbnDevConfig = fromRoot('config/kibana.dev.yml');
|
||||
if (statSync(kbnDevConfig).isFile()) {
|
||||
opts.config.push(kbnDevConfig);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore, kibana.dev.yml does not exist
|
||||
}
|
||||
}
|
||||
|
||||
const getCurrentSettings = () => readServerSettings(opts, this.getUnknownOptions());
|
||||
const settings = getCurrentSettings();
|
||||
|
||||
if (canCluster && opts.dev && !isWorker) {
|
||||
// stop processing the action and handoff to cluster manager
|
||||
const ClusterManager = require('../cluster/cluster_manager');
|
||||
new ClusterManager(opts, settings);
|
||||
return;
|
||||
}
|
||||
|
||||
let kbnServer = {};
|
||||
const KbnServer = require('../../server/kbn_server');
|
||||
try {
|
||||
kbnServer = new KbnServer(settings);
|
||||
await kbnServer.ready();
|
||||
} catch (error) {
|
||||
const { server } = kbnServer;
|
||||
|
||||
switch (error.code) {
|
||||
case 'EADDRINUSE':
|
||||
logFatal(`Port ${error.port} is already in use. Another instance of Kibana may be running!`, server);
|
||||
break;
|
||||
|
||||
case 'InvalidConfig':
|
||||
logFatal(error.message, server);
|
||||
break;
|
||||
|
||||
default:
|
||||
logFatal(error, server);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kbnServer.close();
|
||||
const exitCode = error.processExitCode == null ? 1 : error.processExitCode;
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
process.on('SIGHUP', function reloadConfig() {
|
||||
const getCurrentSettings = () => readServerSettings(opts, this.getUnknownOptions());
|
||||
const settings = getCurrentSettings();
|
||||
kbnServer.server.log(['info', 'config'], 'Reloading logging configuration due to SIGHUP.');
|
||||
kbnServer.applyLoggingConfiguration(settings);
|
||||
kbnServer.server.log(['info', 'config'], 'Reloaded logging configuration due to SIGHUP.');
|
||||
});
|
||||
|
||||
return kbnServer;
|
||||
});
|
||||
if (canCluster && opts.dev && !isWorker) {
|
||||
// stop processing the action and handoff to cluster manager
|
||||
const ClusterManager = require('../cluster/cluster_manager');
|
||||
new ClusterManager(opts, settings);
|
||||
return;
|
||||
}
|
||||
|
||||
let kbnServer = {};
|
||||
const KbnServer = require('../../server/kbn_server');
|
||||
try {
|
||||
kbnServer = new KbnServer(settings);
|
||||
await kbnServer.ready();
|
||||
} catch (error) {
|
||||
const { server } = kbnServer;
|
||||
|
||||
switch (error.code) {
|
||||
case 'EADDRINUSE':
|
||||
logFatal(`Port ${error.port} is already in use. Another instance of Kibana may be running!`, server);
|
||||
break;
|
||||
|
||||
case 'InvalidConfig':
|
||||
logFatal(error.message, server);
|
||||
break;
|
||||
|
||||
default:
|
||||
logFatal(error, server);
|
||||
break;
|
||||
}
|
||||
|
||||
kbnServer.close();
|
||||
const exitCode = error.processExitCode == null ? 1 : error.processExitCode;
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
process.on('SIGHUP', function reloadConfig() {
|
||||
const settings = getCurrentSettings();
|
||||
kbnServer.server.log(['info', 'config'], 'Reloading logging configuration due to SIGHUP.');
|
||||
kbnServer.applyLoggingConfiguration(settings);
|
||||
kbnServer.server.log(['info', 'config'], 'Reloaded logging configuration due to SIGHUP.');
|
||||
});
|
||||
|
||||
return kbnServer;
|
||||
});
|
||||
}
|
||||
|
||||
function logFatal(message, server) {
|
||||
|
|
|
@ -9,30 +9,30 @@ const argv = process.env.kbnWorkerArgv ? JSON.parse(process.env.kbnWorkerArgv) :
|
|||
const program = new Command('bin/kibana-plugin');
|
||||
|
||||
program
|
||||
.version(pkg.version)
|
||||
.description(
|
||||
'The Kibana plugin manager enables you to install and remove plugins that ' +
|
||||
.version(pkg.version)
|
||||
.description(
|
||||
'The Kibana plugin manager enables you to install and remove plugins that ' +
|
||||
'provide additional functionality to Kibana'
|
||||
);
|
||||
);
|
||||
|
||||
listCommand(program);
|
||||
installCommand(program);
|
||||
removeCommand(program);
|
||||
|
||||
program
|
||||
.command('help <command>')
|
||||
.description('get the help for a specific command')
|
||||
.action(function (cmdName) {
|
||||
const cmd = _.find(program.commands, { _name: cmdName });
|
||||
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
||||
cmd.help();
|
||||
});
|
||||
.command('help <command>')
|
||||
.description('get the help for a specific command')
|
||||
.action(function (cmdName) {
|
||||
const cmd = _.find(program.commands, { _name: cmdName });
|
||||
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
||||
cmd.help();
|
||||
});
|
||||
|
||||
program
|
||||
.command('*', null, { noHelp: true })
|
||||
.action(function (cmd) {
|
||||
program.error(`unknown command ${cmd}`);
|
||||
});
|
||||
.command('*', null, { noHelp: true })
|
||||
.action(function (cmd) {
|
||||
program.error(`unknown command ${cmd}`);
|
||||
});
|
||||
|
||||
// check for no command name
|
||||
const subCommand = argv[2] && !String(argv[2][0]).match(/^-|^\.|\//);
|
||||
|
|
|
@ -42,10 +42,10 @@ describe('kibana cli', function () {
|
|||
});
|
||||
|
||||
return cleanPrevious(settings, logger)
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(false);
|
||||
});
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should rethrow any exception except ENOENT from fs.statSync', function () {
|
||||
|
@ -57,10 +57,10 @@ describe('kibana cli', function () {
|
|||
|
||||
errorStub = sinon.stub();
|
||||
return cleanPrevious(settings, logger)
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(true);
|
||||
});
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should log a message if there was a working directory', function () {
|
||||
|
@ -68,10 +68,10 @@ describe('kibana cli', function () {
|
|||
sinon.stub(fs, 'statSync');
|
||||
|
||||
return cleanPrevious(settings, logger)
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(logger.log.calledWith('Found previous install attempt. Deleting...')).to.be(true);
|
||||
});
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(logger.log.calledWith('Found previous install attempt. Deleting...')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should rethrow any exception from rimraf.sync', function () {
|
||||
|
@ -82,10 +82,10 @@ describe('kibana cli', function () {
|
|||
|
||||
errorStub = sinon.stub();
|
||||
return cleanPrevious(settings, logger)
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(true);
|
||||
});
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve if the working path is deleted', function () {
|
||||
|
@ -93,10 +93,10 @@ describe('kibana cli', function () {
|
|||
sinon.stub(fs, 'statSync');
|
||||
|
||||
return cleanPrevious(settings, logger)
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(false);
|
||||
});
|
||||
.catch(errorStub)
|
||||
.then(function () {
|
||||
expect(errorStub.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -70,20 +70,20 @@ describe('kibana cli', function () {
|
|||
const sourceUrl = 'http://example.com/plugin.tar.gz';
|
||||
|
||||
return _downloadSingle(settings, logger, sourceUrl)
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err.message).to.match(/ENOTFOUND/);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err.message).to.match(/ENOTFOUND/);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an UnsupportedProtocolError for an invalid url', function () {
|
||||
const sourceUrl = 'i am an invalid url';
|
||||
|
||||
return _downloadSingle(settings, logger, sourceUrl)
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err).to.be.an(UnsupportedProtocolError);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err).to.be.an(UnsupportedProtocolError);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
it('should download a file from a valid http url', function () {
|
||||
|
@ -100,9 +100,9 @@ describe('kibana cli', function () {
|
|||
const sourceUrl = 'http://example.com/plugin.zip';
|
||||
|
||||
return _downloadSingle(settings, logger, sourceUrl)
|
||||
.then(function () {
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
.then(function () {
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -114,10 +114,10 @@ describe('kibana cli', function () {
|
|||
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
|
||||
|
||||
return _downloadSingle(settings, logger, sourceUrl)
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err.message).to.match(/ENOTFOUND/);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err.message).to.match(/ENOTFOUND/);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy a valid local file', function () {
|
||||
|
@ -125,9 +125,9 @@ describe('kibana cli', function () {
|
|||
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
|
||||
|
||||
return _downloadSingle(settings, logger, sourceUrl)
|
||||
.then(function () {
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
.then(function () {
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -176,24 +176,24 @@ describe('kibana cli', function () {
|
|||
];
|
||||
|
||||
nock('http://example.com')
|
||||
.defaultReplyHeaders({
|
||||
'content-length': '10'
|
||||
})
|
||||
.get('/badfile1.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile2.tar.gz')
|
||||
.reply(404)
|
||||
.get('/goodfile.tar.gz')
|
||||
.replyWithFile(200, filePath);
|
||||
.defaultReplyHeaders({
|
||||
'content-length': '10'
|
||||
})
|
||||
.get('/badfile1.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile2.tar.gz')
|
||||
.reply(404)
|
||||
.get('/goodfile.tar.gz')
|
||||
.replyWithFile(200, filePath);
|
||||
|
||||
return download(settings, logger)
|
||||
.then(function () {
|
||||
expect(logger.log.getCall(0).args[0]).to.match(/badfile1.tar.gz/);
|
||||
expect(logger.log.getCall(1).args[0]).to.match(/badfile2.tar.gz/);
|
||||
expect(logger.log.getCall(2).args[0]).to.match(/I am a bad uri/);
|
||||
expect(logger.log.getCall(3).args[0]).to.match(/goodfile.tar.gz/);
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
.then(function () {
|
||||
expect(logger.log.getCall(0).args[0]).to.match(/badfile1.tar.gz/);
|
||||
expect(logger.log.getCall(1).args[0]).to.match(/badfile2.tar.gz/);
|
||||
expect(logger.log.getCall(2).args[0]).to.match(/I am a bad uri/);
|
||||
expect(logger.log.getCall(3).args[0]).to.match(/goodfile.tar.gz/);
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
it('should stop looping through urls when it finds a good one.', function () {
|
||||
|
@ -206,25 +206,25 @@ describe('kibana cli', function () {
|
|||
];
|
||||
|
||||
nock('http://example.com')
|
||||
.defaultReplyHeaders({
|
||||
'content-length': '10'
|
||||
})
|
||||
.get('/badfile1.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile2.tar.gz')
|
||||
.reply(404)
|
||||
.get('/goodfile.tar.gz')
|
||||
.replyWithFile(200, filePath)
|
||||
.get('/badfile3.tar.gz')
|
||||
.reply(404);
|
||||
.defaultReplyHeaders({
|
||||
'content-length': '10'
|
||||
})
|
||||
.get('/badfile1.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile2.tar.gz')
|
||||
.reply(404)
|
||||
.get('/goodfile.tar.gz')
|
||||
.replyWithFile(200, filePath)
|
||||
.get('/badfile3.tar.gz')
|
||||
.reply(404);
|
||||
|
||||
return download(settings, logger)
|
||||
.then(function () {
|
||||
for (let i = 0; i < logger.log.callCount; i++) {
|
||||
expect(logger.log.getCall(i).args[0]).to.not.match(/badfile3.tar.gz/);
|
||||
}
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
.then(function () {
|
||||
for (let i = 0; i < logger.log.callCount; i++) {
|
||||
expect(logger.log.getCall(i).args[0]).to.not.match(/badfile3.tar.gz/);
|
||||
}
|
||||
expectWorkingPathNotEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when it doesn\'t find a good url.', function () {
|
||||
|
@ -235,21 +235,21 @@ describe('kibana cli', function () {
|
|||
];
|
||||
|
||||
nock('http://example.com')
|
||||
.defaultReplyHeaders({
|
||||
'content-length': '10'
|
||||
})
|
||||
.get('/badfile1.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile2.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile3.tar.gz')
|
||||
.reply(404);
|
||||
.defaultReplyHeaders({
|
||||
'content-length': '10'
|
||||
})
|
||||
.get('/badfile1.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile2.tar.gz')
|
||||
.reply(404)
|
||||
.get('/badfile3.tar.gz')
|
||||
.reply(404);
|
||||
|
||||
return download(settings, logger)
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err.message).to.match(/no valid url specified/i);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
.then(shouldReject, function (err) {
|
||||
expect(err.message).to.match(/no valid url specified/i);
|
||||
expectWorkingPathEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
|
|
|
@ -66,25 +66,25 @@ describe('kibana cli', function () {
|
|||
//Ignores the others.
|
||||
it('successfully extract a valid zip', function () {
|
||||
return copyReplyFile('test_plugin.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
return extract(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
const files = glob.sync('**/*', { cwd: testWorkingPath });
|
||||
const expected = [
|
||||
'archive.part',
|
||||
'README.md',
|
||||
'index.js',
|
||||
'package.json',
|
||||
'public',
|
||||
'public/app.js',
|
||||
'extra file only in zip.txt'
|
||||
];
|
||||
expect(files.sort()).to.eql(expected.sort());
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
return extract(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
const files = glob.sync('**/*', { cwd: testWorkingPath });
|
||||
const expected = [
|
||||
'archive.part',
|
||||
'README.md',
|
||||
'index.js',
|
||||
'package.json',
|
||||
'public',
|
||||
'public/app.js',
|
||||
'extra file only in zip.txt'
|
||||
];
|
||||
expect(files.sort()).to.eql(expected.sort());
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -93,99 +93,99 @@ describe('kibana cli', function () {
|
|||
|
||||
it('populate settings.plugins', function () {
|
||||
return copyReplyFile('test_plugin.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].name).to.be('test-plugin');
|
||||
expect(settings.plugins[0].archivePath).to.be('kibana/test-plugin');
|
||||
expect(settings.plugins[0].version).to.be('1.0.0');
|
||||
expect(settings.plugins[0].kibanaVersion).to.be('1.0.0');
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].name).to.be('test-plugin');
|
||||
expect(settings.plugins[0].archivePath).to.be('kibana/test-plugin');
|
||||
expect(settings.plugins[0].version).to.be('1.0.0');
|
||||
expect(settings.plugins[0].kibanaVersion).to.be('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('populate settings.plugin.kibanaVersion', function () {
|
||||
//kibana.version is defined in this package.json and is different than plugin version
|
||||
return copyReplyFile('test_plugin_different_version.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].kibanaVersion).to.be('5.0.1');
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].kibanaVersion).to.be('5.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
it('populate settings.plugin.kibanaVersion (default to plugin version)', function () {
|
||||
//kibana.version is not defined in this package.json, defaults to plugin version
|
||||
return copyReplyFile('test_plugin.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].kibanaVersion).to.be('1.0.0');
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].kibanaVersion).to.be('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('populate settings.plugins with multiple plugins', function () {
|
||||
return copyReplyFile('test_plugin_many.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].name).to.be('funger-plugin');
|
||||
expect(settings.plugins[0].archivePath).to.be('kibana/funger-plugin');
|
||||
expect(settings.plugins[0].version).to.be('1.0.0');
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].name).to.be('funger-plugin');
|
||||
expect(settings.plugins[0].archivePath).to.be('kibana/funger-plugin');
|
||||
expect(settings.plugins[0].version).to.be('1.0.0');
|
||||
|
||||
expect(settings.plugins[1].name).to.be('pdf');
|
||||
expect(settings.plugins[1].archivePath).to.be('kibana/pdf-linux');
|
||||
expect(settings.plugins[1].version).to.be('1.0.0');
|
||||
expect(settings.plugins[1].name).to.be('pdf');
|
||||
expect(settings.plugins[1].archivePath).to.be('kibana/pdf-linux');
|
||||
expect(settings.plugins[1].version).to.be('1.0.0');
|
||||
|
||||
expect(settings.plugins[2].name).to.be('pdf');
|
||||
expect(settings.plugins[2].archivePath).to.be('kibana/pdf-win32');
|
||||
expect(settings.plugins[2].version).to.be('1.0.0');
|
||||
expect(settings.plugins[2].name).to.be('pdf');
|
||||
expect(settings.plugins[2].archivePath).to.be('kibana/pdf-win32');
|
||||
expect(settings.plugins[2].version).to.be('1.0.0');
|
||||
|
||||
expect(settings.plugins[3].name).to.be('pdf');
|
||||
expect(settings.plugins[3].archivePath).to.be('kibana/pdf-win64');
|
||||
expect(settings.plugins[3].version).to.be('1.0.0');
|
||||
expect(settings.plugins[3].name).to.be('pdf');
|
||||
expect(settings.plugins[3].archivePath).to.be('kibana/pdf-win64');
|
||||
expect(settings.plugins[3].version).to.be('1.0.0');
|
||||
|
||||
expect(settings.plugins[4].name).to.be('pdf');
|
||||
expect(settings.plugins[4].archivePath).to.be('kibana/pdf');
|
||||
expect(settings.plugins[4].version).to.be('1.0.0');
|
||||
expect(settings.plugins[4].name).to.be('pdf');
|
||||
expect(settings.plugins[4].archivePath).to.be('kibana/pdf');
|
||||
expect(settings.plugins[4].version).to.be('1.0.0');
|
||||
|
||||
expect(settings.plugins[5].name).to.be('test-plugin');
|
||||
expect(settings.plugins[5].archivePath).to.be('kibana/test-plugin');
|
||||
expect(settings.plugins[5].version).to.be('1.0.0');
|
||||
});
|
||||
expect(settings.plugins[5].name).to.be('test-plugin');
|
||||
expect(settings.plugins[5].archivePath).to.be('kibana/test-plugin');
|
||||
expect(settings.plugins[5].version).to.be('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('throw an error if there is no kibana plugin', function () {
|
||||
return copyReplyFile('test_plugin_no_kibana.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).to.match(/No kibana plugins found in archive/i);
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).to.match(/No kibana plugins found in archive/i);
|
||||
});
|
||||
});
|
||||
|
||||
it('throw an error with a corrupt zip', function () {
|
||||
return copyReplyFile('corrupt.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).to.match(/error retrieving/i);
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).to.match(/error retrieving/i);
|
||||
});
|
||||
});
|
||||
|
||||
it('throw an error if there an invalid plugin name', function () {
|
||||
return copyReplyFile('invalid_name.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).to.match(/invalid plugin name/i);
|
||||
});
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).to.match(/invalid plugin name/i);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -23,10 +23,10 @@ describe('plugin folder rename', function () {
|
|||
});
|
||||
|
||||
return renamePlugin('/foo/bar', '/bar/foo')
|
||||
.catch(function (err) {
|
||||
expect(err.code).to.be('error');
|
||||
expect(renameStub.callCount).to.be(1);
|
||||
});
|
||||
.catch(function (err) {
|
||||
expect(err.code).to.be('error');
|
||||
expect(renameStub.callCount).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve if there are no errors', function () {
|
||||
|
@ -35,12 +35,12 @@ describe('plugin folder rename', function () {
|
|||
});
|
||||
|
||||
return renamePlugin('/foo/bar', '/bar/foo')
|
||||
.then(function () {
|
||||
expect(renameStub.callCount).to.be(1);
|
||||
})
|
||||
.catch(function () {
|
||||
throw new Error('We shouln\'t have any errors');
|
||||
});
|
||||
.then(function () {
|
||||
expect(renameStub.callCount).to.be(1);
|
||||
})
|
||||
.catch(function () {
|
||||
throw new Error('We shouln\'t have any errors');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Windows', function () {
|
||||
|
@ -63,10 +63,10 @@ describe('plugin folder rename', function () {
|
|||
});
|
||||
});
|
||||
return renamePlugin('/foo/bar', '/bar/foo')
|
||||
.catch(function (err) {
|
||||
expect(err.code).to.be('EPERM');
|
||||
expect(renameStub.callCount).to.be.greaterThan(1);
|
||||
});
|
||||
.catch(function (err) {
|
||||
expect(err.code).to.be('EPERM');
|
||||
expect(renameStub.callCount).to.be.greaterThan(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,14 +53,14 @@ export function download(settings, logger) {
|
|||
logger.log(`Attempting to transfer from ${sourceUrl}`);
|
||||
|
||||
return _downloadSingle(settings, logger, sourceUrl)
|
||||
.catch((err) => {
|
||||
const isUnsupportedProtocol = err instanceof UnsupportedProtocolError;
|
||||
const isDownloadResourceNotFound = err.message === 'ENOTFOUND';
|
||||
if (isUnsupportedProtocol || isDownloadResourceNotFound) {
|
||||
return tryNext();
|
||||
}
|
||||
throw (err);
|
||||
});
|
||||
.catch((err) => {
|
||||
const isUnsupportedProtocol = err instanceof UnsupportedProtocolError;
|
||||
const isDownloadResourceNotFound = err.message === 'ENOTFOUND';
|
||||
if (isUnsupportedProtocol || isDownloadResourceNotFound) {
|
||||
return tryNext();
|
||||
}
|
||||
throw (err);
|
||||
});
|
||||
}
|
||||
|
||||
return tryNext();
|
||||
|
|
|
@ -60,4 +60,4 @@ export default async function copyLocalFile(logger, sourcePath, targetPath) {
|
|||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -90,4 +90,4 @@ export default async function downloadUrl(logger, sourceUrl, targetPath, timeout
|
|||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,28 +22,28 @@ function processCommand(command, options) {
|
|||
|
||||
export default function pluginInstall(program) {
|
||||
program
|
||||
.command('install <plugin/url>')
|
||||
.option('-q, --quiet', 'disable all process messaging except errors')
|
||||
.option('-s, --silent', 'disable all process messaging')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'path to the config file',
|
||||
getConfig()
|
||||
)
|
||||
.option(
|
||||
'-t, --timeout <duration>',
|
||||
'length of time before failing; 0 for never fail',
|
||||
parseMilliseconds
|
||||
)
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description('install a plugin',
|
||||
`Common examples:
|
||||
.command('install <plugin/url>')
|
||||
.option('-q, --quiet', 'disable all process messaging except errors')
|
||||
.option('-s, --silent', 'disable all process messaging')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'path to the config file',
|
||||
getConfig()
|
||||
)
|
||||
.option(
|
||||
'-t, --timeout <duration>',
|
||||
'length of time before failing; 0 for never fail',
|
||||
parseMilliseconds
|
||||
)
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description('install a plugin',
|
||||
`Common examples:
|
||||
install x-pack
|
||||
install file:///Path/to/my/x-pack.zip
|
||||
install https://path.to/my/x-pack.zip`)
|
||||
.action(processCommand);
|
||||
.action(processCommand);
|
||||
}
|
||||
|
|
|
@ -3,18 +3,18 @@ import { join } from 'path';
|
|||
|
||||
export default function list(settings, logger) {
|
||||
readdirSync(settings.pluginDir)
|
||||
.forEach((filename) => {
|
||||
const stat = statSync(join(settings.pluginDir, filename));
|
||||
.forEach((filename) => {
|
||||
const stat = statSync(join(settings.pluginDir, filename));
|
||||
|
||||
if (stat.isDirectory() && filename[0] !== '.') {
|
||||
try {
|
||||
const packagePath = join(settings.pluginDir, filename, 'package.json');
|
||||
const { version } = JSON.parse(readFileSync(packagePath, 'utf8'));
|
||||
logger.log(filename + '@' + version);
|
||||
} catch (e) {
|
||||
throw new Error('Unable to read package.json file for plugin ' + filename);
|
||||
if (stat.isDirectory() && filename[0] !== '.') {
|
||||
try {
|
||||
const packagePath = join(settings.pluginDir, filename, 'package.json');
|
||||
const { version } = JSON.parse(readFileSync(packagePath, 'utf8'));
|
||||
logger.log(filename + '@' + version);
|
||||
} catch (e) {
|
||||
throw new Error('Unable to read package.json file for plugin ' + filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
logger.log(''); //intentional blank line for aesthetics
|
||||
}
|
||||
|
|
|
@ -22,21 +22,21 @@ function processCommand(command, options) {
|
|||
|
||||
export default function pluginRemove(program) {
|
||||
program
|
||||
.command('remove <plugin>')
|
||||
.option('-q, --quiet', 'disable all process messaging except errors')
|
||||
.option('-s, --silent', 'disable all process messaging')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'path to the config file',
|
||||
getConfig()
|
||||
)
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description('remove a plugin',
|
||||
`common examples:
|
||||
.command('remove <plugin>')
|
||||
.option('-q, --quiet', 'disable all process messaging except errors')
|
||||
.option('-s, --silent', 'disable all process messaging')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'path to the config file',
|
||||
getConfig()
|
||||
)
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description('remove a plugin',
|
||||
`common examples:
|
||||
remove x-pack`)
|
||||
.action(processCommand);
|
||||
.action(processCommand);
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ describe('es/healthCheck/patchKibanaIndex()', () => {
|
|||
} catch (error) {
|
||||
expect(error)
|
||||
.to.have.property('message')
|
||||
.contain('Your Kibana index is out of date');
|
||||
.contain('Your Kibana index is out of date');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,16 +10,16 @@ export default function (server) {
|
|||
mappings: server.getKibanaIndexMappingsDsl()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error(`Unable to create Kibana index "${index}"`);
|
||||
})
|
||||
.then(function () {
|
||||
return callWithInternalUser('cluster.health', {
|
||||
waitForStatus: 'yellow',
|
||||
index: index
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error(`Waiting for Kibana index "${index}" to come online failed.`);
|
||||
throw new Error(`Unable to create Kibana index "${index}"`);
|
||||
})
|
||||
.then(function () {
|
||||
return callWithInternalUser('cluster.health', {
|
||||
waitForStatus: 'yellow',
|
||||
index: index
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error(`Waiting for Kibana index "${index}" to come online failed.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,67 +27,67 @@ export function ensureEsVersion(server, kibanaVersion) {
|
|||
'nodes.*.ip',
|
||||
]
|
||||
})
|
||||
.then(function (info) {
|
||||
.then(function (info) {
|
||||
// Aggregate incompatible ES nodes.
|
||||
const incompatibleNodes = [];
|
||||
const incompatibleNodes = [];
|
||||
|
||||
// Aggregate ES nodes which should prompt a Kibana upgrade.
|
||||
const warningNodes = [];
|
||||
// Aggregate ES nodes which should prompt a Kibana upgrade.
|
||||
const warningNodes = [];
|
||||
|
||||
forEach(info.nodes, esNode => {
|
||||
if (!isEsCompatibleWithKibana(esNode.version, kibanaVersion)) {
|
||||
forEach(info.nodes, esNode => {
|
||||
if (!isEsCompatibleWithKibana(esNode.version, kibanaVersion)) {
|
||||
// Exit early to avoid collecting ES nodes with newer major versions in the `warningNodes`.
|
||||
return incompatibleNodes.push(esNode);
|
||||
}
|
||||
return incompatibleNodes.push(esNode);
|
||||
}
|
||||
|
||||
// It's acceptable if ES and Kibana versions are not the same so long as
|
||||
// they are not incompatible, but we should warn about it
|
||||
if (esNode.version !== kibanaVersion) {
|
||||
warningNodes.push(esNode);
|
||||
}
|
||||
});
|
||||
|
||||
function getHumanizedNodeNames(nodes) {
|
||||
return nodes.map(node => {
|
||||
const publishAddress = get(node, 'http.publish_address') ? (get(node, 'http.publish_address') + ' ') : '';
|
||||
return 'v' + node.version + ' @ ' + publishAddress + '(' + node.ip + ')';
|
||||
// It's acceptable if ES and Kibana versions are not the same so long as
|
||||
// they are not incompatible, but we should warn about it
|
||||
if (esNode.version !== kibanaVersion) {
|
||||
warningNodes.push(esNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (warningNodes.length) {
|
||||
const simplifiedNodes = warningNodes.map(node => ({
|
||||
version: node.version,
|
||||
http: {
|
||||
publish_address: get(node, 'http.publish_address')
|
||||
},
|
||||
ip: node.ip,
|
||||
}));
|
||||
|
||||
// Don't show the same warning over and over again.
|
||||
const warningNodeNames = getHumanizedNodeNames(simplifiedNodes).join(', ');
|
||||
if (lastWarnedNodesForServer.get(server) !== warningNodeNames) {
|
||||
lastWarnedNodesForServer.set(server, warningNodeNames);
|
||||
server.log(['warning'], {
|
||||
tmpl: (
|
||||
`You're running Kibana ${kibanaVersion} with some different versions of ` +
|
||||
'Elasticsearch. Update Kibana or Elasticsearch to the same ' +
|
||||
`version to prevent compatibility issues: ${warningNodeNames}`
|
||||
),
|
||||
kibanaVersion,
|
||||
nodes: simplifiedNodes,
|
||||
function getHumanizedNodeNames(nodes) {
|
||||
return nodes.map(node => {
|
||||
const publishAddress = get(node, 'http.publish_address') ? (get(node, 'http.publish_address') + ' ') : '';
|
||||
return 'v' + node.version + ' @ ' + publishAddress + '(' + node.ip + ')';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (incompatibleNodes.length) {
|
||||
const incompatibleNodeNames = getHumanizedNodeNames(incompatibleNodes);
|
||||
throw new Error(
|
||||
`This version of Kibana requires Elasticsearch v` +
|
||||
if (warningNodes.length) {
|
||||
const simplifiedNodes = warningNodes.map(node => ({
|
||||
version: node.version,
|
||||
http: {
|
||||
publish_address: get(node, 'http.publish_address')
|
||||
},
|
||||
ip: node.ip,
|
||||
}));
|
||||
|
||||
// Don't show the same warning over and over again.
|
||||
const warningNodeNames = getHumanizedNodeNames(simplifiedNodes).join(', ');
|
||||
if (lastWarnedNodesForServer.get(server) !== warningNodeNames) {
|
||||
lastWarnedNodesForServer.set(server, warningNodeNames);
|
||||
server.log(['warning'], {
|
||||
tmpl: (
|
||||
`You're running Kibana ${kibanaVersion} with some different versions of ` +
|
||||
'Elasticsearch. Update Kibana or Elasticsearch to the same ' +
|
||||
`version to prevent compatibility issues: ${warningNodeNames}`
|
||||
),
|
||||
kibanaVersion,
|
||||
nodes: simplifiedNodes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (incompatibleNodes.length) {
|
||||
const incompatibleNodeNames = getHumanizedNodeNames(incompatibleNodes);
|
||||
throw new Error(
|
||||
`This version of Kibana requires Elasticsearch v` +
|
||||
`${kibanaVersion} on all nodes. I found ` +
|
||||
`the following incompatible nodes in your cluster: ${incompatibleNodeNames.join(', ')}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ export function ensureNotTribe(callWithInternalUser) {
|
|||
nodeId: '_local',
|
||||
filterPath: 'nodes.*.settings.tribe'
|
||||
})
|
||||
.then(function (info) {
|
||||
const nodeId = Object.keys(info.nodes || {})[0];
|
||||
const tribeSettings = get(info, ['nodes', nodeId, 'settings', 'tribe']);
|
||||
.then(function (info) {
|
||||
const nodeId = Object.keys(info.nodes || {})[0];
|
||||
const tribeSettings = get(info, ['nodes', nodeId, 'settings', 'tribe']);
|
||||
|
||||
if (tribeSettings) {
|
||||
throw new Error('Kibana does not support using tribe nodes as the primary elasticsearch connection.');
|
||||
}
|
||||
if (tribeSettings) {
|
||||
throw new Error('Kibana does not support using tribe nodes as the primary elasticsearch connection.');
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,49 +37,49 @@ export default function (plugin, server) {
|
|||
index: config.get('kibana.index'),
|
||||
ignore: [408]
|
||||
})
|
||||
.then(function (resp) {
|
||||
.then(function (resp) {
|
||||
// if "timed_out" === true then elasticsearch could not
|
||||
// find any idices matching our filter within 5 seconds
|
||||
if (!resp || resp.timed_out) {
|
||||
return NO_INDEX;
|
||||
}
|
||||
if (!resp || resp.timed_out) {
|
||||
return NO_INDEX;
|
||||
}
|
||||
|
||||
// If status === "red" that means that index(es) were found
|
||||
// but the shards are not ready for queries
|
||||
if (resp.status === 'red') {
|
||||
return INITIALIZING;
|
||||
}
|
||||
// If status === "red" that means that index(es) were found
|
||||
// but the shards are not ready for queries
|
||||
if (resp.status === 'red') {
|
||||
return INITIALIZING;
|
||||
}
|
||||
|
||||
return READY;
|
||||
});
|
||||
return READY;
|
||||
});
|
||||
}
|
||||
|
||||
function waitUntilReady() {
|
||||
return getHealth()
|
||||
.then(function (health) {
|
||||
if (health !== READY) {
|
||||
return Promise.delay(REQUEST_DELAY).then(waitUntilReady);
|
||||
}
|
||||
.then(function (health) {
|
||||
if (health !== READY) {
|
||||
return Promise.delay(REQUEST_DELAY).then(waitUntilReady);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
plugin.status.once('green', resolve);
|
||||
return new Promise((resolve) => {
|
||||
plugin.status.once('green', resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function waitForShards() {
|
||||
return getHealth()
|
||||
.then(function (health) {
|
||||
if (health === NO_INDEX) {
|
||||
plugin.status.yellow('No existing Kibana index found');
|
||||
return createKibanaIndex(server);
|
||||
}
|
||||
.then(function (health) {
|
||||
if (health === NO_INDEX) {
|
||||
plugin.status.yellow('No existing Kibana index found');
|
||||
return createKibanaIndex(server);
|
||||
}
|
||||
|
||||
if (health === INITIALIZING) {
|
||||
plugin.status.red('Elasticsearch is still initializing the kibana index.');
|
||||
return Promise.delay(REQUEST_DELAY).then(waitForShards);
|
||||
}
|
||||
});
|
||||
if (health === INITIALIZING) {
|
||||
plugin.status.red('Elasticsearch is still initializing the kibana index.');
|
||||
return Promise.delay(REQUEST_DELAY).then(waitForShards);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function waitForEsVersion() {
|
||||
|
@ -96,26 +96,26 @@ export default function (plugin, server) {
|
|||
function check() {
|
||||
const healthCheck =
|
||||
waitForPong(callAdminAsKibanaUser, config.get('elasticsearch.url'))
|
||||
.then(waitForEsVersion)
|
||||
.then(() => ensureNotTribe(callAdminAsKibanaUser))
|
||||
.then(waitForShards)
|
||||
.then(() => patchKibanaIndex({
|
||||
callCluster: callAdminAsKibanaUser,
|
||||
log: (...args) => server.log(...args),
|
||||
indexName: config.get('kibana.index'),
|
||||
kibanaIndexMappingsDsl: server.getKibanaIndexMappingsDsl()
|
||||
}))
|
||||
.then(() => {
|
||||
const tribeUrl = config.get('elasticsearch.tribe.url');
|
||||
if (tribeUrl) {
|
||||
return waitForPong(callDataAsKibanaUser, tribeUrl)
|
||||
.then(() => ensureEsVersion(server, kibanaVersion.get(), callDataAsKibanaUser));
|
||||
}
|
||||
});
|
||||
.then(waitForEsVersion)
|
||||
.then(() => ensureNotTribe(callAdminAsKibanaUser))
|
||||
.then(waitForShards)
|
||||
.then(() => patchKibanaIndex({
|
||||
callCluster: callAdminAsKibanaUser,
|
||||
log: (...args) => server.log(...args),
|
||||
indexName: config.get('kibana.index'),
|
||||
kibanaIndexMappingsDsl: server.getKibanaIndexMappingsDsl()
|
||||
}))
|
||||
.then(() => {
|
||||
const tribeUrl = config.get('elasticsearch.tribe.url');
|
||||
if (tribeUrl) {
|
||||
return waitForPong(callDataAsKibanaUser, tribeUrl)
|
||||
.then(() => ensureEsVersion(server, kibanaVersion.get(), callDataAsKibanaUser));
|
||||
}
|
||||
});
|
||||
|
||||
return healthCheck
|
||||
.then(setGreenStatus)
|
||||
.catch(err => plugin.status.red(err));
|
||||
.then(setGreenStatus)
|
||||
.catch(err => plugin.status.red(err));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -55,16 +55,16 @@ export class PhraseFilterManager extends FilterManager {
|
|||
// bool filter - multiple phrase filters
|
||||
if (_.has(kbnFilter, 'query.bool.should')) {
|
||||
return _.get(kbnFilter, 'query.bool.should')
|
||||
.map((kbnFilter) => {
|
||||
return this._getValueFromFilter(kbnFilter);
|
||||
})
|
||||
.filter((value) => {
|
||||
if (value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.join(this.delimiter);
|
||||
.map((kbnFilter) => {
|
||||
return this._getValueFromFilter(kbnFilter);
|
||||
})
|
||||
.filter((value) => {
|
||||
if (value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.join(this.delimiter);
|
||||
}
|
||||
|
||||
// scripted field filter
|
||||
|
|
|
@ -49,10 +49,10 @@ class VisController {
|
|||
// ignore controls that do not have indexPattern or field
|
||||
return controlParams.indexPattern && controlParams.fieldName;
|
||||
})
|
||||
.map((controlParams) => {
|
||||
const factory = controlFactory(controlParams);
|
||||
return factory(controlParams, this.vis.API);
|
||||
})
|
||||
.map((controlParams) => {
|
||||
const factory = controlFactory(controlParams);
|
||||
return factory(controlParams, this.vis.API);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -73,12 +73,12 @@ class VisController {
|
|||
});
|
||||
|
||||
const newFilters = stagedControls
|
||||
.filter((control) => {
|
||||
return control.hasKbnFilter();
|
||||
})
|
||||
.map((control) => {
|
||||
return control.getKbnFilter();
|
||||
});
|
||||
.filter((control) => {
|
||||
return control.hasKbnFilter();
|
||||
})
|
||||
.map((control) => {
|
||||
return control.getKbnFilter();
|
||||
});
|
||||
|
||||
stagedControls.forEach((control) => {
|
||||
// to avoid duplicate filters, remove any old filters for control
|
||||
|
@ -108,18 +108,18 @@ class VisController {
|
|||
return this.controls.map((control) => {
|
||||
return control.hasChanged();
|
||||
})
|
||||
.reduce((a, b) => {
|
||||
return a || b;
|
||||
});
|
||||
.reduce((a, b) => {
|
||||
return a || b;
|
||||
});
|
||||
}
|
||||
|
||||
hasValues() {
|
||||
return this.controls.map((control) => {
|
||||
return control.hasValue();
|
||||
})
|
||||
.reduce((a, b) => {
|
||||
return a || b;
|
||||
});
|
||||
.reduce((a, b) => {
|
||||
return a || b;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,8 +93,8 @@ UrlFormat.prototype._convert = {
|
|||
// is tell screen readers where the image comes from.
|
||||
const imageLabel =
|
||||
label === url
|
||||
? `A dynamically-specified image located at ${url}`
|
||||
: label;
|
||||
? `A dynamically-specified image located at ${url}`
|
||||
: label;
|
||||
|
||||
return `<img src="${url}" alt="${imageLabel}">`;
|
||||
default:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
plugins: [
|
||||
'@elastic/kibana-custom'
|
||||
'@elastic/eslint-plugin-kibana-custom'
|
||||
]
|
||||
rules:
|
||||
no-console: 2
|
||||
kibana-custom/no-default-export: error
|
||||
'@elastic/kibana-custom/no-default-export': error
|
||||
|
|
|
@ -21,10 +21,10 @@ module.directive('contextSizePicker', function ContextSizePicker() {
|
|||
count: '=',
|
||||
isDisabled: '=',
|
||||
onChangeCount: '=', // To avoid inconsistent ngModel states this action
|
||||
// should make sure the new value is propagated back
|
||||
// to the `count` property. If that propagation
|
||||
// fails, the user input will be reset to the value
|
||||
// of `count`.
|
||||
// should make sure the new value is propagated back
|
||||
// to the `count` property. If that propagation
|
||||
// fails, the user input will be reset to the value
|
||||
// of `count`.
|
||||
},
|
||||
template: contextSizePickerTemplate,
|
||||
};
|
||||
|
|
|
@ -9,16 +9,16 @@ import contextAppRouteTemplate from './index.html';
|
|||
|
||||
|
||||
uiRoutes
|
||||
.when('/context/:indexPatternId/:type/:id*', {
|
||||
controller: ContextAppRouteController,
|
||||
controllerAs: 'contextAppRoute',
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPatternId);
|
||||
.when('/context/:indexPatternId/:type/:id*', {
|
||||
controller: ContextAppRouteController,
|
||||
controllerAs: 'contextAppRoute',
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPatternId);
|
||||
},
|
||||
},
|
||||
},
|
||||
template: contextAppRouteTemplate,
|
||||
});
|
||||
template: contextAppRouteTemplate,
|
||||
});
|
||||
|
||||
|
||||
function ContextAppRouteController(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* global jest */
|
||||
export function getEmbeddableFactoryMock(config) {
|
||||
const embeddableFactoryMockDefaults = {
|
||||
getEditPath: () => {},
|
||||
|
|
|
@ -59,4 +59,3 @@ test('renders DashboardGrid with no visualizations', () => {
|
|||
const component = shallow(<DashboardGrid {...getProps({ panels: {} })} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import expect from 'expect.js';
|
||||
import { PanelUtils } from '../panel_utils';
|
||||
import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../../dashboard_constants';
|
||||
|
||||
|
|
|
@ -48,4 +48,3 @@ test('renders an error when error prop is passed', () => {
|
|||
const panelError = component.find(PanelError);
|
||||
expect(panelError.length).toBe(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -14,15 +14,15 @@ export function getTopNavConfig(dashboardMode, actions, hideWriteControls) {
|
|||
case DashboardViewMode.VIEW:
|
||||
return (
|
||||
hideWriteControls ?
|
||||
[
|
||||
getFullScreenConfig(actions[TopNavIds.FULL_SCREEN])
|
||||
]
|
||||
: [
|
||||
getFullScreenConfig(actions[TopNavIds.FULL_SCREEN]),
|
||||
getShareConfig(),
|
||||
getCloneConfig(actions[TopNavIds.CLONE]),
|
||||
getEditConfig(actions[TopNavIds.ENTER_EDIT_MODE])
|
||||
]
|
||||
[
|
||||
getFullScreenConfig(actions[TopNavIds.FULL_SCREEN])
|
||||
]
|
||||
: [
|
||||
getFullScreenConfig(actions[TopNavIds.FULL_SCREEN]),
|
||||
getShareConfig(),
|
||||
getCloneConfig(actions[TopNavIds.CLONE]),
|
||||
getEditConfig(actions[TopNavIds.ENTER_EDIT_MODE])
|
||||
]
|
||||
);
|
||||
case DashboardViewMode.EDIT:
|
||||
return [
|
||||
|
|
|
@ -5,23 +5,23 @@ import 'plugins/kibana/dev_tools/styles/dev_tools_app.less';
|
|||
import 'ui/kbn_top_nav';
|
||||
|
||||
uiModules
|
||||
.get('apps/dev_tools')
|
||||
.directive('kbnDevToolsApp', function (Private, $location) {
|
||||
const devToolsRegistry = Private(DevToolsRegistryProvider);
|
||||
.get('apps/dev_tools')
|
||||
.directive('kbnDevToolsApp', function (Private, $location) {
|
||||
const devToolsRegistry = Private(DevToolsRegistryProvider);
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template,
|
||||
transclude: true,
|
||||
scope: {
|
||||
topNavConfig: '='
|
||||
},
|
||||
bindToController: true,
|
||||
controllerAs: 'kbnDevToolsApp',
|
||||
controller() {
|
||||
this.devTools = devToolsRegistry.inOrder;
|
||||
this.currentPath = `#${$location.path()}`;
|
||||
}
|
||||
};
|
||||
});
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template,
|
||||
transclude: true,
|
||||
scope: {
|
||||
topNavConfig: '='
|
||||
},
|
||||
bindToController: true,
|
||||
controllerAs: 'kbnDevToolsApp',
|
||||
controller() {
|
||||
this.devTools = devToolsRegistry.inOrder;
|
||||
this.currentPath = `#${$location.path()}`;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -4,14 +4,14 @@ import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/r
|
|||
import 'plugins/kibana/dev_tools/directives/dev_tools_app';
|
||||
|
||||
uiRoutes
|
||||
.when('/dev_tools', {
|
||||
resolve: {
|
||||
redirect(Private, kbnUrl) {
|
||||
const items = Private(DevToolsRegistryProvider).inOrder;
|
||||
kbnUrl.redirect(items[0].url.substring(1));
|
||||
.when('/dev_tools', {
|
||||
resolve: {
|
||||
redirect(Private, kbnUrl) {
|
||||
const items = Private(DevToolsRegistryProvider).inOrder;
|
||||
kbnUrl.redirect(items[0].url.substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
FeatureCatalogueRegistryProvider.register(() => {
|
||||
return {
|
||||
|
|
|
@ -31,10 +31,10 @@ describe('hit sort function', function () {
|
|||
});
|
||||
|
||||
hits.sort(createHitSortFn(dir))
|
||||
.forEach(function (hit, i) {
|
||||
const group = Math.floor(i / groupSize);
|
||||
expect(hit.sort).to.eql(sortOpts[group]);
|
||||
});
|
||||
.forEach(function (hit, i) {
|
||||
const group = Math.floor(i / groupSize);
|
||||
expect(hit.sort).to.eql(sortOpts[group]);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// eslint-disable-next-line kibana-custom/no-default-export
|
||||
// eslint-disable-next-line @elastic/kibana-custom/no-default-export
|
||||
export default function HitSortFnFactory() {
|
||||
/**
|
||||
* Creates a sort function that will resort hits based on the value
|
||||
|
|
|
@ -121,32 +121,32 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
|
||||
// group the fields into popular and up-popular lists
|
||||
_.chain(fields)
|
||||
.each(function (field) {
|
||||
field.displayOrder = _.indexOf(columns, field.name) + 1;
|
||||
field.display = !!field.displayOrder;
|
||||
field.rowCount = fieldCounts[field.name];
|
||||
})
|
||||
.sortBy(function (field) {
|
||||
return (field.count || 0) * -1;
|
||||
})
|
||||
.groupBy(function (field) {
|
||||
if (field.display) return 'selected';
|
||||
return field.count > 0 ? 'popular' : 'unpopular';
|
||||
})
|
||||
.tap(function (groups) {
|
||||
groups.selected = _.sortBy(groups.selected || [], 'displayOrder');
|
||||
.each(function (field) {
|
||||
field.displayOrder = _.indexOf(columns, field.name) + 1;
|
||||
field.display = !!field.displayOrder;
|
||||
field.rowCount = fieldCounts[field.name];
|
||||
})
|
||||
.sortBy(function (field) {
|
||||
return (field.count || 0) * -1;
|
||||
})
|
||||
.groupBy(function (field) {
|
||||
if (field.display) return 'selected';
|
||||
return field.count > 0 ? 'popular' : 'unpopular';
|
||||
})
|
||||
.tap(function (groups) {
|
||||
groups.selected = _.sortBy(groups.selected || [], 'displayOrder');
|
||||
|
||||
groups.popular = groups.popular || [];
|
||||
groups.unpopular = groups.unpopular || [];
|
||||
groups.popular = groups.popular || [];
|
||||
groups.unpopular = groups.unpopular || [];
|
||||
|
||||
// move excess popular fields to un-popular list
|
||||
const extras = groups.popular.splice(config.get('fields:popularLimit'));
|
||||
groups.unpopular = extras.concat(groups.unpopular);
|
||||
})
|
||||
.each(function (group, name) {
|
||||
$scope[name + 'Fields'] = _.sortBy(group, name === 'selected' ? 'display' : 'name');
|
||||
})
|
||||
.commit();
|
||||
// move excess popular fields to un-popular list
|
||||
const extras = groups.popular.splice(config.get('fields:popularLimit'));
|
||||
groups.unpopular = extras.concat(groups.unpopular);
|
||||
})
|
||||
.each(function (group, name) {
|
||||
$scope[name + 'Fields'] = _.sortBy(group, name === 'selected' ? 'display' : 'name');
|
||||
})
|
||||
.commit();
|
||||
|
||||
// include undefined so the user can clear the filter
|
||||
$scope.fieldTypes = _.union(['any'], _.pluck(fields, 'type'));
|
||||
|
@ -245,12 +245,12 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
const fieldNamesInIndexPattern = _.keys(indexPattern.fields.byName);
|
||||
|
||||
_.difference(fieldNamesInDocs, fieldNamesInIndexPattern)
|
||||
.forEach(function (unknownFieldName) {
|
||||
fieldSpecs.push({
|
||||
name: unknownFieldName,
|
||||
type: 'unknown'
|
||||
.forEach(function (unknownFieldName) {
|
||||
fieldSpecs.push({
|
||||
name: unknownFieldName,
|
||||
type: 'unknown'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const fields = new FieldList(indexPattern, fieldSpecs);
|
||||
|
||||
|
|
|
@ -38,24 +38,24 @@ const app = uiModules.get('apps/discover', [
|
|||
]);
|
||||
|
||||
uiRoutes
|
||||
.defaults(/discover/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when('/discover/:id?', {
|
||||
template: indexTemplate,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
ip: function (Promise, courier, config, $location, Private) {
|
||||
const State = Private(StateProvider);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
.defaults(/discover/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when('/discover/:id?', {
|
||||
template: indexTemplate,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
ip: function (Promise, courier, config, $location, Private) {
|
||||
const State = Private(StateProvider);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
perPage: 10000
|
||||
})
|
||||
.then(({ savedObjects }) => {
|
||||
/**
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
perPage: 10000
|
||||
})
|
||||
.then(({ savedObjects }) => {
|
||||
/**
|
||||
* In making the indexPattern modifiable it was placed in appState. Unfortunately,
|
||||
* the load order of AppState conflicts with the load order of many other things
|
||||
* so in order to get the name of the index we should use, and to switch to the
|
||||
|
@ -64,30 +64,30 @@ uiRoutes
|
|||
*
|
||||
* @type {State}
|
||||
*/
|
||||
const state = new State('_a', {});
|
||||
const state = new State('_a', {});
|
||||
|
||||
const specified = !!state.index;
|
||||
const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1;
|
||||
const id = exists ? state.index : config.get('defaultIndex');
|
||||
state.destroy();
|
||||
const specified = !!state.index;
|
||||
const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1;
|
||||
const id = exists ? state.index : config.get('defaultIndex');
|
||||
state.destroy();
|
||||
|
||||
return Promise.props({
|
||||
list: savedObjects,
|
||||
loaded: courier.indexPatterns.get(id),
|
||||
stateVal: state.index,
|
||||
stateValFound: specified && exists
|
||||
});
|
||||
});
|
||||
},
|
||||
savedSearch: function (courier, savedSearches, $route) {
|
||||
return savedSearches.get($route.current.params.id)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'search': '/discover',
|
||||
'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id
|
||||
}));
|
||||
return Promise.props({
|
||||
list: savedObjects,
|
||||
loaded: courier.indexPatterns.get(id),
|
||||
stateVal: state.index,
|
||||
stateValFound: specified && exists
|
||||
});
|
||||
});
|
||||
},
|
||||
savedSearch: function (courier, savedSearches, $route) {
|
||||
return savedSearches.get($route.current.params.id)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'search': '/discover',
|
||||
'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.directive('discoverApp', function () {
|
||||
return {
|
||||
|
@ -163,9 +163,9 @@ function discoverController(
|
|||
$scope.searchSource = savedSearch.searchSource;
|
||||
$scope.indexPattern = resolveIndexPatternLoading();
|
||||
$scope.searchSource
|
||||
.set('index', $scope.indexPattern)
|
||||
.highlightAll(true)
|
||||
.version(true);
|
||||
.set('index', $scope.indexPattern)
|
||||
.highlightAll(true)
|
||||
.version(true);
|
||||
|
||||
const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : '';
|
||||
docTitle.change(`Discover${pageTitleSuffix}`);
|
||||
|
@ -293,138 +293,138 @@ function discoverController(
|
|||
$scope.$on('$destroy', () => stateMonitor.destroy());
|
||||
|
||||
$scope.updateDataSource()
|
||||
.then(function () {
|
||||
$scope.$listen(timefilter, 'fetch', function () {
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watchCollection('state.sort', function (sort) {
|
||||
if (!sort) return;
|
||||
|
||||
// get the current sort from {key: val} to ["key", "val"];
|
||||
const currentSort = _.pairs($scope.searchSource.get('sort')).pop();
|
||||
|
||||
// if the searchSource doesn't know, tell it so
|
||||
if (!angular.equals(sort, currentSort)) $scope.fetch();
|
||||
});
|
||||
|
||||
// update data source when filters update
|
||||
$scope.$listen(queryFilter, 'update', function () {
|
||||
return $scope.updateDataSource().then(function () {
|
||||
$state.save();
|
||||
});
|
||||
});
|
||||
|
||||
// update data source when hitting forward/back and the query changes
|
||||
$scope.$listen($state, 'fetch_with_changes', function (diff) {
|
||||
if (diff.indexOf('query') >= 0) $scope.fetch();
|
||||
});
|
||||
|
||||
// fetch data when filters fire fetch event
|
||||
$scope.$listen(queryFilter, 'fetch', $scope.fetch);
|
||||
|
||||
$scope.$watch('opts.timefield', function (timefield) {
|
||||
timefilter.enabled = !!timefield;
|
||||
});
|
||||
|
||||
$scope.$watch('state.interval', function () {
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watch('vis.aggs', function () {
|
||||
// no timefield, no vis, nothing to update
|
||||
if (!$scope.opts.timefield) return;
|
||||
|
||||
const buckets = $scope.vis.getAggConfig().bySchemaGroup.buckets;
|
||||
|
||||
if (buckets && buckets.length === 1) {
|
||||
$scope.bucketInterval = buckets[0].buckets.getInterval();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('state.query', $scope.updateQueryAndFetch);
|
||||
|
||||
$scope.$watchMulti([
|
||||
'rows',
|
||||
'fetchStatus'
|
||||
], (function updateResultState() {
|
||||
let prev = {};
|
||||
const status = {
|
||||
LOADING: 'loading', // initial data load
|
||||
READY: 'ready', // results came back
|
||||
NO_RESULTS: 'none' // no results came back
|
||||
};
|
||||
|
||||
function pick(rows, oldRows, fetchStatus) {
|
||||
// initial state, pretend we are loading
|
||||
if (rows == null && oldRows == null) return status.LOADING;
|
||||
|
||||
const rowsEmpty = _.isEmpty(rows);
|
||||
// An undefined fetchStatus means the requests are still being
|
||||
// prepared to be sent. When all requests are completed,
|
||||
// fetchStatus is set to null, so it's important that we
|
||||
// specifically check for undefined to determine a loading status.
|
||||
const preparingForFetch = _.isUndefined(fetchStatus);
|
||||
if (preparingForFetch) return status.LOADING;
|
||||
else if (rowsEmpty && fetchStatus) return status.LOADING;
|
||||
else if (!rowsEmpty) return status.READY;
|
||||
else return status.NO_RESULTS;
|
||||
}
|
||||
|
||||
return function () {
|
||||
const current = {
|
||||
rows: $scope.rows,
|
||||
fetchStatus: $scope.fetchStatus
|
||||
};
|
||||
|
||||
$scope.resultState = pick(
|
||||
current.rows,
|
||||
prev.rows,
|
||||
current.fetchStatus,
|
||||
prev.fetchStatus
|
||||
);
|
||||
|
||||
prev = current;
|
||||
};
|
||||
}()));
|
||||
|
||||
function initForTime() {
|
||||
return setupVisualization().then($scope.updateTime);
|
||||
}
|
||||
|
||||
return Promise.resolve($scope.opts.timefield && initForTime())
|
||||
.then(function () {
|
||||
init.complete = true;
|
||||
$state.replace();
|
||||
$scope.$emit('application.load');
|
||||
$scope.$listen(timefilter, 'fetch', function () {
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watchCollection('state.sort', function (sort) {
|
||||
if (!sort) return;
|
||||
|
||||
// get the current sort from {key: val} to ["key", "val"];
|
||||
const currentSort = _.pairs($scope.searchSource.get('sort')).pop();
|
||||
|
||||
// if the searchSource doesn't know, tell it so
|
||||
if (!angular.equals(sort, currentSort)) $scope.fetch();
|
||||
});
|
||||
|
||||
// update data source when filters update
|
||||
$scope.$listen(queryFilter, 'update', function () {
|
||||
return $scope.updateDataSource().then(function () {
|
||||
$state.save();
|
||||
});
|
||||
});
|
||||
|
||||
// update data source when hitting forward/back and the query changes
|
||||
$scope.$listen($state, 'fetch_with_changes', function (diff) {
|
||||
if (diff.indexOf('query') >= 0) $scope.fetch();
|
||||
});
|
||||
|
||||
// fetch data when filters fire fetch event
|
||||
$scope.$listen(queryFilter, 'fetch', $scope.fetch);
|
||||
|
||||
$scope.$watch('opts.timefield', function (timefield) {
|
||||
timefilter.enabled = !!timefield;
|
||||
});
|
||||
|
||||
$scope.$watch('state.interval', function () {
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watch('vis.aggs', function () {
|
||||
// no timefield, no vis, nothing to update
|
||||
if (!$scope.opts.timefield) return;
|
||||
|
||||
const buckets = $scope.vis.getAggConfig().bySchemaGroup.buckets;
|
||||
|
||||
if (buckets && buckets.length === 1) {
|
||||
$scope.bucketInterval = buckets[0].buckets.getInterval();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('state.query', $scope.updateQueryAndFetch);
|
||||
|
||||
$scope.$watchMulti([
|
||||
'rows',
|
||||
'fetchStatus'
|
||||
], (function updateResultState() {
|
||||
let prev = {};
|
||||
const status = {
|
||||
LOADING: 'loading', // initial data load
|
||||
READY: 'ready', // results came back
|
||||
NO_RESULTS: 'none' // no results came back
|
||||
};
|
||||
|
||||
function pick(rows, oldRows, fetchStatus) {
|
||||
// initial state, pretend we are loading
|
||||
if (rows == null && oldRows == null) return status.LOADING;
|
||||
|
||||
const rowsEmpty = _.isEmpty(rows);
|
||||
// An undefined fetchStatus means the requests are still being
|
||||
// prepared to be sent. When all requests are completed,
|
||||
// fetchStatus is set to null, so it's important that we
|
||||
// specifically check for undefined to determine a loading status.
|
||||
const preparingForFetch = _.isUndefined(fetchStatus);
|
||||
if (preparingForFetch) return status.LOADING;
|
||||
else if (rowsEmpty && fetchStatus) return status.LOADING;
|
||||
else if (!rowsEmpty) return status.READY;
|
||||
else return status.NO_RESULTS;
|
||||
}
|
||||
|
||||
return function () {
|
||||
const current = {
|
||||
rows: $scope.rows,
|
||||
fetchStatus: $scope.fetchStatus
|
||||
};
|
||||
|
||||
$scope.resultState = pick(
|
||||
current.rows,
|
||||
prev.rows,
|
||||
current.fetchStatus,
|
||||
prev.fetchStatus
|
||||
);
|
||||
|
||||
prev = current;
|
||||
};
|
||||
}()));
|
||||
|
||||
function initForTime() {
|
||||
return setupVisualization().then($scope.updateTime);
|
||||
}
|
||||
|
||||
return Promise.resolve($scope.opts.timefield && initForTime())
|
||||
.then(function () {
|
||||
init.complete = true;
|
||||
$state.replace();
|
||||
$scope.$emit('application.load');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$scope.opts.saveDataSource = function () {
|
||||
return $scope.updateDataSource()
|
||||
.then(function () {
|
||||
savedSearch.columns = $scope.state.columns;
|
||||
savedSearch.sort = $scope.state.sort;
|
||||
.then(function () {
|
||||
savedSearch.columns = $scope.state.columns;
|
||||
savedSearch.sort = $scope.state.sort;
|
||||
|
||||
return savedSearch.save()
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
return savedSearch.save()
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||
if (savedSearch.id !== $route.current.params.id) {
|
||||
kbnUrl.change('/discover/{{id}}', { id: savedSearch.id });
|
||||
} else {
|
||||
// Update defaults so that "reload saved query" functions correctly
|
||||
$state.setDefaults(getStateDefaults());
|
||||
docTitle.change(savedSearch.lastSavedTitle);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(notify.error);
|
||||
if (id) {
|
||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||
if (savedSearch.id !== $route.current.params.id) {
|
||||
kbnUrl.change('/discover/{{id}}', { id: savedSearch.id });
|
||||
} else {
|
||||
// Update defaults so that "reload saved query" functions correctly
|
||||
$state.setDefaults(getStateDefaults());
|
||||
docTitle.change(savedSearch.lastSavedTitle);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(notify.error);
|
||||
};
|
||||
|
||||
$scope.opts.fetch = $scope.fetch = function () {
|
||||
|
@ -434,12 +434,12 @@ function discoverController(
|
|||
$scope.updateTime();
|
||||
|
||||
$scope.updateDataSource()
|
||||
.then(setupVisualization)
|
||||
.then(function () {
|
||||
$state.save();
|
||||
return courier.fetch();
|
||||
})
|
||||
.catch(notify.error);
|
||||
.then(setupVisualization)
|
||||
.then(function () {
|
||||
$state.save();
|
||||
return courier.fetch();
|
||||
})
|
||||
.catch(notify.error);
|
||||
};
|
||||
|
||||
$scope.updateQueryAndFetch = function (query) {
|
||||
|
@ -592,10 +592,10 @@ function discoverController(
|
|||
|
||||
$scope.updateDataSource = Promise.method(function updateDataSource() {
|
||||
$scope.searchSource
|
||||
.size($scope.opts.sampleSize)
|
||||
.sort(getSort($state.sort, $scope.indexPattern))
|
||||
.query(!$state.query ? null : $state.query)
|
||||
.set('filter', queryFilter.getFilters());
|
||||
.size($scope.opts.sampleSize)
|
||||
.sort(getSort($state.sort, $scope.indexPattern))
|
||||
.query(!$state.query ? null : $state.query)
|
||||
.set('filter', queryFilter.getFilters());
|
||||
});
|
||||
|
||||
$scope.setSortOrder = function setSortOrder(columnName, direction) {
|
||||
|
@ -691,10 +691,10 @@ function discoverController(
|
|||
resolve($scope.vis);
|
||||
});
|
||||
})
|
||||
.finally(function () {
|
||||
.finally(function () {
|
||||
// clear the loading flag
|
||||
loadingVis = null;
|
||||
});
|
||||
loadingVis = null;
|
||||
});
|
||||
|
||||
return loadingVis;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ import noResultsTemplate from '../partials/no_results.html';
|
|||
import 'ui/directives/documentation_href';
|
||||
|
||||
uiModules
|
||||
.get('apps/discover')
|
||||
.directive('discoverNoResults', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: noResultsTemplate
|
||||
};
|
||||
});
|
||||
.get('apps/discover')
|
||||
.directive('discoverNoResults', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: noResultsTemplate
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
import VislibProvider from 'ui/vislib';
|
||||
import { uiModules } from 'ui/modules';
|
||||
uiModules
|
||||
.get('apps/discover')
|
||||
.directive('discoverTimechart', function (Private) {
|
||||
const vislib = Private(VislibProvider);
|
||||
.get('apps/discover')
|
||||
.directive('discoverTimechart', function (Private) {
|
||||
const vislib = Private(VislibProvider);
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
data: '='
|
||||
},
|
||||
link: function ($scope, elem) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
data: '='
|
||||
},
|
||||
link: function ($scope, elem) {
|
||||
|
||||
const init = function () {
|
||||
const init = function () {
|
||||
// This elem should already have a height/width
|
||||
const myChart = new vislib.Chart(elem[0], {
|
||||
addLegend: false
|
||||
});
|
||||
const myChart = new vislib.Chart(elem[0], {
|
||||
addLegend: false
|
||||
});
|
||||
|
||||
$scope.$watch('data', function (data) {
|
||||
if (data != null) {
|
||||
myChart.render(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
$scope.$watch('data', function (data) {
|
||||
if (data != null) {
|
||||
myChart.render(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Start the directive
|
||||
init();
|
||||
}
|
||||
};
|
||||
});
|
||||
// Start the directive
|
||||
init();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -21,14 +21,14 @@ const resolveIndexPattern = {
|
|||
};
|
||||
|
||||
uiRoutes
|
||||
.when('/doc/:indexPattern/:index/:type/:id', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern
|
||||
})
|
||||
.when('/doc/:indexPattern/:index/:type', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern
|
||||
});
|
||||
.when('/doc/:indexPattern/:index/:type/:id', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern
|
||||
})
|
||||
.when('/doc/:indexPattern/:index/:type', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern
|
||||
});
|
||||
|
||||
app.controller('doc', function ($scope, $route, es, timefilter) {
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ const formatIds = [
|
|||
'relative_date'
|
||||
];
|
||||
|
||||
// eslint-disable-next-line kibana-custom/no-default-export
|
||||
// eslint-disable-next-line @elastic/kibana-custom/no-default-export
|
||||
export default describe('conformance', function () {
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
|
|
@ -13,30 +13,30 @@ describe('Duration Format', function () {
|
|||
}));
|
||||
|
||||
test({ inputFormat: 'seconds', outputFormat: 'humanize' })
|
||||
(-60, 'minus a minute')
|
||||
(60, 'a minute')
|
||||
(125, '2 minutes');
|
||||
(-60, 'minus a minute')
|
||||
(60, 'a minute')
|
||||
(125, '2 minutes');
|
||||
|
||||
test({ inputFormat: 'minutes', outputFormat: 'humanize' })
|
||||
(-60, 'minus an hour')
|
||||
(60, 'an hour')
|
||||
(125, '2 hours');
|
||||
(-60, 'minus an hour')
|
||||
(60, 'an hour')
|
||||
(125, '2 hours');
|
||||
|
||||
test({ inputFormat: 'minutes', outputFormat: 'asHours' }) // outputPrecision defaults to: 2
|
||||
(-60, '-1.00')
|
||||
(60, '1.00')
|
||||
(125, '2.08');
|
||||
(-60, '-1.00')
|
||||
(60, '1.00')
|
||||
(125, '2.08');
|
||||
|
||||
test({ inputFormat: 'seconds', outputFormat: 'asSeconds', outputPrecision: 0 })
|
||||
(-60, '-60')
|
||||
(60, '60')
|
||||
(125, '125');
|
||||
(-60, '-60')
|
||||
(60, '60')
|
||||
(125, '125');
|
||||
|
||||
test({ inputFormat: 'seconds', outputFormat: 'asSeconds', outputPrecision: 2 })
|
||||
(-60, '-60.00')
|
||||
(-32.333, '-32.33')
|
||||
(60, '60.00')
|
||||
(125, '125.00');
|
||||
(-60, '-60.00')
|
||||
(-32.333, '-32.33')
|
||||
(60, '60.00')
|
||||
(125, '125.00');
|
||||
|
||||
function test({ inputFormat, outputFormat, outputPrecision }) {
|
||||
return function testFixture(input, output) {
|
||||
|
|
|
@ -13,21 +13,21 @@ export function Home({ addBasePath, directories }) {
|
|||
|
||||
const renderDirectories = (category) => {
|
||||
return directories
|
||||
.filter((directory) => {
|
||||
return directory.showOnHomePage && directory.category === category;
|
||||
})
|
||||
.map((directory) => {
|
||||
return (
|
||||
<KuiFlexItem style={{ minHeight: 64 }} key={directory.id}>
|
||||
<Synopsis
|
||||
description={directory.description}
|
||||
iconUrl={addBasePath(directory.icon)}
|
||||
title={directory.title}
|
||||
url={addBasePath(directory.path)}
|
||||
/>
|
||||
</KuiFlexItem>
|
||||
);
|
||||
});
|
||||
.filter((directory) => {
|
||||
return directory.showOnHomePage && directory.category === category;
|
||||
})
|
||||
.map((directory) => {
|
||||
return (
|
||||
<KuiFlexItem style={{ minHeight: 64 }} key={directory.id}>
|
||||
<Synopsis
|
||||
description={directory.description}
|
||||
iconUrl={addBasePath(directory.icon)}
|
||||
title={directory.title}
|
||||
url={addBasePath(directory.path)}
|
||||
/>
|
||||
</KuiFlexItem>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ import { KibanaRootController } from './kibana_root_controller';
|
|||
routes.enable();
|
||||
|
||||
routes
|
||||
.otherwise({
|
||||
redirectTo: `/${chrome.getInjected('kbnDefaultAppId', 'discover')}`
|
||||
});
|
||||
.otherwise({
|
||||
redirectTo: `/${chrome.getInjected('kbnDefaultAppId', 'discover')}`
|
||||
});
|
||||
|
||||
chrome.setRootController('kibana', KibanaRootController);
|
||||
|
||||
|
|
|
@ -10,54 +10,54 @@ import { management } from 'ui/management';
|
|||
import 'ui/kbn_top_nav';
|
||||
|
||||
uiRoutes
|
||||
.when('/management', {
|
||||
template: landingTemplate
|
||||
});
|
||||
.when('/management', {
|
||||
template: landingTemplate
|
||||
});
|
||||
|
||||
uiRoutes
|
||||
.when('/management/:section', {
|
||||
redirectTo: '/management'
|
||||
});
|
||||
.when('/management/:section', {
|
||||
redirectTo: '/management'
|
||||
});
|
||||
|
||||
require('ui/index_patterns/route_setup/load_default')({
|
||||
whenMissingRedirectTo: '/management/kibana/index'
|
||||
});
|
||||
|
||||
uiModules
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementApp', function (Private, $location, timefilter) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: appTemplate,
|
||||
transclude: true,
|
||||
scope: {
|
||||
sectionName: '@section',
|
||||
omitPages: '@omitBreadcrumbPages',
|
||||
pageTitle: '='
|
||||
},
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementApp', function (Private, $location, timefilter) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: appTemplate,
|
||||
transclude: true,
|
||||
scope: {
|
||||
sectionName: '@section',
|
||||
omitPages: '@omitBreadcrumbPages',
|
||||
pageTitle: '='
|
||||
},
|
||||
|
||||
link: function ($scope) {
|
||||
timefilter.enabled = false;
|
||||
$scope.sections = management.items.inOrder;
|
||||
$scope.section = management.getSection($scope.sectionName) || management;
|
||||
link: function ($scope) {
|
||||
timefilter.enabled = false;
|
||||
$scope.sections = management.items.inOrder;
|
||||
$scope.section = management.getSection($scope.sectionName) || management;
|
||||
|
||||
if ($scope.section) {
|
||||
$scope.section.items.forEach(item => {
|
||||
item.active = `#${$location.path()}`.indexOf(item.url) > -1;
|
||||
});
|
||||
if ($scope.section) {
|
||||
$scope.section.items.forEach(item => {
|
||||
item.active = `#${$location.path()}`.indexOf(item.url) > -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
uiModules
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementLanding', function (kbnVersion) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
$scope.sections = management.items.inOrder;
|
||||
$scope.kbnVersion = kbnVersion;
|
||||
}
|
||||
};
|
||||
});
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementLanding', function (kbnVersion) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
$scope.sections = management.items.inOrder;
|
||||
$scope.kbnVersion = kbnVersion;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -12,276 +12,276 @@ import './step_time_field';
|
|||
import './matching_indices_list';
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/index', {
|
||||
template,
|
||||
});
|
||||
.when('/management/kibana/index', {
|
||||
template,
|
||||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.controller('managementIndicesCreate', function (
|
||||
$routeParams,
|
||||
$scope,
|
||||
$timeout,
|
||||
config,
|
||||
es,
|
||||
indexPatterns,
|
||||
kbnUrl,
|
||||
Notifier,
|
||||
Promise
|
||||
) {
|
||||
.controller('managementIndicesCreate', function (
|
||||
$routeParams,
|
||||
$scope,
|
||||
$timeout,
|
||||
config,
|
||||
es,
|
||||
indexPatterns,
|
||||
kbnUrl,
|
||||
Notifier,
|
||||
Promise
|
||||
) {
|
||||
// This isn't ideal. We want to avoid searching for 20 indices
|
||||
// then filtering out the majority of them because they are sysetm indices.
|
||||
// We'd like to filter system indices out in the query
|
||||
// so if we can accomplish that in the future, this logic can go away
|
||||
const ESTIMATED_NUMBER_OF_SYSTEM_INDICES = 20;
|
||||
const MAX_NUMBER_OF_MATCHING_INDICES = 20;
|
||||
const MAX_SEARCH_SIZE = MAX_NUMBER_OF_MATCHING_INDICES + ESTIMATED_NUMBER_OF_SYSTEM_INDICES;
|
||||
const notify = new Notifier();
|
||||
const disabledDividerOption = {
|
||||
isDisabled: true,
|
||||
display: '───',
|
||||
};
|
||||
const noTimeFieldOption = {
|
||||
display: `I don't want to use the Time Filter`,
|
||||
};
|
||||
const ESTIMATED_NUMBER_OF_SYSTEM_INDICES = 20;
|
||||
const MAX_NUMBER_OF_MATCHING_INDICES = 20;
|
||||
const MAX_SEARCH_SIZE = MAX_NUMBER_OF_MATCHING_INDICES + ESTIMATED_NUMBER_OF_SYSTEM_INDICES;
|
||||
const notify = new Notifier();
|
||||
const disabledDividerOption = {
|
||||
isDisabled: true,
|
||||
display: '───',
|
||||
};
|
||||
const noTimeFieldOption = {
|
||||
display: `I don't want to use the Time Filter`,
|
||||
};
|
||||
|
||||
// Configure the new index pattern we're going to create.
|
||||
this.formValues = {
|
||||
id: $routeParams.id ? decodeURIComponent($routeParams.id) : undefined,
|
||||
name: '',
|
||||
expandWildcard: false,
|
||||
timeFieldOption: undefined,
|
||||
};
|
||||
// Configure the new index pattern we're going to create.
|
||||
this.formValues = {
|
||||
id: $routeParams.id ? decodeURIComponent($routeParams.id) : undefined,
|
||||
name: '',
|
||||
expandWildcard: false,
|
||||
timeFieldOption: undefined,
|
||||
};
|
||||
|
||||
// UI state.
|
||||
this.timeFieldOptions = [];
|
||||
this.wizardStep = 'indexPattern';
|
||||
this.isFetchingExistingIndices = true;
|
||||
this.isFetchingMatchingIndices = false;
|
||||
this.isFetchingTimeFieldOptions = false;
|
||||
this.isCreatingIndexPattern = false;
|
||||
this.doesIncludeSystemIndices = false;
|
||||
let allIndices = [];
|
||||
let matchingIndices = [];
|
||||
let partialMatchingIndices = [];
|
||||
this.allIndices = [];
|
||||
this.matchingIndices = [];
|
||||
this.partialMatchingIndices = [];
|
||||
// UI state.
|
||||
this.timeFieldOptions = [];
|
||||
this.wizardStep = 'indexPattern';
|
||||
this.isFetchingExistingIndices = true;
|
||||
this.isFetchingMatchingIndices = false;
|
||||
this.isFetchingTimeFieldOptions = false;
|
||||
this.isCreatingIndexPattern = false;
|
||||
this.doesIncludeSystemIndices = false;
|
||||
let allIndices = [];
|
||||
let matchingIndices = [];
|
||||
let partialMatchingIndices = [];
|
||||
this.allIndices = [];
|
||||
this.matchingIndices = [];
|
||||
this.partialMatchingIndices = [];
|
||||
|
||||
function createReasonableWait() {
|
||||
return new Promise(resolve => {
|
||||
function createReasonableWait() {
|
||||
return new Promise(resolve => {
|
||||
// Make every fetch take a set amount of time so the user gets some feedback that something
|
||||
// is happening.
|
||||
$timeout(() => {
|
||||
resolve();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
$timeout(() => {
|
||||
resolve();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
function getIndices(pattern, limit = MAX_SEARCH_SIZE) {
|
||||
const params = {
|
||||
index: pattern,
|
||||
ignore: [404],
|
||||
body: {
|
||||
size: 0, // no hits
|
||||
aggs: {
|
||||
indices: {
|
||||
terms: {
|
||||
field: '_index',
|
||||
size: limit,
|
||||
function getIndices(pattern, limit = MAX_SEARCH_SIZE) {
|
||||
const params = {
|
||||
index: pattern,
|
||||
ignore: [404],
|
||||
body: {
|
||||
size: 0, // no hits
|
||||
aggs: {
|
||||
indices: {
|
||||
terms: {
|
||||
field: '_index',
|
||||
size: limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return es.search(params)
|
||||
.then(response => {
|
||||
if (!response || response.error || !response.aggregations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _.sortBy(response.aggregations.indices.buckets.map(bucket => {
|
||||
return {
|
||||
name: bucket.key
|
||||
};
|
||||
}), 'name');
|
||||
});
|
||||
}
|
||||
|
||||
const whiteListIndices = indices => {
|
||||
if (!indices) {
|
||||
return indices;
|
||||
}
|
||||
|
||||
const acceptableIndices = this.doesIncludeSystemIndices
|
||||
? indices
|
||||
// All system indices begin with a period.
|
||||
: indices.filter(index => !index.name.startsWith('.'));
|
||||
|
||||
return acceptableIndices.slice(0, MAX_NUMBER_OF_MATCHING_INDICES);
|
||||
};
|
||||
|
||||
return es.search(params)
|
||||
.then(response => {
|
||||
if (!response || response.error || !response.aggregations) {
|
||||
return [];
|
||||
const updateWhiteListedIndices = () => {
|
||||
this.allIndices = whiteListIndices(allIndices);
|
||||
this.matchingIndices = whiteListIndices(matchingIndices);
|
||||
this.partialMatchingIndices = whiteListIndices(partialMatchingIndices);
|
||||
};
|
||||
|
||||
this.onIncludeSystemIndicesChange = () => {
|
||||
updateWhiteListedIndices();
|
||||
};
|
||||
|
||||
let mostRecentFetchMatchingIndicesRequest;
|
||||
|
||||
this.fetchMatchingIndices = () => {
|
||||
this.isFetchingMatchingIndices = true;
|
||||
|
||||
// Default to searching for all indices.
|
||||
const exactSearchQuery = this.formValues.name;
|
||||
let partialSearchQuery = this.formValues.name;
|
||||
|
||||
if (!_.endsWith(partialSearchQuery, '*')) {
|
||||
partialSearchQuery = `${partialSearchQuery}*`;
|
||||
}
|
||||
if (!_.startsWith(partialSearchQuery, '*')) {
|
||||
partialSearchQuery = `*${partialSearchQuery}`;
|
||||
}
|
||||
|
||||
const thisFetchMatchingIndicesRequest = mostRecentFetchMatchingIndicesRequest = Promise.all([
|
||||
getIndices(exactSearchQuery),
|
||||
getIndices(partialSearchQuery),
|
||||
createReasonableWait()
|
||||
])
|
||||
.then(([
|
||||
matchingIndicesResponse,
|
||||
partialMatchingIndicesResponse
|
||||
]) => {
|
||||
if (thisFetchMatchingIndicesRequest === mostRecentFetchMatchingIndicesRequest) {
|
||||
matchingIndices = matchingIndicesResponse;
|
||||
partialMatchingIndices = partialMatchingIndicesResponse;
|
||||
updateWhiteListedIndices();
|
||||
this.isFetchingMatchingIndices = false;
|
||||
}
|
||||
}).catch(error => {
|
||||
notify.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
this.fetchExistingIndices = () => {
|
||||
this.isFetchingExistingIndices = true;
|
||||
const allExistingLocalIndicesPattern = '*';
|
||||
|
||||
Promise.all([
|
||||
getIndices(allExistingLocalIndicesPattern),
|
||||
createReasonableWait()
|
||||
])
|
||||
.then(([allIndicesResponse]) => {
|
||||
// Cache all indices.
|
||||
allIndices = allIndicesResponse;
|
||||
updateWhiteListedIndices();
|
||||
this.isFetchingExistingIndices = false;
|
||||
}).catch(error => {
|
||||
notify.error(error);
|
||||
this.isFetchingExistingIndices = false;
|
||||
});
|
||||
};
|
||||
|
||||
this.isSystemIndicesCheckBoxVisible = () => (
|
||||
this.wizardStep === 'indexPattern'
|
||||
);
|
||||
|
||||
this.goToIndexPatternStep = () => {
|
||||
this.wizardStep = 'indexPattern';
|
||||
};
|
||||
|
||||
this.goToTimeFieldStep = () => {
|
||||
// Re-initialize this step.
|
||||
this.formValues.timeFieldOption = undefined;
|
||||
this.fetchTimeFieldOptions();
|
||||
this.wizardStep = 'timeField';
|
||||
};
|
||||
|
||||
this.hasIndices = () => (
|
||||
this.allIndices.length
|
||||
);
|
||||
|
||||
const extractTimeFieldsFromFields = fields => {
|
||||
const dateFields = fields.filter(field => field.type === 'date');
|
||||
|
||||
if (dateFields.length === 0) {
|
||||
return [{
|
||||
display: `The indices which match this index pattern don't contain any time fields.`,
|
||||
}];
|
||||
}
|
||||
|
||||
return [
|
||||
...dateFields.map(field => ({
|
||||
display: field.name,
|
||||
fieldName: field.name
|
||||
})),
|
||||
disabledDividerOption,
|
||||
noTimeFieldOption,
|
||||
];
|
||||
};
|
||||
|
||||
this.fetchTimeFieldOptions = () => {
|
||||
this.isFetchingTimeFieldOptions = true;
|
||||
this.formValues.timeFieldOption = undefined;
|
||||
this.timeFieldOptions = [];
|
||||
|
||||
Promise.all([
|
||||
indexPatterns.fieldsFetcher.fetchForWildcard(this.formValues.name),
|
||||
createReasonableWait(),
|
||||
])
|
||||
.then(([fields]) => {
|
||||
this.timeFieldOptions = extractTimeFieldsFromFields(fields);
|
||||
})
|
||||
.catch(error => {
|
||||
notify.error(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isFetchingTimeFieldOptions = false;
|
||||
});
|
||||
};
|
||||
|
||||
this.createIndexPattern = () => {
|
||||
this.isCreatingIndexPattern = true;
|
||||
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
timeFieldOption,
|
||||
} = this.formValues;
|
||||
|
||||
const timeFieldName = timeFieldOption
|
||||
? timeFieldOption.fieldName
|
||||
: undefined;
|
||||
|
||||
sendCreateIndexPatternRequest(indexPatterns, {
|
||||
id,
|
||||
name,
|
||||
timeFieldName,
|
||||
}).then(createdId => {
|
||||
if (!createdId) {
|
||||
return;
|
||||
}
|
||||
|
||||
return _.sortBy(response.aggregations.indices.buckets.map(bucket => {
|
||||
return {
|
||||
name: bucket.key
|
||||
};
|
||||
}), 'name');
|
||||
if (!config.get('defaultIndex')) {
|
||||
config.set('defaultIndex', createdId);
|
||||
}
|
||||
|
||||
indexPatterns.cache.clear(createdId);
|
||||
kbnUrl.change(`/management/kibana/indices/${createdId}`);
|
||||
}).catch(err => {
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
return notify.error(`Couldn't locate any indices matching that pattern. Please add the index to Elasticsearch`);
|
||||
}
|
||||
|
||||
notify.fatal(err);
|
||||
}).finally(() => {
|
||||
this.isCreatingIndexPattern = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const whiteListIndices = indices => {
|
||||
if (!indices) {
|
||||
return indices;
|
||||
}
|
||||
|
||||
const acceptableIndices = this.doesIncludeSystemIndices
|
||||
? indices
|
||||
// All system indices begin with a period.
|
||||
: indices.filter(index => !index.name.startsWith('.'));
|
||||
|
||||
return acceptableIndices.slice(0, MAX_NUMBER_OF_MATCHING_INDICES);
|
||||
};
|
||||
|
||||
const updateWhiteListedIndices = () => {
|
||||
this.allIndices = whiteListIndices(allIndices);
|
||||
this.matchingIndices = whiteListIndices(matchingIndices);
|
||||
this.partialMatchingIndices = whiteListIndices(partialMatchingIndices);
|
||||
};
|
||||
|
||||
this.onIncludeSystemIndicesChange = () => {
|
||||
updateWhiteListedIndices();
|
||||
};
|
||||
|
||||
let mostRecentFetchMatchingIndicesRequest;
|
||||
|
||||
this.fetchMatchingIndices = () => {
|
||||
this.isFetchingMatchingIndices = true;
|
||||
|
||||
// Default to searching for all indices.
|
||||
const exactSearchQuery = this.formValues.name;
|
||||
let partialSearchQuery = this.formValues.name;
|
||||
|
||||
if (!_.endsWith(partialSearchQuery, '*')) {
|
||||
partialSearchQuery = `${partialSearchQuery}*`;
|
||||
}
|
||||
if (!_.startsWith(partialSearchQuery, '*')) {
|
||||
partialSearchQuery = `*${partialSearchQuery}`;
|
||||
}
|
||||
|
||||
const thisFetchMatchingIndicesRequest = mostRecentFetchMatchingIndicesRequest = Promise.all([
|
||||
getIndices(exactSearchQuery),
|
||||
getIndices(partialSearchQuery),
|
||||
createReasonableWait()
|
||||
])
|
||||
.then(([
|
||||
matchingIndicesResponse,
|
||||
partialMatchingIndicesResponse
|
||||
]) => {
|
||||
if (thisFetchMatchingIndicesRequest === mostRecentFetchMatchingIndicesRequest) {
|
||||
matchingIndices = matchingIndicesResponse;
|
||||
partialMatchingIndices = partialMatchingIndicesResponse;
|
||||
updateWhiteListedIndices();
|
||||
this.isFetchingMatchingIndices = false;
|
||||
}
|
||||
}).catch(error => {
|
||||
notify.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
this.fetchExistingIndices = () => {
|
||||
this.isFetchingExistingIndices = true;
|
||||
const allExistingLocalIndicesPattern = '*';
|
||||
|
||||
Promise.all([
|
||||
getIndices(allExistingLocalIndicesPattern),
|
||||
createReasonableWait()
|
||||
])
|
||||
.then(([allIndicesResponse]) => {
|
||||
// Cache all indices.
|
||||
allIndices = allIndicesResponse;
|
||||
updateWhiteListedIndices();
|
||||
this.isFetchingExistingIndices = false;
|
||||
}).catch(error => {
|
||||
notify.error(error);
|
||||
this.isFetchingExistingIndices = false;
|
||||
});
|
||||
};
|
||||
|
||||
this.isSystemIndicesCheckBoxVisible = () => (
|
||||
this.wizardStep === 'indexPattern'
|
||||
);
|
||||
|
||||
this.goToIndexPatternStep = () => {
|
||||
this.wizardStep = 'indexPattern';
|
||||
};
|
||||
|
||||
this.goToTimeFieldStep = () => {
|
||||
// Re-initialize this step.
|
||||
this.formValues.timeFieldOption = undefined;
|
||||
this.fetchTimeFieldOptions();
|
||||
this.wizardStep = 'timeField';
|
||||
};
|
||||
|
||||
this.hasIndices = () => (
|
||||
this.allIndices.length
|
||||
);
|
||||
|
||||
const extractTimeFieldsFromFields = fields => {
|
||||
const dateFields = fields.filter(field => field.type === 'date');
|
||||
|
||||
if (dateFields.length === 0) {
|
||||
return [{
|
||||
display: `The indices which match this index pattern don't contain any time fields.`,
|
||||
}];
|
||||
}
|
||||
|
||||
return [
|
||||
...dateFields.map(field => ({
|
||||
display: field.name,
|
||||
fieldName: field.name
|
||||
})),
|
||||
disabledDividerOption,
|
||||
noTimeFieldOption,
|
||||
];
|
||||
};
|
||||
|
||||
this.fetchTimeFieldOptions = () => {
|
||||
this.isFetchingTimeFieldOptions = true;
|
||||
this.formValues.timeFieldOption = undefined;
|
||||
this.timeFieldOptions = [];
|
||||
|
||||
Promise.all([
|
||||
indexPatterns.fieldsFetcher.fetchForWildcard(this.formValues.name),
|
||||
createReasonableWait(),
|
||||
])
|
||||
.then(([fields]) => {
|
||||
this.timeFieldOptions = extractTimeFieldsFromFields(fields);
|
||||
})
|
||||
.catch(error => {
|
||||
notify.error(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isFetchingTimeFieldOptions = false;
|
||||
});
|
||||
};
|
||||
|
||||
this.createIndexPattern = () => {
|
||||
this.isCreatingIndexPattern = true;
|
||||
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
timeFieldOption,
|
||||
} = this.formValues;
|
||||
|
||||
const timeFieldName = timeFieldOption
|
||||
? timeFieldOption.fieldName
|
||||
: undefined;
|
||||
|
||||
sendCreateIndexPatternRequest(indexPatterns, {
|
||||
id,
|
||||
name,
|
||||
timeFieldName,
|
||||
}).then(createdId => {
|
||||
if (!createdId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.get('defaultIndex')) {
|
||||
config.set('defaultIndex', createdId);
|
||||
}
|
||||
|
||||
indexPatterns.cache.clear(createdId);
|
||||
kbnUrl.change(`/management/kibana/indices/${createdId}`);
|
||||
}).catch(err => {
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
return notify.error(`Couldn't locate any indices matching that pattern. Please add the index to Elasticsearch`);
|
||||
}
|
||||
|
||||
notify.fatal(err);
|
||||
}).finally(() => {
|
||||
this.isCreatingIndexPattern = false;
|
||||
});
|
||||
};
|
||||
|
||||
this.fetchExistingIndices();
|
||||
});
|
||||
this.fetchExistingIndices();
|
||||
});
|
||||
|
|
|
@ -11,130 +11,130 @@ import { uiModules } from 'ui/modules';
|
|||
import template from './edit_index_pattern.html';
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/indices/:indexPatternId', {
|
||||
template,
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns
|
||||
.get($route.current.params.indexPatternId)
|
||||
.catch(courier.redirectWhenMissing('/management/kibana/index'));
|
||||
.when('/management/kibana/indices/:indexPatternId', {
|
||||
template,
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns
|
||||
.get($route.current.params.indexPatternId)
|
||||
.catch(courier.redirectWhenMissing('/management/kibana/index'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/indices', {
|
||||
resolve: {
|
||||
redirect: function ($location, config) {
|
||||
const defaultIndex = config.get('defaultIndex');
|
||||
let path = '/management/kibana/index';
|
||||
.when('/management/kibana/indices', {
|
||||
resolve: {
|
||||
redirect: function ($location, config) {
|
||||
const defaultIndex = config.get('defaultIndex');
|
||||
let path = '/management/kibana/index';
|
||||
|
||||
if (defaultIndex) {
|
||||
path = `/management/kibana/indices/${defaultIndex}`;
|
||||
if (defaultIndex) {
|
||||
path = `/management/kibana/indices/${defaultIndex}`;
|
||||
}
|
||||
|
||||
$location.path(path).replace();
|
||||
}
|
||||
|
||||
$location.path(path).replace();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.controller('managementIndicesEdit', function (
|
||||
.controller('managementIndicesEdit', function (
|
||||
$scope, $location, $route, config, courier, Notifier, Private, AppState, docTitle, confirmModal) {
|
||||
const notify = new Notifier();
|
||||
const $state = $scope.state = new AppState();
|
||||
const notify = new Notifier();
|
||||
const $state = $scope.state = new AppState();
|
||||
|
||||
$scope.kbnUrl = Private(KbnUrlProvider);
|
||||
$scope.indexPattern = $route.current.locals.indexPattern;
|
||||
docTitle.change($scope.indexPattern.title);
|
||||
$scope.kbnUrl = Private(KbnUrlProvider);
|
||||
$scope.indexPattern = $route.current.locals.indexPattern;
|
||||
docTitle.change($scope.indexPattern.title);
|
||||
|
||||
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
|
||||
return pattern.id !== $scope.indexPattern.id;
|
||||
});
|
||||
|
||||
$scope.$watch('indexPattern.fields', function () {
|
||||
$scope.editSections = Private(IndicesEditSectionsProvider)($scope.indexPattern);
|
||||
$scope.refreshFilters();
|
||||
});
|
||||
|
||||
$scope.refreshFilters = function () {
|
||||
const indexedFieldTypes = [];
|
||||
const scriptedFieldLanguages = [];
|
||||
$scope.indexPattern.fields.forEach(field => {
|
||||
if (field.scripted) {
|
||||
scriptedFieldLanguages.push(field.lang);
|
||||
} else {
|
||||
indexedFieldTypes.push(field.type);
|
||||
}
|
||||
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
|
||||
return pattern.id !== $scope.indexPattern.id;
|
||||
});
|
||||
|
||||
$scope.indexedFieldTypes = _.unique(indexedFieldTypes);
|
||||
$scope.scriptedFieldLanguages = _.unique(scriptedFieldLanguages);
|
||||
};
|
||||
$scope.$watch('indexPattern.fields', function () {
|
||||
$scope.editSections = Private(IndicesEditSectionsProvider)($scope.indexPattern);
|
||||
$scope.refreshFilters();
|
||||
});
|
||||
|
||||
$scope.changeFilter = function (filter, val) {
|
||||
$scope[filter] = val || ''; // null causes filter to check for null explicitly
|
||||
};
|
||||
|
||||
$scope.changeTab = function (obj) {
|
||||
$state.tab = obj.index;
|
||||
$state.save();
|
||||
};
|
||||
|
||||
$scope.$watch('state.tab', function (tab) {
|
||||
if (!tab) $scope.changeTab($scope.editSections[0]);
|
||||
});
|
||||
|
||||
$scope.$watchCollection('indexPattern.fields', function () {
|
||||
$scope.conflictFields = $scope.indexPattern.fields
|
||||
.filter(field => field.type === 'conflict');
|
||||
});
|
||||
|
||||
$scope.refreshFields = function () {
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Refresh fields',
|
||||
onConfirm: () => { $scope.indexPattern.refreshFields(); }
|
||||
};
|
||||
confirmModal(
|
||||
'This will reset the field popularity counters. Are you sure you want to refresh your fields?',
|
||||
confirmModalOptions
|
||||
);
|
||||
};
|
||||
|
||||
$scope.removePattern = function () {
|
||||
function doRemove() {
|
||||
if ($scope.indexPattern.id === config.get('defaultIndex')) {
|
||||
config.remove('defaultIndex');
|
||||
|
||||
if (otherPatterns.length) {
|
||||
config.set('defaultIndex', otherPatterns[0].id);
|
||||
$scope.refreshFilters = function () {
|
||||
const indexedFieldTypes = [];
|
||||
const scriptedFieldLanguages = [];
|
||||
$scope.indexPattern.fields.forEach(field => {
|
||||
if (field.scripted) {
|
||||
scriptedFieldLanguages.push(field.lang);
|
||||
} else {
|
||||
indexedFieldTypes.push(field.type);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.indexedFieldTypes = _.unique(indexedFieldTypes);
|
||||
$scope.scriptedFieldLanguages = _.unique(scriptedFieldLanguages);
|
||||
};
|
||||
|
||||
$scope.changeFilter = function (filter, val) {
|
||||
$scope[filter] = val || ''; // null causes filter to check for null explicitly
|
||||
};
|
||||
|
||||
$scope.changeTab = function (obj) {
|
||||
$state.tab = obj.index;
|
||||
$state.save();
|
||||
};
|
||||
|
||||
$scope.$watch('state.tab', function (tab) {
|
||||
if (!tab) $scope.changeTab($scope.editSections[0]);
|
||||
});
|
||||
|
||||
$scope.$watchCollection('indexPattern.fields', function () {
|
||||
$scope.conflictFields = $scope.indexPattern.fields
|
||||
.filter(field => field.type === 'conflict');
|
||||
});
|
||||
|
||||
$scope.refreshFields = function () {
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Refresh fields',
|
||||
onConfirm: () => { $scope.indexPattern.refreshFields(); }
|
||||
};
|
||||
confirmModal(
|
||||
'This will reset the field popularity counters. Are you sure you want to refresh your fields?',
|
||||
confirmModalOptions
|
||||
);
|
||||
};
|
||||
|
||||
$scope.removePattern = function () {
|
||||
function doRemove() {
|
||||
if ($scope.indexPattern.id === config.get('defaultIndex')) {
|
||||
config.remove('defaultIndex');
|
||||
|
||||
if (otherPatterns.length) {
|
||||
config.set('defaultIndex', otherPatterns[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
courier.indexPatterns.delete($scope.indexPattern)
|
||||
.then(function () {
|
||||
$location.url('/management/kibana/index');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
}
|
||||
|
||||
courier.indexPatterns.delete($scope.indexPattern)
|
||||
.then(function () {
|
||||
$location.url('/management/kibana/index');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Remove index pattern',
|
||||
onConfirm: doRemove
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Remove index pattern',
|
||||
onConfirm: doRemove
|
||||
};
|
||||
confirmModal('Are you sure you want to remove this index pattern?', confirmModalOptions);
|
||||
};
|
||||
confirmModal('Are you sure you want to remove this index pattern?', confirmModalOptions);
|
||||
};
|
||||
|
||||
$scope.setDefaultPattern = function () {
|
||||
config.set('defaultIndex', $scope.indexPattern.id);
|
||||
};
|
||||
$scope.setDefaultPattern = function () {
|
||||
config.set('defaultIndex', $scope.indexPattern.id);
|
||||
};
|
||||
|
||||
$scope.setIndexPatternsTimeField = function (field) {
|
||||
if (field.type !== 'date') {
|
||||
notify.error('That field is a ' + field.type + ' not a date.');
|
||||
return;
|
||||
}
|
||||
$scope.indexPattern.timeFieldName = field.name;
|
||||
return $scope.indexPattern.save();
|
||||
};
|
||||
});
|
||||
$scope.setIndexPatternsTimeField = function (field) {
|
||||
if (field.type !== 'date') {
|
||||
notify.error('That field is a ' + field.type + ' not a date.');
|
||||
return;
|
||||
}
|
||||
$scope.indexPattern.timeFieldName = field.name;
|
||||
return $scope.indexPattern.save();
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import template from './index_header.html';
|
||||
uiModules
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementIndexHeader', function (config) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
replace: true,
|
||||
scope: {
|
||||
indexPattern: '=',
|
||||
setDefault: '&',
|
||||
refreshFields: '&',
|
||||
delete: '&',
|
||||
},
|
||||
link: function ($scope, $el, attrs) {
|
||||
$scope.delete = attrs.delete ? $scope.delete : null;
|
||||
$scope.setDefault = attrs.setDefault ? $scope.setDefault : null;
|
||||
$scope.refreshFields = attrs.refreshFields ? $scope.refreshFields : null;
|
||||
config.bindToScope($scope, 'defaultIndex');
|
||||
}
|
||||
};
|
||||
});
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementIndexHeader', function (config) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
replace: true,
|
||||
scope: {
|
||||
indexPattern: '=',
|
||||
setDefault: '&',
|
||||
refreshFields: '&',
|
||||
delete: '&',
|
||||
},
|
||||
link: function ($scope, $el, attrs) {
|
||||
$scope.delete = attrs.delete ? $scope.delete : null;
|
||||
$scope.setDefault = attrs.setDefault ? $scope.setDefault : null;
|
||||
$scope.refreshFields = attrs.refreshFields ? $scope.refreshFields : null;
|
||||
config.bindToScope($scope, 'defaultIndex');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,85 +8,85 @@ import { FieldWildcardProvider } from 'ui/field_wildcard';
|
|||
import template from './indexed_fields_table.html';
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('indexedFieldsTable', function (Private, $filter) {
|
||||
const yesTemplate = '<i class="fa fa-check" aria-label="yes"></i>';
|
||||
const noTemplate = '';
|
||||
const filter = $filter('filter');
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
.directive('indexedFieldsTable', function (Private, $filter) {
|
||||
const yesTemplate = '<i class="fa fa-check" aria-label="yes"></i>';
|
||||
const noTemplate = '';
|
||||
const filter = $filter('filter');
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
scope: true,
|
||||
link: function ($scope) {
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
$scope.perPage = 25;
|
||||
$scope.columns = [
|
||||
{ title: 'name' },
|
||||
{ title: 'type' },
|
||||
{ title: 'format' },
|
||||
{ title: 'searchable', info: 'These fields can be used in the filter bar' },
|
||||
{ title: 'aggregatable', info: 'These fields can be used in visualization aggregations' },
|
||||
{ title: 'excluded', info: 'Fields that are excluded from _source when it is fetched' },
|
||||
{ title: 'controls', sortable: false }
|
||||
];
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
scope: true,
|
||||
link: function ($scope) {
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
$scope.perPage = 25;
|
||||
$scope.columns = [
|
||||
{ title: 'name' },
|
||||
{ title: 'type' },
|
||||
{ title: 'format' },
|
||||
{ title: 'searchable', info: 'These fields can be used in the filter bar' },
|
||||
{ title: 'aggregatable', info: 'These fields can be used in visualization aggregations' },
|
||||
{ title: 'excluded', info: 'Fields that are excluded from _source when it is fetched' },
|
||||
{ title: 'controls', sortable: false }
|
||||
];
|
||||
|
||||
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'indexedFieldTypeFilter'], refreshRows);
|
||||
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'indexedFieldTypeFilter'], refreshRows);
|
||||
|
||||
function refreshRows() {
|
||||
function refreshRows() {
|
||||
// clear and destroy row scopes
|
||||
_.invoke(rowScopes.splice(0), '$destroy');
|
||||
const fields = filter($scope.indexPattern.getNonScriptedFields(), {
|
||||
name: $scope.fieldFilter,
|
||||
type: $scope.indexedFieldTypeFilter
|
||||
});
|
||||
const sourceFilters = $scope.indexPattern.sourceFilters && $scope.indexPattern.sourceFilters.map(f => f.value) || [];
|
||||
const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters);
|
||||
_.find($scope.editSections, { index: 'indexedFields' }).count = fields.length; // Update the tab count
|
||||
_.invoke(rowScopes.splice(0), '$destroy');
|
||||
const fields = filter($scope.indexPattern.getNonScriptedFields(), {
|
||||
name: $scope.fieldFilter,
|
||||
type: $scope.indexedFieldTypeFilter
|
||||
});
|
||||
const sourceFilters = $scope.indexPattern.sourceFilters && $scope.indexPattern.sourceFilters.map(f => f.value) || [];
|
||||
const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters);
|
||||
_.find($scope.editSections, { index: 'indexedFields' }).count = fields.length; // Update the tab count
|
||||
|
||||
$scope.rows = fields.map(function (field) {
|
||||
const childScope = _.assign($scope.$new(), { field: field });
|
||||
rowScopes.push(childScope);
|
||||
$scope.rows = fields.map(function (field) {
|
||||
const childScope = _.assign($scope.$new(), { field: field });
|
||||
rowScopes.push(childScope);
|
||||
|
||||
const excluded = fieldWildcardMatch(field.name);
|
||||
const excluded = fieldWildcardMatch(field.name);
|
||||
|
||||
return [
|
||||
{
|
||||
markup: fieldNameHtml,
|
||||
scope: childScope,
|
||||
value: field.displayName,
|
||||
attr: {
|
||||
'data-test-subj': 'indexedFieldName'
|
||||
return [
|
||||
{
|
||||
markup: fieldNameHtml,
|
||||
scope: childScope,
|
||||
value: field.displayName,
|
||||
attr: {
|
||||
'data-test-subj': 'indexedFieldName'
|
||||
}
|
||||
},
|
||||
{
|
||||
markup: fieldTypeHtml,
|
||||
scope: childScope,
|
||||
value: field.type,
|
||||
attr: {
|
||||
'data-test-subj': 'indexedFieldType'
|
||||
}
|
||||
},
|
||||
_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']),
|
||||
{
|
||||
markup: field.searchable ? yesTemplate : noTemplate,
|
||||
value: field.searchable
|
||||
},
|
||||
{
|
||||
markup: field.aggregatable ? yesTemplate : noTemplate,
|
||||
value: field.aggregatable
|
||||
},
|
||||
{
|
||||
markup: excluded ? yesTemplate : noTemplate,
|
||||
value: excluded
|
||||
},
|
||||
{
|
||||
markup: fieldControlsHtml,
|
||||
scope: childScope
|
||||
}
|
||||
},
|
||||
{
|
||||
markup: fieldTypeHtml,
|
||||
scope: childScope,
|
||||
value: field.type,
|
||||
attr: {
|
||||
'data-test-subj': 'indexedFieldType'
|
||||
}
|
||||
},
|
||||
_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']),
|
||||
{
|
||||
markup: field.searchable ? yesTemplate : noTemplate,
|
||||
value: field.searchable
|
||||
},
|
||||
{
|
||||
markup: field.aggregatable ? yesTemplate : noTemplate,
|
||||
value: field.aggregatable
|
||||
},
|
||||
{
|
||||
markup: excluded ? yesTemplate : noTemplate,
|
||||
value: excluded
|
||||
},
|
||||
{
|
||||
markup: fieldControlsHtml,
|
||||
scope: childScope
|
||||
}
|
||||
];
|
||||
});
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -5,63 +5,63 @@ import uiRoutes from 'ui/routes';
|
|||
import template from './scripted_field_editor.html';
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/indices/:indexPatternId/field/:fieldName*', { mode: 'edit' })
|
||||
.when('/management/kibana/indices/:indexPatternId/create-field/', { mode: 'create' })
|
||||
.defaults(/management\/kibana\/indices\/[^\/]+\/(field|create-field)(\/|$)/, {
|
||||
template,
|
||||
mapBreadcrumbs($route, breadcrumbs) {
|
||||
const { indexPattern } = $route.current.locals;
|
||||
return breadcrumbs.map(crumb => {
|
||||
if (crumb.id !== indexPattern.id) {
|
||||
return crumb;
|
||||
}
|
||||
.when('/management/kibana/indices/:indexPatternId/field/:fieldName*', { mode: 'edit' })
|
||||
.when('/management/kibana/indices/:indexPatternId/create-field/', { mode: 'create' })
|
||||
.defaults(/management\/kibana\/indices\/[^\/]+\/(field|create-field)(\/|$)/, {
|
||||
template,
|
||||
mapBreadcrumbs($route, breadcrumbs) {
|
||||
const { indexPattern } = $route.current.locals;
|
||||
return breadcrumbs.map(crumb => {
|
||||
if (crumb.id !== indexPattern.id) {
|
||||
return crumb;
|
||||
}
|
||||
|
||||
return {
|
||||
...crumb,
|
||||
display: indexPattern.title
|
||||
};
|
||||
});
|
||||
},
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPatternId)
|
||||
.catch(courier.redirectWhenMissing('/management/kibana/indices'));
|
||||
}
|
||||
},
|
||||
controllerAs: 'fieldSettings',
|
||||
controller: function FieldEditorPageController($route, Private, Notifier, docTitle) {
|
||||
const Field = Private(IndexPatternsFieldProvider);
|
||||
const notify = new Notifier({ location: 'Field Editor' });
|
||||
const kbnUrl = Private(KbnUrlProvider);
|
||||
|
||||
this.mode = $route.current.mode;
|
||||
this.indexPattern = $route.current.locals.indexPattern;
|
||||
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
const fieldName = $route.current.params.fieldName;
|
||||
this.field = this.indexPattern.fields.byName[fieldName];
|
||||
|
||||
if (!this.field) {
|
||||
notify.error(this.indexPattern + ' does not have a "' + fieldName + '" field.');
|
||||
kbnUrl.redirectToRoute(this.indexPattern, 'edit');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else if (this.mode === 'create') {
|
||||
this.field = new Field(this.indexPattern, {
|
||||
scripted: true,
|
||||
type: 'number'
|
||||
return {
|
||||
...crumb,
|
||||
display: indexPattern.title
|
||||
};
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error('unknown fieldSettings mode ' + this.mode);
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPatternId)
|
||||
.catch(courier.redirectWhenMissing('/management/kibana/indices'));
|
||||
}
|
||||
},
|
||||
controllerAs: 'fieldSettings',
|
||||
controller: function FieldEditorPageController($route, Private, Notifier, docTitle) {
|
||||
const Field = Private(IndexPatternsFieldProvider);
|
||||
const notify = new Notifier({ location: 'Field Editor' });
|
||||
const kbnUrl = Private(KbnUrlProvider);
|
||||
|
||||
docTitle.change([this.field.name || 'New Scripted Field', this.indexPattern.title]);
|
||||
this.goBack = function () {
|
||||
kbnUrl.changeToRoute(this.indexPattern, 'edit');
|
||||
};
|
||||
}
|
||||
});
|
||||
this.mode = $route.current.mode;
|
||||
this.indexPattern = $route.current.locals.indexPattern;
|
||||
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
const fieldName = $route.current.params.fieldName;
|
||||
this.field = this.indexPattern.fields.byName[fieldName];
|
||||
|
||||
if (!this.field) {
|
||||
notify.error(this.indexPattern + ' does not have a "' + fieldName + '" field.');
|
||||
kbnUrl.redirectToRoute(this.indexPattern, 'edit');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else if (this.mode === 'create') {
|
||||
this.field = new Field(this.indexPattern, {
|
||||
scripted: true,
|
||||
type: 'number'
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error('unknown fieldSettings mode ' + this.mode);
|
||||
}
|
||||
|
||||
docTitle.change([this.field.name || 'New Scripted Field', this.indexPattern.title]);
|
||||
this.goBack = function () {
|
||||
kbnUrl.changeToRoute(this.indexPattern, 'edit');
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import 'ui/paginated_table';
|
||||
import fieldControlsHtml from '../field_controls.html';
|
||||
|
@ -8,124 +9,124 @@ import { getSupportedScriptingLanguages, getDeprecatedScriptingLanguages } from
|
|||
import { documentationLinks } from 'ui/documentation_links/documentation_links';
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('scriptedFieldsTable', function (kbnUrl, Notifier, $filter, confirmModal) {
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
const filter = $filter('filter');
|
||||
.directive('scriptedFieldsTable', function (kbnUrl, Notifier, $filter, confirmModal) {
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
const filter = $filter('filter');
|
||||
|
||||
const notify = new Notifier();
|
||||
const notify = new Notifier();
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
scope: true,
|
||||
link: function ($scope) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
scope: true,
|
||||
link: function ($scope) {
|
||||
|
||||
const fieldCreatorPath = '/management/kibana/indices/{{ indexPattern }}/scriptedField';
|
||||
const fieldEditorPath = fieldCreatorPath + '/{{ fieldName }}';
|
||||
const fieldCreatorPath = '/management/kibana/indices/{{ indexPattern }}/scriptedField';
|
||||
const fieldEditorPath = fieldCreatorPath + '/{{ fieldName }}';
|
||||
|
||||
$scope.docLinks = documentationLinks.scriptedFields;
|
||||
$scope.perPage = 25;
|
||||
$scope.columns = [
|
||||
{ title: 'name' },
|
||||
{ title: 'lang' },
|
||||
{ title: 'script' },
|
||||
{ title: 'format' },
|
||||
{ title: 'controls', sortable: false }
|
||||
];
|
||||
$scope.docLinks = documentationLinks.scriptedFields;
|
||||
$scope.perPage = 25;
|
||||
$scope.columns = [
|
||||
{ title: 'name' },
|
||||
{ title: 'lang' },
|
||||
{ title: 'script' },
|
||||
{ title: 'format' },
|
||||
{ title: 'controls', sortable: false }
|
||||
];
|
||||
|
||||
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'scriptedFieldLanguageFilter'], refreshRows);
|
||||
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'scriptedFieldLanguageFilter'], refreshRows);
|
||||
|
||||
function refreshRows() {
|
||||
_.invoke(rowScopes, '$destroy');
|
||||
rowScopes.length = 0;
|
||||
function refreshRows() {
|
||||
_.invoke(rowScopes, '$destroy');
|
||||
rowScopes.length = 0;
|
||||
|
||||
const fields = filter($scope.indexPattern.getScriptedFields(), {
|
||||
name: $scope.fieldFilter,
|
||||
lang: $scope.scriptedFieldLanguageFilter
|
||||
});
|
||||
_.find($scope.editSections, { index: 'scriptedFields' }).count = fields.length; // Update the tab count
|
||||
const fields = filter($scope.indexPattern.getScriptedFields(), {
|
||||
name: $scope.fieldFilter,
|
||||
lang: $scope.scriptedFieldLanguageFilter
|
||||
});
|
||||
_.find($scope.editSections, { index: 'scriptedFields' }).count = fields.length; // Update the tab count
|
||||
|
||||
$scope.rows = fields.map(function (field) {
|
||||
const rowScope = $scope.$new();
|
||||
rowScope.field = field;
|
||||
rowScopes.push(rowScope);
|
||||
$scope.rows = fields.map(function (field) {
|
||||
const rowScope = $scope.$new();
|
||||
rowScope.field = field;
|
||||
rowScopes.push(rowScope);
|
||||
|
||||
return [
|
||||
_.escape(field.name),
|
||||
{
|
||||
markup: field.lang,
|
||||
attr: {
|
||||
'data-test-subj': 'scriptedFieldLang'
|
||||
return [
|
||||
_.escape(field.name),
|
||||
{
|
||||
markup: field.lang,
|
||||
attr: {
|
||||
'data-test-subj': 'scriptedFieldLang'
|
||||
}
|
||||
},
|
||||
_.escape(field.script),
|
||||
_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']),
|
||||
{
|
||||
markup: fieldControlsHtml,
|
||||
scope: rowScope
|
||||
}
|
||||
},
|
||||
_.escape(field.script),
|
||||
_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']),
|
||||
{
|
||||
markup: fieldControlsHtml,
|
||||
scope: rowScope
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
$scope.addDateScripts = function () {
|
||||
const conflictFields = [];
|
||||
let fieldsAdded = 0;
|
||||
_.each(dateScripts($scope.indexPattern), function (script, field) {
|
||||
try {
|
||||
$scope.indexPattern.addScriptedField(field, script, 'number');
|
||||
fieldsAdded++;
|
||||
} catch (e) {
|
||||
conflictFields.push(field);
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addDateScripts = function () {
|
||||
const conflictFields = [];
|
||||
let fieldsAdded = 0;
|
||||
_.each(dateScripts($scope.indexPattern), function (script, field) {
|
||||
try {
|
||||
$scope.indexPattern.addScriptedField(field, script, 'number');
|
||||
fieldsAdded++;
|
||||
} catch (e) {
|
||||
conflictFields.push(field);
|
||||
if (fieldsAdded > 0) {
|
||||
notify.info(fieldsAdded + ' script fields created');
|
||||
}
|
||||
});
|
||||
|
||||
if (fieldsAdded > 0) {
|
||||
notify.info(fieldsAdded + ' script fields created');
|
||||
if (conflictFields.length > 0) {
|
||||
notify.info('Not adding ' + conflictFields.length + ' duplicate fields: ' + conflictFields.join(', '));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.create = function () {
|
||||
const params = {
|
||||
indexPattern: $scope.indexPattern.id
|
||||
};
|
||||
|
||||
kbnUrl.change(fieldCreatorPath, params);
|
||||
};
|
||||
|
||||
$scope.edit = function (field) {
|
||||
const params = {
|
||||
indexPattern: $scope.indexPattern.id,
|
||||
fieldName: field.name
|
||||
};
|
||||
|
||||
kbnUrl.change(fieldEditorPath, params);
|
||||
};
|
||||
|
||||
$scope.remove = function (field) {
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Delete field',
|
||||
onConfirm: () => { $scope.indexPattern.removeScriptedField(field.name); }
|
||||
};
|
||||
confirmModal(`Are you sure want to delete ${field.name}? This action is irreversible!`, confirmModalOptions);
|
||||
};
|
||||
|
||||
function getLanguagesInUse() {
|
||||
const fields = $scope.indexPattern.getScriptedFields();
|
||||
return _.uniq(_.map(fields, 'lang'));
|
||||
}
|
||||
|
||||
if (conflictFields.length > 0) {
|
||||
notify.info('Not adding ' + conflictFields.length + ' duplicate fields: ' + conflictFields.join(', '));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.create = function () {
|
||||
const params = {
|
||||
indexPattern: $scope.indexPattern.id
|
||||
$scope.getDeprecatedLanguagesInUse = function () {
|
||||
return _.intersection(getLanguagesInUse(), getDeprecatedScriptingLanguages());
|
||||
};
|
||||
|
||||
kbnUrl.change(fieldCreatorPath, params);
|
||||
};
|
||||
|
||||
$scope.edit = function (field) {
|
||||
const params = {
|
||||
indexPattern: $scope.indexPattern.id,
|
||||
fieldName: field.name
|
||||
$scope.getUnsupportedLanguagesInUse = function () {
|
||||
return _.difference(getLanguagesInUse(), _.union(getSupportedScriptingLanguages(), getDeprecatedScriptingLanguages()));
|
||||
};
|
||||
|
||||
kbnUrl.change(fieldEditorPath, params);
|
||||
};
|
||||
|
||||
$scope.remove = function (field) {
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Delete field',
|
||||
onConfirm: () => { $scope.indexPattern.removeScriptedField(field.name); }
|
||||
};
|
||||
confirmModal(`Are you sure want to delete ${field.name}? This action is irreversible!`, confirmModalOptions);
|
||||
};
|
||||
|
||||
function getLanguagesInUse() {
|
||||
const fields = $scope.indexPattern.getScriptedFields();
|
||||
return _.uniq(_.map(fields, 'lang'));
|
||||
}
|
||||
|
||||
$scope.getDeprecatedLanguagesInUse = function () {
|
||||
return _.intersection(getLanguagesInUse(), getDeprecatedScriptingLanguages());
|
||||
};
|
||||
|
||||
$scope.getUnsupportedLanguagesInUse = function () {
|
||||
return _.difference(getLanguagesInUse(), _.union(getSupportedScriptingLanguages(), getDeprecatedScriptingLanguages()));
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -12,116 +12,116 @@ import './source_filters_table.less';
|
|||
const notify = new Notifier();
|
||||
|
||||
uiModules.get('kibana')
|
||||
.directive('sourceFiltersTable', function (Private, $filter, confirmModal) {
|
||||
const angularFilter = $filter('filter');
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
.directive('sourceFiltersTable', function (Private, $filter, confirmModal) {
|
||||
const angularFilter = $filter('filter');
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
indexPattern: '='
|
||||
},
|
||||
template,
|
||||
controllerAs: 'sourceFilters',
|
||||
controller: class FieldFiltersController {
|
||||
constructor($scope) {
|
||||
if (!$scope.indexPattern) {
|
||||
throw new Error('index pattern is required');
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
indexPattern: '='
|
||||
},
|
||||
template,
|
||||
controllerAs: 'sourceFilters',
|
||||
controller: class FieldFiltersController {
|
||||
constructor($scope) {
|
||||
if (!$scope.indexPattern) {
|
||||
throw new Error('index pattern is required');
|
||||
}
|
||||
|
||||
$scope.perPage = 25;
|
||||
$scope.columns = [
|
||||
{
|
||||
title: 'filter'
|
||||
},
|
||||
{
|
||||
title: 'matches',
|
||||
sortable: false,
|
||||
info: 'The source fields that match the filter.'
|
||||
},
|
||||
{
|
||||
title: 'controls',
|
||||
sortable: false
|
||||
}
|
||||
];
|
||||
|
||||
this.$scope = $scope;
|
||||
this.saving = false;
|
||||
this.editing = null;
|
||||
this.newValue = null;
|
||||
this.placeHolder = 'source filter, accepts wildcards (e.g., `user*` to filter fields starting with \'user\')';
|
||||
|
||||
$scope.$watchMulti([ '[]indexPattern.sourceFilters', '$parent.fieldFilter' ], () => {
|
||||
invoke(rowScopes, '$destroy');
|
||||
rowScopes.length = 0;
|
||||
|
||||
if ($scope.indexPattern.sourceFilters) {
|
||||
$scope.rows = [];
|
||||
each($scope.indexPattern.sourceFilters, (filter) => {
|
||||
const matcher = fieldWildcardMatcher([ filter.value ]);
|
||||
// compute which fields match a filter
|
||||
const matches = $scope.indexPattern.getNonScriptedFields().map(f => f.name).filter(matcher).sort();
|
||||
if ($scope.$parent.fieldFilter && !angularFilter(matches, $scope.$parent.fieldFilter).length) {
|
||||
return;
|
||||
}
|
||||
// compute the rows
|
||||
const rowScope = $scope.$new();
|
||||
rowScope.filter = filter;
|
||||
rowScopes.push(rowScope);
|
||||
$scope.rows.push([
|
||||
{
|
||||
markup: filterHtml,
|
||||
scope: rowScope
|
||||
},
|
||||
size(matches) ? escape(matches.join(', ')) : '<em>The source filter doesn\'t match any known fields.</em>',
|
||||
{
|
||||
markup: controlsHtml,
|
||||
scope: rowScope
|
||||
}
|
||||
]);
|
||||
});
|
||||
// Update the tab count
|
||||
find($scope.$parent.editSections, { index: 'sourceFilters' }).count = $scope.rows.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.perPage = 25;
|
||||
$scope.columns = [
|
||||
{
|
||||
title: 'filter'
|
||||
},
|
||||
{
|
||||
title: 'matches',
|
||||
sortable: false,
|
||||
info: 'The source fields that match the filter.'
|
||||
},
|
||||
{
|
||||
title: 'controls',
|
||||
sortable: false
|
||||
}
|
||||
];
|
||||
all() {
|
||||
return this.$scope.indexPattern.sourceFilters || [];
|
||||
}
|
||||
|
||||
this.$scope = $scope;
|
||||
this.saving = false;
|
||||
this.editing = null;
|
||||
this.newValue = null;
|
||||
this.placeHolder = 'source filter, accepts wildcards (e.g., `user*` to filter fields starting with \'user\')';
|
||||
delete(filter) {
|
||||
const doDelete = () => {
|
||||
if (this.editing === filter) {
|
||||
this.editing = null;
|
||||
}
|
||||
|
||||
$scope.$watchMulti([ '[]indexPattern.sourceFilters', '$parent.fieldFilter' ], () => {
|
||||
invoke(rowScopes, '$destroy');
|
||||
rowScopes.length = 0;
|
||||
this.$scope.indexPattern.sourceFilters = without(this.all(), filter);
|
||||
return this.save();
|
||||
};
|
||||
|
||||
if ($scope.indexPattern.sourceFilters) {
|
||||
$scope.rows = [];
|
||||
each($scope.indexPattern.sourceFilters, (filter) => {
|
||||
const matcher = fieldWildcardMatcher([ filter.value ]);
|
||||
// compute which fields match a filter
|
||||
const matches = $scope.indexPattern.getNonScriptedFields().map(f => f.name).filter(matcher).sort();
|
||||
if ($scope.$parent.fieldFilter && !angularFilter(matches, $scope.$parent.fieldFilter).length) {
|
||||
return;
|
||||
}
|
||||
// compute the rows
|
||||
const rowScope = $scope.$new();
|
||||
rowScope.filter = filter;
|
||||
rowScopes.push(rowScope);
|
||||
$scope.rows.push([
|
||||
{
|
||||
markup: filterHtml,
|
||||
scope: rowScope
|
||||
},
|
||||
size(matches) ? escape(matches.join(', ')) : '<em>The source filter doesn\'t match any known fields.</em>',
|
||||
{
|
||||
markup: controlsHtml,
|
||||
scope: rowScope
|
||||
}
|
||||
]);
|
||||
});
|
||||
// Update the tab count
|
||||
find($scope.$parent.editSections, { index: 'sourceFilters' }).count = $scope.rows.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Delete filter',
|
||||
onConfirm: doDelete
|
||||
};
|
||||
confirmModal(`Are you sure want to delete this filter?`, confirmModalOptions);
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.$scope.indexPattern.sourceFilters || [];
|
||||
}
|
||||
|
||||
delete(filter) {
|
||||
const doDelete = () => {
|
||||
if (this.editing === filter) {
|
||||
this.editing = null;
|
||||
}
|
||||
|
||||
this.$scope.indexPattern.sourceFilters = without(this.all(), filter);
|
||||
create() {
|
||||
const value = this.newValue;
|
||||
this.newValue = null;
|
||||
this.$scope.indexPattern.sourceFilters = [...this.all(), { value }];
|
||||
return this.save();
|
||||
};
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: 'Delete filter',
|
||||
onConfirm: doDelete
|
||||
};
|
||||
confirmModal(`Are you sure want to delete this filter?`, confirmModalOptions);
|
||||
save() {
|
||||
this.saving = true;
|
||||
this.$scope.indexPattern.save()
|
||||
.then(() => this.editing = null)
|
||||
.catch(notify.error)
|
||||
.finally(() => this.saving = false);
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
const value = this.newValue;
|
||||
this.newValue = null;
|
||||
this.$scope.indexPattern.sourceFilters = [...this.all(), { value }];
|
||||
return this.save();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.saving = true;
|
||||
this.$scope.indexPattern.save()
|
||||
.then(() => this.editing = null)
|
||||
.catch(notify.error)
|
||||
.finally(() => this.saving = false);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -21,44 +21,44 @@ const indexPatternsResolutions = {
|
|||
|
||||
// add a dependency to all of the subsection routes
|
||||
uiRoutes
|
||||
.defaults(/management\/kibana\/indices/, {
|
||||
resolve: indexPatternsResolutions
|
||||
});
|
||||
.defaults(/management\/kibana\/indices/, {
|
||||
resolve: indexPatternsResolutions
|
||||
});
|
||||
|
||||
uiRoutes
|
||||
.defaults(/management\/kibana\/index/, {
|
||||
resolve: indexPatternsResolutions
|
||||
});
|
||||
.defaults(/management\/kibana\/index/, {
|
||||
resolve: indexPatternsResolutions
|
||||
});
|
||||
|
||||
// wrapper directive, which sets some global stuff up like the left nav
|
||||
uiModules.get('apps/management')
|
||||
.directive('kbnManagementIndices', function ($route, config, kbnUrl) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
template: indexTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.editingId = $route.current.params.indexPatternId;
|
||||
config.bindToScope($scope, 'defaultIndex');
|
||||
.directive('kbnManagementIndices', function ($route, config, kbnUrl) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
template: indexTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.editingId = $route.current.params.indexPatternId;
|
||||
config.bindToScope($scope, 'defaultIndex');
|
||||
|
||||
$scope.$watch('defaultIndex', function () {
|
||||
$scope.indexPatternList = $route.current.locals.indexPatterns.map(pattern => {
|
||||
const id = pattern.id;
|
||||
$scope.$watch('defaultIndex', function () {
|
||||
$scope.indexPatternList = $route.current.locals.indexPatterns.map(pattern => {
|
||||
const id = pattern.id;
|
||||
|
||||
return {
|
||||
id: id,
|
||||
title: pattern.get('title'),
|
||||
url: kbnUrl.eval('#/management/kibana/indices/{{id}}', { id: id }),
|
||||
class: 'sidebar-item-title ' + ($scope.editingId === id ? 'active' : ''),
|
||||
default: $scope.defaultIndex === id
|
||||
};
|
||||
return {
|
||||
id: id,
|
||||
title: pattern.get('title'),
|
||||
url: kbnUrl.eval('#/management/kibana/indices/{{id}}', { id: id }),
|
||||
class: 'sidebar-item-title ' + ($scope.editingId === id ? 'active' : ''),
|
||||
default: $scope.defaultIndex === id
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('application.load');
|
||||
}
|
||||
};
|
||||
});
|
||||
$scope.$emit('application.load');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
management.getSection('kibana').register('indices', {
|
||||
display: 'Index Patterns',
|
||||
|
|
|
@ -23,300 +23,300 @@ const indexPatternsResolutions = {
|
|||
};
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/objects', {
|
||||
template: objectIndexHTML,
|
||||
resolve: indexPatternsResolutions
|
||||
});
|
||||
.when('/management/kibana/objects', {
|
||||
template: objectIndexHTML,
|
||||
resolve: indexPatternsResolutions
|
||||
});
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/objects/:service', {
|
||||
redirectTo: '/management/kibana/objects'
|
||||
});
|
||||
.when('/management/kibana/objects/:service', {
|
||||
redirectTo: '/management/kibana/objects'
|
||||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('kbnManagementObjects', function ($route, kbnIndex, Notifier, Private, kbnUrl, Promise, confirmModal) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
.directive('kbnManagementObjects', function ($route, kbnIndex, Notifier, Private, kbnUrl, Promise, confirmModal) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'managementObjectsController',
|
||||
controller: function ($scope, $injector, $q, AppState) {
|
||||
const notify = new Notifier({ location: 'Saved Objects' });
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'managementObjectsController',
|
||||
controller: function ($scope, $injector, $q, AppState) {
|
||||
const notify = new Notifier({ location: 'Saved Objects' });
|
||||
|
||||
// TODO: Migrate all scope variables to the controller.
|
||||
const $state = $scope.state = new AppState();
|
||||
$scope.currentTab = null;
|
||||
$scope.selectedItems = [];
|
||||
// TODO: Migrate all scope variables to the controller.
|
||||
const $state = $scope.state = new AppState();
|
||||
$scope.currentTab = null;
|
||||
$scope.selectedItems = [];
|
||||
|
||||
this.areAllRowsChecked = function areAllRowsChecked() {
|
||||
if ($scope.currentTab.data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return $scope.selectedItems.length === $scope.currentTab.data.length;
|
||||
};
|
||||
|
||||
const getData = function (filter) {
|
||||
const services = savedObjectManagementRegistry.all().map(function (obj) {
|
||||
const service = $injector.get(obj.service);
|
||||
return service.findAll(filter).then(function (data) {
|
||||
return {
|
||||
service: service,
|
||||
serviceName: obj.service,
|
||||
title: obj.title,
|
||||
type: service.type,
|
||||
data: data.hits,
|
||||
total: data.total
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
$q.all(services).then(function (data) {
|
||||
$scope.services = sortBy(data, 'title');
|
||||
if ($state.tab) $scope.currentTab = find($scope.services, { title: $state.tab });
|
||||
|
||||
$scope.$watch('state.tab', function (tab) {
|
||||
if (!tab) $scope.changeTab($scope.services[0]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const refreshData = () => {
|
||||
return getData(this.advancedFilter);
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.toggleAll = function () {
|
||||
if ($scope.selectedItems.length === $scope.currentTab.data.length) {
|
||||
$scope.selectedItems.length = 0;
|
||||
} else {
|
||||
$scope.selectedItems = [].concat($scope.currentTab.data);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.toggleItem = function (item) {
|
||||
const i = $scope.selectedItems.indexOf(item);
|
||||
if (i >= 0) {
|
||||
$scope.selectedItems.splice(i, 1);
|
||||
} else {
|
||||
$scope.selectedItems.push(item);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.open = function (item) {
|
||||
kbnUrl.change(item.url.substr(1));
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.edit = function (service, item) {
|
||||
const params = {
|
||||
service: service.serviceName,
|
||||
id: item.id
|
||||
this.areAllRowsChecked = function areAllRowsChecked() {
|
||||
if ($scope.currentTab.data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return $scope.selectedItems.length === $scope.currentTab.data.length;
|
||||
};
|
||||
|
||||
kbnUrl.change('/management/kibana/objects/{{ service }}/{{ id }}', params);
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.bulkDelete = function () {
|
||||
function doBulkDelete() {
|
||||
$scope.currentTab.service.delete(pluck($scope.selectedItems, 'id'))
|
||||
.then(refreshData)
|
||||
.then(function () {
|
||||
$scope.selectedItems.length = 0;
|
||||
})
|
||||
.catch(error => notify.error(error));
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: `Delete ${$scope.currentTab.title}`,
|
||||
onConfirm: doBulkDelete
|
||||
};
|
||||
confirmModal(
|
||||
`Are you sure you want to delete the selected ${$scope.currentTab.title}? This action is irreversible!`,
|
||||
confirmModalOptions
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.bulkExport = function () {
|
||||
const objs = $scope.selectedItems.map(item => {
|
||||
return { type: $scope.currentTab.type, id: item.id };
|
||||
});
|
||||
|
||||
retrieveAndExportDocs(objs);
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.exportAll = () => Promise
|
||||
.map($scope.services, service => service.service
|
||||
.scanAll('')
|
||||
.then(result => result.hits)
|
||||
)
|
||||
.then(results => saveToFile(flattenDeep(results)))
|
||||
.catch(error => notify.error(error));
|
||||
|
||||
function retrieveAndExportDocs(objs) {
|
||||
if (!objs.length) return notify.error('No saved objects to export.');
|
||||
|
||||
savedObjectsClient.bulkGet(objs)
|
||||
.then(function (response) {
|
||||
saveToFile(response.savedObjects.map(obj => {
|
||||
const getData = function (filter) {
|
||||
const services = savedObjectManagementRegistry.all().map(function (obj) {
|
||||
const service = $injector.get(obj.service);
|
||||
return service.findAll(filter).then(function (data) {
|
||||
return {
|
||||
_id: obj.id,
|
||||
_type: obj.type,
|
||||
_source: obj.attributes
|
||||
service: service,
|
||||
serviceName: obj.service,
|
||||
title: obj.title,
|
||||
type: service.type,
|
||||
data: data.hits,
|
||||
total: data.total
|
||||
};
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveToFile(results) {
|
||||
const blob = new Blob([angular.toJson(results, true)], { type: 'application/json' });
|
||||
saveAs(blob, 'export.json');
|
||||
}
|
||||
$q.all(services).then(function (data) {
|
||||
$scope.services = sortBy(data, 'title');
|
||||
if ($state.tab) $scope.currentTab = find($scope.services, { title: $state.tab });
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.importAll = function (fileContents) {
|
||||
let docs;
|
||||
try {
|
||||
docs = JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
notify.error('The file could not be processed.');
|
||||
return;
|
||||
}
|
||||
$scope.$watch('state.tab', function (tab) {
|
||||
if (!tab) $scope.changeTab($scope.services[0]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// make sure we have an array, show an error otherwise
|
||||
if (!Array.isArray(docs)) {
|
||||
notify.error('Saved objects file format is invalid and cannot be imported.');
|
||||
return;
|
||||
}
|
||||
const refreshData = () => {
|
||||
return getData(this.advancedFilter);
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.toggleAll = function () {
|
||||
if ($scope.selectedItems.length === $scope.currentTab.data.length) {
|
||||
$scope.selectedItems.length = 0;
|
||||
} else {
|
||||
$scope.selectedItems = [].concat($scope.currentTab.data);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.toggleItem = function (item) {
|
||||
const i = $scope.selectedItems.indexOf(item);
|
||||
if (i >= 0) {
|
||||
$scope.selectedItems.splice(i, 1);
|
||||
} else {
|
||||
$scope.selectedItems.push(item);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.open = function (item) {
|
||||
kbnUrl.change(item.url.substr(1));
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.edit = function (service, item) {
|
||||
const params = {
|
||||
service: service.serviceName,
|
||||
id: item.id
|
||||
};
|
||||
|
||||
kbnUrl.change('/management/kibana/objects/{{ service }}/{{ id }}', params);
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.bulkDelete = function () {
|
||||
function doBulkDelete() {
|
||||
$scope.currentTab.service.delete(pluck($scope.selectedItems, 'id'))
|
||||
.then(refreshData)
|
||||
.then(function () {
|
||||
$scope.selectedItems.length = 0;
|
||||
})
|
||||
.catch(error => notify.error(error));
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: `Delete ${$scope.currentTab.title}`,
|
||||
onConfirm: doBulkDelete
|
||||
};
|
||||
confirmModal(
|
||||
`If any of the objects already exist, do you want to automatically overwrite them?`, {
|
||||
confirmButtonText: `Yes, overwrite all`,
|
||||
cancelButtonText: `No, prompt me for each one`,
|
||||
onConfirm: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
}
|
||||
`Are you sure you want to delete the selected ${$scope.currentTab.title}? This action is irreversible!`,
|
||||
confirmModalOptions
|
||||
);
|
||||
})
|
||||
.then((overwriteAll) => {
|
||||
// Keep a record of the index patterns assigned to our imported saved objects that do not
|
||||
// exist. We will provide a way for the user to manually select a new index pattern for those
|
||||
// saved objects.
|
||||
const conflictedIndexPatterns = [];
|
||||
// We want to do the same for saved searches, but we want to keep them separate because they need
|
||||
// to be applied _first_ because other saved objects can be depedent on those saved searches existing
|
||||
const conflictedSearchDocs = [];
|
||||
};
|
||||
|
||||
function importDocument(swallowErrors, doc) {
|
||||
const { service } = find($scope.services, { type: doc._type }) || {};
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.bulkExport = function () {
|
||||
const objs = $scope.selectedItems.map(item => {
|
||||
return { type: $scope.currentTab.type, id: item.id };
|
||||
});
|
||||
|
||||
if (!service) {
|
||||
const msg = `Skipped import of "${doc._source.title}" (${doc._id})`;
|
||||
const reason = `Invalid type: "${doc._type}"`;
|
||||
retrieveAndExportDocs(objs);
|
||||
};
|
||||
|
||||
notify.warning(`${msg}, ${reason}`, {
|
||||
lifetime: 0,
|
||||
});
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.exportAll = () => Promise
|
||||
.map($scope.services, service => service.service
|
||||
.scanAll('')
|
||||
.then(result => result.hits)
|
||||
)
|
||||
.then(results => saveToFile(flattenDeep(results)))
|
||||
.catch(error => notify.error(error));
|
||||
|
||||
return;
|
||||
}
|
||||
function retrieveAndExportDocs(objs) {
|
||||
if (!objs.length) return notify.error('No saved objects to export.');
|
||||
|
||||
return service.get()
|
||||
.then(function (obj) {
|
||||
obj.id = doc._id;
|
||||
return obj.applyESResp(doc)
|
||||
.then(() => {
|
||||
return obj.save({ confirmOverwrite: !overwriteAll });
|
||||
})
|
||||
.catch((err) => {
|
||||
if (swallowErrors && err instanceof SavedObjectNotFound) {
|
||||
switch (err.savedObjectType) {
|
||||
case 'search':
|
||||
conflictedSearchDocs.push(doc);
|
||||
return;
|
||||
case 'index-pattern':
|
||||
conflictedIndexPatterns.push({ obj, doc });
|
||||
return;
|
||||
}
|
||||
}
|
||||
// swallow errors here so that the remaining promise chain executes
|
||||
err.message = `Importing ${obj.title} (${obj.id}) failed: ${err.message}`;
|
||||
notify.error(err);
|
||||
savedObjectsClient.bulkGet(objs)
|
||||
.then(function (response) {
|
||||
saveToFile(response.savedObjects.map(obj => {
|
||||
return {
|
||||
_id: obj.id,
|
||||
_type: obj.type,
|
||||
_source: obj.attributes
|
||||
};
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function saveToFile(results) {
|
||||
const blob = new Blob([angular.toJson(results, true)], { type: 'application/json' });
|
||||
saveAs(blob, 'export.json');
|
||||
}
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.importAll = function (fileContents) {
|
||||
let docs;
|
||||
try {
|
||||
docs = JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
notify.error('The file could not be processed.');
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we have an array, show an error otherwise
|
||||
if (!Array.isArray(docs)) {
|
||||
notify.error('Saved objects file format is invalid and cannot be imported.');
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
confirmModal(
|
||||
`If any of the objects already exist, do you want to automatically overwrite them?`, {
|
||||
confirmButtonText: `Yes, overwrite all`,
|
||||
cancelButtonText: `No, prompt me for each one`,
|
||||
onConfirm: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
}
|
||||
);
|
||||
})
|
||||
.then((overwriteAll) => {
|
||||
// Keep a record of the index patterns assigned to our imported saved objects that do not
|
||||
// exist. We will provide a way for the user to manually select a new index pattern for those
|
||||
// saved objects.
|
||||
const conflictedIndexPatterns = [];
|
||||
// We want to do the same for saved searches, but we want to keep them separate because they need
|
||||
// to be applied _first_ because other saved objects can be depedent on those saved searches existing
|
||||
const conflictedSearchDocs = [];
|
||||
|
||||
function importDocument(swallowErrors, doc) {
|
||||
const { service } = find($scope.services, { type: doc._type }) || {};
|
||||
|
||||
if (!service) {
|
||||
const msg = `Skipped import of "${doc._source.title}" (${doc._id})`;
|
||||
const reason = `Invalid type: "${doc._type}"`;
|
||||
|
||||
notify.warning(`${msg}, ${reason}`, {
|
||||
lifetime: 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function groupByType(docs) {
|
||||
const defaultDocTypes = {
|
||||
searches: [],
|
||||
other: [],
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
return docs.reduce((types, doc) => {
|
||||
switch (doc._type) {
|
||||
case 'search':
|
||||
types.searches.push(doc);
|
||||
break;
|
||||
default:
|
||||
types.other.push(doc);
|
||||
return service.get()
|
||||
.then(function (obj) {
|
||||
obj.id = doc._id;
|
||||
return obj.applyESResp(doc)
|
||||
.then(() => {
|
||||
return obj.save({ confirmOverwrite: !overwriteAll });
|
||||
})
|
||||
.catch((err) => {
|
||||
if (swallowErrors && err instanceof SavedObjectNotFound) {
|
||||
switch (err.savedObjectType) {
|
||||
case 'search':
|
||||
conflictedSearchDocs.push(doc);
|
||||
return;
|
||||
case 'index-pattern':
|
||||
conflictedIndexPatterns.push({ obj, doc });
|
||||
return;
|
||||
}
|
||||
}
|
||||
// swallow errors here so that the remaining promise chain executes
|
||||
err.message = `Importing ${obj.title} (${obj.id}) failed: ${err.message}`;
|
||||
notify.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
return types;
|
||||
}, defaultDocTypes);
|
||||
}
|
||||
|
||||
function resolveConflicts(objs, { obj }) {
|
||||
const oldIndexId = obj.searchSource.getOwn('index');
|
||||
const newIndexId = objs.find(({ oldId }) => oldId === oldIndexId).newId;
|
||||
// If the user did not select a new index pattern in the modal, the id
|
||||
// will be same as before, so don't try to update it
|
||||
if (newIndexId === oldIndexId) {
|
||||
return;
|
||||
}
|
||||
return obj.hydrateIndexPattern(newIndexId)
|
||||
.then(() => obj.save({ confirmOverwrite: !overwriteAll }));
|
||||
}
|
||||
function groupByType(docs) {
|
||||
const defaultDocTypes = {
|
||||
searches: [],
|
||||
other: [],
|
||||
};
|
||||
|
||||
const docTypes = groupByType(docs);
|
||||
|
||||
return Promise.map(docTypes.searches, importDocument.bind(null, true))
|
||||
.then(() => Promise.map(docTypes.other, importDocument.bind(null, true)))
|
||||
.then(() => {
|
||||
if (conflictedIndexPatterns.length) {
|
||||
return new Promise((resolve, reject) => {
|
||||
showChangeIndexModal(
|
||||
(objs) => {
|
||||
Promise.map(conflictedIndexPatterns, resolveConflicts.bind(null, objs))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
},
|
||||
conflictedIndexPatterns,
|
||||
$route.current.locals.indexPatterns,
|
||||
);
|
||||
});
|
||||
return docs.reduce((types, doc) => {
|
||||
switch (doc._type) {
|
||||
case 'search':
|
||||
types.searches.push(doc);
|
||||
break;
|
||||
default:
|
||||
types.other.push(doc);
|
||||
}
|
||||
return types;
|
||||
}, defaultDocTypes);
|
||||
}
|
||||
})
|
||||
.then(() => Promise.map(conflictedSearchDocs, importDocument.bind(null, false)))
|
||||
.then(refreshData)
|
||||
.catch(notify.error);
|
||||
|
||||
function resolveConflicts(objs, { obj }) {
|
||||
const oldIndexId = obj.searchSource.getOwn('index');
|
||||
const newIndexId = objs.find(({ oldId }) => oldId === oldIndexId).newId;
|
||||
// If the user did not select a new index pattern in the modal, the id
|
||||
// will be same as before, so don't try to update it
|
||||
if (newIndexId === oldIndexId) {
|
||||
return;
|
||||
}
|
||||
return obj.hydrateIndexPattern(newIndexId)
|
||||
.then(() => obj.save({ confirmOverwrite: !overwriteAll }));
|
||||
}
|
||||
|
||||
const docTypes = groupByType(docs);
|
||||
|
||||
return Promise.map(docTypes.searches, importDocument.bind(null, true))
|
||||
.then(() => Promise.map(docTypes.other, importDocument.bind(null, true)))
|
||||
.then(() => {
|
||||
if (conflictedIndexPatterns.length) {
|
||||
return new Promise((resolve, reject) => {
|
||||
showChangeIndexModal(
|
||||
(objs) => {
|
||||
Promise.map(conflictedIndexPatterns, resolveConflicts.bind(null, objs))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
},
|
||||
conflictedIndexPatterns,
|
||||
$route.current.locals.indexPatterns,
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => Promise.map(conflictedSearchDocs, importDocument.bind(null, false)))
|
||||
.then(refreshData)
|
||||
.catch(notify.error);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.changeTab = function (tab) {
|
||||
$scope.currentTab = tab;
|
||||
$scope.selectedItems.length = 0;
|
||||
$state.tab = tab.title;
|
||||
$state.save();
|
||||
};
|
||||
|
||||
$scope.$watch('managementObjectsController.advancedFilter', function (filter) {
|
||||
getData(filter);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Migrate all scope methods to the controller.
|
||||
$scope.changeTab = function (tab) {
|
||||
$scope.currentTab = tab;
|
||||
$scope.selectedItems.length = 0;
|
||||
$state.tab = tab.title;
|
||||
$state.save();
|
||||
};
|
||||
|
||||
$scope.$watch('managementObjectsController.advancedFilter', function (filter) {
|
||||
getData(filter);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -10,21 +10,21 @@ import { castEsToKbnFieldTypeName } from '../../../../../../utils';
|
|||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/objects/:service/:id', {
|
||||
template: objectViewHTML
|
||||
});
|
||||
.when('/management/kibana/objects/:service/:id', {
|
||||
template: objectViewHTML
|
||||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('kbnManagementObjectsView', function (kbnIndex, Notifier, confirmModal) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: function ($scope, $injector, $routeParams, $location, $window, $rootScope, Private) {
|
||||
const notify = new Notifier({ location: 'SavedObject view' });
|
||||
const serviceObj = savedObjectManagementRegistry.get($routeParams.service);
|
||||
const service = $injector.get(serviceObj.service);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
.directive('kbnManagementObjectsView', function (kbnIndex, Notifier, confirmModal) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: function ($scope, $injector, $routeParams, $location, $window, $rootScope, Private) {
|
||||
const notify = new Notifier({ location: 'SavedObject view' });
|
||||
const serviceObj = savedObjectManagementRegistry.get($routeParams.service);
|
||||
const service = $injector.get(serviceObj.service);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Creates a field definition and pushes it to the memo stack. This function
|
||||
* is designed to be used in conjunction with _.reduce(). If the
|
||||
* values is plain object it will recurse through all the keys till it hits
|
||||
|
@ -37,188 +37,188 @@ uiModules.get('apps/management')
|
|||
* @param {array} parents The parent keys to the field
|
||||
* @returns {array}
|
||||
*/
|
||||
const createField = function (memo, val, key, collection, parents) {
|
||||
if (Array.isArray(parents)) {
|
||||
parents.push(key);
|
||||
} else {
|
||||
parents = [key];
|
||||
}
|
||||
|
||||
const field = { type: 'text', name: parents.join('.'), value: val };
|
||||
|
||||
if (_.isString(field.value)) {
|
||||
try {
|
||||
field.value = angular.toJson(JSON.parse(field.value), true);
|
||||
field.type = 'json';
|
||||
} catch (err) {
|
||||
field.value = field.value;
|
||||
}
|
||||
} else if (_.isNumeric(field.value)) {
|
||||
field.type = 'number';
|
||||
} else if (Array.isArray(field.value)) {
|
||||
field.type = 'array';
|
||||
field.value = angular.toJson(field.value, true);
|
||||
} else if (_.isBoolean(field.value)) {
|
||||
field.type = 'boolean';
|
||||
field.value = field.value;
|
||||
} else if (_.isPlainObject(field.value)) {
|
||||
// do something recursive
|
||||
return _.reduce(field.value, _.partialRight(createField, parents), memo);
|
||||
}
|
||||
|
||||
memo.push(field);
|
||||
|
||||
// once the field is added to the object you need to pop the parents
|
||||
// to remove it since we've hit the end of the branch.
|
||||
parents.pop();
|
||||
return memo;
|
||||
};
|
||||
|
||||
const readObjectClass = function (fields, Class) {
|
||||
const fieldMap = _.indexBy(fields, 'name');
|
||||
|
||||
_.forOwn(Class.mapping, function (esType, name) {
|
||||
if (fieldMap[name]) return;
|
||||
|
||||
fields.push({
|
||||
name: name,
|
||||
type: (function () {
|
||||
switch (castEsToKbnFieldTypeName(esType)) {
|
||||
case 'string': return 'text';
|
||||
case 'number': return 'number';
|
||||
case 'boolean': return 'boolean';
|
||||
default: return 'json';
|
||||
}
|
||||
}())
|
||||
});
|
||||
});
|
||||
|
||||
if (Class.searchSource && !fieldMap['kibanaSavedObjectMeta.searchSourceJSON']) {
|
||||
fields.push({
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON',
|
||||
type: 'json',
|
||||
value: '{}'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.notFound = $routeParams.notFound;
|
||||
|
||||
$scope.title = service.type;
|
||||
|
||||
savedObjectsClient.get(service.type, $routeParams.id)
|
||||
.then(function (obj) {
|
||||
$scope.obj = obj;
|
||||
$scope.link = service.urlFor(obj.id);
|
||||
|
||||
const fields = _.reduce(obj.attributes, createField, []);
|
||||
if (service.Class) readObjectClass(fields, service.Class);
|
||||
|
||||
// sorts twice since we want numerical sort to prioritize over name,
|
||||
// and sortBy will do string comparison if trying to match against strings
|
||||
const nameSortedFields = _.sortBy(fields, 'name');
|
||||
$scope.fields = _.sortBy(nameSortedFields, (field) => {
|
||||
const orderIndex = service.Class.fieldOrder ? service.Class.fieldOrder.indexOf(field.name) : -1;
|
||||
return (orderIndex > -1) ? orderIndex : Infinity;
|
||||
});
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
|
||||
// This handles the validation of the Ace Editor. Since we don't have any
|
||||
// other hooks into the editors to tell us if the content is valid or not
|
||||
// we need to use the annotations to see if they have any errors. If they
|
||||
// do then we push the field.name to aceInvalidEditor variable.
|
||||
// Otherwise we remove it.
|
||||
const loadedEditors = [];
|
||||
$scope.aceInvalidEditors = [];
|
||||
|
||||
$scope.aceLoaded = function (editor) {
|
||||
if (_.contains(loadedEditors, editor)) return;
|
||||
loadedEditors.push(editor);
|
||||
|
||||
editor.$blockScrolling = Infinity;
|
||||
|
||||
const session = editor.getSession();
|
||||
const fieldName = editor.container.id;
|
||||
|
||||
session.setTabSize(2);
|
||||
session.setUseSoftTabs(true);
|
||||
session.on('changeAnnotation', function () {
|
||||
const annotations = session.getAnnotations();
|
||||
if (_.some(annotations, { type: 'error' })) {
|
||||
if (!_.contains($scope.aceInvalidEditors, fieldName)) {
|
||||
$scope.aceInvalidEditors.push(fieldName);
|
||||
}
|
||||
const createField = function (memo, val, key, collection, parents) {
|
||||
if (Array.isArray(parents)) {
|
||||
parents.push(key);
|
||||
} else {
|
||||
$scope.aceInvalidEditors = _.without($scope.aceInvalidEditors, fieldName);
|
||||
parents = [key];
|
||||
}
|
||||
|
||||
if (!$rootScope.$$phase) $scope.$apply();
|
||||
});
|
||||
};
|
||||
const field = { type: 'text', name: parents.join('.'), value: val };
|
||||
|
||||
$scope.cancel = function () {
|
||||
$window.history.back();
|
||||
return false;
|
||||
};
|
||||
if (_.isString(field.value)) {
|
||||
try {
|
||||
field.value = angular.toJson(JSON.parse(field.value), true);
|
||||
field.type = 'json';
|
||||
} catch (err) {
|
||||
field.value = field.value;
|
||||
}
|
||||
} else if (_.isNumeric(field.value)) {
|
||||
field.type = 'number';
|
||||
} else if (Array.isArray(field.value)) {
|
||||
field.type = 'array';
|
||||
field.value = angular.toJson(field.value, true);
|
||||
} else if (_.isBoolean(field.value)) {
|
||||
field.type = 'boolean';
|
||||
field.value = field.value;
|
||||
} else if (_.isPlainObject(field.value)) {
|
||||
// do something recursive
|
||||
return _.reduce(field.value, _.partialRight(createField, parents), memo);
|
||||
}
|
||||
|
||||
/**
|
||||
memo.push(field);
|
||||
|
||||
// once the field is added to the object you need to pop the parents
|
||||
// to remove it since we've hit the end of the branch.
|
||||
parents.pop();
|
||||
return memo;
|
||||
};
|
||||
|
||||
const readObjectClass = function (fields, Class) {
|
||||
const fieldMap = _.indexBy(fields, 'name');
|
||||
|
||||
_.forOwn(Class.mapping, function (esType, name) {
|
||||
if (fieldMap[name]) return;
|
||||
|
||||
fields.push({
|
||||
name: name,
|
||||
type: (function () {
|
||||
switch (castEsToKbnFieldTypeName(esType)) {
|
||||
case 'string': return 'text';
|
||||
case 'number': return 'number';
|
||||
case 'boolean': return 'boolean';
|
||||
default: return 'json';
|
||||
}
|
||||
}())
|
||||
});
|
||||
});
|
||||
|
||||
if (Class.searchSource && !fieldMap['kibanaSavedObjectMeta.searchSourceJSON']) {
|
||||
fields.push({
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON',
|
||||
type: 'json',
|
||||
value: '{}'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.notFound = $routeParams.notFound;
|
||||
|
||||
$scope.title = service.type;
|
||||
|
||||
savedObjectsClient.get(service.type, $routeParams.id)
|
||||
.then(function (obj) {
|
||||
$scope.obj = obj;
|
||||
$scope.link = service.urlFor(obj.id);
|
||||
|
||||
const fields = _.reduce(obj.attributes, createField, []);
|
||||
if (service.Class) readObjectClass(fields, service.Class);
|
||||
|
||||
// sorts twice since we want numerical sort to prioritize over name,
|
||||
// and sortBy will do string comparison if trying to match against strings
|
||||
const nameSortedFields = _.sortBy(fields, 'name');
|
||||
$scope.fields = _.sortBy(nameSortedFields, (field) => {
|
||||
const orderIndex = service.Class.fieldOrder ? service.Class.fieldOrder.indexOf(field.name) : -1;
|
||||
return (orderIndex > -1) ? orderIndex : Infinity;
|
||||
});
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
|
||||
// This handles the validation of the Ace Editor. Since we don't have any
|
||||
// other hooks into the editors to tell us if the content is valid or not
|
||||
// we need to use the annotations to see if they have any errors. If they
|
||||
// do then we push the field.name to aceInvalidEditor variable.
|
||||
// Otherwise we remove it.
|
||||
const loadedEditors = [];
|
||||
$scope.aceInvalidEditors = [];
|
||||
|
||||
$scope.aceLoaded = function (editor) {
|
||||
if (_.contains(loadedEditors, editor)) return;
|
||||
loadedEditors.push(editor);
|
||||
|
||||
editor.$blockScrolling = Infinity;
|
||||
|
||||
const session = editor.getSession();
|
||||
const fieldName = editor.container.id;
|
||||
|
||||
session.setTabSize(2);
|
||||
session.setUseSoftTabs(true);
|
||||
session.on('changeAnnotation', function () {
|
||||
const annotations = session.getAnnotations();
|
||||
if (_.some(annotations, { type: 'error' })) {
|
||||
if (!_.contains($scope.aceInvalidEditors, fieldName)) {
|
||||
$scope.aceInvalidEditors.push(fieldName);
|
||||
}
|
||||
} else {
|
||||
$scope.aceInvalidEditors = _.without($scope.aceInvalidEditors, fieldName);
|
||||
}
|
||||
|
||||
if (!$rootScope.$$phase) $scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$window.history.back();
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes an object and sets the notification
|
||||
* @param {type} name description
|
||||
* @returns {type} description
|
||||
*/
|
||||
$scope.delete = function () {
|
||||
function doDelete() {
|
||||
savedObjectsClient.delete(service.type, $routeParams.id)
|
||||
$scope.delete = function () {
|
||||
function doDelete() {
|
||||
savedObjectsClient.delete(service.type, $routeParams.id)
|
||||
.then(function () {
|
||||
return redirectHandler('deleted');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
}
|
||||
const confirmModalOptions = {
|
||||
onConfirm: doDelete,
|
||||
confirmButtonText: 'Delete object'
|
||||
};
|
||||
confirmModal(
|
||||
'Are you sure want to delete this object? This action is irreversible!',
|
||||
confirmModalOptions
|
||||
);
|
||||
};
|
||||
|
||||
$scope.submit = function () {
|
||||
const source = _.cloneDeep($scope.obj.attributes);
|
||||
|
||||
_.each($scope.fields, function (field) {
|
||||
let value = field.value;
|
||||
|
||||
if (field.type === 'number') {
|
||||
value = Number(field.value);
|
||||
}
|
||||
|
||||
if (field.type === 'array') {
|
||||
value = JSON.parse(field.value);
|
||||
}
|
||||
|
||||
_.set(source, field.name, value);
|
||||
});
|
||||
|
||||
savedObjectsClient.update(service.type, $routeParams.id, source)
|
||||
.then(function () {
|
||||
return redirectHandler('deleted');
|
||||
return redirectHandler('updated');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
}
|
||||
const confirmModalOptions = {
|
||||
onConfirm: doDelete,
|
||||
confirmButtonText: 'Delete object'
|
||||
};
|
||||
confirmModal(
|
||||
'Are you sure want to delete this object? This action is irreversible!',
|
||||
confirmModalOptions
|
||||
);
|
||||
};
|
||||
|
||||
$scope.submit = function () {
|
||||
const source = _.cloneDeep($scope.obj.attributes);
|
||||
function redirectHandler(action) {
|
||||
const msg = 'You successfully ' + action + ' the "' + $scope.obj.attributes.title + '" ' + $scope.title.toLowerCase() + ' object';
|
||||
|
||||
_.each($scope.fields, function (field) {
|
||||
let value = field.value;
|
||||
|
||||
if (field.type === 'number') {
|
||||
value = Number(field.value);
|
||||
}
|
||||
|
||||
if (field.type === 'array') {
|
||||
value = JSON.parse(field.value);
|
||||
}
|
||||
|
||||
_.set(source, field.name, value);
|
||||
});
|
||||
|
||||
savedObjectsClient.update(service.type, $routeParams.id, source)
|
||||
.then(function () {
|
||||
return redirectHandler('updated');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
};
|
||||
|
||||
function redirectHandler(action) {
|
||||
const msg = 'You successfully ' + action + ' the "' + $scope.obj.attributes.title + '" ' + $scope.title.toLowerCase() + ' object';
|
||||
|
||||
$location.path('/management/kibana/objects').search({
|
||||
_a: rison.encode({
|
||||
tab: serviceObj.title
|
||||
})
|
||||
});
|
||||
notify.info(msg);
|
||||
$location.path('/management/kibana/objects').search({
|
||||
_a: rison.encode({
|
||||
tab: serviceObj.title
|
||||
})
|
||||
});
|
||||
notify.info(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -158,7 +158,7 @@ export class ChangeIndexModal extends React.Component {
|
|||
Please select the index patterns you'd like re-associated them with.
|
||||
</p>
|
||||
{ totalIndexPatterns > perPage
|
||||
?
|
||||
? (
|
||||
<KuiControlledTable className="kuiVerticalRhythm">
|
||||
<KuiToolBar>
|
||||
<KuiToolBarSection>
|
||||
|
@ -175,8 +175,9 @@ export class ChangeIndexModal extends React.Component {
|
|||
</KuiToolBar>
|
||||
<TableComponent/>
|
||||
</KuiControlledTable>
|
||||
:
|
||||
) : (
|
||||
<TableComponent/>
|
||||
)
|
||||
}
|
||||
</KuiModalBody>
|
||||
|
||||
|
|
|
@ -5,72 +5,72 @@ import { keyCodes } from 'ui_framework/services';
|
|||
import advancedRowTemplate from 'plugins/kibana/management/sections/settings/advanced_row.html';
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('advancedRow', function (config, Notifier) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
replace: true,
|
||||
template: advancedRowTemplate,
|
||||
scope: {
|
||||
conf: '=advancedRow',
|
||||
configs: '='
|
||||
},
|
||||
link: function ($scope) {
|
||||
const notify = new Notifier();
|
||||
.directive('advancedRow', function (config, Notifier) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
replace: true,
|
||||
template: advancedRowTemplate,
|
||||
scope: {
|
||||
conf: '=advancedRow',
|
||||
configs: '='
|
||||
},
|
||||
link: function ($scope) {
|
||||
const notify = new Notifier();
|
||||
|
||||
// To allow passing form validation state back
|
||||
$scope.forms = {};
|
||||
// To allow passing form validation state back
|
||||
$scope.forms = {};
|
||||
|
||||
// setup loading flag, run async op, then clear loading and editing flag (just in case)
|
||||
const loading = function (conf, fn) {
|
||||
conf.loading = true;
|
||||
fn()
|
||||
.then(function () {
|
||||
conf.loading = conf.editing = false;
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
};
|
||||
// setup loading flag, run async op, then clear loading and editing flag (just in case)
|
||||
const loading = function (conf, fn) {
|
||||
conf.loading = true;
|
||||
fn()
|
||||
.then(function () {
|
||||
conf.loading = conf.editing = false;
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
};
|
||||
|
||||
$scope.maybeCancel = function ($event, conf) {
|
||||
if ($event.keyCode === keyCodes.ESCAPE) {
|
||||
$scope.cancelEdit(conf);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.edit = function (conf) {
|
||||
conf.unsavedValue = conf.value == null ? conf.defVal : conf.value;
|
||||
$scope.configs.forEach(function (c) {
|
||||
c.editing = (c === conf);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.save = function (conf) {
|
||||
loading(conf, function () {
|
||||
if (conf.unsavedValue === conf.defVal) {
|
||||
return config.remove(conf.name);
|
||||
$scope.maybeCancel = function ($event, conf) {
|
||||
if ($event.keyCode === keyCodes.ESCAPE) {
|
||||
$scope.cancelEdit(conf);
|
||||
}
|
||||
};
|
||||
|
||||
return config.set(conf.name, conf.unsavedValue);
|
||||
});
|
||||
};
|
||||
$scope.edit = function (conf) {
|
||||
conf.unsavedValue = conf.value == null ? conf.defVal : conf.value;
|
||||
$scope.configs.forEach(function (c) {
|
||||
c.editing = (c === conf);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancelEdit = function (conf) {
|
||||
conf.editing = false;
|
||||
};
|
||||
$scope.save = function (conf) {
|
||||
loading(conf, function () {
|
||||
if (conf.unsavedValue === conf.defVal) {
|
||||
return config.remove(conf.name);
|
||||
}
|
||||
|
||||
$scope.clear = function (conf) {
|
||||
return loading(conf, function () {
|
||||
return config.remove(conf.name);
|
||||
});
|
||||
};
|
||||
return config.set(conf.name, conf.unsavedValue);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isDefaultValue = (conf) => {
|
||||
$scope.cancelEdit = function (conf) {
|
||||
conf.editing = false;
|
||||
};
|
||||
|
||||
$scope.clear = function (conf) {
|
||||
return loading(conf, function () {
|
||||
return config.remove(conf.name);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isDefaultValue = (conf) => {
|
||||
// conf.isCustom = custom setting, provided by user, so there is no notion of
|
||||
// having a default or non-default value for it
|
||||
return conf.isCustom
|
||||
return conf.isCustom
|
||||
|| conf.value === undefined
|
||||
|| conf.value === ''
|
||||
|| String(conf.value) === String(conf.defVal);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,37 +8,37 @@ import indexTemplate from 'plugins/kibana/management/sections/settings/index.htm
|
|||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/settings', {
|
||||
template: indexTemplate
|
||||
});
|
||||
.when('/management/kibana/settings', {
|
||||
template: indexTemplate
|
||||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('kbnManagementAdvanced', function (config) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
.directive('kbnManagementAdvanced', function (config) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
// react to changes of the config values
|
||||
config.watchAll(changed, $scope);
|
||||
config.watchAll(changed, $scope);
|
||||
|
||||
// initial config setup
|
||||
changed();
|
||||
// initial config setup
|
||||
changed();
|
||||
|
||||
function changed() {
|
||||
const all = config.getAll();
|
||||
const editable = _(all)
|
||||
.map((def, name) => toEditableConfig({
|
||||
def,
|
||||
name,
|
||||
value: def.userValue,
|
||||
isCustom: config.isCustom(name)
|
||||
}))
|
||||
.value();
|
||||
const writable = _.reject(editable, 'readonly');
|
||||
$scope.configs = writable;
|
||||
function changed() {
|
||||
const all = config.getAll();
|
||||
const editable = _(all)
|
||||
.map((def, name) => toEditableConfig({
|
||||
def,
|
||||
name,
|
||||
value: def.userValue,
|
||||
isCustom: config.isCustom(name)
|
||||
}))
|
||||
.value();
|
||||
const writable = _.reject(editable, 'readonly');
|
||||
$scope.configs = writable;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
management.getSection('kibana').register('settings', {
|
||||
display: 'Advanced Settings',
|
||||
|
|
|
@ -2,7 +2,7 @@ import { propFilter } from 'ui/filters/_prop_filter';
|
|||
import { uiModules } from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
.filter('aggFilter', function () {
|
||||
return propFilter('name');
|
||||
});
|
||||
.get('kibana')
|
||||
.filter('aggFilter', function () {
|
||||
return propFilter('name');
|
||||
});
|
||||
|
|
|
@ -23,52 +23,52 @@ import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
|
|||
import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
|
||||
|
||||
uiRoutes
|
||||
.when(VisualizeConstants.CREATE_PATH, {
|
||||
template: editorTemplate,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route, Private) {
|
||||
const visTypes = Private(VisTypesRegistryProvider);
|
||||
const visType = _.find(visTypes, { name: $route.current.params.type });
|
||||
const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection;
|
||||
const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId;
|
||||
if (shouldHaveIndex && !hasIndex) {
|
||||
throw new Error('You must provide either an indexPattern or a savedSearchId');
|
||||
}
|
||||
.when(VisualizeConstants.CREATE_PATH, {
|
||||
template: editorTemplate,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route, Private) {
|
||||
const visTypes = Private(VisTypesRegistryProvider);
|
||||
const visType = _.find(visTypes, { name: $route.current.params.type });
|
||||
const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection;
|
||||
const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId;
|
||||
if (shouldHaveIndex && !hasIndex) {
|
||||
throw new Error('You must provide either an indexPattern or a savedSearchId');
|
||||
}
|
||||
|
||||
return savedVisualizations.get($route.current.params)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'*': '/visualize'
|
||||
}));
|
||||
return savedVisualizations.get($route.current.params)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'*': '/visualize'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.when(`${VisualizeConstants.EDIT_PATH}/:id`, {
|
||||
template: editorTemplate,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route) {
|
||||
return savedVisualizations.get($route.current.params.id)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'visualization': '/visualize',
|
||||
'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id
|
||||
}));
|
||||
})
|
||||
.when(`${VisualizeConstants.EDIT_PATH}/:id`, {
|
||||
template: editorTemplate,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route) {
|
||||
return savedVisualizations.get($route.current.params.id)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'visualization': '/visualize',
|
||||
'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
uiModules
|
||||
.get('app/visualize', [
|
||||
'kibana/notify',
|
||||
'kibana/courier'
|
||||
])
|
||||
.directive('visualizeApp', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'visualizeApp',
|
||||
controller: VisEditor,
|
||||
};
|
||||
});
|
||||
.get('app/visualize', [
|
||||
'kibana/notify',
|
||||
'kibana/courier'
|
||||
])
|
||||
.directive('visualizeApp', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'visualizeApp',
|
||||
controller: VisEditor,
|
||||
};
|
||||
});
|
||||
|
||||
function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courier, Private, Promise, config, kbnBaseUrl) {
|
||||
const docTitle = Private(DocTitleProvider);
|
||||
|
@ -145,9 +145,9 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
Promise.try(function () {
|
||||
vis.setState(appState.vis);
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'index-pattern-field': '/visualize'
|
||||
}));
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'index-pattern-field': '/visualize'
|
||||
}));
|
||||
}
|
||||
|
||||
return appState;
|
||||
|
@ -240,36 +240,36 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges());
|
||||
|
||||
savedVis.save()
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
notify.info('Saved Visualization "' + savedVis.title + '"');
|
||||
if ($scope.isAddToDashMode()) {
|
||||
const savedVisualizationParsedUrl = new KibanaParsedUrl({
|
||||
basePath: chrome.getBasePath(),
|
||||
appId: kbnBaseUrl.slice('/app/'.length),
|
||||
appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }),
|
||||
});
|
||||
// Manually insert a new url so the back button will open the saved visualization.
|
||||
$window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath());
|
||||
// Since we aren't reloading the page, only inserting a new browser history item, we need to manually update
|
||||
// the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved
|
||||
// url, not the unsaved one.
|
||||
chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl);
|
||||
if (id) {
|
||||
notify.info('Saved Visualization "' + savedVis.title + '"');
|
||||
if ($scope.isAddToDashMode()) {
|
||||
const savedVisualizationParsedUrl = new KibanaParsedUrl({
|
||||
basePath: chrome.getBasePath(),
|
||||
appId: kbnBaseUrl.slice('/app/'.length),
|
||||
appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }),
|
||||
});
|
||||
// Manually insert a new url so the back button will open the saved visualization.
|
||||
$window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath());
|
||||
// Since we aren't reloading the page, only inserting a new browser history item, we need to manually update
|
||||
// the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved
|
||||
// url, not the unsaved one.
|
||||
chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl);
|
||||
|
||||
const lastDashboardAbsoluteUrl = chrome.getNavLinkById('kibana:dashboard').lastSubUrl;
|
||||
const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, chrome.getBasePath());
|
||||
dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id);
|
||||
kbnUrl.change(dashboardParsedUrl.appPath);
|
||||
} else if (savedVis.id === $route.current.params.id) {
|
||||
docTitle.change(savedVis.lastSavedTitle);
|
||||
} else {
|
||||
kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id });
|
||||
const lastDashboardAbsoluteUrl = chrome.getNavLinkById('kibana:dashboard').lastSubUrl;
|
||||
const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, chrome.getBasePath());
|
||||
dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id);
|
||||
kbnUrl.change(dashboardParsedUrl.appPath);
|
||||
} else if (savedVis.id === $route.current.params.id) {
|
||||
docTitle.change(savedVis.lastSavedTitle);
|
||||
} else {
|
||||
kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, notify.error);
|
||||
}, notify.error);
|
||||
};
|
||||
|
||||
$scope.unlink = function () {
|
||||
|
@ -286,11 +286,11 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
|
||||
// copy over all state except "aggs", "query" and "filter"
|
||||
_(parent.toJSON())
|
||||
.omit(['aggs', 'filter', 'query'])
|
||||
.forOwn(function (val, key) {
|
||||
searchSource.set(key, val);
|
||||
})
|
||||
.commit();
|
||||
.omit(['aggs', 'filter', 'query'])
|
||||
.forOwn(function (val, key) {
|
||||
searchSource.set(key, val);
|
||||
})
|
||||
.commit();
|
||||
|
||||
$state.query = searchSource.get('query');
|
||||
$state.filters = searchSource.get('filter');
|
||||
|
|
|
@ -16,14 +16,14 @@ import { VisualizeConstants } from './visualize_constants';
|
|||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
|
||||
uiRoutes
|
||||
.defaults(/visualize/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when(VisualizeConstants.LANDING_PAGE_PATH, {
|
||||
template: visualizeListingTemplate,
|
||||
controller: VisualizeListingController,
|
||||
controllerAs: 'listingController',
|
||||
});
|
||||
.defaults(/visualize/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when(VisualizeConstants.LANDING_PAGE_PATH, {
|
||||
template: visualizeListingTemplate,
|
||||
controller: VisualizeListingController,
|
||||
controllerAs: 'listingController',
|
||||
});
|
||||
|
||||
FeatureCatalogueRegistryProvider.register(() => {
|
||||
return {
|
||||
|
|
|
@ -11,131 +11,131 @@ import { VisProvider } from 'ui/vis';
|
|||
import { uiModules } from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.factory('SavedVis', function (config, $injector, courier, Promise, savedSearches, Private) {
|
||||
const Vis = Private(VisProvider);
|
||||
.get('app/visualize')
|
||||
.factory('SavedVis', function (config, $injector, courier, Promise, savedSearches, Private) {
|
||||
const Vis = Private(VisProvider);
|
||||
|
||||
_.class(SavedVis).inherits(courier.SavedObject);
|
||||
function SavedVis(opts) {
|
||||
const self = this;
|
||||
opts = opts || {};
|
||||
if (typeof opts !== 'object') opts = { id: opts };
|
||||
_.class(SavedVis).inherits(courier.SavedObject);
|
||||
function SavedVis(opts) {
|
||||
const self = this;
|
||||
opts = opts || {};
|
||||
if (typeof opts !== 'object') opts = { id: opts };
|
||||
|
||||
SavedVis.Super.call(self, {
|
||||
type: SavedVis.type,
|
||||
mapping: SavedVis.mapping,
|
||||
searchSource: SavedVis.searchSource,
|
||||
SavedVis.Super.call(self, {
|
||||
type: SavedVis.type,
|
||||
mapping: SavedVis.mapping,
|
||||
searchSource: SavedVis.searchSource,
|
||||
|
||||
id: opts.id,
|
||||
indexPattern: opts.indexPattern,
|
||||
defaults: {
|
||||
title: 'New Visualization',
|
||||
visState: (function () {
|
||||
if (!opts.type) return null;
|
||||
const def = {};
|
||||
def.type = opts.type;
|
||||
return def;
|
||||
}()),
|
||||
uiStateJSON: '{}',
|
||||
description: '',
|
||||
savedSearchId: opts.savedSearchId,
|
||||
version: 1
|
||||
},
|
||||
id: opts.id,
|
||||
indexPattern: opts.indexPattern,
|
||||
defaults: {
|
||||
title: 'New Visualization',
|
||||
visState: (function () {
|
||||
if (!opts.type) return null;
|
||||
const def = {};
|
||||
def.type = opts.type;
|
||||
return def;
|
||||
}()),
|
||||
uiStateJSON: '{}',
|
||||
description: '',
|
||||
savedSearchId: opts.savedSearchId,
|
||||
version: 1
|
||||
},
|
||||
|
||||
afterESResp: this._afterEsResp
|
||||
afterESResp: this._afterEsResp
|
||||
});
|
||||
}
|
||||
|
||||
SavedVis.type = 'visualization';
|
||||
|
||||
SavedVis.mapping = {
|
||||
title: 'text',
|
||||
visState: 'json',
|
||||
uiStateJSON: 'text',
|
||||
description: 'text',
|
||||
savedSearchId: 'keyword',
|
||||
version: 'integer'
|
||||
};
|
||||
|
||||
// Order these fields to the top, the rest are alphabetical
|
||||
SavedVis.fieldOrder = ['title', 'description'];
|
||||
|
||||
SavedVis.searchSource = true;
|
||||
|
||||
SavedVis.prototype._afterEsResp = function () {
|
||||
const self = this;
|
||||
|
||||
return self._getLinkedSavedSearch()
|
||||
.then(function () {
|
||||
self.searchSource.size(0);
|
||||
|
||||
return self.vis ? self._updateVis() : self._createVis();
|
||||
})
|
||||
.then(function () {
|
||||
self.searchSource.onRequestStart((searchSource, searchRequest) => {
|
||||
return self.vis.onSearchRequestStart(searchSource, searchRequest);
|
||||
});
|
||||
|
||||
self.searchSource.aggs(function () {
|
||||
return self.vis.aggs.toDsl();
|
||||
});
|
||||
|
||||
return self;
|
||||
});
|
||||
};
|
||||
|
||||
SavedVis.prototype._getLinkedSavedSearch = Promise.method(function () {
|
||||
const self = this;
|
||||
const linkedSearch = !!self.savedSearchId;
|
||||
const current = self.savedSearch;
|
||||
|
||||
if (linkedSearch && current && current.id === self.savedSearchId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.savedSearch) {
|
||||
self.searchSource.inherits(self.savedSearch.searchSource.getParent());
|
||||
self.savedSearch.destroy();
|
||||
self.savedSearch = null;
|
||||
}
|
||||
|
||||
if (linkedSearch) {
|
||||
return savedSearches.get(self.savedSearchId)
|
||||
.then(function (savedSearch) {
|
||||
self.savedSearch = savedSearch;
|
||||
self.searchSource.inherits(self.savedSearch.searchSource);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SavedVis.type = 'visualization';
|
||||
SavedVis.prototype._createVis = function () {
|
||||
const self = this;
|
||||
|
||||
SavedVis.mapping = {
|
||||
title: 'text',
|
||||
visState: 'json',
|
||||
uiStateJSON: 'text',
|
||||
description: 'text',
|
||||
savedSearchId: 'keyword',
|
||||
version: 'integer'
|
||||
};
|
||||
if (self.stateJSON) {
|
||||
self.visState = Vis.convertOldState(self.typeName, JSON.parse(self.stateJSON));
|
||||
}
|
||||
|
||||
// Order these fields to the top, the rest are alphabetical
|
||||
SavedVis.fieldOrder = ['title', 'description'];
|
||||
// visState doesn't yet exist when importing a visualization, so we can't
|
||||
// assume that exists at this point. If it does exist, then we're not
|
||||
// importing a visualization, so we want to sync the title.
|
||||
if (self.visState) {
|
||||
self.visState.title = self.title;
|
||||
}
|
||||
self.vis = new Vis(
|
||||
self.searchSource.get('index'),
|
||||
self.visState
|
||||
);
|
||||
|
||||
SavedVis.searchSource = true;
|
||||
return self.vis;
|
||||
};
|
||||
|
||||
SavedVis.prototype._afterEsResp = function () {
|
||||
const self = this;
|
||||
SavedVis.prototype._updateVis = function () {
|
||||
const self = this;
|
||||
|
||||
return self._getLinkedSavedSearch()
|
||||
.then(function () {
|
||||
self.searchSource.size(0);
|
||||
|
||||
return self.vis ? self._updateVis() : self._createVis();
|
||||
})
|
||||
.then(function () {
|
||||
self.searchSource.onRequestStart((searchSource, searchRequest) => {
|
||||
return self.vis.onSearchRequestStart(searchSource, searchRequest);
|
||||
});
|
||||
|
||||
self.searchSource.aggs(function () {
|
||||
return self.vis.aggs.toDsl();
|
||||
});
|
||||
|
||||
return self;
|
||||
});
|
||||
};
|
||||
|
||||
SavedVis.prototype._getLinkedSavedSearch = Promise.method(function () {
|
||||
const self = this;
|
||||
const linkedSearch = !!self.savedSearchId;
|
||||
const current = self.savedSearch;
|
||||
|
||||
if (linkedSearch && current && current.id === self.savedSearchId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.savedSearch) {
|
||||
self.searchSource.inherits(self.savedSearch.searchSource.getParent());
|
||||
self.savedSearch.destroy();
|
||||
self.savedSearch = null;
|
||||
}
|
||||
|
||||
if (linkedSearch) {
|
||||
return savedSearches.get(self.savedSearchId)
|
||||
.then(function (savedSearch) {
|
||||
self.savedSearch = savedSearch;
|
||||
self.searchSource.inherits(self.savedSearch.searchSource);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
SavedVis.prototype._createVis = function () {
|
||||
const self = this;
|
||||
|
||||
if (self.stateJSON) {
|
||||
self.visState = Vis.convertOldState(self.typeName, JSON.parse(self.stateJSON));
|
||||
}
|
||||
|
||||
// visState doesn't yet exist when importing a visualization, so we can't
|
||||
// assume that exists at this point. If it does exist, then we're not
|
||||
// importing a visualization, so we want to sync the title.
|
||||
if (self.visState) {
|
||||
self.vis.indexPattern = self.searchSource.get('index');
|
||||
self.visState.title = self.title;
|
||||
}
|
||||
self.vis = new Vis(
|
||||
self.searchSource.get('index'),
|
||||
self.visState
|
||||
);
|
||||
self.vis.setState(self.visState);
|
||||
};
|
||||
|
||||
return self.vis;
|
||||
};
|
||||
|
||||
SavedVis.prototype._updateVis = function () {
|
||||
const self = this;
|
||||
|
||||
self.vis.indexPattern = self.searchSource.get('index');
|
||||
self.visState.title = self.title;
|
||||
self.vis.setState(self.visState);
|
||||
};
|
||||
|
||||
return SavedVis;
|
||||
});
|
||||
return SavedVis;
|
||||
});
|
||||
|
|
|
@ -143,8 +143,8 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
|
|||
$scope.getVisTypeUrl = function (visType) {
|
||||
const baseUrl =
|
||||
visType.requiresSearch && visType.options.showIndexSelection
|
||||
? `#${VisualizeConstants.WIZARD_STEP_2_PAGE_PATH}?`
|
||||
: `#${VisualizeConstants.CREATE_PATH}?`;
|
||||
? `#${VisualizeConstants.WIZARD_STEP_2_PAGE_PATH}?`
|
||||
: `#${VisualizeConstants.CREATE_PATH}?`;
|
||||
|
||||
const params = [`type=${encodeURIComponent(visType.name)}`];
|
||||
|
||||
|
|
|
@ -13,14 +13,14 @@ export default function registerCount(server) {
|
|||
allowNoIndices: false,
|
||||
index: req.params.id
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
reply({ count: res.count });
|
||||
},
|
||||
function (error) {
|
||||
reply(handleESError(error));
|
||||
}
|
||||
);
|
||||
.then(
|
||||
function (res) {
|
||||
reply({ count: res.count });
|
||||
},
|
||||
function (error) {
|
||||
reply(handleESError(error));
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,20 +39,20 @@ function StandardAgg(props) {
|
|||
</div>
|
||||
{
|
||||
model.type !== 'count'
|
||||
? (
|
||||
<div className="vis_editor__item">
|
||||
<label className="vis_editor__label" htmlFor={htmlId('field')}>Field</label>
|
||||
<FieldSelect
|
||||
id={htmlId('field')}
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict={restrict}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
? (
|
||||
<div className="vis_editor__item">
|
||||
<label className="vis_editor__label" htmlFor={htmlId('field')}>Field</label>
|
||||
<FieldSelect
|
||||
id={htmlId('field')}
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict={restrict}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</AggRow>
|
||||
);
|
||||
|
|
|
@ -81,18 +81,18 @@ class ColorPicker extends Component {
|
|||
{ clear }
|
||||
{
|
||||
this.state.displayPicker
|
||||
? (
|
||||
<div className="vis_editor__color_picker-popover">
|
||||
<div
|
||||
className="vis_editor__color_picker-cover"
|
||||
onClick={this.handleClose}
|
||||
/>
|
||||
<Picker
|
||||
color={value}
|
||||
onChangeComplete={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
? (
|
||||
<div className="vis_editor__color_picker-popover">
|
||||
<div
|
||||
className="vis_editor__color_picker-cover"
|
||||
onClick={this.handleClose}
|
||||
/>
|
||||
<Picker
|
||||
color={value}
|
||||
onChangeComplete={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -58,7 +58,7 @@ export default function executorProvider(Promise, $timeout, timefilter) {
|
|||
.then(service.handleResponse || noop)
|
||||
.catch(service.handleError || noop);
|
||||
}))
|
||||
.finally(reset);
|
||||
.finally(reset);
|
||||
}
|
||||
|
||||
function reFetch() {
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'ui/vis/map/service_settings';
|
|||
|
||||
const module = uiModules.get('kibana/region_map', ['kibana']);
|
||||
module.controller('KbnRegionMapController', function ($scope, $element, Private, Notifier, getAppState,
|
||||
serviceSettings, config) {
|
||||
serviceSettings, config) {
|
||||
|
||||
const DEFAULT_ZOOM_SETTINGS = {
|
||||
zoom: 2,
|
||||
|
|
|
@ -28,30 +28,30 @@ const linkReqRespStats = function ($scope) {
|
|||
};
|
||||
|
||||
SpyModesRegistryProvider
|
||||
.register(function () {
|
||||
return {
|
||||
name: 'request',
|
||||
display: 'Request',
|
||||
order: 2,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
};
|
||||
})
|
||||
.register(function () {
|
||||
return {
|
||||
name: 'response',
|
||||
display: 'Response',
|
||||
order: 3,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
};
|
||||
})
|
||||
.register(function () {
|
||||
return {
|
||||
name: 'stats',
|
||||
display: 'Statistics',
|
||||
order: 4,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
};
|
||||
});
|
||||
.register(function () {
|
||||
return {
|
||||
name: 'request',
|
||||
display: 'Request',
|
||||
order: 2,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
};
|
||||
})
|
||||
.register(function () {
|
||||
return {
|
||||
name: 'response',
|
||||
display: 'Response',
|
||||
order: 3,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
};
|
||||
})
|
||||
.register(function () {
|
||||
return {
|
||||
name: 'stats',
|
||||
display: 'Statistics',
|
||||
order: 4,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
};
|
||||
});
|
||||
|
|
|
@ -5,15 +5,15 @@ import uiRoutes from 'ui/routes';
|
|||
|
||||
uiRoutes.enable();
|
||||
uiRoutes
|
||||
.when('/', {
|
||||
resolve: {
|
||||
url: function (AppState, globalState, $window) {
|
||||
const redirectUrl = chrome.getInjected('redirectUrl');
|
||||
.when('/', {
|
||||
resolve: {
|
||||
url: function (AppState, globalState, $window) {
|
||||
const redirectUrl = chrome.getInjected('redirectUrl');
|
||||
|
||||
const hashedUrl = hashUrl([new AppState(), globalState], redirectUrl);
|
||||
const url = chrome.addBasePath(hashedUrl);
|
||||
const hashedUrl = hashUrl([new AppState(), globalState], redirectUrl);
|
||||
const url = chrome.addBasePath(hashedUrl);
|
||||
|
||||
$window.location = url;
|
||||
$window.location = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,85 +7,85 @@ import { uiModules } from 'ui/modules';
|
|||
|
||||
|
||||
const chrome = require('ui/chrome')
|
||||
.setRootTemplate(require('plugins/status_page/status_page.html'))
|
||||
.setRootController('ui', function ($http, buildNum, buildSha) {
|
||||
const ui = this;
|
||||
ui.loading = false;
|
||||
.setRootTemplate(require('plugins/status_page/status_page.html'))
|
||||
.setRootController('ui', function ($http, buildNum, buildSha) {
|
||||
const ui = this;
|
||||
ui.loading = false;
|
||||
|
||||
ui.buildInfo = {
|
||||
num: buildNum,
|
||||
sha: buildSha.substr(0, 8)
|
||||
};
|
||||
ui.buildInfo = {
|
||||
num: buildNum,
|
||||
sha: buildSha.substr(0, 8)
|
||||
};
|
||||
|
||||
ui.refresh = function () {
|
||||
ui.loading = true;
|
||||
ui.refresh = function () {
|
||||
ui.loading = true;
|
||||
|
||||
// go ahead and get the info you want
|
||||
return $http
|
||||
.get(chrome.addBasePath('/api/status'))
|
||||
.then(function (resp) {
|
||||
// go ahead and get the info you want
|
||||
return $http
|
||||
.get(chrome.addBasePath('/api/status'))
|
||||
.then(function (resp) {
|
||||
|
||||
if (ui.fetchError) {
|
||||
ui.fetchError.clear();
|
||||
ui.fetchError = null;
|
||||
}
|
||||
if (ui.fetchError) {
|
||||
ui.fetchError.clear();
|
||||
ui.fetchError = null;
|
||||
}
|
||||
|
||||
const data = resp.data;
|
||||
const metrics = data.metrics;
|
||||
if (metrics) {
|
||||
ui.metrics = [{
|
||||
name: 'Heap Total',
|
||||
value: _.get(metrics, 'process.mem.heap_max_in_bytes'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Heap Used',
|
||||
value: _.get(metrics, 'process.mem.heap_used_in_bytes'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Load',
|
||||
value: [
|
||||
_.get(metrics, 'os.cpu.load_average.1m'),
|
||||
_.get(metrics, 'os.cpu.load_average.5m'),
|
||||
_.get(metrics, 'os.cpu.load_average.15m')
|
||||
],
|
||||
type: 'float'
|
||||
}, {
|
||||
name: 'Response Time Avg',
|
||||
value: _.get(metrics, 'response_times.avg_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Response Time Max',
|
||||
value: _.get(metrics, 'response_times.max_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Requests Per Second',
|
||||
value: _.get(metrics, 'requests.total') * 1000 / _.get(metrics, 'collection_interval_in_millis')
|
||||
}];
|
||||
}
|
||||
const data = resp.data;
|
||||
const metrics = data.metrics;
|
||||
if (metrics) {
|
||||
ui.metrics = [{
|
||||
name: 'Heap Total',
|
||||
value: _.get(metrics, 'process.mem.heap_max_in_bytes'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Heap Used',
|
||||
value: _.get(metrics, 'process.mem.heap_used_in_bytes'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Load',
|
||||
value: [
|
||||
_.get(metrics, 'os.cpu.load_average.1m'),
|
||||
_.get(metrics, 'os.cpu.load_average.5m'),
|
||||
_.get(metrics, 'os.cpu.load_average.15m')
|
||||
],
|
||||
type: 'float'
|
||||
}, {
|
||||
name: 'Response Time Avg',
|
||||
value: _.get(metrics, 'response_times.avg_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Response Time Max',
|
||||
value: _.get(metrics, 'response_times.max_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Requests Per Second',
|
||||
value: _.get(metrics, 'requests.total') * 1000 / _.get(metrics, 'collection_interval_in_millis')
|
||||
}];
|
||||
}
|
||||
|
||||
ui.name = data.name;
|
||||
ui.statuses = data.status.statuses;
|
||||
ui.name = data.name;
|
||||
ui.statuses = data.status.statuses;
|
||||
|
||||
const overall = data.status.overall;
|
||||
if (!ui.serverState || (ui.serverState !== overall.state)) {
|
||||
ui.serverState = overall.state;
|
||||
ui.serverStateMessage = overall.title;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (ui.fetchError) return;
|
||||
ui.fetchError = notify.error('Failed to request server ui. Perhaps your server is down?');
|
||||
ui.metrics = ui.statuses = ui.overall = null;
|
||||
})
|
||||
.then(function () {
|
||||
ui.loading = false;
|
||||
});
|
||||
};
|
||||
const overall = data.status.overall;
|
||||
if (!ui.serverState || (ui.serverState !== overall.state)) {
|
||||
ui.serverState = overall.state;
|
||||
ui.serverStateMessage = overall.title;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (ui.fetchError) return;
|
||||
ui.fetchError = notify.error('Failed to request server ui. Perhaps your server is down?');
|
||||
ui.metrics = ui.statuses = ui.overall = null;
|
||||
})
|
||||
.then(function () {
|
||||
ui.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
ui.refresh();
|
||||
});
|
||||
ui.refresh();
|
||||
});
|
||||
|
||||
uiModules.get('kibana')
|
||||
.config(function (appSwitcherEnsureNavigationProvider) {
|
||||
appSwitcherEnsureNavigationProvider.forceNavigation(true);
|
||||
});
|
||||
.config(function (appSwitcherEnsureNavigationProvider) {
|
||||
appSwitcherEnsureNavigationProvider.forceNavigation(true);
|
||||
});
|
||||
|
|
|
@ -3,22 +3,22 @@ import { uiModules } from 'ui/modules';
|
|||
import statusPageMetricTemplate from 'plugins/status_page/status_page_metric.html';
|
||||
|
||||
uiModules
|
||||
.get('kibana', [])
|
||||
.filter('statusMetric', function () {
|
||||
return function (input, type) {
|
||||
const metrics = [].concat(input);
|
||||
return metrics.map(function (metric) {
|
||||
return formatNumber(metric, type);
|
||||
}).join(', ');
|
||||
};
|
||||
})
|
||||
.directive('statusPageMetric', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: statusPageMetricTemplate,
|
||||
scope: {
|
||||
metric: '=',
|
||||
},
|
||||
controllerAs: 'metric'
|
||||
};
|
||||
});
|
||||
.get('kibana', [])
|
||||
.filter('statusMetric', function () {
|
||||
return function (input, type) {
|
||||
const metrics = [].concat(input);
|
||||
return metrics.map(function (metric) {
|
||||
return formatNumber(metric, type);
|
||||
}).join(', ');
|
||||
};
|
||||
})
|
||||
.directive('statusPageMetric', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: statusPageMetricTemplate,
|
||||
scope: {
|
||||
metric: '=',
|
||||
},
|
||||
controllerAs: 'metric'
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,26 +2,26 @@ import { uiModules } from 'ui/modules';
|
|||
import tableVisParamsTemplate from 'plugins/table_vis/table_vis_params.html';
|
||||
|
||||
uiModules.get('kibana/table_vis')
|
||||
.directive('tableVisParams', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: tableVisParamsTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.totalAggregations = ['sum', 'avg', 'min', 'max', 'count'];
|
||||
.directive('tableVisParams', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: tableVisParamsTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.totalAggregations = ['sum', 'avg', 'min', 'max', 'count'];
|
||||
|
||||
$scope.$watchMulti([
|
||||
'vis.params.showPartialRows',
|
||||
'vis.params.showMeticsAtAllLevels'
|
||||
], function () {
|
||||
if (!$scope.vis) return;
|
||||
$scope.$watchMulti([
|
||||
'vis.params.showPartialRows',
|
||||
'vis.params.showMeticsAtAllLevels'
|
||||
], function () {
|
||||
if (!$scope.vis) return;
|
||||
|
||||
const params = $scope.vis.params;
|
||||
if (params.showPartialRows || params.showMeticsAtAllLevels) {
|
||||
$scope.metricsAtAllLevels = true;
|
||||
} else {
|
||||
$scope.metricsAtAllLevels = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
const params = $scope.vis.params;
|
||||
if (params.showPartialRows || params.showMeticsAtAllLevels) {
|
||||
$scope.metricsAtAllLevels = true;
|
||||
} else {
|
||||
$scope.metricsAtAllLevels = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -158,7 +158,7 @@ class TagCloud extends EventEmitter {
|
|||
|
||||
async _pickPendingJob() {
|
||||
return await new Promise((resolve) => {
|
||||
this._setTimeoutId = setTimeout(async() => {
|
||||
this._setTimeoutId = setTimeout(async () => {
|
||||
const job = this._pendingJob;
|
||||
this._pendingJob = null;
|
||||
this._setTimeoutId = null;
|
||||
|
|
|
@ -25,10 +25,10 @@ const findSourceFiles = async (patterns, cwd = fromRoot('.')) => {
|
|||
});
|
||||
|
||||
return chain(matches)
|
||||
.flatten()
|
||||
.uniq()
|
||||
.map(match => resolve(cwd, match))
|
||||
.value();
|
||||
.flatten()
|
||||
.uniq()
|
||||
.map(match => resolve(cwd, match))
|
||||
.value();
|
||||
};
|
||||
|
||||
findSourceFiles.symlinks = {};
|
||||
|
|
|
@ -42,7 +42,7 @@ export function MapsVisualizationProvider(serviceSettings, Notifier, getAppState
|
|||
|
||||
async render(esResponse, status) {
|
||||
|
||||
return new Promise(async(resolve) => {
|
||||
return new Promise(async (resolve) => {
|
||||
|
||||
await this._kibanaMapReady;
|
||||
if (status.resize) {
|
||||
|
|
|
@ -39,28 +39,28 @@ require('ui/routes')
|
|||
resolve: {
|
||||
savedSheet: function (courier, savedSheets, $route) {
|
||||
return savedSheets.get($route.current.params.id)
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'search': '/'
|
||||
}));
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'search': '/'
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.controller('timelion', function (
|
||||
$http,
|
||||
$route,
|
||||
$routeParams,
|
||||
$scope,
|
||||
$timeout,
|
||||
AppState,
|
||||
config,
|
||||
confirmModal,
|
||||
courier,
|
||||
kbnUrl,
|
||||
Notifier,
|
||||
Private,
|
||||
timefilter
|
||||
) {
|
||||
$http,
|
||||
$route,
|
||||
$routeParams,
|
||||
$scope,
|
||||
$timeout,
|
||||
AppState,
|
||||
config,
|
||||
confirmModal,
|
||||
courier,
|
||||
kbnUrl,
|
||||
Notifier,
|
||||
Private,
|
||||
timefilter
|
||||
) {
|
||||
|
||||
// Keeping this at app scope allows us to keep the current page when the user
|
||||
// switches to say, the timepicker.
|
||||
|
@ -222,30 +222,30 @@ app.controller('timelion', function (
|
|||
timezone: timezone
|
||||
}),
|
||||
})
|
||||
.then(resp => resp.data)
|
||||
.catch(resp => { throw resp.data; });
|
||||
.then(resp => resp.data)
|
||||
.catch(resp => { throw resp.data; });
|
||||
|
||||
httpResult
|
||||
.then(function (resp) {
|
||||
dismissNotifications();
|
||||
$scope.stats = resp.stats;
|
||||
$scope.sheet = resp.sheet;
|
||||
_.each(resp.sheet, function (cell) {
|
||||
if (cell.exception) {
|
||||
$scope.state.selected = cell.plot;
|
||||
}
|
||||
.then(function (resp) {
|
||||
dismissNotifications();
|
||||
$scope.stats = resp.stats;
|
||||
$scope.sheet = resp.sheet;
|
||||
_.each(resp.sheet, function (cell) {
|
||||
if (cell.exception) {
|
||||
$scope.state.selected = cell.plot;
|
||||
}
|
||||
});
|
||||
$scope.running = false;
|
||||
})
|
||||
.catch(function (resp) {
|
||||
$scope.sheet = [];
|
||||
$scope.running = false;
|
||||
|
||||
const err = new Error(resp.message);
|
||||
err.stack = resp.stack;
|
||||
notify.error(err);
|
||||
|
||||
});
|
||||
$scope.running = false;
|
||||
})
|
||||
.catch(function (resp) {
|
||||
$scope.sheet = [];
|
||||
$scope.running = false;
|
||||
|
||||
const err = new Error(resp.message);
|
||||
err.stack = resp.stack;
|
||||
notify.error(err);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
$scope.safeSearch = _.debounce($scope.search, 500);
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
import panelRegistryProvider from 'plugins/timelion/lib/panel_registry';
|
||||
|
||||
require('ui/modules')
|
||||
.get('apps/timelion', [])
|
||||
.directive('chart', function (Private) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
seriesList: '=chart', // The flot object, data, config and all
|
||||
search: '=', // The function to execute to kick off a search
|
||||
interval: '=' // Required for formatting x-axis ticks
|
||||
},
|
||||
link: function ($scope, $elem) {
|
||||
.get('apps/timelion', [])
|
||||
.directive('chart', function (Private) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
seriesList: '=chart', // The flot object, data, config and all
|
||||
search: '=', // The function to execute to kick off a search
|
||||
interval: '=' // Required for formatting x-axis ticks
|
||||
},
|
||||
link: function ($scope, $elem) {
|
||||
|
||||
const panelRegistry = Private(panelRegistryProvider);
|
||||
let panelScope = $scope.$new(true);
|
||||
const panelRegistry = Private(panelRegistryProvider);
|
||||
let panelScope = $scope.$new(true);
|
||||
|
||||
function render(seriesList) {
|
||||
panelScope.$destroy();
|
||||
function render(seriesList) {
|
||||
panelScope.$destroy();
|
||||
|
||||
if (!seriesList) return;
|
||||
if (!seriesList) return;
|
||||
|
||||
seriesList.render = seriesList.render || {
|
||||
type: 'timechart'
|
||||
};
|
||||
seriesList.render = seriesList.render || {
|
||||
type: 'timechart'
|
||||
};
|
||||
|
||||
const panelSchema = panelRegistry.byName[seriesList.render.type];
|
||||
const panelSchema = panelRegistry.byName[seriesList.render.type];
|
||||
|
||||
if (!panelSchema) {
|
||||
$elem.text('No such panel type: ' + seriesList.render.type);
|
||||
return;
|
||||
if (!panelSchema) {
|
||||
$elem.text('No such panel type: ' + seriesList.render.type);
|
||||
return;
|
||||
}
|
||||
|
||||
panelScope = $scope.$new(true);
|
||||
panelScope.seriesList = seriesList;
|
||||
panelScope.interval = $scope.interval;
|
||||
panelScope.search = $scope.search;
|
||||
|
||||
panelSchema.render(panelScope, $elem);
|
||||
}
|
||||
|
||||
panelScope = $scope.$new(true);
|
||||
panelScope.seriesList = seriesList;
|
||||
panelScope.interval = $scope.interval;
|
||||
panelScope.search = $scope.search;
|
||||
|
||||
panelSchema.render(panelScope, $elem);
|
||||
$scope.$watch('seriesList', render);
|
||||
}
|
||||
|
||||
$scope.$watch('seriesList', render);
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -17,14 +17,14 @@ export default function (directory) {
|
|||
|
||||
// Get a list of all directories with an index.js, use the directory name as the key in the object
|
||||
const directories = _.chain(glob.sync(path.resolve(__dirname, '../' + directory + '/*/index.js')))
|
||||
.filter(function (file) {
|
||||
return file.match(/__test__/) == null;
|
||||
})
|
||||
.map(function (file) {
|
||||
const parts = file.split('/');
|
||||
const name = parts[parts.length - 2];
|
||||
return getTuple(directory, name);
|
||||
}).value();
|
||||
.filter(function (file) {
|
||||
return file.match(/__test__/) == null;
|
||||
})
|
||||
.map(function (file) {
|
||||
const parts = file.split('/');
|
||||
const name = parts[parts.length - 2];
|
||||
return getTuple(directory, name);
|
||||
}).value();
|
||||
|
||||
const functions = _.zipObject(files.concat(directories));
|
||||
|
||||
|
|
|
@ -49,18 +49,18 @@ describe(filename, () => {
|
|||
_shards: { total: 0 }
|
||||
});
|
||||
return invoke(es, [5], tlConfig)
|
||||
.then(expect.fail)
|
||||
.catch((e) => {
|
||||
expect(e).to.be.an('error');
|
||||
});
|
||||
.then(expect.fail)
|
||||
.catch((e) => {
|
||||
expect(e).to.be.an('error');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a seriesList', () => {
|
||||
tlConfig = stubRequestAndServer(esResponse);
|
||||
return invoke(es, [5], tlConfig)
|
||||
.then((r) => {
|
||||
expect(r.output.type).to.eql('seriesList');
|
||||
});
|
||||
.then((r) => {
|
||||
expect(r.output.type).to.eql('seriesList');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -55,10 +55,10 @@ describe(filename, () => {
|
|||
|
||||
it('does not allow undefined symbols', () => {
|
||||
return invoke(fn, [seriesList, null, null, null, null, 'beer'])
|
||||
.then(expect.fail)
|
||||
.catch((e) => {
|
||||
expect(e).to.be.an('error');
|
||||
});
|
||||
.then(expect.fail)
|
||||
.catch((e) => {
|
||||
expect(e).to.be.an('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -67,10 +67,10 @@ describe(filename, function () {
|
|||
|
||||
it('should throw an error is passed an unsupported interval', function () {
|
||||
return invoke(fn, [], { time: { interval: '2d' } })
|
||||
.then(expect.fail)
|
||||
.catch(function (r) {
|
||||
expect(r).to.be.an('error');
|
||||
});
|
||||
.then(expect.fail)
|
||||
.catch(function (r) {
|
||||
expect(r).to.be.an('error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the configured API key when talking to quandl', function () {
|
||||
|
|
|
@ -16,7 +16,7 @@ function initSeasonalComponents(samplePoints, seasonLength) {
|
|||
let currentSeason = [];
|
||||
const seasonalAverages = _.reduce(samplePoints, (result, point, i) => {
|
||||
currentSeason.push(point);
|
||||
// If this is the end of the season, add it to the result;
|
||||
// If this is the end of the season, add it to the result;
|
||||
if (i % seasonLength === seasonLength - 1) {
|
||||
result.push(_.sum(currentSeason) / seasonLength);
|
||||
currentSeason = [];
|
||||
|
|
|
@ -58,9 +58,9 @@ export default new Chainable('movingaverage', {
|
|||
|
||||
function toPoint(point, pairSlice) {
|
||||
const average = _.chain(pairSlice)
|
||||
.map(1).reduce(function (memo, num) {
|
||||
return (memo + num);
|
||||
}).value() / _window;
|
||||
.map(1).reduce(function (memo, num) {
|
||||
return (memo + num);
|
||||
}).value() / _window;
|
||||
|
||||
return [point[0], average];
|
||||
}
|
||||
|
|
|
@ -26,18 +26,18 @@ export default new Chainable('movingstd', {
|
|||
if (i < _window) { return [point[0], null]; }
|
||||
|
||||
const average = _.chain(pairs.slice(i - _window, i))
|
||||
.map(function (point) {
|
||||
return point[1];
|
||||
}).reduce(function (memo, num) {
|
||||
return (memo + num);
|
||||
}).value() / _window;
|
||||
.map(function (point) {
|
||||
return point[1];
|
||||
}).reduce(function (memo, num) {
|
||||
return (memo + num);
|
||||
}).value() / _window;
|
||||
|
||||
const variance = _.chain(pairs.slice(i - _window, i))
|
||||
.map(function (point) {
|
||||
return point[1];
|
||||
}).reduce(function (memo, num) {
|
||||
return memo + Math.pow(num - average, 2);
|
||||
}).value() / (_window - 1);
|
||||
.map(function (point) {
|
||||
return point[1];
|
||||
}).reduce(function (memo, num) {
|
||||
return memo + Math.pow(num - average, 2);
|
||||
}).value() / (_window - 1);
|
||||
|
||||
return [point[0], Math.sqrt(variance)];
|
||||
});
|
||||
|
|
|
@ -27,20 +27,20 @@ export default function GeoHashGridAggResponseFixture() {
|
|||
const buckets = _.times(_.random(40, 200), function () {
|
||||
return _.sample(geoHashCharts, 3).join('');
|
||||
})
|
||||
.sort()
|
||||
.map(function (geoHash) {
|
||||
const count = _.random(1, 5000);
|
||||
.sort()
|
||||
.map(function (geoHash) {
|
||||
const count = _.random(1, 5000);
|
||||
|
||||
docCount += count;
|
||||
docCount += count;
|
||||
|
||||
return {
|
||||
key: geoHash,
|
||||
doc_count: count,
|
||||
1: {
|
||||
value: 2048 + i
|
||||
}
|
||||
};
|
||||
});
|
||||
return {
|
||||
key: geoHash,
|
||||
doc_count: count,
|
||||
1: {
|
||||
value: 2048 + i
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
key: 'tag ' + (i + 1),
|
||||
|
|
|
@ -87,32 +87,32 @@ export default () => Joi.object({
|
|||
silent: Joi.boolean().default(false),
|
||||
|
||||
quiet: Joi.boolean()
|
||||
.when('silent', {
|
||||
is: true,
|
||||
then: Joi.default(true).valid(true),
|
||||
otherwise: Joi.default(false)
|
||||
}),
|
||||
.when('silent', {
|
||||
is: true,
|
||||
then: Joi.default(true).valid(true),
|
||||
otherwise: Joi.default(false)
|
||||
}),
|
||||
|
||||
verbose: Joi.boolean()
|
||||
.when('quiet', {
|
||||
is: true,
|
||||
then: Joi.valid(false).default(false),
|
||||
otherwise: Joi.default(false)
|
||||
}),
|
||||
.when('quiet', {
|
||||
is: true,
|
||||
then: Joi.valid(false).default(false),
|
||||
otherwise: Joi.default(false)
|
||||
}),
|
||||
|
||||
events: Joi.any().default({}),
|
||||
dest: Joi.string().default('stdout'),
|
||||
filter: Joi.any().default({}),
|
||||
json: Joi.boolean()
|
||||
.when('dest', {
|
||||
is: 'stdout',
|
||||
then: Joi.default(!process.stdout.isTTY),
|
||||
otherwise: Joi.default(true)
|
||||
}),
|
||||
.when('dest', {
|
||||
is: 'stdout',
|
||||
then: Joi.default(!process.stdout.isTTY),
|
||||
otherwise: Joi.default(true)
|
||||
}),
|
||||
|
||||
useUTC: Joi.boolean().default(true),
|
||||
})
|
||||
.default(),
|
||||
.default(),
|
||||
|
||||
ops: Joi.object({
|
||||
interval: Joi.number().default(5000),
|
||||
|
|
|
@ -119,7 +119,7 @@ export default async function (kbnServer, server, config) {
|
|||
search: req.url.search,
|
||||
pathname: pathPrefix + path.slice(0, -1),
|
||||
}))
|
||||
.permanent(true);
|
||||
.permanent(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -169,4 +169,4 @@ export default async function (kbnServer, server, config) {
|
|||
kbnServer.mixin(versionCheckMixin);
|
||||
|
||||
return kbnServer.mixin(xsrfMixin);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const createFieldsForTimePatternRoute = pre => ({
|
|||
lookBack,
|
||||
metaFields
|
||||
})
|
||||
.then(fields => ({ fields }))
|
||||
.then(fields => ({ fields }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export const createFieldsForWildcardRoute = pre => ({
|
|||
pattern,
|
||||
metaFields
|
||||
})
|
||||
.then(fields => ({ fields }))
|
||||
.then(fields => ({ fields }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,14 +49,14 @@ export default class KbnLoggerStringFormat extends LogFormat {
|
|||
const msg = data.error ? color('error')(data.error.stack) : color('message')(data.message);
|
||||
|
||||
const tags = _(data.tags)
|
||||
.sortBy(function (tag) {
|
||||
if (color(tag) === _.identity) return `2${tag}`;
|
||||
if (_.includes(statuses, tag)) return `0${tag}`;
|
||||
return `1${tag}`;
|
||||
})
|
||||
.reduce(function (s, t) {
|
||||
return s + `[${ color(t)(t) }]`;
|
||||
}, '');
|
||||
.sortBy(function (tag) {
|
||||
if (color(tag) === _.identity) return `2${tag}`;
|
||||
if (_.includes(statuses, tag)) return `0${tag}`;
|
||||
return `1${tag}`;
|
||||
})
|
||||
.reduce(function (s, t) {
|
||||
return s + `[${ color(t)(t) }]`;
|
||||
}, '');
|
||||
|
||||
return `${workerType}${type(data.type)} [${time}] ${tags} ${msg}`;
|
||||
}
|
||||
|
|
|
@ -11,45 +11,45 @@ export default Promise.method(function (kbnServer, server, config) {
|
|||
const pid = String(process.pid);
|
||||
|
||||
return writeFile(path, pid, { flag: 'wx' })
|
||||
.catch(function (err) {
|
||||
if (err.code !== 'EEXIST') throw err;
|
||||
.catch(function (err) {
|
||||
if (err.code !== 'EEXIST') throw err;
|
||||
|
||||
const log = {
|
||||
tmpl: 'pid file already exists at <%= path %>',
|
||||
path: path,
|
||||
pid: pid
|
||||
};
|
||||
const log = {
|
||||
tmpl: 'pid file already exists at <%= path %>',
|
||||
path: path,
|
||||
pid: pid
|
||||
};
|
||||
|
||||
if (config.get('pid.exclusive')) {
|
||||
throw Boom.create(500, _.template(log.tmpl)(log), log);
|
||||
} else {
|
||||
server.log(['pid', 'warning'], log);
|
||||
}
|
||||
if (config.get('pid.exclusive')) {
|
||||
throw Boom.create(500, _.template(log.tmpl)(log), log);
|
||||
} else {
|
||||
server.log(['pid', 'warning'], log);
|
||||
}
|
||||
|
||||
return writeFile(path, pid);
|
||||
})
|
||||
.then(function () {
|
||||
return writeFile(path, pid);
|
||||
})
|
||||
.then(function () {
|
||||
|
||||
server.log(['pid', 'debug'], {
|
||||
tmpl: 'wrote pid file to <%= path %>',
|
||||
path: path,
|
||||
pid: pid
|
||||
server.log(['pid', 'debug'], {
|
||||
tmpl: 'wrote pid file to <%= path %>',
|
||||
path: path,
|
||||
pid: pid
|
||||
});
|
||||
|
||||
const clean = _.once(function () {
|
||||
unlink(path);
|
||||
});
|
||||
|
||||
process.once('exit', clean); // for "natural" exits
|
||||
process.once('SIGINT', function () { // for Ctrl-C exits
|
||||
clean();
|
||||
|
||||
// resend SIGINT
|
||||
process.kill(process.pid, 'SIGINT');
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', function (reason) {
|
||||
server.log(['warning'], `Detected an unhandled Promise rejection.\n${reason}`);
|
||||
});
|
||||
});
|
||||
|
||||
const clean = _.once(function () {
|
||||
unlink(path);
|
||||
});
|
||||
|
||||
process.once('exit', clean); // for "natural" exits
|
||||
process.once('SIGINT', function () { // for Ctrl-C exits
|
||||
clean();
|
||||
|
||||
// resend SIGINT
|
||||
process.kill(process.pid, 'SIGINT');
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', function (reason) {
|
||||
server.log(['warning'], `Detected an unhandled Promise rejection.\n${reason}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,4 +22,4 @@ export default async function (kbnServer, server, config) {
|
|||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,4 +33,4 @@ export default async function (kbnServer, server) {
|
|||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue