diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 439adbae83fd6..ccfc58bd084f1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,7 +46,6 @@ # Platform /src/core/ @elastic/kibana-platform /src/legacy/server/saved_objects/ @elastic/kibana-platform -/src/legacy/ui/public/saved_objects @elastic/kibana-platform /config/kibana.yml @elastic/kibana-platform /x-pack/plugins/features/ @elastic/kibana-platform @@ -56,6 +55,7 @@ /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security /src/legacy/server/csp/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security +/x-pack/test/api_integration/apis/security/ @elastic/kibana-security # Kibana Stack Services /packages/kbn-analytics/ @elastic/kibana-stack-services diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00d61addeff34..2903f23f55c9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -306,6 +306,8 @@ IntelliJ | Settings » Languages & Frameworks » JavaScript » Code Quality To Another tool we use for enforcing consistent coding style is EditorConfig, which can be set up by installing a plugin in your editor that dynamically updates its configuration. Take a look at the [EditorConfig](http://editorconfig.org/#download) site to find a plugin for your editor, and browse our [`.editorconfig`](https://github.com/elastic/kibana/blob/master/.editorconfig) file to see what config rules we set up. +#### Setup Guide for VS Code Users + Note that for VSCode, to enable "live" linting of TypeScript (and other) file types, you will need to modify your local settings, as shown below. The default for the ESLint extension is to only lint JavaScript file types. ```json @@ -317,6 +319,14 @@ Note that for VSCode, to enable "live" linting of TypeScript (and other) file ty ] ``` +`eslint` can automatically fix trivial lint errors when you save a file by adding this line in your setting. + +```json + "eslint.autoFixOnSave": true, +``` + +It is **not** recommended to use `prettier` plugin on Kibana project. Because settings are in `eslintrc.js` file and it is applied to too many files that shouldn't be prettier-ized. + ### Internationalization All user-facing labels and info texts in Kibana should be internationalized. Please take a look at the [readme](packages/kbn-i18n/README.md) and the [guideline](packages/kbn-i18n/GUIDELINE.md) of the i18n package on how to do so. diff --git a/docs/management/dashboard_only_mode/index.asciidoc b/docs/management/dashboard_only_mode/index.asciidoc index 320a123ab1089..3040c73b468e1 100644 --- a/docs/management/dashboard_only_mode/index.asciidoc +++ b/docs/management/dashboard_only_mode/index.asciidoc @@ -2,7 +2,7 @@ [[xpack-dashboard-only-mode]] == Dashboard-only mode -deprecated[7.4.0, Using the `kibana_dashboard_only_user` role is deprecated. Use <> instead.] +deprecated[7.4.0, "Using the `kibana_dashboard_only_user` role is deprecated. Use <> instead."] In dashboard-only mode, users have access to only the *Dashboard* app. Users can view and filter the dashboards, but cannot create, edit, or delete diff --git a/docs/settings/code-settings.asciidoc b/docs/settings/code-settings.asciidoc index e01858e6230b1..5dbcb9b3508b8 100644 --- a/docs/settings/code-settings.asciidoc +++ b/docs/settings/code-settings.asciidoc @@ -35,6 +35,12 @@ Whitelist of protocols for git clone address. Defaults to `[ 'https', 'git', 'ss `xpack.code.security.enableGitCertCheck`:: Whether enable HTTPS certificate check when clone from HTTPS URL. +`xpack.code.security.enableJavaSecurityManager`:: +Whether enable Java security manager for Java langserver. Defaults to `true`. + +`xpack.code.security.extraJavaRepositoryWhitelist`:: +Whitelist of extra repository to download dependencies for Java language. Defaults to `[]`. + `xpack.code.maxWorkspace`:: Maximal number of workspaces each language server allows to span. Defaults to `5`. diff --git a/package.json b/package.json index d475578840c31..29410042a865d 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "@babel/register": "^7.5.5", "@elastic/charts": "^13.5.1", "@elastic/datemath": "5.0.2", + "@elastic/ems-client": "^1.0.2", "@elastic/eui": "14.4.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", @@ -171,7 +172,7 @@ "hapi": "^17.5.3", "hapi-auth-cookie": "^9.0.0", "history": "^4.9.0", - "hjson": "3.1.2", + "hjson": "3.2.0", "hoek": "^5.0.4", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.2", @@ -358,7 +359,7 @@ "babel-jest": "^24.9.0", "babel-plugin-dynamic-import-node": "^2.3.0", "babel-plugin-istanbul": "^5.2.0", - "backport": "4.7.1", + "backport": "4.7.3", "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", @@ -446,7 +447,7 @@ "strip-ansi": "^3.0.1", "supertest": "^3.1.0", "supertest-as-promised": "^4.0.2", - "tree-kill": "^1.1.0", + "tree-kill": "^1.2.1", "typescript": "3.5.3", "typings-tester": "^0.3.2", "vinyl-fs": "^3.0.3", diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index 45d726f6e28b4..8fc532bd572a9 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -17,20 +17,28 @@ * under the License. */ +const plugins = [ + require.resolve('babel-plugin-add-module-exports'), + + // The class properties proposal was merged with the private fields proposal + // into the "class fields" proposal. Babel doesn't support this combined + // proposal yet, which includes private field, so this transform is + // TECHNICALLY stage 2, but for all intents and purposes it's stage 3 + // + // See https://github.com/babel/proposals/issues/12 for progress + require.resolve('@babel/plugin-proposal-class-properties'), +]; + module.exports = { presets: [require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')], - plugins: [ - require.resolve('@kbn/elastic-idx/babel'), - require.resolve('babel-plugin-add-module-exports'), - - // The class properties proposal was merged with the private fields proposal - // into the "class fields" proposal. Babel doesn't support this combined - // proposal yet, which includes private field, so this transform is - // TECHNICALLY stage 2, but for all intents and purposes it's stage 3 - // - // See https://github.com/babel/proposals/issues/12 for progress - require.resolve('@babel/plugin-proposal-class-properties'), - ], + plugins: plugins.concat(require.resolve('@kbn/elastic-idx/babel')), + // Do not use the idx plugin in the test environment because it causes + // causes conflicts with Jest's coverage mapping. + env: { + test: { + plugins, + }, + }, overrides: [ { // Babel 7 don't support the namespace feature on typescript code. diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 2e7e4c5500a3c..5c64be294f707 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -17,7 +17,7 @@ "getopts": "^2.2.5", "moment": "^2.20.1", "rxjs": "^6.2.1", - "tree-kill": "^1.2.0", + "tree-kill": "^1.2.1", "tslib": "^1.9.3" }, "devDependencies": { diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts index 3b7d595e4b8cf..f29fb5f4b17f6 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts @@ -26,7 +26,7 @@ import chalk from 'chalk'; import treeKill from 'tree-kill'; import { promisify } from 'util'; -const treeKillAsync = promisify(treeKill); +const treeKillAsync = promisify((...args: [number, string, any]) => treeKill(...args)); import { ToolingLog } from '../tooling_log'; import { observeLines } from './observe_lines'; diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index 5521d57c22e86..5280c671450fa 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -17,7 +17,7 @@ "node-fetch": "^2.6.0", "simple-git": "^1.91.0", "tar-fs": "^1.16.3", - "tree-kill": "^1.1.0", + "tree-kill": "^1.2.1", "yauzl": "^2.10.0" } } diff --git a/src/fixtures/fake_row.js b/src/fixtures/fake_row.js index 5be52f3e16bc3..5bc752de299b2 100644 --- a/src/fixtures/fake_row.js +++ b/src/fixtures/fake_row.js @@ -17,13 +17,13 @@ * under the License. */ -import _ from 'lodash'; const longString = Array(200).join('_'); export function getFakeRowVals(type, id, mapping) { - return _.mapValues(mapping, function (f, c) { - return c + '_' + type + '_' + id + longString; - }); + return mapping.reduce((collector, field) => { + collector[field.name] = `${field.name}_${type}_${id}_${longString}`; + return collector; + }, {}); } export function getFakeRow(id, mapping) { diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts index 4e8ad594a735a..7b85adacce1d0 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts @@ -20,7 +20,7 @@ import { Filter, RangeFilter, FILTERS, isRangeFilter, isScriptedRangeFilter } from '@kbn/es-query'; import { get, has } from 'lodash'; import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; -import { IndexPatterns, IndexPattern } from '../../../index_patterns'; +import { IndexPatterns, IndexPattern, Field } from '../../../index_patterns'; const getFirstRangeKey = (filter: RangeFilter) => filter.range && Object.keys(filter.range)[0]; const getRangeByKey = (filter: RangeFilter, key: string) => get(filter, ['range', key]); @@ -44,9 +44,8 @@ function getParams(filter: RangeFilter, indexPattern?: IndexPattern) { // for example a user might manually edit the url or the index pattern's ID might change due to // external factors e.g. a reindex. We only need the index in order to grab the field formatter, so we fallback // on displaying the raw value if the index is invalid. - if (key && indexPattern && indexPattern.fields.byName[key]) { - const convert = indexPattern.fields.byName[key].format.getConverterFor('text'); - + if (key && indexPattern && indexPattern.fields.getByName(key)) { + const convert = (indexPattern.fields.getByName(key) as Field).format.getConverterFor('text'); value = `${convert(left)} to ${convert(right)}`; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts index 4047260b69501..d429fc7f70f38 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts @@ -21,7 +21,7 @@ export class StubIndexPatterns { async get(index: string) { return { fields: { - byName: {}, + getByName: () => undefined, }, }; } diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index bb9cda238d636..cb3869ff57711 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -33,6 +33,7 @@ export { FilterBar, ApplyFiltersPopover } from './filter'; export { Field, FieldType, + FieldListInterface, IndexPattern, IndexPatterns, StaticIndexPattern, diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts b/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts index 2fa7a92063107..30f4df66f3863 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts @@ -17,24 +17,69 @@ * under the License. */ -import { IndexedArray } from 'ui/indexed_array'; import { NotificationsSetup } from 'kibana/public'; +import { findIndex } from 'lodash'; import { IndexPattern } from '../index_patterns'; -import { Field, FieldSpec } from './field'; +import { Field, FieldType, FieldSpec } from './field'; -export class FieldList extends IndexedArray { +type FieldMap = Map; + +export interface FieldListInterface extends Array { + getByName(name: Field['name']): Field | undefined; + getByType(type: Field['type']): Field[]; + add(field: FieldSpec): void; + remove(field: FieldType): void; +} + +export class FieldList extends Array implements FieldListInterface { + private byName: FieldMap = new Map(); + private groups: Map = new Map(); + private indexPattern: IndexPattern; + private shortDotsEnable: boolean; + private notifications: NotificationsSetup; + private setByName = (field: Field) => this.byName.set(field.name, field); + private setByGroup = (field: Field) => { + if (typeof this.groups.get(field.type) === 'undefined') { + this.groups.set(field.type, new Map()); + } + this.groups.get(field.type)!.set(field.name, field); + }; + private removeByGroup = (field: FieldType) => this.groups.get(field.type)!.delete(field.name); constructor( indexPattern: IndexPattern, - specs: FieldSpec[], + specs: FieldSpec[] = [], shortDotsEnable = false, notifications: NotificationsSetup ) { - super({ - index: ['name'], - group: ['type'], - initialSet: specs.map(function(field) { - return new Field(indexPattern, field, shortDotsEnable, notifications); - }), - }); + super(); + this.indexPattern = indexPattern; + this.shortDotsEnable = shortDotsEnable; + this.notifications = notifications; + specs.map(field => this.add(field)); } + + getByName = (name: Field['name']) => this.byName.get(name); + getByType = (type: Field['type']) => [...(this.groups.get(type) || new Map()).values()]; + add = (field: FieldSpec) => { + const newField = new Field(this.indexPattern, field, this.shortDotsEnable, this.notifications); + this.push(newField); + this.setByName(newField); + this.setByGroup(newField); + }; + + remove = (field: FieldType) => { + this.removeByGroup(field); + this.byName.delete(field.name); + + const fieldIndex = findIndex(this, { name: field.name }); + this.splice(fieldIndex, 1); + }; + + update = (field: Field) => { + const index = this.findIndex(f => f.name === field.name); + this.splice(index, 1, field); + this.setByName(field); + this.removeByGroup(field); + this.setByGroup(field); + }; } diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts index b10c0dca857ff..1ab229f802ab2 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts @@ -27,14 +27,15 @@ function flattenHit(indexPattern: IndexPattern, hit: Record, deep: const flat = {} as Record; // recursively merge _source - const fields = indexPattern.fields.byName; + const fields = indexPattern.fields.getByName; (function flatten(obj, keyPrefix = '') { keyPrefix = keyPrefix ? keyPrefix + '.' : ''; _.forOwn(obj, function(val, key) { key = keyPrefix + key; if (deep) { - const isNestedField = fields[key] && fields[key].type === 'nested'; + const field = fields(key); + const isNestedField = field && field.type === 'nested'; const isArrayOfObjects = Array.isArray(val) && _.isPlainObject(_.first(val)); if (isArrayOfObjects && !isNestedField) { _.each(val, v => flatten(v, key)); @@ -44,7 +45,8 @@ function flattenHit(indexPattern: IndexPattern, hit: Record, deep: return; } - const hasValidMapping = fields[key] && fields[key].type !== 'conflict'; + const field = fields(key); + const hasValidMapping = field && field.type !== 'conflict'; const isValue = !_.isPlainObject(val); if (hasValidMapping || isValue) { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts index 39101ef36ca8d..02d61f8b32c86 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts @@ -27,7 +27,7 @@ const partialFormattedCache = new WeakMap(); // returns a formatted version export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any) { function convert(hit: Record, val: any, fieldName: string, type: string = 'html') { - const field = indexPattern.fields.byName[fieldName]; + const field = indexPattern.fields.getByName(fieldName); if (!field) return defaultFormat.convert(val, type); const parsedUrl = { origin: window.location.origin, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index f8df2ed29fbde..479feabd07943 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -18,7 +18,6 @@ */ import { defaults, pluck, last, get } from 'lodash'; -import { IndexedArray } from 'ui/indexed_array'; import { IndexPattern } from './index_pattern'; import { DuplicateField } from '../../../../../../plugins/kibana_utils/public'; @@ -170,7 +169,6 @@ describe('IndexPattern', () => { test('should append the found fields', () => { expect(savedObjectsClient.get).toHaveBeenCalled(); expect(indexPattern.fields).toHaveLength(mockLogStashFields().length); - expect(indexPattern.fields).toBeInstanceOf(IndexedArray); }); }); @@ -295,7 +293,9 @@ describe('IndexPattern', () => { const scriptedFields = indexPattern.getScriptedFields(); // expect(saveSpy.callCount).to.equal(1); expect(scriptedFields).toHaveLength(oldCount + 1); - expect(indexPattern.fields.byName[scriptedField.name].name).toEqual(scriptedField.name); + expect((indexPattern.fields.getByName(scriptedField.name) as Field).name).toEqual( + scriptedField.name + ); }); test('should remove scripted field, by name', async () => { @@ -304,11 +304,11 @@ describe('IndexPattern', () => { const oldCount = scriptedFields.length; const scriptedField = last(scriptedFields); - await indexPattern.removeScriptedField(scriptedField.name); + await indexPattern.removeScriptedField(scriptedField); // expect(saveSpy.callCount).to.equal(1); expect(indexPattern.getScriptedFields().length).toEqual(oldCount - 1); - expect(indexPattern.fields.byName[scriptedField.name]).toEqual(undefined); + expect(indexPattern.fields.getByName(scriptedField.name)).toEqual(undefined); }); test('should not allow duplicate names', async () => { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 94909dd288567..a5e02e43adf56 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -23,13 +23,12 @@ import { i18n } from '@kbn/i18n'; import { fieldFormats } from 'ui/registry/field_formats'; // @ts-ignore import { expandShorthand } from 'ui/utils/mapping_setup'; -import { toastNotifications } from 'ui/notify'; import { findObjectByTitle } from 'ui/saved_objects'; import { NotificationsSetup, SavedObjectsClientContract } from 'src/core/public'; import { SavedObjectNotFound, DuplicateField } from '../../../../../../plugins/kibana_utils/public'; import { IndexPatternMissingIndices } from '../errors'; -import { Field, FieldList, FieldType } from '../fields'; +import { Field, FieldList, FieldType, FieldListInterface } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { getRoutes } from '../utils'; import { formatHitProvider } from './format_hit'; @@ -64,7 +63,7 @@ export class IndexPattern implements StaticIndexPattern { public type?: string; public fieldFormatMap: any; public typeMeta: any; - public fields: FieldList; + public fields: FieldListInterface; public timeFieldName: string | undefined; public formatHit: any; public formatField: any; @@ -212,15 +211,17 @@ export class IndexPattern implements StaticIndexPattern { // Date value returned in "_source" could be in any number of formats // Use a docvalue for each date field to ensure standardized formats when working with date fields // indexPattern.flattenHit will override "_source" values when the same field is also defined in "fields" - const docvalueFields = reject(this.fields.byType.date, 'scripted').map((dateField: any) => { - return { - field: dateField.name, - format: - dateField.esTypes && dateField.esTypes.indexOf('date_nanos') !== -1 - ? 'strict_date_time' - : 'date_time', - }; - }); + const docvalueFields = reject(this.fields.getByType('date'), 'scripted').map( + (dateField: any) => { + return { + field: dateField.name, + format: + dateField.esTypes && dateField.esTypes.indexOf('date_nanos') !== -1 + ? 'strict_date_time' + : 'date_time', + }; + } + ); each(this.getScriptedFields(), function(field) { scriptFields[field.name] = { @@ -276,7 +277,7 @@ export class IndexPattern implements StaticIndexPattern { throw new DuplicateField(name); } - this.fields.push( + this.fields.add( new Field( this, { @@ -297,21 +298,13 @@ export class IndexPattern implements StaticIndexPattern { await this.save(); } - removeScriptedField(name: string) { - const fieldIndex = _.findIndex(this.fields, { - name, - scripted: true, - }); - - if (fieldIndex > -1) { - this.fields.splice(fieldIndex, 1); - delete this.fieldFormatMap[name]; - return this.save(); - } + removeScriptedField(field: FieldType) { + this.fields.remove(field); + return this.save(); } async popularizeField(fieldName: string, unit = 1) { - const field = this.fields.byName[fieldName]; + const field = this.fields.getByName(fieldName); if (!field) { return; } @@ -345,13 +338,13 @@ export class IndexPattern implements StaticIndexPattern { } getTimeField() { - if (!this.timeFieldName || !this.fields || !this.fields.byName) return; - return this.fields.byName[this.timeFieldName]; + if (!this.timeFieldName || !this.fields || !this.fields.getByName) return; + return this.fields.getByName(this.timeFieldName); } getFieldByName(name: string): Field | void { - if (!this.fields || !this.fields.byName) return; - return this.fields.byName[name]; + if (!this.fields || !this.fields.getByName) return; + return this.fields.getByName(name); } isWildcard() { @@ -468,7 +461,7 @@ export class IndexPattern implements StaticIndexPattern { 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', } // eslint-disable-line max-len ); - toastNotifications.addDanger(message); + this.notifications.toasts.addDanger(message); throw err; } @@ -507,11 +500,11 @@ export class IndexPattern implements StaticIndexPattern { // but we do not want to potentially make any pages unusable // so do not rethrow the error here if (err instanceof IndexPatternMissingIndices) { - toastNotifications.addDanger((err as any).message); + this.notifications.toasts.addDanger((err as any).message); return []; } - toastNotifications.addError(err, { + this.notifications.toasts.addError(err, { title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { defaultMessage: 'Error fetching fields', }), diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts index 5447ae7ca96f9..17a0f45865d11 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts @@ -23,7 +23,7 @@ import { HttpServiceBase, NotificationsSetup, } from 'src/core/public'; -import { Field, FieldList, FieldType } from './fields'; +import { Field, FieldList, FieldListInterface, FieldType } from './fields'; import { createFlattenHitWrapper } from './index_patterns'; import { createIndexPatternSelect } from './components'; import { @@ -107,4 +107,4 @@ export type IndexPatternsSetup = ReturnType; export type IndexPatternsStart = ReturnType; /** @public */ -export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType }; +export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType, FieldListInterface }; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx index ac296a4222c84..b2e47e8b4e850 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx @@ -38,8 +38,7 @@ import { SavedQuery } from '../../index'; import { SavedQueryService } from '../../lib/saved_query_service'; import { SavedQueryListItem } from './saved_query_list_item'; -const pageCount = 50; - +const perPage = 50; interface Props { showSaveQuery?: boolean; loadedSavedQuery?: SavedQuery; @@ -61,18 +60,22 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ }) => { const [isOpen, setIsOpen] = useState(false); const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); + const [count, setTotalCount] = useState(0); const [activePage, setActivePage] = useState(0); useEffect(() => { - const fetchQueries = async () => { - const allSavedQueries = await savedQueryService.getAllSavedQueries(); - const sortedAllSavedQueries = sortBy(allSavedQueries, 'attributes.title'); - setSavedQueries(sortedAllSavedQueries); + const fetchCountAndSavedQueries = async () => { + const savedQueryCount = await savedQueryService.getSavedQueryCount(); + setTotalCount(savedQueryCount); + + const savedQueryItems = await savedQueryService.findSavedQueries('', perPage, activePage + 1); + const sortedSavedQueryItems = sortBy(savedQueryItems, 'attributes.title'); + setSavedQueries(sortedSavedQueryItems); }; if (isOpen) { - fetchQueries(); + fetchCountAndSavedQueries(); } - }, [isOpen]); + }, [isOpen, activePage]); const goToPage = (pageNumber: number) => { setActivePage(pageNumber); @@ -131,7 +134,6 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ ); const savedQueryRows = () => { - // we should be recalculating the savedQueryRows after a delete action const savedQueriesWithoutCurrent = savedQueries.filter(savedQuery => { if (!loadedSavedQuery) return true; return savedQuery.id !== loadedSavedQuery.id; @@ -140,11 +142,7 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ loadedSavedQuery && savedQueriesWithoutCurrent.length !== savedQueries.length ? [loadedSavedQuery, ...savedQueriesWithoutCurrent] : [...savedQueriesWithoutCurrent]; - const savedQueriesDisplayRows = savedQueriesReordered.slice( - activePage * pageCount, - activePage * pageCount + pageCount - ); - return savedQueriesDisplayRows.map(savedQuery => ( + return savedQueriesReordered.map(savedQuery => ( = ({ diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts b/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts index 8549e47f36464..ac5fdb7fe99d5 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts +++ b/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts @@ -29,6 +29,14 @@ const savedQueryAttributes: SavedQueryAttributes = { query: 'response:200', }, }; +const savedQueryAttributesBar: SavedQueryAttributes = { + title: 'bar', + description: 'baz', + query: { + language: 'kuery', + query: 'response:200', + }, +}; const savedQueryAttributesWithFilters: SavedQueryAttributes = { ...savedQueryAttributes, @@ -61,7 +69,14 @@ const mockSavedObjectsClient = { delete: jest.fn(), }; -const { deleteSavedQuery, getSavedQuery, findSavedQueries, saveQuery } = createSavedQueryService( +const { + deleteSavedQuery, + getSavedQuery, + findSavedQueries, + saveQuery, + getAllSavedQueries, + getSavedQueryCount, +} = createSavedQueryService( // @ts-ignore mockSavedObjectsClient ); @@ -151,7 +166,7 @@ describe('saved query service', () => { }); }); describe('findSavedQueries', function() { - it('should find and return saved queries without search text', async () => { + it('should find and return saved queries without search text or pagination parameters', async () => { mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], }); @@ -166,6 +181,8 @@ describe('saved query service', () => { }); const response = await findSavedQueries('foo'); expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ + page: 1, + perPage: 50, search: 'foo', searchFields: ['title^5', 'description'], sortField: '_score', @@ -203,6 +220,43 @@ describe('saved query service', () => { ]) ); }); + it('should accept perPage and page properties', async () => { + mockSavedObjectsClient.find.mockReturnValue({ + savedObjects: [ + { id: 'foo', attributes: savedQueryAttributes }, + { id: 'bar', attributes: savedQueryAttributesBar }, + ], + }); + const response = await findSavedQueries(undefined, 2, 1); + expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ + page: 1, + perPage: 2, + search: '', + searchFields: ['title^5', 'description'], + sortField: '_score', + type: 'query', + }); + expect(response).toEqual( + expect.objectContaining([ + { + attributes: { + description: 'bar', + query: { language: 'kuery', query: 'response:200' }, + title: 'foo', + }, + id: 'foo', + }, + { + attributes: { + description: 'baz', + query: { language: 'kuery', query: 'response:200' }, + title: 'bar', + }, + id: 'bar', + }, + ]) + ); + }); }); describe('getSavedQuery', function() { @@ -226,4 +280,40 @@ describe('saved query service', () => { expect(mockSavedObjectsClient.delete).toHaveBeenCalledWith('query', 'foo'); }); }); + + describe('getAllSavedQueries', function() { + it('should return all the saved queries', async () => { + mockSavedObjectsClient.find.mockReturnValue({ + savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], + }); + const response = await getAllSavedQueries(); + expect(response).toEqual( + expect.objectContaining([ + { + attributes: { + description: 'bar', + query: { language: 'kuery', query: 'response:200' }, + title: 'foo', + }, + id: 'foo', + }, + ]) + ); + expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ + page: 1, + perPage: 0, + type: 'query', + }); + }); + }); + + describe('getSavedQueryCount', function() { + it('should return the total number of saved queries', async () => { + mockSavedObjectsClient.find.mockReturnValue({ + total: 1, + }); + const response = await getSavedQueryCount(); + expect(response).toEqual(1); + }); + }); }); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts b/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts index 3e882b6df5c71..2668ce911c371 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts +++ b/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts @@ -35,9 +35,14 @@ export interface SavedQueryService { config?: { overwrite: boolean } ) => Promise; getAllSavedQueries: () => Promise; - findSavedQueries: (searchText?: string) => Promise; + findSavedQueries: ( + searchText?: string, + perPage?: number, + activePage?: number + ) => Promise; getSavedQuery: (id: string) => Promise; deleteSavedQuery: (id: string) => Promise<{}>; + getSavedQueryCount: () => Promise; } export const createSavedQueryService = ( @@ -89,24 +94,32 @@ export const createSavedQueryService = ( return parseSavedQueryObject(rawQueryResponse); }; - + // we have to tell the saved objects client how many to fetch, otherwise it defaults to fetching 20 per page const getAllSavedQueries = async (): Promise => { + const count = await getSavedQueryCount(); const response = await savedObjectsClient.find({ type: 'query', + perPage: count, + page: 1, }); - return response.savedObjects.map( (savedObject: { id: string; attributes: SerializedSavedQueryAttributes }) => parseSavedQueryObject(savedObject) ); }; - - const findSavedQueries = async (searchText: string = ''): Promise => { + // findSavedQueries will do a 'match_all' if no search string is passed in + const findSavedQueries = async ( + searchText: string = '', + perPage: number = 50, + activePage: number = 1 + ): Promise => { const response = await savedObjectsClient.find({ type: 'query', search: searchText, searchFields: ['title^5', 'description'], sortField: '_score', + perPage, + page: activePage, }); return response.savedObjects.map( @@ -154,11 +167,21 @@ export const createSavedQueryService = ( }; }; + const getSavedQueryCount = async (): Promise => { + const response = await savedObjectsClient.find({ + type: 'query', + perPage: 0, + page: 1, + }); + return response.total; + }; + return { saveQuery, getAllSavedQueries, findSavedQueries, getSavedQuery, deleteSavedQuery, + getSavedQueryCount, }; }; diff --git a/src/legacy/core_plugins/elasticsearch/lib/create_proxy.js b/src/legacy/core_plugins/elasticsearch/lib/create_proxy.js index cc4438ba29b80..266ed7bafdf90 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/create_proxy.js +++ b/src/legacy/core_plugins/elasticsearch/lib/create_proxy.js @@ -19,7 +19,6 @@ import Joi from 'joi'; import { abortableRequestHandler } from './abortable_request_handler'; -import { handleESError } from './handle_es_error'; export function createProxy(server) { const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); @@ -63,7 +62,7 @@ export function createProxy(server) { body }, { signal }); } catch (error) { - throw handleESError(error); + return JSON.parse(error.response); } }) }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js index f1f3036c0031a..621eb62b443a1 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js @@ -33,7 +33,7 @@ export class FilterManager { } getField() { - return this.indexPattern.fields.byName[this.fieldName]; + return this.indexPattern.fields.getByName(this.fieldName); } createFilter() { diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js index b64f9817ab6d3..68b837aebdc9d 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js @@ -38,12 +38,12 @@ export class PhraseFilterManager extends FilterManager { let newFilter; if (phrases.length === 1) { newFilter = buildPhraseFilter( - this.indexPattern.fields.byName[this.fieldName], + this.indexPattern.fields.getByName(this.fieldName), phrases[0], this.indexPattern); } else { newFilter = buildPhrasesFilter( - this.indexPattern.fields.byName[this.fieldName], + this.indexPattern.fields.getByName(this.fieldName), phrases, this.indexPattern); } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js index 5e9fbc6cb7a5f..a921922e061d0 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js @@ -35,8 +35,9 @@ describe('PhraseFilterManager', function () { const indexPatternMock = { id: indexPatternId, fields: { - byName: { - field1: fieldMock + getByName: name => { + const fields = { field1: fieldMock }; + return fields[name]; } } }; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js index ada8d0f207b14..1c8f5e2aa5a3e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js @@ -56,7 +56,7 @@ export class RangeFilterManager extends FilterManager { */ createFilter(value) { const newFilter = buildRangeFilter( - this.indexPattern.fields.byName[this.fieldName], + this.indexPattern.fields.getByName(this.fieldName), toRange(value), this.indexPattern); newFilter.meta.key = this.fieldName; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js index 0d13ef8a02c2f..7f9e6f83f3401 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js @@ -32,8 +32,11 @@ describe('RangeFilterManager', function () { const indexPatternMock = { id: indexPatternId, fields: { - byName: { - field1: fieldMock + getByName: name => { + const fields = { + field1: fieldMock + }; + return fields[name]; } } }; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js index ff28988d04e95..7d20d07ba05f6 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js @@ -110,7 +110,7 @@ class ListControl extends Control { terminate_after: chrome.getInjected('autocompleteTerminateAfter') }; const aggs = termsAgg({ - field: indexPattern.fields.byName[fieldName], + field: indexPattern.fields.getByName(fieldName), size: this.options.dynamicOptions ? null : _.get(this.options, 'size', 5), direction: 'desc', query diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js index 1f4acd122748d..3bef0bb196d48 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js @@ -29,7 +29,10 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({ indexPatterns: { indexPatterns: { get: () => ({ - fields: { byName: { myField: { name: 'myField' } } } + fields: { getByName: name => { + const fields = { myField: { name: 'myField' } }; + return fields[name]; + } } }), } }, @@ -37,7 +40,10 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({ filterManager: { fieldName: 'myNumberField', getIndexPattern: () => ({ - fields: { byName: { myField: { name: 'myField' } } } + fields: { getByName: name => { + const fields = { myField: { name: 'myField' } }; + return fields[name]; + } } }), getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js index 3c4c1ec236f55..cb1c3111addf5 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js @@ -64,7 +64,7 @@ class RangeControl extends Control { const fieldName = this.filterManager.fieldName; - const aggs = minMaxAgg(indexPattern.fields.byName[fieldName]); + const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName)); const searchSource = createSearchSource(this.kbnApi, null, indexPattern, aggs, this.useTimeFilter); this.abortController.signal.addEventListener('abort', () => searchSource.cancelQueued()); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js index ad7b5b90bc371..a22cc1a045e04 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js @@ -37,16 +37,22 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({ indexPatterns: { indexPatterns: { get: () => ({ - fields: { byName: { myNumberField: { name: 'myNumberField' } } } - }), + fields: { getByName: name => { + const fields = { myNumberField: { name: 'myNumberField' } }; + return fields[name]; + } + } }), } }, filter: { filterManager: { fieldName: 'myNumberField', getIndexPattern: () => ({ - fields: { byName: { myNumberField: { name: 'myNumberField' } } } - }), + fields: { getByName: name => { + const fields = { myNumberField: { name: 'myNumberField' } }; + return fields[name]; + } + } }), getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js index be40b963df158..68b444147d009 100644 --- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js +++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js @@ -119,15 +119,6 @@ class VisController { } submitFilters = () => { - // Clean up filter pills for nested controls that are now disabled because ancestors are not set - this.controls.map(async (control) => { - if (control.hasAncestors() && control.hasUnsetAncestor()) { - control.filterManager.findFilters().forEach((existingFilter) => { - this.filterManager.removeFilter(existingFilter); - }); - } - }); - const stagedControls = this.controls.filter((control) => { return control.hasChanged(); }); @@ -147,6 +138,17 @@ class VisController { }); }); + // Clean up filter pills for nested controls that are now disabled because ancestors are not set. + // This has to be done after looking up the staged controls because otherwise removing a filter + // will re-sync the controls of all other filters. + this.controls.map((control) => { + if (control.hasAncestors() && control.hasUnsetAncestor()) { + control.filterManager.findFilters().forEach((existingFilter) => { + this.filterManager.removeFilter(existingFilter); + }); + } + }); + this.filterManager.addFilters(newFilters, this.visParams.pinFilters); } diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx index d70402af83345..f7e16ead932fa 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx @@ -27,37 +27,40 @@ import { DocViewTable } from './table'; // @ts-ignore const indexPattern = { fields: { - byName: { - _index: { - name: '_index', - type: 'string', - scripted: false, - filterable: true, - }, - message: { - name: 'message', - type: 'string', - scripted: false, - filterable: false, - }, - extension: { - name: 'extension', - type: 'string', - scripted: false, - filterable: true, - }, - bytes: { - name: 'bytes', - type: 'number', - scripted: false, - filterable: true, - }, - scripted: { - name: 'scripted', - type: 'number', - scripted: true, - filterable: false, - }, + getByName: (name: string) => { + const fields: { [name: string]: {} } = { + _index: { + name: '_index', + type: 'string', + scripted: false, + filterable: true, + }, + message: { + name: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + extension: { + name: 'extension', + type: 'string', + scripted: false, + filterable: true, + }, + bytes: { + name: 'bytes', + type: 'number', + scripted: false, + filterable: true, + }, + scripted: { + name: 'scripted', + type: 'number', + scripted: true, + filterable: false, + }, + }; + return fields[name]; }, }, metaFields: ['_index', '_score'], diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx index ff784f6159870..8309eaa403f4c 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx @@ -31,7 +31,7 @@ export function DocViewTable({ onAddColumn, onRemoveColumn, }: DocViewRenderProps) { - const mapping = indexPattern.fields.byName; + const mapping = indexPattern.fields.getByName; const flattened = indexPattern.flattenHit(hit); const formatted = indexPattern.formatHit(hit, 'html'); const [fieldRowOpen, setFieldRowOpen] = useState({} as Record); @@ -63,15 +63,15 @@ export function DocViewTable({ : undefined; const isArrayOfObjects = Array.isArray(flattened[field]) && arrayContainsObjects(flattened[field]); - const displayUnderscoreWarning = !mapping[field] && field.indexOf('_') === 0; + const displayUnderscoreWarning = !mapping(field) && field.indexOf('_') === 0; const displayNoMappingWarning = - !mapping[field] && !displayUnderscoreWarning && !isArrayOfObjects; + !mapping(field) && !displayUnderscoreWarning && !isArrayOfObjects; return ( META_FIELD_NAMES.includes(fieldName) || // @ts-ignore - (indexPattern.fields.byName[fieldName] || { sortable: false }).sortable + (indexPattern.fields.getByName(fieldName) || { sortable: false }).sortable ); return sortableFields[0]; } diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js index 50e9067b48367..5e8cfc8e1609c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js @@ -47,7 +47,7 @@ describe('discoverField', function () { indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); _.assign($rootScope, { - field: indexPattern.fields.byName.extension, + field: indexPattern.fields.getByName('extension'), addField: sinon.spy(() => $rootScope.field.display = true), removeField: sinon.spy(() => $rootScope.field.display = false), showDetails: sinon.spy(() => $rootScope.field.details = { exists: true }), diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js index cce6127ed9c47..3130ac29eb84d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js @@ -114,14 +114,14 @@ describe('fieldCalculator', function () { }); it('Should return an array of values for _source fields', function () { - const extensions = fieldCalculator.getFieldValues(hits, indexPattern.fields.byName.extension); + const extensions = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('extension')); expect(extensions).to.be.an(Array); expect(_.filter(extensions, function (v) { return v === 'html'; }).length).to.be(8); expect(_.uniq(_.clone(extensions)).sort()).to.eql(['gif', 'html', 'php', 'png']); }); it('Should return an array of values for core meta fields', function () { - const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.byName._type); + const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('_type')); expect(types).to.be.an(Array); expect(_.filter(types, function (v) { return v === 'apache'; }).length).to.be(18); expect(_.uniq(_.clone(types)).sort()).to.eql(['apache', 'nginx']); @@ -134,7 +134,7 @@ describe('fieldCalculator', function () { beforeEach(function () { params = { hits: require('fixtures/real_hits.js'), - field: indexPattern.fields.byName.extension, + field: indexPattern.fields.getByName('extension'), count: 3 }; }); @@ -149,18 +149,18 @@ describe('fieldCalculator', function () { }); it('fails to analyze geo and attachment types', function () { - params.field = indexPattern.fields.byName.point; + params.field = indexPattern.fields.getByName('point'); expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined); - params.field = indexPattern.fields.byName.area; + params.field = indexPattern.fields.getByName('area'); expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined); - params.field = indexPattern.fields.byName.request_body; + params.field = indexPattern.fields.getByName('request_body'); expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined); }); it('fails to analyze fields that are in the mapping, but not the hits', function () { - params.field = indexPattern.fields.byName.ip; + params.field = indexPattern.fields.getByName('ip'); expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined); }); @@ -169,7 +169,7 @@ describe('fieldCalculator', function () { }); it('counts the hits the field exists in', function () { - params.field = indexPattern.fields.byName.phpmemory; + params.field = indexPattern.fields.getByName('phpmemory'); expect(fieldCalculator.getFieldValueCounts(params).exists).to.be(5); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss index 22f53512be46b..6960c7101fa10 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss @@ -22,5 +22,8 @@ } .dscToggleFieldFilterButton { - min-height: $euiSizeXL; + width: calc(100% - #{$euiSizeS}); + color: $euiColorPrimary; + padding-left: $euiSizeXS; + margin-left: $euiSizeXS; } diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx index cf853d798a8ab..2655d28af985b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx @@ -28,6 +28,7 @@ describe('DiscoverFieldSearch', () => { onChange: jest.fn(), onShowFilter: jest.fn(), showFilter: false, + filtersActive: 0, value: 'test', }; const comp = mountWithIntl(); @@ -49,10 +50,21 @@ describe('DiscoverFieldSearch', () => { expect(props.onShowFilter).toBeCalledTimes(1); }); - test('change showFilter value should change button label', () => { - const { btn, comp } = mountComponent(); - const prevFilterBtnHTML = btn.html(); + test('change showFilter value should change aria label', () => { + const { comp } = mountComponent(); + let btn = findTestSubject(comp, 'toggleFieldFilterButton'); + expect(btn.prop('aria-label')).toEqual('Show field filter settings'); comp.setProps({ showFilter: true }); - expect(btn.html()).not.toBe(prevFilterBtnHTML); + btn = findTestSubject(comp, 'toggleFieldFilterButton'); + expect(btn.prop('aria-label')).toEqual('Hide field filter settings'); + }); + + test('change filtersActive should change facet selection', () => { + const { comp } = mountComponent(); + let btn = findTestSubject(comp, 'toggleFieldFilterButton'); + expect(btn.hasClass('euiFacetButton--isSelected')).toBeFalsy(); + comp.setProps({ filtersActive: 3 }); + btn = findTestSubject(comp, 'toggleFieldFilterButton'); + expect(btn.hasClass('euiFacetButton--isSelected')).toBe(true); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx index 666ccf0acfc7a..f748cdae1b4fc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx @@ -18,7 +18,8 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonIcon, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiFacetButton, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export interface Props { /** @@ -37,13 +38,23 @@ export interface Props { * the input value of the user */ value?: string; + /** + * the number of selected filters + */ + filtersActive: number; } /** * Component is Discover's side bar to search of available fields * Additionally there's a button displayed that allows the user to show/hide more filter fields */ -export function DiscoverFieldSearch({ showFilter, onChange, onShowFilter, value }: Props) { +export function DiscoverFieldSearch({ + showFilter, + onChange, + onShowFilter, + value, + filtersActive, +}: Props) { if (typeof value !== 'string') { // at initial rendering value is undefined (angular related), this catches the warning // should be removed once all is react @@ -61,31 +72,34 @@ export function DiscoverFieldSearch({ showFilter, onChange, onShowFilter, value }); return ( - - - onChange('name', event.currentTarget.value)} - placeholder={searchPlaceholder} - value={value} - /> - - - - onShowFilter()} - size="m" + + + + onChange('name', event.currentTarget.value)} + placeholder={searchPlaceholder} + value={value} /> - - - + + + } + isSelected={filtersActive > 0} + quantity={filtersActive} + onClick={() => onShowFilter()} + > + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts index baf8f3040d6b0..8af23caedd78a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts @@ -29,5 +29,6 @@ app.directive('discoverFieldSearch', function(reactDirective: any) { ['onShowFilter', { watchDepth: 'reference' }], ['showFilter', { watchDepth: 'value' }], ['value', { watchDepth: 'value' }], + ['filtersActive', { watchDepth: 'value' }], ]); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html index d1a75adac5b82..3d8b38c278f31 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html @@ -12,6 +12,7 @@ on-show-filter="toggleShowFilter" show-filter="showFilter" value="filter.vals.name" + filters-active="filtersActive" >
@@ -41,7 +42,7 @@ class="form-control">
-
+