* chore: merge canavs into x-pack squashed, since 6.x history isn't important and i want to be able to rebase... * chore: eslint fixes in canvas adds license headers and includes project linting overrides * Chore: Reorg the x-pack gulp tasks (#22785) - Removes deprecated, non-functional lint scripts - Removes some unused (and barely used) dependencies - Replaces deprecated `gulp-util` dependency - Adds eslint rule to prevent future use of deprecated `gulp-util` dependency - Moves all gulp tasks into `tasks` path - Moves `gulp_helpers` into `tasks/helpers` - All tasks in `gulpfile.js` were moved into `tasks` and broken up by domain This is basically a no-op moving files around PR. All the existing tasks appear to work the same with these changes. <img width="334" alt="screenshot 2018-09-06 15 42 45" src="https://user-images.githubusercontent.com/404731/45188971-8618c000-b1eb-11e8-9b26-b072ccc7ddb7.png"> * chore: rename files to match rules * chore: copy canvas dependencies * chore: reduce package.json to essentials * chore: make canvas work in xpack * chore: make browser tests work * chore: fix include paths node_modules is in a different relative path * chore: fix ml tests with canvas code in x-pack explicitely enable state management so the mlStateFactory tests pass * chore: fix RBAC tests with canvas * chore: split up the xpack prepare scripts * chore: canvas tasks - dev, peg, and plugins get dev, peg, and plugin building tasks working * chore: canvas tasks - local tests * chore: additional file cleanup * chore: yarn lockfiles and eslint fixes license headers and some small formatting stuff
|
@ -27,5 +27,8 @@ bower_components
|
|||
/x-pack/coverage
|
||||
/x-pack/build
|
||||
/x-pack/plugins/**/__tests__/fixtures/**
|
||||
/x-pack/plugins/canvas/common/lib/grammar.js
|
||||
/x-pack/plugins/canvas/canvas_plugin
|
||||
/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/*
|
||||
**/*.js.snap
|
||||
!/.eslintrc.js
|
||||
|
|
119
.eslintrc.js
|
@ -2,6 +2,8 @@ const { resolve } = require('path');
|
|||
const { readdirSync } = require('fs');
|
||||
const dedent = require('dedent');
|
||||
|
||||
const restrictedModules = { paths: ['gulp-util'] };
|
||||
|
||||
module.exports = {
|
||||
extends: ['@elastic/eslint-config-kibana', '@elastic/eslint-config-kibana/jest'],
|
||||
|
||||
|
@ -17,6 +19,11 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
'no-restricted-imports': [2, restrictedModules],
|
||||
'no-restricted-modules': [2, restrictedModules],
|
||||
},
|
||||
|
||||
overrides: [
|
||||
/**
|
||||
* Prettier
|
||||
|
@ -113,7 +120,7 @@ module.exports = {
|
|||
'packages/kbn-ui-framework/generator-kui/**/*',
|
||||
'packages/kbn-ui-framework/Gruntfile.js',
|
||||
'packages/kbn-es/src/**/*',
|
||||
'x-pack/{dev-tools,gulp_helpers,scripts,test,build_chromium}/**/*',
|
||||
'x-pack/{dev-tools,tasks,scripts,test,build_chromium}/**/*',
|
||||
'x-pack/**/{__tests__,__test__,__jest__,__fixtures__,__mocks__}/**/*',
|
||||
'x-pack/**/*.test.js',
|
||||
'x-pack/gulpfile.js',
|
||||
|
@ -320,5 +327,115 @@ module.exports = {
|
|||
files: ['x-pack/plugins/monitoring/public/**/*'],
|
||||
env: { browser: true },
|
||||
},
|
||||
|
||||
/**
|
||||
* Canvas overrides
|
||||
*/
|
||||
{
|
||||
files: ['x-pack/plugins/canvas/**/*'],
|
||||
plugins: ['prettier'],
|
||||
rules: {
|
||||
// preferences
|
||||
'comma-dangle': [2, 'always-multiline'],
|
||||
'no-multiple-empty-lines': [2, { max: 1, maxEOF: 1 }],
|
||||
'no-multi-spaces': 2,
|
||||
radix: 2,
|
||||
curly: [2, 'multi-or-nest', 'consistent'],
|
||||
|
||||
// annoying rules that conflict with prettier
|
||||
'space-before-function-paren': 0,
|
||||
indent: 0,
|
||||
'wrap-iife': 0,
|
||||
'max-len': 0,
|
||||
|
||||
// module importing
|
||||
'import/order': [
|
||||
2,
|
||||
{ groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'] },
|
||||
],
|
||||
'import/extensions': [2, 'never', { json: 'always', less: 'always', svg: 'always' }],
|
||||
|
||||
// prettier
|
||||
'prettier/prettier': 2,
|
||||
|
||||
// react
|
||||
'jsx-quotes': 2,
|
||||
'react/no-did-mount-set-state': 2,
|
||||
'react/no-did-update-set-state': 2,
|
||||
'react/no-multi-comp': [2, { ignoreStateless: true }],
|
||||
'react/self-closing-comp': 2,
|
||||
'react/sort-comp': 2,
|
||||
'react/jsx-boolean-value': 2,
|
||||
'react/jsx-wrap-multilines': 2,
|
||||
'react/no-unescaped-entities': [2, { forbid: ['>', '}'] }],
|
||||
'react/forbid-elements': [
|
||||
2,
|
||||
{
|
||||
forbid: [
|
||||
{
|
||||
element: 'EuiConfirmModal',
|
||||
message: 'Use <ConfirmModal> instead',
|
||||
},
|
||||
{
|
||||
element: 'EuiPopover',
|
||||
message: 'Use <Popover> instead',
|
||||
},
|
||||
{
|
||||
element: 'EuiIconTip',
|
||||
message: 'Use <TooltipIcon> instead',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['x-pack/plugins/canvas/*', 'x-pack/plugins/canvas/**/*'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
packageDir: resolve(__dirname, 'x-pack/package.json'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'x-pack/plugins/canvas/gulpfile.js',
|
||||
'x-pack/plugins/canvas/tasks/*.js',
|
||||
'x-pack/plugins/canvas/tasks/**/*.js',
|
||||
'x-pack/plugins/canvas/__tests__/**/*',
|
||||
'x-pack/plugins/canvas/**/{__tests__,__test__,__jest__,__fixtures__,__mocks__}/**/*',
|
||||
],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
peerDependencies: true,
|
||||
packageDir: resolve(__dirname, 'x-pack/package.json'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['x-pack/plugins/canvas/canvas_plugin_src/**/*'],
|
||||
globals: { canvas: true, $: true },
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
packageDir: resolve(__dirname, 'x-pack/package.json'),
|
||||
},
|
||||
],
|
||||
'import/no-unresolved': [
|
||||
'error',
|
||||
{
|
||||
ignore: ['!!raw-loader.+.svg$'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -61,6 +61,9 @@ export const LICENSE_WHITELIST = [
|
|||
];
|
||||
|
||||
export const LICENSE_OVERRIDES = {
|
||||
'scriptjs@2.5.8': ['MIT'], // license header appended in the dist
|
||||
'react-lib-adler32@1.0.1': ['BSD'], // adler32 extracted from react source
|
||||
|
||||
// TODO can be removed once we upgrade past elasticsearch-browser@14.0.0
|
||||
'elasticsearch-browser@13.0.1': ['Apache-2.0'],
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ export const IGNORE_FILE_GLOBS = [
|
|||
'tasks/config/**/*',
|
||||
'**/{Dockerfile,docker-compose.yml}',
|
||||
'x-pack/plugins/apm/**/*',
|
||||
'x-pack/plugins/canvas/tasks/**/*',
|
||||
'x-pack/plugins/canvas/canvas_plugin_src/**/*',
|
||||
'**/.*',
|
||||
'**/{webpackShims,__mocks__}/**/*',
|
||||
'x-pack/docs/**/*',
|
||||
|
|
|
@ -7,120 +7,37 @@
|
|||
require('@kbn/plugin-helpers').babelRegister();
|
||||
require('dotenv').config({ silent: true });
|
||||
|
||||
const { writeFileSync } = require('fs');
|
||||
|
||||
const gulp = require('gulp');
|
||||
const g = require('gulp-load-plugins')();
|
||||
const path = require('path');
|
||||
const del = require('del');
|
||||
const runSequence = require('run-sequence');
|
||||
const pluginHelpers = require('@kbn/plugin-helpers');
|
||||
const { ToolingLog } = require('@kbn/dev-utils');
|
||||
|
||||
const logger = require('./gulp_helpers/logger');
|
||||
const buildVersion = require('./gulp_helpers/build_version')();
|
||||
const gitInfo = require('./gulp_helpers/git_info');
|
||||
const fileGlobs = require('./gulp_helpers/globs');
|
||||
const { getEnabledPlugins } = require('./gulp_helpers/get_plugins');
|
||||
const getFlags = require('./gulp_helpers/get_flags');
|
||||
|
||||
const gulp = require('gulp');
|
||||
const mocha = require('gulp-mocha');
|
||||
const pegjs = require('gulp-pegjs');
|
||||
const multiProcess = require('gulp-multi-process');
|
||||
const fancyLog = require('fancy-log');
|
||||
const ansiColors = require('ansi-colors');
|
||||
const pkg = require('./package.json');
|
||||
const { ensureAllBrowsersDownloaded } = require('./plugins/reporting/server/browsers');
|
||||
const { createAutoJUnitReporter, generateNoticeFromSource } = require('../src/dev');
|
||||
|
||||
const buildDir = path.resolve(__dirname, 'build');
|
||||
const buildTarget = path.resolve(buildDir, 'plugin');
|
||||
const packageDir = path.resolve(buildDir, 'distributions');
|
||||
const coverageDir = path.resolve(__dirname, 'coverage');
|
||||
|
||||
const MOCHA_OPTIONS = {
|
||||
ui: 'bdd',
|
||||
reporter: createAutoJUnitReporter({
|
||||
reportName: 'X-Pack Mocha Tests',
|
||||
rootDirectory: __dirname,
|
||||
}),
|
||||
const gulpHelpers = {
|
||||
buildDir,
|
||||
buildTarget,
|
||||
colors: ansiColors,
|
||||
coverageDir,
|
||||
log: fancyLog,
|
||||
mocha,
|
||||
multiProcess,
|
||||
packageDir,
|
||||
pegjs,
|
||||
pkg,
|
||||
};
|
||||
|
||||
gulp.task('prepare', () => ensureAllBrowsersDownloaded());
|
||||
|
||||
gulp.task('dev', ['prepare'], () => pluginHelpers.run('start', { flags: getFlags() }));
|
||||
|
||||
gulp.task('clean-test', () => {
|
||||
logger('Deleting', coverageDir);
|
||||
return del([coverageDir]);
|
||||
});
|
||||
|
||||
gulp.task('clean', ['clean-test'], () => {
|
||||
const toDelete = [
|
||||
buildDir,
|
||||
packageDir,
|
||||
];
|
||||
logger('Deleting', toDelete.join(', '));
|
||||
return del(toDelete);
|
||||
});
|
||||
|
||||
gulp.task('report', () => {
|
||||
return gitInfo()
|
||||
.then(function (info) {
|
||||
g.util.log('Package Name', g.util.colors.yellow(pkg.name));
|
||||
g.util.log('Version', g.util.colors.yellow(buildVersion));
|
||||
g.util.log('Build Number', g.util.colors.yellow(info.number));
|
||||
g.util.log('Build SHA', g.util.colors.yellow(info.sha));
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('build', ['clean', 'report', 'prepare'], async () => {
|
||||
await pluginHelpers.run('build', {
|
||||
skipArchive: true,
|
||||
buildDestination: buildTarget,
|
||||
});
|
||||
|
||||
const buildRoot = path.resolve(buildTarget, 'kibana/x-pack');
|
||||
const log = new ToolingLog({
|
||||
level: 'info',
|
||||
writeTo: process.stdout
|
||||
});
|
||||
|
||||
writeFileSync(
|
||||
path.resolve(buildRoot, 'NOTICE.txt'),
|
||||
await generateNoticeFromSource({
|
||||
productName: 'Kibana X-Pack',
|
||||
log,
|
||||
directory: buildRoot
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task('test', (cb) => {
|
||||
const preTasks = ['clean-test'];
|
||||
runSequence(preTasks, 'testserver', 'testbrowser', cb);
|
||||
});
|
||||
|
||||
gulp.task('testonly', ['testserver', 'testbrowser']);
|
||||
|
||||
gulp.task('testserver', () => {
|
||||
const globs = [
|
||||
'common/**/__tests__/**/*.js',
|
||||
'server/**/__tests__/**/*.js',
|
||||
].concat(fileGlobs.forPluginServerTests());
|
||||
|
||||
return gulp.src(globs, { read: false })
|
||||
.pipe(g.mocha(MOCHA_OPTIONS));
|
||||
});
|
||||
|
||||
gulp.task('testbrowser', () => {
|
||||
return getEnabledPlugins().then(plugins => {
|
||||
return pluginHelpers.run('testBrowser', {
|
||||
plugins: plugins.join(','),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('testbrowser-dev', () => {
|
||||
return getEnabledPlugins().then(plugins => {
|
||||
return pluginHelpers.run('testBrowser', {
|
||||
dev: true,
|
||||
plugins: plugins.join(','),
|
||||
});
|
||||
});
|
||||
});
|
||||
require('./tasks/build')(gulp, gulpHelpers);
|
||||
require('./tasks/clean')(gulp, gulpHelpers);
|
||||
require('./tasks/dev')(gulp, gulpHelpers);
|
||||
require('./tasks/prepare')(gulp, gulpHelpers);
|
||||
require('./tasks/report')(gulp, gulpHelpers);
|
||||
require('./tasks/test')(gulp, gulpHelpers);
|
||||
require('./plugins/canvas/tasks')(gulp, gulpHelpers);
|
||||
|
|
|
@ -23,6 +23,7 @@ import { indexManagement } from './plugins/index_management';
|
|||
import { consoleExtensions } from './plugins/console_extensions';
|
||||
import { notifications } from './plugins/notifications';
|
||||
import { kueryAutocomplete } from './plugins/kuery_autocomplete';
|
||||
import { canvas } from './plugins/canvas';
|
||||
|
||||
module.exports = function (kibana) {
|
||||
return [
|
||||
|
@ -39,6 +40,7 @@ module.exports = function (kibana) {
|
|||
dashboardMode(kibana),
|
||||
logstash(kibana),
|
||||
apm(kibana),
|
||||
canvas(kibana),
|
||||
licenseManagement(kibana),
|
||||
cloud(kibana),
|
||||
indexManagement(kibana),
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
"kbn": "node ../scripts/kbn",
|
||||
"start": "gulp dev",
|
||||
"build": "gulp build",
|
||||
"lint": "gulp lint",
|
||||
"lintroller": "gulp lint --fixLint",
|
||||
"testonly": "gulp testonly",
|
||||
"test": "gulp test",
|
||||
"test:browser:dev": "gulp testbrowser-dev",
|
||||
|
@ -30,10 +28,16 @@
|
|||
"@types/jest": "^23.3.1",
|
||||
"@types/pngjs": "^3.3.1",
|
||||
"abab": "^1.0.4",
|
||||
"ansi-colors": "^3.0.5",
|
||||
"ansicolors": "0.3.2",
|
||||
"aws-sdk": "2.2.33",
|
||||
"axios": "^0.18.0",
|
||||
"babel-jest": "^23.4.2",
|
||||
"babel-plugin-inline-react-svg": "^0.5.4",
|
||||
"babel-plugin-mock-imports": "^0.0.5",
|
||||
"babel-plugin-pegjs-inline-precompile": "^0.1.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.14",
|
||||
"babel-register": "^6.26.0",
|
||||
"chalk": "^2.4.1",
|
||||
"chance": "1.0.10",
|
||||
"checksum": "0.1.1",
|
||||
|
@ -44,17 +48,17 @@
|
|||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-to-json": "3.3.1",
|
||||
"expect.js": "0.3.1",
|
||||
"fancy-log": "^1.3.2",
|
||||
"fetch-mock": "^5.13.1",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-load-plugins": "1.2.0",
|
||||
"gulp-mocha": "2.2.0",
|
||||
"gulp-rename": "1.2.2",
|
||||
"gulp-util": "3.0.7",
|
||||
"gulp-zip": "3.1.0",
|
||||
"gulp-pegjs": "^0.1.0",
|
||||
"gulp-multi-process": "^1.3.1",
|
||||
"hapi": "14.2.0",
|
||||
"jest": "^23.5.0",
|
||||
"jest-cli": "^23.5.0",
|
||||
"jest-styled-components": "^6.1.1",
|
||||
"jsdom": "9.9.1",
|
||||
"mocha": "3.3.0",
|
||||
"mustache": "^2.3.0",
|
||||
"mutation-observer": "^1.0.3",
|
||||
|
@ -67,8 +71,10 @@
|
|||
"redux-test-utils": "0.2.2",
|
||||
"rsync": "0.4.0",
|
||||
"run-sequence": "^2.2.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"simple-git": "1.37.0",
|
||||
"sinon": "^5.0.7",
|
||||
"squel": "^5.12.2",
|
||||
"supertest": "^3.1.0",
|
||||
"supertest-as-promised": "^4.0.2",
|
||||
"tmp": "0.0.31",
|
||||
|
@ -80,6 +86,7 @@
|
|||
"yargs": "4.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elastic/datemath": "^4.0.2",
|
||||
"@elastic/eui": "4.0.1",
|
||||
"@elastic/node-crypto": "0.1.2",
|
||||
"@elastic/node-phantom-simple": "2.2.4",
|
||||
|
@ -88,47 +95,67 @@
|
|||
"@kbn/i18n": "link:../packages/kbn-i18n",
|
||||
"@kbn/ui-framework": "link:../packages/kbn-ui-framework",
|
||||
"@samverschueren/stream-to-observable": "^0.3.0",
|
||||
"@scant/router": "^0.1.0",
|
||||
"@slack/client": "^4.2.2",
|
||||
"@types/moment-timezone": "^0.5.8",
|
||||
"angular-paging": "2.2.1",
|
||||
"angular-resource": "1.4.9",
|
||||
"angular-sanitize": "1.4.9",
|
||||
"angular-ui-ace": "0.2.3",
|
||||
"axios": "^0.18.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"base64-js": "^1.2.1",
|
||||
"bluebird": "3.1.1",
|
||||
"boom": "3.1.1",
|
||||
"brace": "0.11.1",
|
||||
"chroma-js": "^1.3.6",
|
||||
"classnames": "2.2.5",
|
||||
"concat-stream": "1.5.1",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"d3": "3.5.6",
|
||||
"d3-scale": "1.0.6",
|
||||
"dedent": "^0.7.0",
|
||||
"dragselect": "1.7.17",
|
||||
"elasticsearch": "^14.1.0",
|
||||
"extract-zip": "1.5.0",
|
||||
"file-saver": "^1.3.8",
|
||||
"font-awesome": "4.4.0",
|
||||
"get-port": "2.1.0",
|
||||
"getos": "^3.1.0",
|
||||
"glob": "6.0.4",
|
||||
"handlebars": "^4.0.10",
|
||||
"hapi-auth-cookie": "6.1.1",
|
||||
"history": "4.7.2",
|
||||
"humps": "2.0.1",
|
||||
"icalendar": "0.7.1",
|
||||
"inline-style": "^2.0.0",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"joi": "6.10.1",
|
||||
"jquery": "^3.3.1",
|
||||
"jstimezonedetect": "1.0.5",
|
||||
"lodash": "3.10.1",
|
||||
"lodash.clone": "^4.5.0",
|
||||
"lodash.keyby": "^4.6.0",
|
||||
"lodash.lowercase": "^4.3.0",
|
||||
"lodash.mean": "^4.1.0",
|
||||
"lodash.omitby": "^4.6.0",
|
||||
"lodash.orderby": "4.6.0",
|
||||
"lodash.pickby": "^4.6.0",
|
||||
"lodash.topath": "^4.5.2",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"markdown-it": "^8.4.1",
|
||||
"mime": "^2.2.2",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "^2.20.1",
|
||||
"moment-duration-format": "^1.3.0",
|
||||
"moment-timezone": "^0.5.14",
|
||||
"ngreact": "^0.5.1",
|
||||
"nodemailer": "^4.6.4",
|
||||
"object-path-immutable": "^0.5.3",
|
||||
"papaparse": "^4.6.0",
|
||||
"pdfmake": "0.1.33",
|
||||
"pivotal-ui": "13.0.1",
|
||||
"pluralize": "3.1.0",
|
||||
|
@ -137,9 +164,13 @@
|
|||
"prop-types": "^15.6.0",
|
||||
"puid": "1.0.5",
|
||||
"puppeteer-core": "^1.7.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"react": "^16.3.0",
|
||||
"react-beautiful-dnd": "^8.0.7",
|
||||
"react-clipboard.js": "^1.1.2",
|
||||
"react-datetime": "^2.14.0",
|
||||
"react-dom": "^16.3.0",
|
||||
"react-dropzone": "^4.2.9",
|
||||
"react-markdown-renderer": "^1.4.0",
|
||||
"react-portal": "^3.2.0",
|
||||
"react-redux": "^5.0.7",
|
||||
|
@ -147,21 +178,31 @@
|
|||
"react-router-breadcrumbs-hoc": "1.1.2",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-select": "^1.2.1",
|
||||
"react-shortcuts": "^2.0.0",
|
||||
"react-sticky": "^6.0.1",
|
||||
"react-syntax-highlighter": "^5.7.0",
|
||||
"react-vis": "^1.8.1",
|
||||
"recompose": "^0.26.0",
|
||||
"reduce-reducers": "^0.1.2",
|
||||
"redux": "4.0.0",
|
||||
"redux-actions": "2.2.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"redux-thunks": "^1.0.0",
|
||||
"request": "^2.85.0",
|
||||
"reselect": "3.0.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"rison-node": "0.3.1",
|
||||
"rxjs": "^6.2.1",
|
||||
"scriptjs": "^2.5.8",
|
||||
"semver": "5.1.0",
|
||||
"socket.io": "^1.7.3",
|
||||
"socket.io-client": "^1.7.3",
|
||||
"stream-stream": "^1.2.6",
|
||||
"style-it": "^1.6.12",
|
||||
"styled-components": "3.3.3",
|
||||
"tar-fs": "1.13.0",
|
||||
"tinycolor2": "1.3.0",
|
||||
"tinymath": "^0.5.0",
|
||||
"tslib": "^1.9.3",
|
||||
"ui-select": "0.19.4",
|
||||
"unbzip2-stream": "1.0.9",
|
||||
|
|
54
x-pack/plugins/canvas/.gitignore
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
target
|
||||
build
|
||||
.kibana-plugin-helpers.dev.json
|
||||
|
||||
# JetBrains IDEs
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# TEMPORARY: sass build output
|
||||
public/style/index.css
|
||||
|
||||
# Don't commit built plugin files
|
||||
canvas_plugin/*
|
6
x-pack/plugins/canvas/.prettierrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5"
|
||||
}
|
184
x-pack/plugins/canvas/README.md
Normal file
|
@ -0,0 +1,184 @@
|
|||
# kibana-canvas
|
||||
|
||||
"Never look back. The past is done. The future is a blank canvas." ― Suzy Kassem, Rise Up and Salute the Sun
|
||||
|
||||
### Getting Started
|
||||
|
||||
Use the following directory structure to run Canvas:
|
||||
|
||||
```bash
|
||||
$ ls $PATH_TO_REPOS
|
||||
├── kibana
|
||||
└── kibana-extra/kibana-canvas
|
||||
```
|
||||
|
||||
Setup `kibana` and `elasticsearch`. See instructions [here](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#setting-up-your-development-environment).
|
||||
|
||||
Fork, then clone the [Canvas](https://github.com/elastic/kibana-canvas) repo into `kibana-extra/` and change directory into it.
|
||||
|
||||
```bash
|
||||
# cd kibana-extra/
|
||||
git clone https://github.com/[YOUR_USERNAME]/kibana-canvas.git
|
||||
cd kibana-canvas
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```bash
|
||||
# in kibana-canvas/
|
||||
yarn kbn bootstrap
|
||||
```
|
||||
|
||||
Start Canvas
|
||||
|
||||
```bash
|
||||
# in kibana-canvas/
|
||||
yarn start
|
||||
```
|
||||
|
||||
### Feature Questions
|
||||
|
||||
**Why are there no tooltips**
|
||||
|
||||
We've opted for always available data labels instead, for now. While there exists much functionality that can be used for analytical purposes in Canvas our core concern in presentational. In a hands-off presentation format, such as a report or a slideshow, there is no facility for user to mouseover a chart to see a tooltip; data labels are a better fit for us.
|
||||
|
||||
### Background
|
||||
|
||||
**What is Kibana Canvas?**
|
||||
|
||||
Kibana Canvas is a new visualization application on top of Elasticsearch data. Canvas is extremely versatile, but particularly differentiating example use cases include live infographics, presentations with live-updating charts, and highly customized reports.
|
||||
|
||||
**Why did we build it? How does this align with the larger Kibana vision?**
|
||||
|
||||
We realized early on that we are not trying to build one UI “to rule them all” in Kibana. Elasticsearch caters to a wide variety of use cases, users, and audiences and Kibana provides different experiences for these users to explore and interact with their data. Canvas is one of such applications, in particular catering to users looking for desktop-publishing level of control for the presentation of their data summaries.
|
||||
|
||||
**Does Canvas replace any part of Kibana?**
|
||||
|
||||
No, it is an alternative experience that does not conflict with other parts of Kibana.
|
||||
|
||||
**Isn’t there overlap between Canvas and Dashboard?**
|
||||
|
||||
While both can be used as a way to build up reports, Canvas and Dashboard have different goals. Canvas focuses on highly customizable layout more suited to highly curated presentations, while Dashboard provides a fast and efficient way to build up and manage business analytics and operational dashboards that don’t require a high degree of layout control and customizability.
|
||||
|
||||
**Where can I see a demo of Canvas?**
|
||||
|
||||
Internal demo at dev demo day (starts at 00:02:04)
|
||||
https://drive.google.com/file/d/0B1QVAZnA-FxtdGNNRW9vY09fTkE/view
|
||||
|
||||
Elasticon 2017 keynote (starts at 01:27:00)
|
||||
https://www.elastic.co/elasticon/conf/2017/sf/opening-keynote
|
||||
|
||||
**How can I get an early build?**
|
||||
|
||||
No internal build available yet.
|
||||
|
||||
**OK, fine, be like that. Where can I get screenshots?**
|
||||
|
||||
If you want a stream of conciousness of the absolute latest development, scroll to the end of Rashid's "blog issue"
|
||||
https://github.com/elastic/kibana-canvas/issues/109
|
||||
|
||||
Screenshots from the ElasticON talk are available here:
|
||||
https://drive.google.com/drive/u/0/folders/0B1DdqIqU4qUNZklhU0xaM1lRYUE
|
||||
|
||||
### Engineering
|
||||
|
||||
**Where does Canvas code live?**
|
||||
|
||||
For now all of the code lives in this repo: https://github.com/elastic/kibana-canvas
|
||||
|
||||
**Where can I find Canvas milestones / roadmap?**
|
||||
|
||||
Some notes [here](https://docs.google.com/document/d/1UPHeTqugEo0CbCKGK-afNK1iEbQtWQv6t7DTDumRY14/edit?pli=1#), permanent place TBD. The roadmap is, as usual, subject to change.
|
||||
|
||||
**How will embeddability work? Will it be possible to embed visualizations (including Timelion and TSVB) in Canvas? Will it be possible to embed Canvas visualizations in Dashboard?**
|
||||
|
||||
We plan to allow for saved Kibana visualizations to be embedded within Canvas. Going the other direction is less certain and requires review of the benefits, engineering and tradeoffs.
|
||||
|
||||
**How will Canvas work with “Dashboard only” mode?**
|
||||
|
||||
Canvas work pads have an editable and non-editable mode. In dashboard only mode there will be no option to enable editing of the work pad.
|
||||
|
||||
**How will Canvas work with reporting?**
|
||||
|
||||
We plan to allow Canvas work pads to be exportable to PDF via reporting. Canvas pages can be setup as paper-sized to allow for pixel perfect printing
|
||||
|
||||
### Go-to-market
|
||||
|
||||
**Will this be Open Source? Basic? Gold? Platinum?**
|
||||
|
||||
The current plan is X-Pack Basic (not to share externally). Some parts and plugins may be open source but the core will part of X-Pack
|
||||
|
||||
**We demoed this internally and then at Elastic{ON}, and it looked pretty finished. When will this be released in GA?**
|
||||
|
||||
What you saw in the previous demos was a well-polished prototype. There are still a number of important engineering considerations to work out, which we are in the process of doing, so GA is TBD.
|
||||
|
||||
**What are the next planned milestones?**
|
||||
|
||||
Refer to details of planned milestones [here](https://docs.google.com/document/d/1UPHeTqugEo0CbCKGK-afNK1iEbQtWQv6t7DTDumRY14/edit?pli=1#).
|
||||
|
||||
**Will there be an internal and external testing / beta testing period?**
|
||||
|
||||
Yes, here is the tentative release process for Milestone 1
|
||||
|
||||
- Internal release (a few weeks?)
|
||||
- Goal: Make Milestone 1 candidate build good enough that we could release it publicly if we so choose, but to get feedback internally first
|
||||
- Decide if it’s good enough for public release
|
||||
- Public “research” build (a couple of months?)
|
||||
- Goal: Fast iterations as feedback comes in (daily, if necessary)
|
||||
- Separate plugin that requires X-Pack
|
||||
- We’ll enforce it on the plugin layer, so it won't install or run without x-pack, but it will be distributed separately
|
||||
- Public beta or GA distributed with the stack
|
||||
- Details TBD
|
||||
|
||||
### Contact
|
||||
|
||||
**Who should I contact internally to talk about Canvas engineering or go-to-market questions?**
|
||||
|
||||
Canvas is a functional area within Kibana with Rashid Khan as lead, Joe Fleming as engineer, and Alex Francouer & Tanya Bragin as product managers.
|
||||
|
||||
**Can customers that saw a demo at Canvas at Keynote provide feedback and get an update?**
|
||||
|
||||
Absolutely. Kibana team is open to feedback on the concept of Canvas. Please contact pm@elastic.co to schedule a conversation about your use case and how you envision using Canvas.
|
||||
|
||||
### Releases
|
||||
|
||||
Releases are uploaded to AWS S3. These instructions assume you have already setup MFA and have installed the AWS CLI tools. To get your release credentials run:
|
||||
|
||||
```
|
||||
aws sts get-session-token --serial-number <AWS Assigned MFA Device ID> --token-code <Token Code>
|
||||
```
|
||||
|
||||
You can find the MFA Device ID at:
|
||||
|
||||
```
|
||||
AWS Console -> IAM -> Find your username -> Security Credentials -> Assign MFA Device (string starting with arn:aws....)
|
||||
```
|
||||
|
||||
That will dump something that looks like:
|
||||
|
||||
```
|
||||
[default]
|
||||
{
|
||||
"Credentials": {
|
||||
"SecretAccessKey": "<Your new secret access key>",
|
||||
"SessionToken": "<Your new big long session token>",
|
||||
"Expiration": "2018-01-31T09:22:34Z",
|
||||
"AccessKeyId": "<Your new access key id>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can then move this information to your `~/.aws/credentials` file:
|
||||
|
||||
```
|
||||
[default]
|
||||
aws_secret_access_key = <Your new secret access key>
|
||||
aws_session_token = <Your new big long session token>
|
||||
aws_access_key_id = <Your new access key id>
|
||||
```
|
||||
|
||||
To publish the release run:
|
||||
|
||||
```
|
||||
npm run release
|
||||
```
|
15
x-pack/plugins/canvas/__tests__/fixtures/elasticsearch.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export default function Client() {
|
||||
this.indices = {
|
||||
putMapping: () => Promise.resolve({ acknowledged: true }),
|
||||
exists: () => Promise.resolve(false),
|
||||
refresh: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
this.transport = {};
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import MockElasticsearchClient from './elasticsearch';
|
||||
|
||||
export default {
|
||||
getCluster: () => ({
|
||||
getClient: () => new MockElasticsearchClient(),
|
||||
}),
|
||||
status: {
|
||||
once: () => Promise.resolve(),
|
||||
},
|
||||
};
|
48
x-pack/plugins/canvas/__tests__/fixtures/kibana.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get, has, noop } from 'lodash';
|
||||
import mockElasticsearch from './elasticsearch_plugin';
|
||||
|
||||
const config = {
|
||||
canvas: {
|
||||
enabled: true,
|
||||
indexPrefix: '.canvas',
|
||||
},
|
||||
};
|
||||
|
||||
export class Plugin {
|
||||
constructor(props) {
|
||||
this.props = props;
|
||||
this.routes = [];
|
||||
this.server = {
|
||||
plugins: {
|
||||
[this.props.name]: {},
|
||||
elasticsearch: mockElasticsearch,
|
||||
},
|
||||
injectUiAppVars: noop,
|
||||
config: () => ({
|
||||
get: key => get(config, key),
|
||||
has: key => has(config, key),
|
||||
}),
|
||||
route: def => this.routes.push(def),
|
||||
usage: {
|
||||
collectorSet: {
|
||||
makeUsageCollector: () => {},
|
||||
register: () => {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { init } = this.props;
|
||||
|
||||
this.init = () => init(this.server);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Plugin,
|
||||
};
|
112
x-pack/plugins/canvas/__tests__/fixtures/workpads.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const workpads = [
|
||||
{
|
||||
pages: [
|
||||
{
|
||||
elements: [
|
||||
{
|
||||
expression: `
|
||||
demodata |
|
||||
ply by=age fn={rowCount | as count} |
|
||||
staticColumn total value={math 'sum(count)'} |
|
||||
mapColumn percentage fn={math 'count/total * 100'} |
|
||||
sort age |
|
||||
pointseries x=age y=percentage |
|
||||
plot defaultStyle={seriesStyle points=0 lines=5}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pages: [{ elements: [{ expression: 'filters | demodata | markdown "hello" | render' }] }],
|
||||
},
|
||||
{
|
||||
pages: [
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'filters | demodata | markdown "hello" | render' },
|
||||
{ expression: 'filters | demodata | pointseries | pie | render' },
|
||||
],
|
||||
},
|
||||
{ elements: [{ expression: 'filters | demodata | table | render' }] },
|
||||
{ elements: [{ expression: 'image | render' }] },
|
||||
{ elements: [{ expression: 'image | render' }] },
|
||||
],
|
||||
},
|
||||
{
|
||||
pages: [
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'filters | demodata | markdown "hello" | render' },
|
||||
{ expression: 'filters | demodata | markdown "hello" | render' },
|
||||
{ expression: 'image | render' },
|
||||
],
|
||||
},
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'filters | demodata | markdown "hello" | render' },
|
||||
{ expression: 'filters | demodata | pointseries | pie | render' },
|
||||
{ expression: 'image | render' },
|
||||
],
|
||||
},
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'filters | demodata | pointseries | pie | render' },
|
||||
{
|
||||
expression:
|
||||
'filters | demodata | pointseries | plot defaultStyle={seriesStyle points=0 lines=5} | render',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pages: [
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'demodata | render as=debug' },
|
||||
{ expression: 'filters | demodata | pointseries | plot | render' },
|
||||
{ expression: 'filters | demodata | table | render' },
|
||||
{ expression: 'filters | demodata | table | render' },
|
||||
],
|
||||
},
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'filters | demodata | pointseries | pie | render' },
|
||||
{ expression: 'image | render' },
|
||||
],
|
||||
},
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'demodata | render as=debug' },
|
||||
{ expression: 'shape "square" | render' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pages: [
|
||||
{
|
||||
elements: [
|
||||
{ expression: 'demodata | pointseries | getCell | repeatImage | render' },
|
||||
{ expression: 'filters | demodata | markdown "hello" | render' },
|
||||
],
|
||||
},
|
||||
{ elements: [{ expression: 'image | render' }] },
|
||||
{ elements: [{ expression: 'image | render' }] },
|
||||
{ elements: [{ expression: 'filters | demodata | table | render' }] },
|
||||
],
|
||||
},
|
||||
];
|
18
x-pack/plugins/canvas/__tests__/helpers/function_wrapper.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { mapValues } from 'lodash';
|
||||
|
||||
// It takes a function spec and passes in default args into the spec fn
|
||||
export const functionWrapper = (fnSpec, mockReduxStore) => {
|
||||
const spec = fnSpec();
|
||||
const defaultArgs = mapValues(spec.args, argSpec => {
|
||||
return argSpec.default;
|
||||
});
|
||||
|
||||
return (context, args, handlers) =>
|
||||
spec.fn(context, { ...defaultArgs, ...args }, handlers, mockReduxStore);
|
||||
};
|
0
x-pack/plugins/canvas/canvas_plugin/.empty
Normal file
After Width: | Height: | Size: 149 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const areaChart = () => {
|
||||
return {
|
||||
name: 'areaChart',
|
||||
displayName: 'Area Chart',
|
||||
help: 'A line chart with a filled body',
|
||||
image: require('./header.png'),
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="time" y="mean(price)"
|
||||
| plot defaultStyle={seriesStyle lines=1 fill=1}
|
||||
| render`,
|
||||
};
|
||||
};
|
After Width: | Height: | Size: 67 KiB |
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const bubbleChart = () => ({
|
||||
name: 'bubbleChart',
|
||||
displayName: 'Bubble Chart',
|
||||
help: 'A customizable bubble chart',
|
||||
width: 700,
|
||||
height: 300,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="project" y="sum(price)" color="state" size="size(username)"
|
||||
| plot defaultStyle={seriesStyle points=5 fill=1}
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const debug = () => ({
|
||||
name: 'debug',
|
||||
displayName: 'Debug',
|
||||
help: 'Just dumps the configuration of the element',
|
||||
image: header,
|
||||
expression: `demodata
|
||||
| render as=debug`,
|
||||
});
|
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const donut = () => ({
|
||||
name: 'donut',
|
||||
displayName: 'Donut Chart',
|
||||
help: 'A customizable donut chart',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries color="project" size="max(price)"
|
||||
| pie hole=50 labels=false legend="ne"
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const dropdownFilter = () => ({
|
||||
name: 'dropdown_filter',
|
||||
displayName: 'Dropdown Filter',
|
||||
help: 'A dropdown from which you can select values for an "exactly" filter',
|
||||
image: header,
|
||||
height: 50,
|
||||
expression: `demodata
|
||||
| dropdownControl valueColumn=project filterColumn=project | render`,
|
||||
filter: '',
|
||||
});
|
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const horizontalBarChart = () => ({
|
||||
name: 'horizontalBarChart',
|
||||
displayName: 'Horizontal Bar Chart',
|
||||
help: 'A customizable horizontal bar chart',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="size(cost)" y="project" color="project"
|
||||
| plot defaultStyle={seriesStyle bars=0.75 horizontalBars=true} legend=false
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 91 KiB |
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const image = () => ({
|
||||
name: 'image',
|
||||
displayName: 'Image',
|
||||
help: 'A static image.',
|
||||
image: header,
|
||||
expression: `image mode="contain"
|
||||
| render`,
|
||||
});
|
47
x-pack/plugins/canvas/canvas_plugin_src/elements/index.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { areaChart } from './area_chart';
|
||||
import { bubbleChart } from './bubble_chart';
|
||||
import { debug } from './debug';
|
||||
import { donut } from './donut';
|
||||
import { dropdownFilter } from './dropdown_filter';
|
||||
import { image } from './image';
|
||||
import { horizontalBarChart } from './horiz_bar_chart';
|
||||
import { lineChart } from './line_chart';
|
||||
import { markdown } from './markdown';
|
||||
import { metric } from './metric';
|
||||
import { pie } from './pie';
|
||||
import { plot } from './plot';
|
||||
import { repeatImage } from './repeatImage';
|
||||
import { revealImage } from './revealImage';
|
||||
import { shape } from './shape';
|
||||
import { table } from './table';
|
||||
import { tiltedPie } from './tilted_pie';
|
||||
import { timeFilter } from './time_filter';
|
||||
import { verticalBarChart } from './vert_bar_chart';
|
||||
|
||||
export const elementSpecs = [
|
||||
areaChart,
|
||||
bubbleChart,
|
||||
debug,
|
||||
donut,
|
||||
dropdownFilter,
|
||||
image,
|
||||
horizontalBarChart,
|
||||
lineChart,
|
||||
markdown,
|
||||
metric,
|
||||
pie,
|
||||
plot,
|
||||
repeatImage,
|
||||
revealImage,
|
||||
shape,
|
||||
table,
|
||||
tiltedPie,
|
||||
timeFilter,
|
||||
verticalBarChart,
|
||||
];
|
After Width: | Height: | Size: 58 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const lineChart = () => ({
|
||||
name: 'lineChart',
|
||||
displayName: 'Line Chart',
|
||||
help: 'A customizable line chart',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="time" y="mean(price)"
|
||||
| plot defaultStyle={seriesStyle lines=3}
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 33 KiB |
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const markdown = () => ({
|
||||
name: 'markdown',
|
||||
displayName: 'Markdown',
|
||||
help: 'Markup from Markdown',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| markdown "### Welcome to the Markdown Element.
|
||||
|
||||
Good news! You're already connected to some demo data!
|
||||
|
||||
The datatable contains
|
||||
**{{rows.length}} rows**, each containing
|
||||
the following columns:
|
||||
{{#each columns}}
|
||||
**{{name}}**
|
||||
{{/each}}
|
||||
|
||||
You can use standard Markdown in here, but you can also access your piped-in data using Handlebars. If you want to know more, check out the [Handlebars Documentation](http://handlebarsjs.com/expressions.html)
|
||||
|
||||
#### Enjoy!" | render`,
|
||||
});
|
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const metric = () => ({
|
||||
name: 'metric',
|
||||
displayName: 'Metric',
|
||||
help: 'A number with a label',
|
||||
width: 200,
|
||||
height: 100,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "unique(country)"
|
||||
| metric "Countries"
|
||||
metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48}
|
||||
labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"}
|
||||
| render`,
|
||||
});
|
BIN
x-pack/plugins/canvas/canvas_plugin_src/elements/pie/header.png
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const pie = () => ({
|
||||
name: 'pie',
|
||||
displayName: 'Pie chart',
|
||||
width: 300,
|
||||
height: 300,
|
||||
help: 'A simple pie chart',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries color="state" size="max(price)"
|
||||
| pie
|
||||
| render`,
|
||||
});
|
BIN
x-pack/plugins/canvas/canvas_plugin_src/elements/plot/header.png
Normal file
After Width: | Height: | Size: 37 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const plot = () => ({
|
||||
name: 'plot',
|
||||
displayName: 'Coordinate plot',
|
||||
help: 'Mixed line, bar or dot charts',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="time" y="sum(price)" color="state"
|
||||
| plot defaultStyle={seriesStyle points=5}
|
||||
| render`,
|
||||
});
|
|
@ -4,9 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
module.exports = function logger() {
|
||||
const DEBUG = process.env.DEBUG || false;
|
||||
import { elementSpecs } from './index';
|
||||
|
||||
if (!DEBUG) return;
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
elementSpecs.forEach(canvas.register);
|
After Width: | Height: | Size: 82 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const repeatImage = () => ({
|
||||
name: 'repeatImage',
|
||||
displayName: 'Image Repeat',
|
||||
help: 'Repeats an image N times',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(cost)"
|
||||
| repeatImage image=null
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 8.8 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const revealImage = () => ({
|
||||
name: 'revealImage',
|
||||
displayName: 'Image Reveal',
|
||||
help: 'Reveals a percentage of an image',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "sum(min(cost) / max(cost))"
|
||||
| revealImage origin=bottom image=null
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const shape = () => ({
|
||||
name: 'shape',
|
||||
displayName: 'Shape',
|
||||
help: 'A customizable shape',
|
||||
width: 200,
|
||||
height: 200,
|
||||
image: header,
|
||||
expression:
|
||||
'shape "square" fill="#4cbce4" border="rgba(255,255,255,0)" borderWidth=0 maintainAspect=true | render',
|
||||
});
|
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const table = () => ({
|
||||
name: 'table',
|
||||
displayName: 'Data Table',
|
||||
help: 'A scrollable grid for displaying data in a tabular format',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| table
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const tiltedPie = () => ({
|
||||
name: 'tiltedPie',
|
||||
displayName: 'Tilted Pie Chart',
|
||||
width: 500,
|
||||
height: 250,
|
||||
help: 'A customizable tilted pie chart',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries color="project" size="max(price)"
|
||||
| pie tilt=0.5
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 33 KiB |
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const timeFilter = () => ({
|
||||
name: 'time_filter',
|
||||
displayName: 'Time Filter',
|
||||
help: 'Set a time window',
|
||||
image: header,
|
||||
height: 50,
|
||||
expression: `timefilterControl compact=true column=@timestamp
|
||||
| render`,
|
||||
filter: 'timefilter column=@timestamp from=now-24h to=now',
|
||||
});
|
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import header from './header.png';
|
||||
|
||||
export const verticalBarChart = () => ({
|
||||
name: 'verticalBarChart',
|
||||
displayName: 'Vertical Bar Chart',
|
||||
help: 'A customizable vertical bar chart',
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="project" y="size(cost)" color="project"
|
||||
| plot defaultStyle={seriesStyle bars=0.75} legend=false
|
||||
| render`,
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { markdown } from '../markdown';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { testTable } from '../../common/__tests__/fixtures/test_tables';
|
||||
import { fontStyle } from '../../common/__tests__/fixtures/test_styles';
|
||||
|
||||
describe('markdown', () => {
|
||||
const fn = functionWrapper(markdown);
|
||||
|
||||
it('returns a render as markdown', () => {
|
||||
const result = fn(null, { expression: [''], font: fontStyle });
|
||||
expect(result)
|
||||
.to.have.property('type', 'render')
|
||||
.and.to.have.property('as', 'markdown');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('expression', () => {
|
||||
it('sets the content to all strings in expression concatenated', () => {
|
||||
const result = fn(null, {
|
||||
expression: ['# this ', 'is ', 'some ', 'markdown'],
|
||||
font: fontStyle,
|
||||
});
|
||||
|
||||
expect(result.value).to.have.property('content', '# this is some markdown');
|
||||
});
|
||||
|
||||
it('compiles and concatenates handlebars expressions using context', () => {
|
||||
let expectedContent = 'Columns:';
|
||||
testTable.columns.map(col => (expectedContent += ` ${col.name}`));
|
||||
|
||||
const result = fn(testTable, {
|
||||
expression: ['Columns:', '{{#each columns}} {{name}}{{/each}}'],
|
||||
});
|
||||
|
||||
expect(result.value).to.have.property('content', expectedContent);
|
||||
});
|
||||
|
||||
// it('returns a markdown object with no content', () => {
|
||||
// const result = fn(null, { font: fontStyle });
|
||||
|
||||
// expect(result.value).to.have.property('content', '');
|
||||
// });
|
||||
});
|
||||
|
||||
describe('font', () => {
|
||||
it('sets the font style for the markdown', () => {
|
||||
const result = fn(null, {
|
||||
expression: ['some ', 'markdown'],
|
||||
font: fontStyle,
|
||||
});
|
||||
|
||||
expect(result.value).to.have.property('font', fontStyle);
|
||||
});
|
||||
|
||||
// TODO: write test when using an instance of the interpreter
|
||||
// it("defaults to the expression '{font}'", () => {});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const browser = () => ({
|
||||
name: 'browser',
|
||||
help: 'Force the interpreter to return to the browser',
|
||||
args: {},
|
||||
fn: context => context,
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { browser } from './browser';
|
||||
import { location } from './location';
|
||||
import { urlparam } from './urlparam';
|
||||
import { markdown } from './markdown';
|
||||
|
||||
export const functions = [browser, location, urlparam, markdown];
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export const location = () => ({
|
||||
name: 'location',
|
||||
type: 'datatable',
|
||||
context: {
|
||||
types: ['null'],
|
||||
},
|
||||
help:
|
||||
"Use the browser's location functionality to get your current location. Usually quite slow, but fairly accurate",
|
||||
fn: () => {
|
||||
return new Promise(resolve => {
|
||||
function createLocation(geoposition) {
|
||||
const { latitude, longitude } = geoposition.coords;
|
||||
return resolve({
|
||||
type: 'datatable',
|
||||
columns: [{ name: 'latitude', type: 'number' }, { name: 'longitude', type: 'number' }],
|
||||
rows: [{ latitude, longitude }],
|
||||
});
|
||||
}
|
||||
return navigator.geolocation.getCurrentPosition(createLocation, noop, {
|
||||
maximumAge: 5000,
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Handlebars } from '../../../common/lib/handlebars';
|
||||
|
||||
export const markdown = () => ({
|
||||
name: 'markdown',
|
||||
aliases: [],
|
||||
type: 'render',
|
||||
help:
|
||||
'An element for rendering markdown text. Great for single numbers, metrics or paragraphs of text.',
|
||||
context: {
|
||||
types: ['datatable', 'null'],
|
||||
},
|
||||
args: {
|
||||
expression: {
|
||||
aliases: ['_'],
|
||||
types: ['string'],
|
||||
help: 'A markdown expression. You can pass this multiple times to achieve concatenation',
|
||||
default: '""',
|
||||
multi: true,
|
||||
},
|
||||
font: {
|
||||
types: ['style'],
|
||||
help: 'Font settings. Technically you can stick other styles in here too!',
|
||||
default: '{font}',
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
const compileFunctions = args.expression.map(str => Handlebars.compile(String(str)));
|
||||
const ctx = {
|
||||
columns: [],
|
||||
rows: [],
|
||||
type: null,
|
||||
...context,
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'markdown',
|
||||
value: {
|
||||
content: compileFunctions.map(fn => fn(ctx)).join(''),
|
||||
font: args.font,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { functions } from './index';
|
||||
|
||||
functions.forEach(canvas.register);
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { parse } from 'url';
|
||||
|
||||
export const urlparam = () => ({
|
||||
name: 'urlparam',
|
||||
aliases: [],
|
||||
type: 'string',
|
||||
help:
|
||||
'Access URL parameters and use them in expressions. Eg https://localhost:5601/app/canvas?myVar=20. This will always return a string',
|
||||
context: {
|
||||
types: ['null'],
|
||||
},
|
||||
args: {
|
||||
param: {
|
||||
types: ['string'],
|
||||
aliases: ['_', 'var', 'variable'],
|
||||
help: 'The URL hash parameter to access',
|
||||
multi: false,
|
||||
},
|
||||
default: {
|
||||
types: ['string'],
|
||||
default: '""',
|
||||
help: 'Return this string if the url parameter is not defined',
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
const query = parse(window.location.href, true).query;
|
||||
return query[args.param] || args.default;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { all } from '../all';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('all', () => {
|
||||
const fn = functionWrapper(all);
|
||||
|
||||
it('should return true with no conditions', () => {
|
||||
expect(fn(null, {})).to.be(true);
|
||||
expect(fn(null, { condition: [] })).to.be(true);
|
||||
});
|
||||
|
||||
it('should return true when all conditions are true', () => {
|
||||
expect(fn(null, { condition: [true] })).to.be(true);
|
||||
expect(fn(null, { condition: [true, true, true] })).to.be(true);
|
||||
});
|
||||
|
||||
it('should return true when all conditions are truthy', () => {
|
||||
expect(fn(null, { condition: [true, 1, 'hooray', {}] })).to.be(true);
|
||||
});
|
||||
|
||||
it('should return false when at least one condition is false', () => {
|
||||
expect(fn(null, { condition: [false, true, true] })).to.be(false);
|
||||
expect(fn(null, { condition: [false, false, true] })).to.be(false);
|
||||
expect(fn(null, { condition: [false, false, false] })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return false when at least one condition is falsy', () => {
|
||||
expect(fn(null, { condition: [true, 0, 'hooray', {}] })).to.be(false);
|
||||
expect(fn(null, { condition: [true, 1, 'hooray', null] })).to.be(false);
|
||||
expect(fn(null, { condition: [true, 1, '', {}] })).to.be(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { alterColumn } from '../alterColumn';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { emptyTable, testTable } from './fixtures/test_tables';
|
||||
|
||||
describe('alterColumn', () => {
|
||||
const fn = functionWrapper(alterColumn);
|
||||
const nameColumnIndex = testTable.columns.findIndex(({ name }) => name === 'name');
|
||||
const timeColumnIndex = testTable.columns.findIndex(({ name }) => name === 'time');
|
||||
const priceColumnIndex = testTable.columns.findIndex(({ name }) => name === 'price');
|
||||
const inStockColumnIndex = testTable.columns.findIndex(({ name }) => name === 'in_stock');
|
||||
|
||||
it('returns a datatable', () => {
|
||||
const alteredTable = fn(testTable, { column: 'price', type: 'string', name: 'priceString' });
|
||||
|
||||
expect(alteredTable.type).to.be('datatable');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
it('returns original context if no args are provided', () => {
|
||||
expect(fn(testTable)).to.eql(testTable);
|
||||
});
|
||||
|
||||
describe('column', () => {
|
||||
// ISO 8601 string -> date
|
||||
it('specifies which column to alter', () => {
|
||||
const dateToString = fn(testTable, { column: 'time', type: 'string', name: 'timeISO' });
|
||||
const originalColumn = testTable.columns[timeColumnIndex];
|
||||
const newColumn = dateToString.columns[timeColumnIndex];
|
||||
const arbitraryRowIndex = 6;
|
||||
|
||||
expect(newColumn.name).to.not.be(originalColumn.name);
|
||||
expect(newColumn.type).to.not.be(originalColumn.type);
|
||||
expect(dateToString.rows[arbitraryRowIndex].timeISO).to.be.a('string');
|
||||
expect(new Date(dateToString.rows[arbitraryRowIndex].timeISO)).to.eql(
|
||||
new Date(testTable.rows[arbitraryRowIndex].time)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns original context if column is not specified', () => {
|
||||
expect(fn(testTable, { type: 'date', name: 'timeISO' })).to.eql(testTable);
|
||||
});
|
||||
|
||||
it('throws if column does not exists', () => {
|
||||
expect(() => fn(emptyTable, { column: 'foo', type: 'number' })).to.throwException(e => {
|
||||
expect(e.message).to.be("Column not found: 'foo'");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('type', () => {
|
||||
it('converts the column to the specified type', () => {
|
||||
const dateToString = fn(testTable, { column: 'time', type: 'string', name: 'timeISO' });
|
||||
|
||||
expect(dateToString.columns[timeColumnIndex].type).to.be('string');
|
||||
expect(dateToString.rows[timeColumnIndex].timeISO).to.be.a('string');
|
||||
expect(new Date(dateToString.rows[timeColumnIndex].timeISO)).to.eql(
|
||||
new Date(testTable.rows[timeColumnIndex].time)
|
||||
);
|
||||
});
|
||||
|
||||
it('does not change column if type is not specified', () => {
|
||||
const unconvertedColumn = fn(testTable, { column: 'price', name: 'foo' });
|
||||
const originalType = testTable.columns[priceColumnIndex].type;
|
||||
const arbitraryRowIndex = 2;
|
||||
|
||||
expect(unconvertedColumn.columns[priceColumnIndex].type).to.be(originalType);
|
||||
expect(unconvertedColumn.rows[arbitraryRowIndex].foo).to.be.a(
|
||||
originalType,
|
||||
testTable.rows[arbitraryRowIndex].price
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when converting to an invalid type', () => {
|
||||
expect(() => fn(testTable, { column: 'name', type: 'foo' })).to.throwException(e => {
|
||||
expect(e.message).to.be('Cannot convert to foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('name', () => {
|
||||
it('changes column name to specified name', () => {
|
||||
const dateToString = fn(testTable, { column: 'time', type: 'date', name: 'timeISO' });
|
||||
const arbitraryRowIndex = 8;
|
||||
|
||||
expect(dateToString.columns[timeColumnIndex].name).to.be('timeISO');
|
||||
expect(dateToString.rows[arbitraryRowIndex]).to.have.property('timeISO');
|
||||
});
|
||||
|
||||
it('overwrites existing column if provided an existing column name', () => {
|
||||
const overwriteName = fn(testTable, { column: 'time', type: 'string', name: 'name' });
|
||||
const originalColumn = testTable.columns[timeColumnIndex];
|
||||
const newColumn = overwriteName.columns[nameColumnIndex];
|
||||
const arbitraryRowIndex = 5;
|
||||
|
||||
expect(newColumn.name).to.not.be(originalColumn.name);
|
||||
expect(newColumn.type).to.not.be(originalColumn.type);
|
||||
expect(overwriteName.rows[arbitraryRowIndex].name).to.be.a('string');
|
||||
expect(new Date(overwriteName.rows[arbitraryRowIndex].name)).to.eql(
|
||||
new Date(testTable.rows[arbitraryRowIndex].time)
|
||||
);
|
||||
});
|
||||
|
||||
it('retains original column name if name is not provided', () => {
|
||||
const unchangedName = fn(testTable, { column: 'price', type: 'string' });
|
||||
|
||||
expect(unchangedName.columns[priceColumnIndex].name).to.be(
|
||||
testTable.columns[priceColumnIndex].name
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('valid type conversions', () => {
|
||||
it('converts number <-> string', () => {
|
||||
const arbitraryRowIndex = 4;
|
||||
const numberToString = fn(testTable, { column: 'price', type: 'string' });
|
||||
|
||||
expect(numberToString.columns[priceColumnIndex])
|
||||
.to.have.property('name', 'price')
|
||||
.and.to.have.property('type', 'string');
|
||||
expect(numberToString.rows[arbitraryRowIndex].price)
|
||||
.to.be.a('string')
|
||||
.and.to.eql(testTable.rows[arbitraryRowIndex].price);
|
||||
|
||||
const stringToNumber = fn(numberToString, { column: 'price', type: 'number' });
|
||||
|
||||
expect(stringToNumber.columns[priceColumnIndex])
|
||||
.to.have.property('name', 'price')
|
||||
.and.to.have.property('type', 'number');
|
||||
expect(stringToNumber.rows[arbitraryRowIndex].price)
|
||||
.to.be.a('number')
|
||||
.and.to.eql(numberToString.rows[arbitraryRowIndex].price);
|
||||
});
|
||||
|
||||
it('converts date <-> string', () => {
|
||||
const arbitraryRowIndex = 4;
|
||||
const dateToString = fn(testTable, { column: 'time', type: 'string' });
|
||||
|
||||
expect(dateToString.columns[timeColumnIndex])
|
||||
.to.have.property('name', 'time')
|
||||
.and.to.have.property('type', 'string');
|
||||
expect(dateToString.rows[arbitraryRowIndex].time).to.be.a('string');
|
||||
expect(new Date(dateToString.rows[arbitraryRowIndex].time)).to.eql(
|
||||
new Date(testTable.rows[arbitraryRowIndex].time)
|
||||
);
|
||||
|
||||
const stringToDate = fn(dateToString, { column: 'time', type: 'date' });
|
||||
|
||||
expect(stringToDate.columns[timeColumnIndex])
|
||||
.to.have.property('name', 'time')
|
||||
.and.to.have.property('type', 'date');
|
||||
expect(new Date(stringToDate.rows[timeColumnIndex].time))
|
||||
.to.be.a(Date)
|
||||
.and.to.eql(new Date(dateToString.rows[timeColumnIndex].time));
|
||||
});
|
||||
|
||||
it('converts date <-> number', () => {
|
||||
const dateToNumber = fn(testTable, { column: 'time', type: 'number' });
|
||||
const arbitraryRowIndex = 1;
|
||||
|
||||
expect(dateToNumber.columns[timeColumnIndex])
|
||||
.to.have.property('name', 'time')
|
||||
.and.to.have.property('type', 'number');
|
||||
expect(dateToNumber.rows[arbitraryRowIndex].time)
|
||||
.to.be.a('number')
|
||||
.and.to.eql(testTable.rows[arbitraryRowIndex].time);
|
||||
|
||||
const numberToDate = fn(dateToNumber, { column: 'time', type: 'date' });
|
||||
|
||||
expect(numberToDate.columns[timeColumnIndex])
|
||||
.to.have.property('name', 'time')
|
||||
.and.to.have.property('type', 'date');
|
||||
expect(new Date(numberToDate.rows[arbitraryRowIndex].time))
|
||||
.to.be.a(Date)
|
||||
.and.to.eql(testTable.rows[arbitraryRowIndex].time);
|
||||
});
|
||||
|
||||
it('converts bool <-> number', () => {
|
||||
const booleanToNumber = fn(testTable, { column: 'in_stock', type: 'number' });
|
||||
const arbitraryRowIndex = 7;
|
||||
|
||||
expect(booleanToNumber.columns[inStockColumnIndex])
|
||||
.to.have.property('name', 'in_stock')
|
||||
.and.to.have.property('type', 'number');
|
||||
expect(booleanToNumber.rows[arbitraryRowIndex].in_stock)
|
||||
.to.be.a('number')
|
||||
.and.to.eql(booleanToNumber.rows[arbitraryRowIndex].in_stock);
|
||||
|
||||
const numberToBoolean = fn(booleanToNumber, { column: 'in_stock', type: 'boolean' });
|
||||
|
||||
expect(numberToBoolean.columns[inStockColumnIndex])
|
||||
.to.have.property('name', 'in_stock')
|
||||
.and.to.have.property('type', 'boolean');
|
||||
expect(numberToBoolean.rows[arbitraryRowIndex].in_stock)
|
||||
.to.be.a('boolean')
|
||||
.and.to.eql(numberToBoolean.rows[arbitraryRowIndex].in_stock);
|
||||
});
|
||||
|
||||
it('converts any type -> null', () => {
|
||||
const stringToNull = fn(testTable, { column: 'name', type: 'null' });
|
||||
const arbitraryRowIndex = 0;
|
||||
|
||||
expect(stringToNull.columns[nameColumnIndex])
|
||||
.to.have.property('name', 'name')
|
||||
.and.to.have.property('type', 'null');
|
||||
expect(stringToNull.rows[arbitraryRowIndex].name).to.be(null);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { any } from '../any';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('any', () => {
|
||||
const fn = functionWrapper(any);
|
||||
|
||||
it('should return false with no conditions', () => {
|
||||
expect(fn(null, {})).to.be(false);
|
||||
expect(fn(null, { condition: [] })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return false when no conditions are true', () => {
|
||||
expect(fn(null, null, { condition: [false] })).to.be(false);
|
||||
expect(fn(null, { condition: [false, false, false] })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return false when all conditions are falsy', () => {
|
||||
expect(fn(null, { condition: [false, 0, '', null] })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return true when at least one condition is true', () => {
|
||||
expect(fn(null, { condition: [false, false, true] })).to.be(true);
|
||||
expect(fn(null, { condition: [false, true, true] })).to.be(true);
|
||||
expect(fn(null, { condition: [true, true, true] })).to.be(true);
|
||||
});
|
||||
|
||||
it('should return true when at least one condition is truthy', () => {
|
||||
expect(fn(null, { condition: [false, 0, '', null, 1] })).to.be(true);
|
||||
expect(fn(null, { condition: [false, 0, 'hooray', null] })).to.be(true);
|
||||
expect(fn(null, { condition: [false, 0, {}, null] })).to.be(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { asFn } from '../as';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('as', () => {
|
||||
const fn = functionWrapper(asFn);
|
||||
|
||||
it('returns a datatable with a single column and single row', () => {
|
||||
expect(fn('foo', { name: 'bar' })).to.eql({
|
||||
type: 'datatable',
|
||||
columns: [{ name: 'bar', type: 'string' }],
|
||||
rows: [{ bar: 'foo' }],
|
||||
});
|
||||
|
||||
expect(fn(2, { name: 'num' })).to.eql({
|
||||
type: 'datatable',
|
||||
columns: [{ name: 'num', type: 'number' }],
|
||||
rows: [{ num: 2 }],
|
||||
});
|
||||
|
||||
expect(fn(true, { name: 'bool' })).to.eql({
|
||||
type: 'datatable',
|
||||
columns: [{ name: 'bool', type: 'boolean' }],
|
||||
rows: [{ bool: true }],
|
||||
});
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('name', () => {
|
||||
it('sets the column name of the resulting datatable', () => {
|
||||
expect(fn(null, { name: 'foo' }).columns[0].name).to.eql('foo');
|
||||
});
|
||||
|
||||
it("returns a datatable with the column name 'value'", () => {
|
||||
expect(fn(null).columns[0].name).to.eql('value');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { axisConfig } from '../axisConfig';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { testTable } from '../__tests__/fixtures/test_tables';
|
||||
|
||||
describe('axisConfig', () => {
|
||||
const fn = functionWrapper(axisConfig);
|
||||
|
||||
it('returns an axisConfig', () => {
|
||||
const result = fn(testTable, { show: true, position: 'right' });
|
||||
expect(result).to.have.property('type', 'axisConfig');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('show', () => {
|
||||
it('hides labels', () => {
|
||||
const result = fn(testTable, { show: false });
|
||||
expect(result).to.have.property('show', false);
|
||||
});
|
||||
|
||||
it('shows labels', () => {
|
||||
const result = fn(testTable, { show: true });
|
||||
expect(result).to.have.property('show', true);
|
||||
});
|
||||
|
||||
it('defaults to true', () => {
|
||||
const result = fn(testTable);
|
||||
expect(result).to.have.property('show', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('position', () => {
|
||||
it('sets the position of the axis labels', () => {
|
||||
let result = fn(testTable, { position: 'left' });
|
||||
expect(result).to.have.property('position', 'left');
|
||||
|
||||
result = fn(testTable, { position: 'top' });
|
||||
expect(result).to.have.property('position', 'top');
|
||||
|
||||
result = fn(testTable, { position: 'right' });
|
||||
expect(result).to.have.property('position', 'right');
|
||||
|
||||
result = fn(testTable, { position: 'bottom' });
|
||||
expect(result).to.have.property('position', 'bottom');
|
||||
});
|
||||
|
||||
it('defaults to an empty string if not provided', () => {
|
||||
const result = fn(testTable);
|
||||
expect(result).to.have.property('position', '');
|
||||
});
|
||||
|
||||
it('throws when given an invalid position', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable, { position: 'foo' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid position foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('min', () => {
|
||||
it('sets the minimum value shown of the axis', () => {
|
||||
let result = fn(testTable, { min: -100 });
|
||||
expect(result).to.have.property('min', -100);
|
||||
result = fn(testTable, { min: 1010101010101 });
|
||||
expect(result).to.have.property('min', 1010101010101);
|
||||
result = fn(testTable, { min: '2017-09-01T00:00:00Z' });
|
||||
expect(result).to.have.property('min', 1504224000000);
|
||||
result = fn(testTable, { min: '2017-09-01' });
|
||||
expect(result).to.have.property('min', 1504224000000);
|
||||
result = fn(testTable, { min: '1 Sep 2017' });
|
||||
expect(result).to.have.property('min', 1504224000000);
|
||||
});
|
||||
|
||||
it('throws when given an invalid date string', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable, { min: 'foo' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be(
|
||||
`Invalid date string 'foo' found. 'min' must be a number, date in ms, or ISO8601 date string`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', () => {
|
||||
it('sets the maximum value shown of the axis', () => {
|
||||
let result = fn(testTable, { max: 2000 });
|
||||
expect(result).to.have.property('max', 2000);
|
||||
result = fn(testTable, { max: 1234567000000 });
|
||||
expect(result).to.have.property('max', 1234567000000);
|
||||
result = fn(testTable, { max: '2018-10-06T00:00:00Z' });
|
||||
expect(result).to.have.property('max', 1538784000000);
|
||||
result = fn(testTable, { max: '10/06/2018' });
|
||||
expect(result).to.have.property('max', 1538784000000);
|
||||
result = fn(testTable, { max: 'October 6 2018' });
|
||||
expect(result).to.have.property('max', 1538784000000);
|
||||
});
|
||||
|
||||
it('throws when given an invalid date string', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable, { max: '20/02/17' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be(
|
||||
`Invalid date string '20/02/17' found. 'max' must be a number, date in ms, or ISO8601 date string`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tickSize ', () => {
|
||||
it('sets the increment size between ticks of the axis', () => {
|
||||
const result = fn(testTable, { tickSize: 100 });
|
||||
expect(result).to.have.property('tickSize', 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { caseFn } from '../case';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('case', () => {
|
||||
const fn = functionWrapper(caseFn);
|
||||
|
||||
describe('spec', () => {
|
||||
it('is a function', () => {
|
||||
expect(fn).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('function', () => {
|
||||
describe('no args', () => {
|
||||
it('should return a case object that matches with the result as the context', async () => {
|
||||
const context = null;
|
||||
const args = {};
|
||||
expect(await fn(context, args)).to.eql({
|
||||
type: 'case',
|
||||
matches: true,
|
||||
result: context,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no if or value', () => {
|
||||
it('should return the result if provided', async () => {
|
||||
const context = null;
|
||||
const args = {
|
||||
then: () => 'foo',
|
||||
};
|
||||
expect(await fn(context, args)).to.eql({
|
||||
type: 'case',
|
||||
matches: true,
|
||||
result: args.then(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with if', () => {
|
||||
it('should return as the matches prop', async () => {
|
||||
const context = null;
|
||||
const args = { if: false };
|
||||
expect(await fn(context, args)).to.eql({
|
||||
type: 'case',
|
||||
matches: args.if,
|
||||
result: context,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with value', () => {
|
||||
it('should return whether it matches the context as the matches prop', async () => {
|
||||
const args = {
|
||||
when: () => 'foo',
|
||||
then: () => 'bar',
|
||||
};
|
||||
expect(await fn('foo', args)).to.eql({
|
||||
type: 'case',
|
||||
matches: true,
|
||||
result: args.then(),
|
||||
});
|
||||
expect(await fn('bar', args)).to.eql({
|
||||
type: 'case',
|
||||
matches: false,
|
||||
result: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with if and value', () => {
|
||||
it('should return the if as the matches prop', async () => {
|
||||
const context = null;
|
||||
const args = {
|
||||
when: () => 'foo',
|
||||
if: true,
|
||||
};
|
||||
expect(await fn(context, args)).to.eql({
|
||||
type: 'case',
|
||||
matches: args.if,
|
||||
result: context,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { columns } from '../columns';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { emptyTable, testTable } from './fixtures/test_tables';
|
||||
|
||||
describe('columns', () => {
|
||||
const fn = functionWrapper(columns);
|
||||
|
||||
it('returns a datatable', () => {
|
||||
expect(fn(testTable, { include: 'name' }).type).to.be('datatable');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
it('returns a datatable with included columns and without excluded columns', () => {
|
||||
const arbitraryRowIndex = 7;
|
||||
const result = fn(testTable, {
|
||||
include: 'name, price, quantity, foo, bar',
|
||||
exclude: 'price, quantity, fizz, buzz',
|
||||
});
|
||||
|
||||
expect(result.columns[0]).to.have.property('name', 'name');
|
||||
expect(result.rows[arbitraryRowIndex])
|
||||
.to.have.property('name', testTable.rows[arbitraryRowIndex].name)
|
||||
.and.to.not.have.property('price')
|
||||
.and.to.not.have.property('quantity')
|
||||
.and.to.not.have.property('foo')
|
||||
.and.to.not.have.property('bar')
|
||||
.and.to.not.have.property('fizz')
|
||||
.and.to.not.have.property('buzz');
|
||||
});
|
||||
|
||||
it('returns original context if args are not provided', () => {
|
||||
expect(fn(testTable)).to.eql(testTable);
|
||||
});
|
||||
|
||||
it('returns an empty datatable if include and exclude both reference the same column(s)', () => {
|
||||
expect(fn(testTable, { include: 'price', exclude: 'price' })).to.eql(emptyTable);
|
||||
|
||||
expect(
|
||||
fn(testTable, {
|
||||
include: 'price, quantity, in_stock',
|
||||
exclude: 'price, quantity, in_stock',
|
||||
})
|
||||
).to.eql(emptyTable);
|
||||
});
|
||||
|
||||
describe('include', () => {
|
||||
it('returns a datatable with included columns only', () => {
|
||||
const arbitraryRowIndex = 3;
|
||||
const result = fn(testTable, {
|
||||
include: 'name, time, in_stock',
|
||||
});
|
||||
|
||||
expect(result.columns).to.have.length(3);
|
||||
expect(Object.keys(result.rows[0])).to.have.length(3);
|
||||
|
||||
expect(result.columns[0]).to.have.property('name', 'name');
|
||||
expect(result.columns[1]).to.have.property('name', 'time');
|
||||
expect(result.columns[2]).to.have.property('name', 'in_stock');
|
||||
expect(result.rows[arbitraryRowIndex])
|
||||
.to.have.property('name', testTable.rows[arbitraryRowIndex].name)
|
||||
.and.to.have.property('time', testTable.rows[arbitraryRowIndex].time)
|
||||
.and.to.have.property('in_stock', testTable.rows[arbitraryRowIndex].in_stock);
|
||||
});
|
||||
|
||||
it('ignores invalid columns', () => {
|
||||
const arbitraryRowIndex = 6;
|
||||
const result = fn(testTable, {
|
||||
include: 'name, foo, bar',
|
||||
});
|
||||
|
||||
expect(result.columns[0]).to.have.property('name', 'name');
|
||||
expect(result.rows[arbitraryRowIndex])
|
||||
.to.have.property('name', testTable.rows[arbitraryRowIndex].name)
|
||||
.and.to.not.have.property('foo')
|
||||
.and.to.not.have.property('bar');
|
||||
});
|
||||
|
||||
it('returns an empty datable if include only has invalid columns', () => {
|
||||
expect(fn(testTable, { include: 'foo, bar' })).to.eql(emptyTable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exclude', () => {
|
||||
it('returns a datatable without excluded columns', () => {
|
||||
const arbitraryRowIndex = 5;
|
||||
const result = fn(testTable, { exclude: 'price, quantity, foo, bar' });
|
||||
|
||||
expect(result.columns.length).to.equal(testTable.columns.length - 2);
|
||||
expect(Object.keys(result.rows[0])).to.have.length(testTable.columns.length - 2);
|
||||
expect(result.rows[arbitraryRowIndex])
|
||||
.to.not.have.property('price')
|
||||
.and.to.not.have.property('quantity')
|
||||
.and.to.not.have.property('foo')
|
||||
.and.to.not.have.property('bar');
|
||||
});
|
||||
|
||||
it('ignores invalid columns', () => {
|
||||
const arbitraryRowIndex = 1;
|
||||
const result = fn(testTable, { exclude: 'time, foo, bar' });
|
||||
|
||||
expect(result.columns.length).to.equal(testTable.columns.length - 1);
|
||||
expect(result.rows[arbitraryRowIndex])
|
||||
.to.not.have.property('time')
|
||||
.and.to.not.have.property('foo')
|
||||
.and.to.not.have.property('bar');
|
||||
});
|
||||
|
||||
it('returns original context if exclude only references invalid column name(s)', () => {
|
||||
expect(fn(testTable, { exclude: 'foo, bar, fizz, buzz' })).to.eql(testTable);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { compare } from '../compare';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('compare', () => {
|
||||
const fn = functionWrapper(compare);
|
||||
|
||||
describe('args', () => {
|
||||
describe('op', () => {
|
||||
it('sets the operator', () => {
|
||||
expect(fn(0, { op: 'lt', to: 1 })).to.be(true);
|
||||
});
|
||||
|
||||
it("defaults to 'eq'", () => {
|
||||
expect(fn(0, { to: 1 })).to.be(false);
|
||||
expect(fn(0, { to: 0 })).to.be(true);
|
||||
});
|
||||
|
||||
it('throws when invalid op is provided', () => {
|
||||
expect(() => fn(1, { op: 'boo', to: 2 })).to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid compare operator. Use eq, ne, lt, gt, lte, or gte.');
|
||||
});
|
||||
expect(() => fn(1, { op: 'boo' })).to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid compare operator. Use eq, ne, lt, gt, lte, or gte.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('to', () => {
|
||||
it('sets the value that context is compared to', () => {
|
||||
expect(fn(0, { to: 1 })).to.be(false);
|
||||
});
|
||||
|
||||
it('if not provided, ne returns true while every other operator returns false', () => {
|
||||
expect(fn(null, { op: 'ne' })).to.be(true);
|
||||
expect(fn(0, { op: 'ne' })).to.be(true);
|
||||
expect(fn(true, { op: 'lte' })).to.be(false);
|
||||
expect(fn(1, { op: 'gte' })).to.be(false);
|
||||
expect(fn('foo', { op: 'lt' })).to.be(false);
|
||||
expect(fn(null, { op: 'gt' })).to.be(false);
|
||||
expect(fn(null, { op: 'eq' })).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('same type comparisons', () => {
|
||||
describe('null', () => {
|
||||
it('returns true', () => {
|
||||
expect(fn(null, { op: 'eq', to: null })).to.be(true);
|
||||
expect(fn(null, { op: 'lte', to: null })).to.be(true);
|
||||
expect(fn(null, { op: 'gte', to: null })).to.be(true);
|
||||
});
|
||||
|
||||
it('returns false', () => {
|
||||
expect(fn(null, { op: 'ne', to: null })).to.be(false);
|
||||
expect(fn(null, { op: 'lt', to: null })).to.be(false);
|
||||
expect(fn(null, { op: 'gt', to: null })).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('number', () => {
|
||||
it('returns true', () => {
|
||||
expect(fn(-2.34, { op: 'lt', to: 10 })).to.be(true);
|
||||
expect(fn(2, { op: 'gte', to: 2 })).to.be(true);
|
||||
});
|
||||
|
||||
it('returns false', () => {
|
||||
expect(fn(2, { op: 'eq', to: 10 })).to.be(false);
|
||||
expect(fn(10, { op: 'ne', to: 10 })).to.be(false);
|
||||
expect(fn(1, { op: 'lte', to: -3 })).to.be(false);
|
||||
expect(fn(2, { op: 'gt', to: 2 })).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('string', () => {
|
||||
it('returns true', () => {
|
||||
expect(fn('foo', { op: 'gte', to: 'foo' })).to.be(true);
|
||||
expect(fn('foo', { op: 'lte', to: 'foo' })).to.be(true);
|
||||
expect(fn('bar', { op: 'lt', to: 'foo' })).to.be(true);
|
||||
});
|
||||
|
||||
it('returns false', () => {
|
||||
expect(fn('foo', { op: 'eq', to: 'bar' })).to.be(false);
|
||||
expect(fn('foo', { op: 'ne', to: 'foo' })).to.be(false);
|
||||
expect(fn('foo', { op: 'gt', to: 'foo' })).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('boolean', () => {
|
||||
it('returns true', () => {
|
||||
expect(fn(true, { op: 'eq', to: true })).to.be(true);
|
||||
expect(fn(false, { op: 'eq', to: false })).to.be(true);
|
||||
expect(fn(true, { op: 'ne', to: false })).to.be(true);
|
||||
expect(fn(false, { op: 'ne', to: true })).to.be(true);
|
||||
});
|
||||
it('returns false', () => {
|
||||
expect(fn(true, { op: 'eq', to: false })).to.be(false);
|
||||
expect(fn(false, { op: 'eq', to: true })).to.be(false);
|
||||
expect(fn(true, { op: 'ne', to: true })).to.be(false);
|
||||
expect(fn(false, { op: 'ne', to: false })).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('different type comparisons', () => {
|
||||
it("returns true for 'ne' only", () => {
|
||||
expect(fn(0, { op: 'ne', to: '0' })).to.be(true);
|
||||
});
|
||||
|
||||
it('otherwise always returns false', () => {
|
||||
expect(fn(0, { op: 'eq', to: '0' })).to.be(false);
|
||||
expect(fn('foo', { op: 'lt', to: 10 })).to.be(false);
|
||||
expect(fn('foo', { op: 'lte', to: true })).to.be(false);
|
||||
expect(fn(0, { op: 'gte', to: null })).to.be(false);
|
||||
expect(fn(0, { op: 'eq', to: false })).to.be(false);
|
||||
expect(fn(true, { op: 'gte', to: null })).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { containerStyle } from '../containerStyle';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { elasticLogo } from '../../../lib/elastic_logo';
|
||||
|
||||
describe('containerStyle', () => {
|
||||
const fn = functionWrapper(containerStyle);
|
||||
|
||||
describe('default output', () => {
|
||||
const result = fn(null);
|
||||
|
||||
it('returns a containerStyle', () => {
|
||||
expect(result).to.have.property('type', 'containerStyle');
|
||||
});
|
||||
|
||||
it('all style properties are omitted if args not provided', () => {
|
||||
expect(result).to.only.have.key('type');
|
||||
});
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('border', () => {
|
||||
it('sets border', () => {
|
||||
const result = fn(null, { border: '1px solid black' });
|
||||
expect(result).to.have.property('border', '1px solid black');
|
||||
});
|
||||
});
|
||||
|
||||
describe('borderRadius', () => {
|
||||
it('sets border-radius', () => {
|
||||
const result = fn(null, { borderRadius: '20px' });
|
||||
expect(result).to.have.property('borderRadius', '20px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('padding', () => {
|
||||
it('sets padding', () => {
|
||||
const result = fn(null, { padding: '10px' });
|
||||
expect(result).to.have.property('padding', '10px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundColor', () => {
|
||||
it('sets backgroundColor', () => {
|
||||
const result = fn(null, { backgroundColor: '#3f9939' });
|
||||
expect(result).to.have.property('backgroundColor', '#3f9939');
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundImage', () => {
|
||||
it('sets backgroundImage', () => {
|
||||
let result = fn(null, { backgroundImage: elasticLogo });
|
||||
expect(result).to.have.property('backgroundImage', `url(${elasticLogo})`);
|
||||
|
||||
const imageURL = 'https://www.elastic.co/assets/blt45b0886c90beceee/logo-elastic.svg';
|
||||
result = fn(null, {
|
||||
backgroundImage: imageURL,
|
||||
});
|
||||
expect(result).to.have.property('backgroundImage', `url(${imageURL})`);
|
||||
});
|
||||
|
||||
it('omitted when provided a null value', () => {
|
||||
let result = fn(null, { backgroundImage: '' });
|
||||
expect(result).to.not.have.property('backgroundImage');
|
||||
|
||||
result = fn(null, { backgroundImage: null });
|
||||
expect(result).to.not.have.property('backgroundImage');
|
||||
});
|
||||
|
||||
it('throws when provided an invalid dataurl/url', () => {
|
||||
expect(fn)
|
||||
.withArgs(null, { backgroundImage: 'foo' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid backgroundImage. Please provide an asset or a URL.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundSize', () => {
|
||||
it('sets backgroundSize when backgroundImage is provided', () => {
|
||||
const result = fn(null, { backgroundImage: elasticLogo, backgroundSize: 'cover' });
|
||||
expect(result).to.have.property('backgroundSize', 'cover');
|
||||
});
|
||||
|
||||
it("defaults to 'contain' when backgroundImage is provided", () => {
|
||||
const result = fn(null, { backgroundImage: elasticLogo });
|
||||
expect(result).to.have.property('backgroundSize', 'contain');
|
||||
});
|
||||
|
||||
it('omitted when backgroundImage is not provided', () => {
|
||||
const result = fn(null, { backgroundSize: 'cover' });
|
||||
expect(result).to.not.have.property('backgroundSize');
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundRepeat', () => {
|
||||
it('sets backgroundRepeat when backgroundImage is provided', () => {
|
||||
const result = fn(null, { backgroundImage: elasticLogo, backgroundRepeat: 'repeat' });
|
||||
expect(result).to.have.property('backgroundRepeat', 'repeat');
|
||||
});
|
||||
|
||||
it("defaults to 'no-repeat'", () => {
|
||||
const result = fn(null, { backgroundImage: elasticLogo });
|
||||
expect(result).to.have.property('backgroundRepeat', 'no-repeat');
|
||||
});
|
||||
|
||||
it('omitted when backgroundImage is not provided', () => {
|
||||
const result = fn(null, { backgroundRepeat: 'repeat' });
|
||||
expect(result).to.not.have.property('backgroundRepeat');
|
||||
});
|
||||
});
|
||||
|
||||
describe('opacity', () => {
|
||||
it('sets opacity', () => {
|
||||
const result = fn(null, { opacity: 0.5 });
|
||||
expect(result).to.have.property('opacity', 0.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('overflow', () => {
|
||||
it('sets overflow', () => {
|
||||
let result = fn(null, { overflow: 'visible' });
|
||||
expect(result).to.have.property('overflow', 'visible');
|
||||
result = fn(null, { overflow: 'hidden' });
|
||||
expect(result).to.have.property('overflow', 'hidden');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { context } from '../context';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { testTable, emptyTable } from './fixtures/test_tables';
|
||||
|
||||
describe('context', () => {
|
||||
const fn = functionWrapper(context);
|
||||
|
||||
it('returns whatever context you pass into', () => {
|
||||
expect(fn(null)).to.be(null);
|
||||
expect(fn(true)).to.be(true);
|
||||
expect(fn(1)).to.be(1);
|
||||
expect(fn('foo')).to.be('foo');
|
||||
expect(fn({})).to.eql({});
|
||||
expect(fn(emptyTable)).to.eql(emptyTable);
|
||||
expect(fn(testTable)).to.eql(testTable);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { csv } from '../csv';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('csv', () => {
|
||||
const fn = functionWrapper(csv);
|
||||
const expected = {
|
||||
type: 'datatable',
|
||||
columns: [{ name: 'name', type: 'string' }, { name: 'number', type: 'string' }],
|
||||
rows: [
|
||||
{ name: 'one', number: 1 },
|
||||
{ name: 'two', number: 2 },
|
||||
{ name: 'fourty two', number: 42 },
|
||||
],
|
||||
};
|
||||
|
||||
it('should return a datatable', () => {
|
||||
expect(
|
||||
fn(null, {
|
||||
data: `name,number
|
||||
one,1
|
||||
two,2
|
||||
fourty two,42`,
|
||||
})
|
||||
).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should allow custom delimiter', () => {
|
||||
expect(
|
||||
fn(null, {
|
||||
data: `name\tnumber
|
||||
one\t1
|
||||
two\t2
|
||||
fourty two\t42`,
|
||||
delimiter: '\t',
|
||||
})
|
||||
).to.eql(expected);
|
||||
|
||||
expect(
|
||||
fn(null, {
|
||||
data: `name%SPLIT%number
|
||||
one%SPLIT%1
|
||||
two%SPLIT%2
|
||||
fourty two%SPLIT%42`,
|
||||
delimiter: '%SPLIT%',
|
||||
})
|
||||
).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should allow custom newline', () => {
|
||||
expect(
|
||||
fn(null, {
|
||||
data: `name,number\rone,1\rtwo,2\rfourty two,42`,
|
||||
newline: '\r',
|
||||
})
|
||||
).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should trim column names', () => {
|
||||
expect(
|
||||
fn(null, {
|
||||
data: `foo," bar ", baz, " buz "
|
||||
1,2,3,4`,
|
||||
})
|
||||
).to.eql({
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ name: 'foo', type: 'string' },
|
||||
{ name: 'bar', type: 'string' },
|
||||
{ name: 'baz', type: 'string' },
|
||||
{ name: 'buz', type: 'string' },
|
||||
],
|
||||
rows: [{ foo: '1', bar: '2', baz: '3', buz: '4' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle odd spaces correctly', () => {
|
||||
expect(
|
||||
fn(null, {
|
||||
data: `foo," bar ", baz, " buz "
|
||||
1," best ",3, " ok"
|
||||
" good", bad, better , " worst " `,
|
||||
})
|
||||
).to.eql({
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ name: 'foo', type: 'string' },
|
||||
{ name: 'bar', type: 'string' },
|
||||
{ name: 'baz', type: 'string' },
|
||||
{ name: 'buz', type: 'string' },
|
||||
],
|
||||
rows: [
|
||||
{ foo: '1', bar: ' best ', baz: '3', buz: ' ok' },
|
||||
{ foo: ' good', bar: ' bad', baz: ' better ', buz: ' worst ' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { date } from '../date';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('date', () => {
|
||||
const fn = functionWrapper(date);
|
||||
|
||||
let clock;
|
||||
// stubbed date constructor to check current dates match when no args are passed in
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('returns a date in ms from a date string with the provided format', () => {
|
||||
expect(fn(null, { value: '20111031', format: 'YYYYMMDD' })).to.be(1320019200000);
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('value', () => {
|
||||
it('sets the date string to convert into ms', () => {
|
||||
expect(fn(null, { value: '2011-10-05T14:48:00.000Z' })).to.be(1317826080000);
|
||||
});
|
||||
|
||||
it('defaults to current date (ms)', () => {
|
||||
expect(fn(null)).to.be(new Date().valueOf());
|
||||
});
|
||||
});
|
||||
|
||||
describe('format', () => {
|
||||
it('sets the format to parse the date string', () => {
|
||||
expect(fn(null, { value: '20111031', format: 'YYYYMMDD' })).to.be(1320019200000);
|
||||
});
|
||||
|
||||
it('defaults to ISO 8601 format', () => {
|
||||
expect(fn(null, { value: '2011-10-05T14:48:00.000Z' })).to.be(1317826080000);
|
||||
});
|
||||
|
||||
it('throws when passing an invalid date string and format is not specified', () => {
|
||||
expect(() => fn(null, { value: '23/25/2014' })).to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid date input: 23/25/2014');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { doFn } from '../do';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('do', () => {
|
||||
const fn = functionWrapper(doFn);
|
||||
|
||||
it('should only pass context', () => {
|
||||
expect(fn(1, { fn: '1' })).to.equal(1);
|
||||
expect(fn(true, {})).to.equal(true);
|
||||
expect(fn(null, {})).to.equal(null);
|
||||
expect(fn(null, { fn: 'not null' })).to.equal(null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { dropdownControl } from '../dropdownControl';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { testTable } from './fixtures/test_tables';
|
||||
|
||||
describe('dropdownControl', () => {
|
||||
const fn = functionWrapper(dropdownControl);
|
||||
const uniqueNames = testTable.rows.reduce(
|
||||
(unique, { name }) => (unique.includes(name) ? unique : unique.concat([name])),
|
||||
[]
|
||||
);
|
||||
|
||||
it('returns a render as dropdown_filter', () => {
|
||||
expect(fn(testTable, { filterColumn: 'name', valueColumn: 'name' }))
|
||||
.to.have.property('type', 'render')
|
||||
.and.to.have.property('as', 'dropdown_filter');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('valueColumn', () => {
|
||||
it('populates dropdown choices with unique values in valueColumn', () => {
|
||||
expect(fn(testTable, { valueColumn: 'name' }).value.choices).to.eql(uniqueNames);
|
||||
});
|
||||
|
||||
it('returns an empty array when provided an invalid column', () => {
|
||||
expect(fn(testTable, { valueColumn: 'foo' }).value.choices).to.be.empty();
|
||||
expect(fn(testTable, { valueColumn: '' }).value.choices).to.be.empty();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterColumn', () => {
|
||||
it('sets which column the filter is applied to', () => {
|
||||
expect(fn(testTable, { filterColumn: 'name' }).value).to.have.property('column', 'name');
|
||||
expect(fn(testTable, { filterColumn: 'name', valueColumn: 'price' }).value).to.have.property(
|
||||
'column',
|
||||
'name'
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to valueColumn if not provided', () => {
|
||||
expect(fn(testTable, { valueColumn: 'price' }).value).to.have.property('column', 'price');
|
||||
});
|
||||
|
||||
it('sets column to undefined if no args are provided', () => {
|
||||
expect(fn(testTable).value).to.have.property('column', undefined);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { eq } from '../eq';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('eq', () => {
|
||||
const fn = functionWrapper(eq);
|
||||
|
||||
it('should return false when the types are different', () => {
|
||||
expect(fn(1, { value: '1' })).to.be(false);
|
||||
expect(fn(true, { value: 'true' })).to.be(false);
|
||||
expect(fn(null, { value: 'null' })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return false when the values are different', () => {
|
||||
expect(fn(1, { value: 2 })).to.be(false);
|
||||
expect(fn('foo', { value: 'bar' })).to.be(false);
|
||||
expect(fn(true, { value: false })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return true when the values are the same', () => {
|
||||
expect(fn(1, { value: 1 })).to.be(true);
|
||||
expect(fn('foo', { value: 'foo' })).to.be(true);
|
||||
expect(fn(true, { value: true })).to.be(true);
|
||||
expect(fn(null, { value: null })).to.be(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { exactly } from '../exactly';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { emptyFilter } from './fixtures/test_filters';
|
||||
|
||||
describe('exactly', () => {
|
||||
const fn = functionWrapper(exactly);
|
||||
|
||||
it('returns a filter', () => {
|
||||
const args = { column: 'name', value: 'product2' };
|
||||
expect(fn(emptyFilter, args)).to.have.property('type', 'filter');
|
||||
});
|
||||
|
||||
it("adds an exactly object to 'and'", () => {
|
||||
const result = fn(emptyFilter, { column: 'name', value: 'product2' });
|
||||
expect(result.and[0]).to.have.property('type', 'exactly');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('column', () => {
|
||||
it('sets the column to apply the filter to', () => {
|
||||
const result = fn(emptyFilter, { column: 'name' });
|
||||
expect(result.and[0]).to.have.property('column', 'name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('value', () => {
|
||||
it('sets the exact value to filter on in a column', () => {
|
||||
const result = fn(emptyFilter, { value: 'product2' });
|
||||
expect(result.and[0]).to.have.property('value', 'product2');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { filterrows } from '../filterrows';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { testTable } from './fixtures/test_tables';
|
||||
|
||||
const inStock = datatable => datatable.rows[0].in_stock;
|
||||
const returnFalse = () => false;
|
||||
|
||||
describe('filterrows', () => {
|
||||
const fn = functionWrapper(filterrows);
|
||||
|
||||
it('returns a datable', () => {
|
||||
return fn(testTable, { fn: inStock }).then(result => {
|
||||
expect(result).to.have.property('type', 'datatable');
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps rows that evaluate to true and removes rows that evaluate to false', () => {
|
||||
const inStockRows = testTable.rows.filter(row => row.in_stock);
|
||||
|
||||
return fn(testTable, { fn: inStock }).then(result => {
|
||||
expect(result.columns).to.eql(testTable.columns);
|
||||
expect(result.rows).to.eql(inStockRows);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns datatable with no rows when no rows meet function condition', () => {
|
||||
return fn(testTable, { fn: returnFalse }).then(result => {
|
||||
expect(result.rows).to.be.empty();
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when no function is provided', () => {
|
||||
expect(() => fn(testTable)).to.throwException(e => {
|
||||
expect(e.message).to.be('fn is not a function');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const emptyFilter = {
|
||||
type: 'filter',
|
||||
meta: {},
|
||||
and: [],
|
||||
};
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const testPlot = {
|
||||
type: 'pointseries',
|
||||
columns: {
|
||||
x: { type: 'date', role: 'dimension', expression: 'time' },
|
||||
y: {
|
||||
type: 'number',
|
||||
role: 'dimension',
|
||||
expression: 'price',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
role: 'dimension',
|
||||
expression: 'quantity',
|
||||
},
|
||||
color: {
|
||||
type: 'string',
|
||||
role: 'dimension',
|
||||
expression: 'name',
|
||||
},
|
||||
text: {
|
||||
type: 'number',
|
||||
role: 'dimension',
|
||||
expression: 'price',
|
||||
},
|
||||
},
|
||||
rows: [
|
||||
{
|
||||
x: 1517842800950,
|
||||
y: 67,
|
||||
size: 240,
|
||||
color: 'product3',
|
||||
text: 67,
|
||||
},
|
||||
{
|
||||
x: 1517842800950,
|
||||
y: 605,
|
||||
size: 100,
|
||||
color: 'product1',
|
||||
text: 605,
|
||||
},
|
||||
{
|
||||
x: 1517842800950,
|
||||
y: 216,
|
||||
size: 350,
|
||||
color: 'product2',
|
||||
text: 216,
|
||||
},
|
||||
{
|
||||
x: 1517929200950,
|
||||
y: 583,
|
||||
size: 200,
|
||||
color: 'product1',
|
||||
text: 583,
|
||||
},
|
||||
{
|
||||
x: 1517929200950,
|
||||
y: 200,
|
||||
size: 256,
|
||||
color: 'product2',
|
||||
text: 200,
|
||||
},
|
||||
{
|
||||
x: 1517842800950,
|
||||
y: 311,
|
||||
size: 447,
|
||||
color: 'product4',
|
||||
text: 311,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const testPie = {
|
||||
type: 'pointseries',
|
||||
columns: {
|
||||
color: {
|
||||
type: 'string',
|
||||
role: 'dimension',
|
||||
expression: 'name',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
role: 'measure',
|
||||
expression: 'mean(price)',
|
||||
},
|
||||
},
|
||||
rows: [
|
||||
{ color: 'product2', size: 202 },
|
||||
{ color: 'product3', size: 67 },
|
||||
{ color: 'product4', size: 311 },
|
||||
{ color: 'product1', size: 536 },
|
||||
{ color: 'product5', size: 288 },
|
||||
],
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { elasticLogo } from '../../../../lib/elastic_logo';
|
||||
|
||||
export const fontStyle = {
|
||||
type: 'style',
|
||||
spec: {
|
||||
fontFamily: 'Chalkboard, serif',
|
||||
fontWeight: 'bolder',
|
||||
fontStyle: 'normal',
|
||||
textDecoration: 'underline',
|
||||
color: 'pink',
|
||||
textAlign: 'center',
|
||||
fontSize: '14px',
|
||||
lineHeight: '21px',
|
||||
},
|
||||
css:
|
||||
'font-family:Chalkboard, serif;font-weight:bolder;font-style:normal;text-decoration:underline;color:pink;text-align:center;font-size:14px;line-height:21px',
|
||||
};
|
||||
|
||||
export const containerStyle = {
|
||||
type: 'containerStyle',
|
||||
border: '3px dotted blue',
|
||||
borderRadius: '5px',
|
||||
padding: '10px',
|
||||
backgroundColor: 'red',
|
||||
backgroundImage: `url(${elasticLogo})`,
|
||||
opacity: 0.5,
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
};
|
||||
|
||||
export const defaultStyle = {
|
||||
type: 'seriesStyle',
|
||||
label: null,
|
||||
color: null,
|
||||
lines: 0,
|
||||
bars: 0,
|
||||
points: 3,
|
||||
fill: false,
|
||||
stack: undefined,
|
||||
horizontalBars: true,
|
||||
};
|
||||
|
||||
export const seriesStyle = {
|
||||
type: 'seriesStyle',
|
||||
label: 'product1',
|
||||
color: 'blue',
|
||||
lines: 0,
|
||||
bars: 0,
|
||||
points: 5,
|
||||
fill: true,
|
||||
stack: 1,
|
||||
horizontalBars: true,
|
||||
};
|
||||
|
||||
export const grayscalePalette = {
|
||||
type: 'palette',
|
||||
colors: ['#FFFFFF', '#888888', '#000000'],
|
||||
gradient: false,
|
||||
};
|
||||
|
||||
export const gradientPalette = {
|
||||
type: 'palette',
|
||||
colors: ['#FFFFFF', '#000000'],
|
||||
gradient: true,
|
||||
};
|
||||
|
||||
export const xAxisConfig = {
|
||||
type: 'axisConfig',
|
||||
show: true,
|
||||
position: 'top',
|
||||
};
|
||||
|
||||
export const yAxisConfig = {
|
||||
type: 'axisConfig',
|
||||
show: true,
|
||||
position: 'right',
|
||||
};
|
||||
|
||||
export const hideAxis = {
|
||||
type: 'axisConfig',
|
||||
show: false,
|
||||
position: 'right',
|
||||
};
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
const emptyTable = {
|
||||
type: 'datatable',
|
||||
columns: [],
|
||||
rows: [],
|
||||
};
|
||||
|
||||
const testTable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
name: 'in_stock',
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
name: 'product1',
|
||||
time: 1517842800950, //05 Feb 2018 15:00:00 GMT
|
||||
price: 605,
|
||||
quantity: 100,
|
||||
in_stock: true,
|
||||
},
|
||||
{
|
||||
name: 'product1',
|
||||
time: 1517929200950, //06 Feb 2018 15:00:00 GMT
|
||||
price: 583,
|
||||
quantity: 200,
|
||||
in_stock: true,
|
||||
},
|
||||
{
|
||||
name: 'product1',
|
||||
time: 1518015600950, //07 Feb 2018 15:00:00 GMT
|
||||
price: 420,
|
||||
quantity: 300,
|
||||
in_stock: true,
|
||||
},
|
||||
{
|
||||
name: 'product2',
|
||||
time: 1517842800950, //05 Feb 2018 15:00:00 GMT
|
||||
price: 216,
|
||||
quantity: 350,
|
||||
in_stock: false,
|
||||
},
|
||||
{
|
||||
name: 'product2',
|
||||
time: 1517929200950, //06 Feb 2018 15:00:00 GMT
|
||||
price: 200,
|
||||
quantity: 256,
|
||||
in_stock: false,
|
||||
},
|
||||
{
|
||||
name: 'product2',
|
||||
time: 1518015600950, //07 Feb 2018 15:00:00 GMT
|
||||
price: 190,
|
||||
quantity: 231,
|
||||
in_stock: false,
|
||||
},
|
||||
{
|
||||
name: 'product3',
|
||||
time: 1517842800950, //05 Feb 2018 15:00:00 GMT
|
||||
price: 67,
|
||||
quantity: 240,
|
||||
in_stock: true,
|
||||
},
|
||||
{
|
||||
name: 'product4',
|
||||
time: 1517842800950, //05 Feb 2018 15:00:00 GMT
|
||||
price: 311,
|
||||
quantity: 447,
|
||||
in_stock: false,
|
||||
},
|
||||
{
|
||||
name: 'product5',
|
||||
time: 1517842800950, //05 Feb 2018 15:00:00 GMT
|
||||
price: 288,
|
||||
quantity: 384,
|
||||
in_stock: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const stringTable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'in_stock',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
name: 'product1',
|
||||
time: '2018-02-05T15:00:00.950Z',
|
||||
price: '605',
|
||||
quantity: '100',
|
||||
in_stock: 'true',
|
||||
},
|
||||
{
|
||||
name: 'product1',
|
||||
time: '2018-02-06T15:00:00.950Z',
|
||||
price: '583',
|
||||
quantity: '200',
|
||||
in_stock: 'true',
|
||||
},
|
||||
{
|
||||
name: 'product1',
|
||||
time: '2018-02-07T15:00:00.950Z',
|
||||
price: '420',
|
||||
quantity: '300',
|
||||
in_stock: 'true',
|
||||
},
|
||||
{
|
||||
name: 'product2',
|
||||
time: '2018-02-05T15:00:00.950Z',
|
||||
price: '216',
|
||||
quantity: '350',
|
||||
in_stock: 'false',
|
||||
},
|
||||
{
|
||||
name: 'product2',
|
||||
time: '2018-02-06T15:00:00.950Z',
|
||||
price: '200',
|
||||
quantity: '256',
|
||||
in_stock: 'false',
|
||||
},
|
||||
{
|
||||
name: 'product2',
|
||||
time: '2018-02-07T15:00:00.950Z',
|
||||
price: '190',
|
||||
quantity: '231',
|
||||
in_stock: 'false',
|
||||
},
|
||||
{
|
||||
name: 'product3',
|
||||
time: '2018-02-05T15:00:00.950Z',
|
||||
price: '67',
|
||||
quantity: '240',
|
||||
in_stock: 'true',
|
||||
},
|
||||
{
|
||||
name: 'product4',
|
||||
time: '2018-02-05T15:00:00.950Z',
|
||||
price: '311',
|
||||
quantity: '447',
|
||||
in_stock: 'false',
|
||||
},
|
||||
{
|
||||
name: 'product5',
|
||||
time: '2018-02-05T15:00:00.950Z',
|
||||
price: '288',
|
||||
quantity: '384',
|
||||
in_stock: 'true',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export { emptyTable, testTable, stringTable };
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { openSans } from '../../../../common/lib/fonts';
|
||||
import { font } from '../font';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('font', () => {
|
||||
const fn = functionWrapper(font);
|
||||
|
||||
describe('default output', () => {
|
||||
const result = fn(null);
|
||||
|
||||
it('returns a style', () => {
|
||||
expect(result)
|
||||
.to.have.property('type', 'style')
|
||||
.and.to.have.property('spec')
|
||||
.and.to.have.property('css');
|
||||
});
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('size', () => {
|
||||
it('sets font size', () => {
|
||||
const result = fn(null, { size: 20 });
|
||||
expect(result.spec).to.have.property('fontSize', '20px');
|
||||
expect(result.css).to.contain('font-size:20px');
|
||||
});
|
||||
|
||||
it('defaults to 14px', () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('fontSize', '14px');
|
||||
expect(result.css).to.contain('font-size:14px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lHeight', () => {
|
||||
it('sets line height', () => {
|
||||
const result = fn(null, { lHeight: 30 });
|
||||
expect(result.spec).to.have.property('lineHeight', '30px');
|
||||
expect(result.css).to.contain('line-height:30px');
|
||||
});
|
||||
|
||||
it('defaults to 1', () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('lineHeight', 1);
|
||||
expect(result.css).to.contain('line-height:1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('family', () => {
|
||||
it('sets font family', () => {
|
||||
const result = fn(null, { family: 'Optima, serif' });
|
||||
expect(result.spec).to.have.property('fontFamily', 'Optima, serif');
|
||||
expect(result.css).to.contain('font-family:Optima, serif');
|
||||
});
|
||||
|
||||
it(`defaults to "${openSans.value}"`, () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('fontFamily', `"${openSans.value}"`);
|
||||
expect(result.css).to.contain(`font-family:"${openSans.value}"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('color', () => {
|
||||
it('sets font color', () => {
|
||||
const result = fn(null, { color: 'blue' });
|
||||
expect(result.spec).to.have.property('color', 'blue');
|
||||
expect(result.css).to.contain('color:blue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('weight', () => {
|
||||
it('sets font weight', () => {
|
||||
let result = fn(null, { weight: 'normal' });
|
||||
expect(result.spec).to.have.property('fontWeight', 'normal');
|
||||
expect(result.css).to.contain('font-weight:normal');
|
||||
|
||||
result = fn(null, { weight: 'bold' });
|
||||
expect(result.spec).to.have.property('fontWeight', 'bold');
|
||||
expect(result.css).to.contain('font-weight:bold');
|
||||
|
||||
result = fn(null, { weight: 'bolder' });
|
||||
expect(result.spec).to.have.property('fontWeight', 'bolder');
|
||||
expect(result.css).to.contain('font-weight:bolder');
|
||||
|
||||
result = fn(null, { weight: 'lighter' });
|
||||
expect(result.spec).to.have.property('fontWeight', 'lighter');
|
||||
expect(result.css).to.contain('font-weight:lighter');
|
||||
|
||||
result = fn(null, { weight: '400' });
|
||||
expect(result.spec).to.have.property('fontWeight', '400');
|
||||
expect(result.css).to.contain('font-weight:400');
|
||||
});
|
||||
|
||||
it("defaults to 'normal'", () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('fontWeight', 'normal');
|
||||
expect(result.css).to.contain('font-weight:normal');
|
||||
});
|
||||
|
||||
it('throws when provided an invalid weight', () => {
|
||||
expect(() => fn(null, { weight: 'foo' })).to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid font weight: foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('underline', () => {
|
||||
it('sets text underline', () => {
|
||||
let result = fn(null, { underline: true });
|
||||
expect(result.spec).to.have.property('textDecoration', 'underline');
|
||||
expect(result.css).to.contain('text-decoration:underline');
|
||||
|
||||
result = fn(null, { underline: false });
|
||||
expect(result.spec).to.have.property('textDecoration', 'none');
|
||||
expect(result.css).to.contain('text-decoration:none');
|
||||
});
|
||||
|
||||
it('defaults to false', () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('textDecoration', 'none');
|
||||
expect(result.css).to.contain('text-decoration:none');
|
||||
});
|
||||
});
|
||||
|
||||
describe('italic', () => {
|
||||
it('sets italic', () => {
|
||||
let result = fn(null, { italic: true });
|
||||
expect(result.spec).to.have.property('fontStyle', 'italic');
|
||||
expect(result.css).to.contain('font-style:italic');
|
||||
|
||||
result = fn(null, { italic: false });
|
||||
expect(result.spec).to.have.property('fontStyle', 'normal');
|
||||
expect(result.css).to.contain('font-style:normal');
|
||||
});
|
||||
|
||||
it('defaults to false', () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('fontStyle', 'normal');
|
||||
expect(result.css).to.contain('font-style:normal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('align', () => {
|
||||
it('sets text alignment', () => {
|
||||
let result = fn(null, { align: 'left' });
|
||||
expect(result.spec).to.have.property('textAlign', 'left');
|
||||
expect(result.css).to.contain('text-align:left');
|
||||
|
||||
result = fn(null, { align: 'center' });
|
||||
expect(result.spec).to.have.property('textAlign', 'center');
|
||||
expect(result.css).to.contain('text-align:center');
|
||||
|
||||
result = fn(null, { align: 'right' });
|
||||
expect(result.spec).to.have.property('textAlign', 'right');
|
||||
expect(result.css).to.contain('text-align:right');
|
||||
|
||||
result = fn(null, { align: 'justified' });
|
||||
expect(result.spec).to.have.property('textAlign', 'justified');
|
||||
expect(result.css).to.contain('text-align:justified');
|
||||
});
|
||||
|
||||
it(`defaults to 'left'`, () => {
|
||||
const result = fn(null);
|
||||
expect(result.spec).to.have.property('textAlign', 'left');
|
||||
expect(result.css).to.contain('text-align:left');
|
||||
});
|
||||
|
||||
it('throws when provided an invalid alignment', () => {
|
||||
expect(fn)
|
||||
.withArgs(null, { align: 'foo' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Invalid text alignment: foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { formatdate } from '../formatdate';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('formatdate', () => {
|
||||
const fn = functionWrapper(formatdate);
|
||||
|
||||
it('returns formatted date string from ms or ISO8601 string using the given format', () => {
|
||||
const testDate = new Date('2011-10-31T12:30:45Z').valueOf();
|
||||
expect(fn(testDate, { format: 'MM/DD/YYYY' })).to.be('10/31/2011');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('format', () => {
|
||||
it('sets the format of the returned date string', () => {
|
||||
const testDate = new Date('2013-03-12T08:03:27Z').valueOf();
|
||||
expect(fn(testDate, { format: 'MMMM Do YYYY, h:mm:ss a' })).to.be(
|
||||
'March 12th 2013, 8:03:27 am'
|
||||
);
|
||||
expect(fn(testDate, { format: 'MMM Do YY' })).to.be('Mar 12th 13');
|
||||
});
|
||||
|
||||
it('defaults to ISO 8601 format', () => {
|
||||
const testDate = new Date('2018-01-08T20:15:59Z').valueOf();
|
||||
expect(fn(testDate)).to.be('2018-01-08T20:15:59.000Z');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { formatnumber } from '../formatnumber';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('formatnumber', () => {
|
||||
const fn = functionWrapper(formatnumber);
|
||||
|
||||
it('returns number as formatted string with given format', () => {
|
||||
expect(fn(140000, { format: '$0,0.00' })).to.be('$140,000.00');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('format', () => {
|
||||
it('sets the format of the resulting number string', () => {
|
||||
expect(fn(0.68, { format: '0.000%' })).to.be('68.000%');
|
||||
});
|
||||
|
||||
it('casts number to a string if format is not specified', () => {
|
||||
expect(fn(140000.999999)).to.be('140000.999999');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { getCell } from '../getCell';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { emptyTable, testTable } from './fixtures/test_tables';
|
||||
|
||||
describe('getCell', () => {
|
||||
const fn = functionWrapper(getCell);
|
||||
|
||||
it('returns the value from the specified row and column', () => {
|
||||
const arbitraryRowIndex = 3;
|
||||
|
||||
expect(fn(testTable, { column: 'quantity', row: arbitraryRowIndex })).to.eql(
|
||||
testTable.rows[arbitraryRowIndex].quantity
|
||||
);
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
const firstColumn = testTable.columns[0].name;
|
||||
|
||||
it('defaults to first column in first row if no args are provided', () => {
|
||||
expect(fn(testTable)).to.be(testTable.rows[0][firstColumn]);
|
||||
});
|
||||
|
||||
describe('column', () => {
|
||||
const arbitraryRowIndex = 1;
|
||||
|
||||
it('sets which column to get the value from', () => {
|
||||
expect(fn(testTable, { column: 'price', row: arbitraryRowIndex })).to.be(
|
||||
testTable.rows[arbitraryRowIndex].price
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to first column if not provided', () => {
|
||||
expect(fn(testTable, { row: arbitraryRowIndex })).to.be(
|
||||
testTable.rows[arbitraryRowIndex][firstColumn]
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when invalid column is provided', () => {
|
||||
expect(() => fn(testTable, { column: 'foo' })).to.throwException(e => {
|
||||
expect(e.message).to.be('Column not found: foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('row', () => {
|
||||
it('sets which row to get the value from', () => {
|
||||
const arbitraryRowIndex = 8;
|
||||
|
||||
expect(fn(testTable, { column: 'in_stock', row: arbitraryRowIndex })).to.eql(
|
||||
testTable.rows[arbitraryRowIndex].in_stock
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to first row if not specified', () => {
|
||||
expect(fn(testTable, { column: 'name' })).to.eql(testTable.rows[0].name);
|
||||
});
|
||||
|
||||
it('throws when row does not exist', () => {
|
||||
const invalidRow = testTable.rows.length;
|
||||
|
||||
expect(() => fn(testTable, { column: 'name', row: invalidRow })).to.throwException(e => {
|
||||
expect(e.message).to.be(`Row not found: ${invalidRow}`);
|
||||
});
|
||||
|
||||
expect(() => fn(emptyTable, { column: 'foo' })).to.throwException(e => {
|
||||
expect(e.message).to.be('Row not found: 0');
|
||||
});
|
||||
|
||||
expect(() => fn(emptyTable)).to.throwException(e => {
|
||||
expect(e.message).to.be('Row not found: 0');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { getFlotAxisConfig } from '../plot/get_flot_axis_config';
|
||||
import { xAxisConfig, yAxisConfig, hideAxis } from './fixtures/test_styles';
|
||||
|
||||
describe('getFlotAxisConfig', () => {
|
||||
const columns = {
|
||||
x: { type: 'string', role: 'dimension', expression: 'project' },
|
||||
y: { type: 'date', role: 'dimension', expression: 'location' },
|
||||
};
|
||||
|
||||
const ticks = {
|
||||
x: { hash: { product1: 2, product2: 1 }, counter: 3 },
|
||||
y: { hash: {}, counter: 0 },
|
||||
};
|
||||
|
||||
describe('show', () => {
|
||||
it('hides the axis', () => {
|
||||
expect(getFlotAxisConfig('x', false, { columns, ticks }))
|
||||
.to.only.have.key('show')
|
||||
.and.to.have.property('show', false);
|
||||
expect(getFlotAxisConfig('y', false, { columns, ticks }))
|
||||
.to.only.have.key('show')
|
||||
.and.to.have.property('show', false);
|
||||
});
|
||||
|
||||
it('shows the axis', () => {
|
||||
expect(getFlotAxisConfig('x', true, { columns, ticks })).to.have.property('show', true);
|
||||
expect(getFlotAxisConfig('y', true, { columns, ticks })).to.have.property('show', true);
|
||||
});
|
||||
|
||||
it('sets show using an AxisConfig', () => {
|
||||
let result = getFlotAxisConfig('x', xAxisConfig, { columns, ticks });
|
||||
expect(result).to.have.property('show', xAxisConfig.show);
|
||||
|
||||
result = getFlotAxisConfig('y', yAxisConfig, { columns, ticks });
|
||||
expect(result).to.have.property('show', yAxisConfig.show);
|
||||
|
||||
result = getFlotAxisConfig('x', hideAxis, { columns, ticks });
|
||||
expect(result).to.have.property('show', hideAxis.show);
|
||||
|
||||
result = getFlotAxisConfig('y', hideAxis, { columns, ticks });
|
||||
expect(result).to.have.property('show', hideAxis.show);
|
||||
});
|
||||
});
|
||||
|
||||
describe('position', () => {
|
||||
it('sets the position of the axis when given an AxisConfig', () => {
|
||||
let result = getFlotAxisConfig('x', xAxisConfig, { columns, ticks });
|
||||
expect(result).to.have.property('position', xAxisConfig.position);
|
||||
|
||||
result = getFlotAxisConfig('y', yAxisConfig, { columns, ticks });
|
||||
expect(result).to.have.property('position', yAxisConfig.position);
|
||||
});
|
||||
|
||||
it("defaults position to 'bottom' for the x-axis", () => {
|
||||
const invalidXPosition = {
|
||||
type: 'axisConfig',
|
||||
show: true,
|
||||
position: 'left',
|
||||
};
|
||||
|
||||
const result = getFlotAxisConfig('x', invalidXPosition, { columns, ticks });
|
||||
expect(result).to.have.property('position', 'bottom');
|
||||
});
|
||||
|
||||
it("defaults position to 'left' for the y-axis", () => {
|
||||
const invalidYPosition = {
|
||||
type: 'axisConfig',
|
||||
show: true,
|
||||
position: 'bottom',
|
||||
};
|
||||
|
||||
const result = getFlotAxisConfig('y', invalidYPosition, { columns, ticks });
|
||||
expect(result).to.have.property('position', 'left');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ticks', () => {
|
||||
it('adds a tick mark mapping for string columns', () => {
|
||||
let result = getFlotAxisConfig('x', true, { columns, ticks });
|
||||
expect(result.ticks).to.eql([[2, 'product1'], [1, 'product2']]);
|
||||
|
||||
result = getFlotAxisConfig('x', xAxisConfig, { columns, ticks });
|
||||
expect(result.ticks).to.eql([[2, 'product1'], [1, 'product2']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mode', () => {
|
||||
it('sets the mode to time for date columns', () => {
|
||||
let result = getFlotAxisConfig('y', true, { columns, ticks });
|
||||
expect(result).to.have.property('mode', 'time');
|
||||
|
||||
result = getFlotAxisConfig('y', yAxisConfig, { columns, ticks });
|
||||
expect(result).to.have.property('mode', 'time');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { defaultSpec, getFontSpec } from '../plot/get_font_spec';
|
||||
import { fontStyle } from './fixtures/test_styles';
|
||||
|
||||
describe('getFontSpec', () => {
|
||||
describe('default output', () => {
|
||||
it('returns the default spec object', () => {
|
||||
expect(getFontSpec()).to.eql(defaultSpec);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convert from fontStyle object', () => {
|
||||
it('returns plot font spec', () => {
|
||||
expect(getFontSpec(fontStyle)).to.eql({
|
||||
size: 14,
|
||||
lHeight: 21,
|
||||
style: 'normal',
|
||||
weight: 'bolder',
|
||||
family: 'Chalkboard, serif',
|
||||
color: 'pink',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { getTickHash } from '../plot/get_tick_hash';
|
||||
|
||||
describe('getTickHash', () => {
|
||||
it('creates a hash for tick marks for string columns only', () => {
|
||||
const columns = {
|
||||
x: { type: 'string', role: 'dimension', expression: 'project' },
|
||||
y: { type: 'string', role: 'dimension', expression: 'location' },
|
||||
};
|
||||
const rows = [
|
||||
{ x: 'product1', y: 'AZ' },
|
||||
{ x: 'product2', y: 'AZ' },
|
||||
{ x: 'product1', y: 'CA' },
|
||||
{ x: 'product2', y: 'CA' },
|
||||
];
|
||||
|
||||
expect(getTickHash(columns, rows)).to.eql({
|
||||
x: { hash: { product1: 2, product2: 1 }, counter: 3 },
|
||||
y: { hash: { CA: 1, AZ: 2 }, counter: 3 },
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores columns of any other type', () => {
|
||||
const columns = {
|
||||
x: { type: 'number', role: 'dimension', expression: 'id' },
|
||||
y: { type: 'boolean', role: 'dimension', expression: 'running' },
|
||||
};
|
||||
const rows = [{ x: 1, y: true }, { x: 2, y: true }, { x: 1, y: false }, { x: 2, y: false }];
|
||||
|
||||
expect(getTickHash(columns, rows)).to.eql({
|
||||
x: { hash: {}, counter: 0 },
|
||||
y: { hash: {}, counter: 0 },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { gt } from '../gt';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('gt', () => {
|
||||
const fn = functionWrapper(gt);
|
||||
|
||||
it('should return false when the types are different', () => {
|
||||
expect(fn(1, { value: '1' })).to.be(false);
|
||||
expect(fn(true, { value: 'true' })).to.be(false);
|
||||
expect(fn(null, { value: 'null' })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return true when greater than', () => {
|
||||
expect(fn(2, { value: 1 })).to.be(true);
|
||||
expect(fn('foo', { value: 'bar' })).to.be(true);
|
||||
expect(fn(true, { value: false })).to.be(true);
|
||||
});
|
||||
|
||||
it('should return false when less than or equal to', () => {
|
||||
expect(fn(1, { value: 2 })).to.be(false);
|
||||
expect(fn(2, { value: 2 })).to.be(false);
|
||||
expect(fn('bar', { value: 'foo' })).to.be(false);
|
||||
expect(fn('foo', { value: 'foo' })).to.be(false);
|
||||
expect(fn(false, { value: true })).to.be(false);
|
||||
expect(fn(true, { value: true })).to.be(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { gte } from '../gte';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('gte', () => {
|
||||
const fn = functionWrapper(gte);
|
||||
|
||||
it('should return false when the types are different', () => {
|
||||
expect(fn(1, { value: '1' })).to.be(false);
|
||||
expect(fn(true, { value: 'true' })).to.be(false);
|
||||
expect(fn(null, { value: 'null' })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return true when greater than or equal to', () => {
|
||||
expect(fn(2, { value: 1 })).to.be(true);
|
||||
expect(fn(2, { value: 2 })).to.be(true);
|
||||
expect(fn('foo', { value: 'bar' })).to.be(true);
|
||||
expect(fn('foo', { value: 'foo' })).to.be(true);
|
||||
expect(fn(true, { value: false })).to.be(true);
|
||||
expect(fn(true, { value: true })).to.be(true);
|
||||
});
|
||||
|
||||
it('should return false when less than', () => {
|
||||
expect(fn(1, { value: 2 })).to.be(false);
|
||||
expect(fn('bar', { value: 'foo' })).to.be(false);
|
||||
expect(fn(false, { value: true })).to.be(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { head } from '../head';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { emptyTable, testTable } from './fixtures/test_tables';
|
||||
|
||||
describe('head', () => {
|
||||
const fn = functionWrapper(head);
|
||||
|
||||
it('returns a datatable with the first N rows of the context', () => {
|
||||
const result = fn(testTable, { count: 2 });
|
||||
|
||||
expect(result.type).to.be('datatable');
|
||||
expect(result.columns).to.eql(testTable.columns);
|
||||
expect(result.rows).to.have.length(2);
|
||||
expect(result.rows[0]).to.eql(testTable.rows[0]);
|
||||
expect(result.rows[1]).to.eql(testTable.rows[1]);
|
||||
});
|
||||
|
||||
it('returns the original context if N >= context.rows.length', () => {
|
||||
expect(fn(testTable, { count: testTable.rows.length + 5 })).to.eql(testTable);
|
||||
expect(fn(testTable, { count: testTable.rows.length })).to.eql(testTable);
|
||||
expect(fn(emptyTable)).to.eql(emptyTable);
|
||||
});
|
||||
|
||||
it('returns the first row if N is not specified', () => {
|
||||
const result = fn(testTable);
|
||||
|
||||
expect(result.rows).to.have.length(1);
|
||||
expect(result.rows[0]).to.eql(testTable.rows[0]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { ifFn } from '../if';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('if', () => {
|
||||
const fn = functionWrapper(ifFn);
|
||||
|
||||
describe('spec', () => {
|
||||
it('is a function', () => {
|
||||
expect(fn).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('function', () => {
|
||||
describe('condition passed', () => {
|
||||
it('with then', async () => {
|
||||
expect(await fn(null, { condition: true, then: () => 'foo' })).to.be('foo');
|
||||
expect(await fn(null, { condition: true, then: () => 'foo', else: () => 'bar' })).to.be(
|
||||
'foo'
|
||||
);
|
||||
});
|
||||
|
||||
it('without then', async () => {
|
||||
expect(await fn(null, { condition: true })).to.be(null);
|
||||
expect(await fn('some context', { condition: true })).to.be('some context');
|
||||
});
|
||||
});
|
||||
|
||||
describe('condition failed', () => {
|
||||
it('with else', async () =>
|
||||
expect(
|
||||
await fn('some context', { condition: false, then: () => 'foo', else: () => 'bar' })
|
||||
).to.be('bar'));
|
||||
|
||||
it('without else', async () =>
|
||||
expect(await fn('some context', { condition: false, then: () => 'foo' })).to.be(
|
||||
'some context'
|
||||
));
|
||||
});
|
||||
|
||||
describe('falsy values', () => {
|
||||
describe('for then', () => {
|
||||
it('with null', async () =>
|
||||
expect(await fn('some context', { condition: true, then: () => null })).to.be(null));
|
||||
|
||||
it('with false', async () =>
|
||||
expect(await fn('some context', { condition: true, then: () => false })).to.be(false));
|
||||
|
||||
it('with 0', async () =>
|
||||
expect(await fn('some context', { condition: true, then: () => 0 })).to.be(0));
|
||||
});
|
||||
|
||||
describe('for else', () => {
|
||||
it('with null', async () =>
|
||||
expect(await fn('some context', { condition: false, else: () => null })).to.be(null));
|
||||
|
||||
it('with false', async () =>
|
||||
expect(await fn('some context', { condition: false, else: () => false })).to.be(false));
|
||||
|
||||
it('with 0', async () =>
|
||||
expect(await fn('some context', { condition: false, else: () => 0 })).to.be(0));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Passing through context
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { image } from '../image';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { elasticLogo } from '../../../lib/elastic_logo';
|
||||
import { elasticOutline } from '../../../lib/elastic_outline';
|
||||
|
||||
describe('image', () => {
|
||||
const fn = functionWrapper(image);
|
||||
|
||||
it('returns an image object using a dataUrl', () => {
|
||||
const result = fn(null, { dataurl: elasticOutline, mode: 'cover' });
|
||||
expect(result).to.have.property('type', 'image');
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('dataurl', () => {
|
||||
it('sets the source of the image using dataurl', () => {
|
||||
const result = fn(null, { dataurl: elasticOutline });
|
||||
expect(result).to.have.property('dataurl', elasticOutline);
|
||||
});
|
||||
|
||||
it.skip('sets the source of the image using url', () => {
|
||||
// This is skipped because functionWrapper doesn't use the actual
|
||||
// interpreter and doesn't resolve aliases
|
||||
const result = fn(null, { url: elasticOutline });
|
||||
expect(result).to.have.property('dataurl', elasticOutline);
|
||||
});
|
||||
|
||||
it('defaults to the elasticLogo if not provided', () => {
|
||||
const result = fn(null);
|
||||
expect(result).to.have.property('dataurl', elasticLogo);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mode', () => {
|
||||
it('sets the mode', () => {
|
||||
it('to contain', () => {
|
||||
const result = fn(null, { mode: 'contain' });
|
||||
expect(result).to.have.property('mode', 'contain');
|
||||
});
|
||||
|
||||
it('to cover', () => {
|
||||
const result = fn(null, { mode: 'cover' });
|
||||
expect(result).to.have.property('mode', 'cover');
|
||||
});
|
||||
|
||||
it('to stretch', () => {
|
||||
const result = fn(null, { mode: 'stretch' });
|
||||
expect(result).to.have.property('mode', 'stretch');
|
||||
});
|
||||
|
||||
it("defaults to 'contain' if not provided", () => {
|
||||
const result = fn(null);
|
||||
expect(result).to.have.property('mode', 'contain');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { lt } from '../lt';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('lt', () => {
|
||||
const fn = functionWrapper(lt);
|
||||
|
||||
it('should return false when the types are different', () => {
|
||||
expect(fn(1, { value: '1' })).to.be(false);
|
||||
expect(fn(true, { value: 'true' })).to.be(false);
|
||||
expect(fn(null, { value: 'null' })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return false when greater than or equal to', () => {
|
||||
expect(fn(2, { value: 1 })).to.be(false);
|
||||
expect(fn(2, { value: 2 })).to.be(false);
|
||||
expect(fn('foo', { value: 'bar' })).to.be(false);
|
||||
expect(fn('foo', { value: 'foo' })).to.be(false);
|
||||
expect(fn(true, { value: false })).to.be(false);
|
||||
expect(fn(true, { value: true })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return true when less than', () => {
|
||||
expect(fn(1, { value: 2 })).to.be(true);
|
||||
expect(fn('bar', { value: 'foo' })).to.be(true);
|
||||
expect(fn(false, { value: true })).to.be(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { lte } from '../lte';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
|
||||
describe('lte', () => {
|
||||
const fn = functionWrapper(lte);
|
||||
|
||||
it('should return false when the types are different', () => {
|
||||
expect(fn(1, { value: '1' })).to.be(false);
|
||||
expect(fn(true, { value: 'true' })).to.be(false);
|
||||
expect(fn(null, { value: 'null' })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return false when greater than', () => {
|
||||
expect(fn(2, { value: 1 })).to.be(false);
|
||||
expect(fn('foo', { value: 'bar' })).to.be(false);
|
||||
expect(fn(true, { value: false })).to.be(false);
|
||||
});
|
||||
|
||||
it('should return true when less than or equal to', () => {
|
||||
expect(fn(1, { value: 2 })).to.be(true);
|
||||
expect(fn(2, { value: 2 })).to.be(true);
|
||||
expect(fn('bar', { value: 'foo' })).to.be(true);
|
||||
expect(fn('foo', { value: 'foo' })).to.be(true);
|
||||
expect(fn(false, { value: true })).to.be(true);
|
||||
expect(fn(true, { value: true })).to.be(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { mapColumn } from '../mapColumn';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { testTable } from './fixtures/test_tables';
|
||||
|
||||
const pricePlusTwo = datatable => Promise.resolve(datatable.rows[0].price + 2);
|
||||
|
||||
describe('mapColumn', () => {
|
||||
const fn = functionWrapper(mapColumn);
|
||||
|
||||
it('returns a datatable with a new column with the values from mapping a function over each row in a datatable', () => {
|
||||
return fn(testTable, { name: 'pricePlusTwo', expression: pricePlusTwo }).then(result => {
|
||||
const arbitraryRowIndex = 2;
|
||||
|
||||
expect(result.type).to.be('datatable');
|
||||
expect(result.columns).to.eql([
|
||||
...testTable.columns,
|
||||
{ name: 'pricePlusTwo', type: 'number' },
|
||||
]);
|
||||
expect(result.columns[result.columns.length - 1]).to.have.property('name', 'pricePlusTwo');
|
||||
expect(result.rows[arbitraryRowIndex]).to.have.property('pricePlusTwo');
|
||||
});
|
||||
});
|
||||
|
||||
it('overwrites existing column with the new column if an existing column name is provided', () => {
|
||||
return fn(testTable, { name: 'name', expression: pricePlusTwo }).then(result => {
|
||||
const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name');
|
||||
const arbitraryRowIndex = 4;
|
||||
|
||||
expect(result.type).to.be('datatable');
|
||||
expect(result.columns).to.have.length(testTable.columns.length);
|
||||
expect(result.columns[nameColumnIndex])
|
||||
.to.have.property('name', 'name')
|
||||
.and.to.have.property('type', 'number');
|
||||
expect(result.rows[arbitraryRowIndex]).to.have.property('name', 202);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expression', () => {
|
||||
it('maps null values to the new column', () => {
|
||||
return fn(testTable, { name: 'empty' }).then(result => {
|
||||
const emptyColumnIndex = result.columns.findIndex(({ name }) => name === 'empty');
|
||||
const arbitraryRowIndex = 8;
|
||||
|
||||
expect(result.columns[emptyColumnIndex])
|
||||
.to.have.property('name', 'empty')
|
||||
.and.to.have.property('type', 'null');
|
||||
expect(result.rows[arbitraryRowIndex]).to.have.property('empty', null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { math } from '../math';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { emptyTable, testTable } from './fixtures/test_tables';
|
||||
|
||||
describe('math', () => {
|
||||
const fn = functionWrapper(math);
|
||||
|
||||
it('evaluates math expressions without reference to context', () => {
|
||||
expect(fn(null, { expression: '10.5345' })).to.be(10.5345);
|
||||
expect(fn(null, { expression: '123 + 456' })).to.be(579);
|
||||
expect(fn(null, { expression: '100 - 46' })).to.be(54);
|
||||
expect(fn(1, { expression: '100 / 5' })).to.be(20);
|
||||
expect(fn('foo', { expression: '100 / 5' })).to.be(20);
|
||||
expect(fn(true, { expression: '100 / 5' })).to.be(20);
|
||||
expect(fn(testTable, { expression: '100 * 5' })).to.be(500);
|
||||
expect(fn(emptyTable, { expression: '100 * 5' })).to.be(500);
|
||||
});
|
||||
|
||||
it('evaluates math expressions with reference to the value of the context, must be a number', () => {
|
||||
expect(fn(-103, { expression: 'abs(value)' })).to.be(103);
|
||||
});
|
||||
|
||||
it('evaluates math expressions with references to columns in a datatable', () => {
|
||||
expect(fn(testTable, { expression: 'unique(in_stock)' })).to.be(2);
|
||||
expect(fn(testTable, { expression: 'sum(quantity)' })).to.be(2508);
|
||||
expect(fn(testTable, { expression: 'mean(price)' })).to.be(320);
|
||||
expect(fn(testTable, { expression: 'min(price)' })).to.be(67);
|
||||
expect(fn(testTable, { expression: 'median(quantity)' })).to.be(256);
|
||||
expect(fn(testTable, { expression: 'max(price)' })).to.be(605);
|
||||
});
|
||||
|
||||
describe('args', () => {
|
||||
describe('expression', () => {
|
||||
it('sets the math expression to be evaluted', () => {
|
||||
expect(fn(null, { expression: '10' })).to.be(10);
|
||||
expect(fn(23.23, { expression: 'floor(value)' })).to.be(23);
|
||||
expect(fn(testTable, { expression: 'count(price)' })).to.be(9);
|
||||
expect(fn(testTable, { expression: 'count(name)' })).to.be(9);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid expressions', () => {
|
||||
it('throws when expression evaluates to an array', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable, { expression: 'multiply(price, 2)' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be(
|
||||
'Expressions must return a single number. Try wrapping your expression in mean() or sum()'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when using an unknown context variable', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable, { expression: 'sum(foo)' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Unknown variable: foo');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when using non-numeric data', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable, { expression: 'mean(name)' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Failed to execute math expression. Check your column names');
|
||||
});
|
||||
expect(fn)
|
||||
.withArgs(testTable, { expression: 'mean(in_stock)' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Failed to execute math expression. Check your column names');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when missing expression', () => {
|
||||
expect(fn)
|
||||
.withArgs(testTable)
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Empty expression');
|
||||
});
|
||||
expect(fn)
|
||||
.withArgs(testTable, { expression: '' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Empty expression');
|
||||
});
|
||||
expect(fn)
|
||||
.withArgs(testTable, { expression: ' ' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Empty expression');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when passing a context variable from an empty datatable', () => {
|
||||
expect(fn)
|
||||
.withArgs(emptyTable, { expression: 'mean(foo)' })
|
||||
.to.throwException(e => {
|
||||
expect(e.message).to.be('Empty datatable');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|