diff --git a/src/legacy/core_plugins/console/public/index.html b/src/legacy/core_plugins/console/public/index.html index f3e4b2a8708c8..91bcb05bf64cc 100644 --- a/src/legacy/core_plugins/console/public/index.html +++ b/src/legacy/core_plugins/console/public/index.html @@ -1,4 +1,7 @@ - +
diff --git a/src/legacy/core_plugins/console/public/src/helpers/get_top_nav.ts b/src/legacy/core_plugins/console/public/src/helpers/get_top_nav.ts index 4b9b598da8d4f..3b5fae6ff669d 100644 --- a/src/legacy/core_plugins/console/public/src/helpers/get_top_nav.ts +++ b/src/legacy/core_plugins/console/public/src/helpers/get_top_nav.ts @@ -28,7 +28,7 @@ import { showHelpPanel } from './help_show_panel'; export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) { return [ { - key: 'history', + id: 'history', label: i18n.translate('console.topNav.historyTabLabel', { defaultMessage: 'History', }), @@ -36,12 +36,12 @@ export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) { defaultMessage: 'History', }), run: () => { - toggleHistory(); + $scope.$evalAsync(toggleHistory); }, testId: 'consoleHistoryButton', }, { - key: 'settings', + id: 'settings', label: i18n.translate('console.topNav.settingsTabLabel', { defaultMessage: 'Settings', }), @@ -54,7 +54,7 @@ export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) { testId: 'consoleSettingsButton', }, { - key: 'help', + id: 'help', label: i18n.translate('console.topNav.helpTabLabel', { defaultMessage: 'Help', }), diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss index e1c89a538dc87..7fbe295b95571 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss @@ -1,4 +1,8 @@ // SASSTODO: Probably not the right file for this selector, but temporary until the files get re-organized +.globalQueryBar { + padding: 0px $euiSizeS $euiSizeS $euiSizeS; +} + .globalQueryBar:not(:empty) { padding-bottom: $euiSizeS; } diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 20be02510e61b..adfcc2f03c25a 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -90,6 +90,7 @@ export { ExpressionRenderer, ExpressionRendererProps, ExpressionRunner } from '. /** @public types */ export { IndexPattern, StaticIndexPattern, StaticIndexPatternField, Field } from './index_patterns'; export { Query } from './query'; +export { SearchBar, SearchBarProps } from './search'; export { FilterManager, FilterStateManager, uniqFilters } from './filter/filter_manager'; /** @public static code */ diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar.test.tsx.snap b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar.test.tsx.snap index bc0b15ca88e3e..8680f269d93ed 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar.test.tsx.snap +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar.test.tsx.snap @@ -2,9 +2,9 @@ exports[`QueryBar Should render the given query 1`] = ` - + + + + + + + + `; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.test.tsx index 2be00deb16d9e..87bcebc4c510a 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.test.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.test.tsx @@ -66,6 +66,8 @@ const mockIndexPattern = { }; describe('QueryBar', () => { + const QUERY_INPUT_SELECTOR = 'InjectIntl(QueryBarInputUI)'; + const TIMEPICKER_SELECTOR = 'EuiSuperDatePicker'; beforeEach(() => { jest.clearAllMocks(); }); @@ -102,4 +104,104 @@ describe('QueryBar', () => { expect(mockPersistedLogFactory.mock.calls[0][0]).toBe('typeahead:discover-kuery'); }); + + it('Should render only timepicker when no options provided', () => { + const component = shallowWithIntl( + + ); + + expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(0); + expect(component.find(TIMEPICKER_SELECTOR).length).toBe(1); + }); + + it('Should not show timepicker when asked', () => { + const component = shallowWithIntl( + + ); + + expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(0); + expect(component.find(TIMEPICKER_SELECTOR).length).toBe(0); + }); + + it('Should render timepicker with options', () => { + const component = shallowWithIntl( + + ); + + expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(0); + expect(component.find(TIMEPICKER_SELECTOR).length).toBe(1); + }); + + it('Should render only query input bar', () => { + const component = shallowWithIntl( + + ); + + expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(1); + expect(component.find(TIMEPICKER_SELECTOR).length).toBe(0); + }); + + it('Should NOT render query input bar if disabled', () => { + const component = shallowWithIntl( + + ); + + expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(0); + expect(component.find(TIMEPICKER_SELECTOR).length).toBe(0); + }); + + it('Should NOT render query input bar if missing options', () => { + const component = shallowWithIntl( + + ); + + expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(0); + expect(component.find(TIMEPICKER_SELECTOR).length).toBe(0); + }); }); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx index e1df6286bd120..19a584e34a978 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx @@ -18,7 +18,6 @@ */ import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query'; -import { IndexPattern } from 'ui/index_patterns'; import classNames from 'classnames'; import _ from 'lodash'; @@ -37,6 +36,7 @@ import { documentationLinks } from 'ui/documentation_links'; import { Toast, toastNotifications } from 'ui/notify'; import chrome from 'ui/chrome'; import { PersistedLog } from 'ui/persisted_log'; +import { IndexPattern } from '../../../index_patterns'; import { QueryBarInput } from './query_bar_input'; import { getQueryLog } from '../lib/get_query_log'; @@ -50,15 +50,16 @@ interface DateRange { } interface Props { - query: Query; - onSubmit: (payload: { dateRange: DateRange; query: Query }) => void; + query?: Query; + onSubmit: (payload: { dateRange: DateRange; query?: Query }) => void; disableAutoFocus?: boolean; appName: string; - screenTitle: string; - indexPatterns: Array; + screenTitle?: string; + indexPatterns?: Array; store: Storage; intl: InjectedIntl; prepend?: any; + showQueryInput?: boolean; showDatePicker?: boolean; dateRangeFrom?: string; dateRangeTo?: string; @@ -70,7 +71,7 @@ interface Props { } interface State { - query: Query; + query?: Query; inputIsPristine: boolean; currentProps?: Props; dateRangeFrom: string; @@ -79,22 +80,30 @@ interface State { } export class QueryBarUI extends Component { + public static defaultProps = { + showQueryInput: true, + showDatePicker: true, + showAutoRefreshOnly: false, + }; + public static getDerivedStateFromProps(nextProps: Props, prevState: State) { if (isEqual(prevState.currentProps, nextProps)) { return null; } let nextQuery = null; - if (nextProps.query.query !== prevState.query.query) { - nextQuery = { - query: nextProps.query.query, - language: nextProps.query.language, - }; - } else if (nextProps.query.language !== prevState.query.language) { - nextQuery = { - query: '', - language: nextProps.query.language, - }; + if (nextProps.query && prevState.query) { + if (nextProps.query.query !== prevState.query.query) { + nextQuery = { + query: nextProps.query.query, + language: nextProps.query.language, + }; + } else if (nextProps.query.language !== prevState.query.language) { + nextQuery = { + query: '', + language: nextProps.query.language, + }; + } } let nextDateRange = null; @@ -134,7 +143,7 @@ export class QueryBarUI extends Component { See https://github.com/elastic/kibana/issues/14086 */ public state = { - query: { + query: this.props.query && { query: this.props.query.query, language: this.props.query.language, }, @@ -149,20 +158,26 @@ export class QueryBarUI extends Component { private persistedLog: PersistedLog | undefined; + private isQueryDirty = () => { + return ( + !!this.props.query && !!this.state.query && this.state.query.query !== this.props.query.query + ); + }; + public isDirty = () => { if (!this.props.showDatePicker) { - return this.state.query.query !== this.props.query.query; + return this.isQueryDirty(); } return ( - this.state.query.query !== this.props.query.query || + this.isQueryDirty() || this.state.dateRangeFrom !== this.props.dateRangeFrom || this.state.dateRangeTo !== this.props.dateRangeTo ); }; public onClickSubmitButton = (event: React.MouseEvent) => { - if (this.persistedLog) { + if (this.persistedLog && this.state.query) { this.persistedLog.add(this.state.query.query); } this.onSubmit(() => event.preventDefault()); @@ -209,7 +224,7 @@ export class QueryBarUI extends Component { }); this.props.onSubmit({ - query: { + query: this.state.query && { query: this.state.query.query, language: this.state.query.language, }, @@ -227,10 +242,12 @@ export class QueryBarUI extends Component { }; public componentDidMount() { + if (!this.props.query) return; this.persistedLog = getQueryLog(this.props.appName, this.props.query.language); } public componentDidUpdate(prevProps: Props) { + if (!this.props.query || !prevProps.query) return; if (prevProps.query.language !== this.props.query.language) { this.persistedLog = getQueryLog(this.props.appName, this.props.query.language); } @@ -243,25 +260,40 @@ export class QueryBarUI extends Component { return ( - - - + {this.renderQueryInput()} {this.renderUpdateButton()} ); } + private renderQueryInput() { + if (!this.shouldRenderQueryInput()) return; + return ( + + + + ); + } + + private shouldRenderDatePicker() { + return this.props.showDatePicker || this.props.showAutoRefreshOnly; + } + + private shouldRenderQueryInput() { + return this.props.showQueryInput && this.props.indexPatterns && this.props.query; + } + private renderUpdateButton() { const button = this.props.customSubmitButton ? ( React.cloneElement(this.props.customSubmitButton, { onClick: this.onClickSubmitButton }) @@ -274,7 +306,7 @@ export class QueryBarUI extends Component { /> ); - if (!this.props.showDatePicker) { + if (!this.shouldRenderDatePicker()) { return button; } @@ -287,7 +319,7 @@ export class QueryBarUI extends Component { } private renderDatePicker() { - if (!this.props.showDatePicker) { + if (!this.shouldRenderDatePicker()) { return null; } @@ -330,6 +362,7 @@ export class QueryBarUI extends Component { } private handleLuceneSyntaxWarning() { + if (!this.state.query) return; const { intl, store } = this.props; const { query, language } = this.state.query; if ( diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts index 30736e9fd5a5e..b2f308127b0e1 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts @@ -42,6 +42,16 @@ const mockChromeFactory = jest.fn(() => { return { get: (key: string) => { switch (key) { + case 'timepicker:quickRanges': + return [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + ]; + case 'dateFormat': + return 'YY'; case 'history:limit': return 10; default: diff --git a/src/legacy/core_plugins/data/public/search/index.tsx b/src/legacy/core_plugins/data/public/search/index.tsx index b0687ea3c6bcd..6a9687ba7e325 100644 --- a/src/legacy/core_plugins/data/public/search/index.tsx +++ b/src/legacy/core_plugins/data/public/search/index.tsx @@ -18,3 +18,5 @@ */ export { SearchService, SearchSetup } from './search_service'; + +export * from './search_bar'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/index.tsx index 131c6059934a4..24ffa939a746e 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/index.tsx @@ -17,4 +17,4 @@ * under the License. */ -export { SearchBar } from './search_bar'; +export * from './search_bar'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx new file mode 100644 index 0000000000000..2b631e1f547db --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { SearchBar } from './search_bar'; + +jest.mock('../../../filter/filter_bar', () => { + return { + FilterBar: () =>
, + }; +}); + +jest.mock('../../../query/query_bar', () => { + return { + QueryBar: () =>
, + }; +}); + +const noop = jest.fn(); + +const createMockWebStorage = () => ({ + clear: jest.fn(), + getItem: jest.fn(), + key: jest.fn(), + removeItem: jest.fn(), + setItem: jest.fn(), + length: 0, +}); + +const createMockStorage = () => ({ + store: createMockWebStorage(), + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), +}); + +const mockIndexPattern = { + id: '1234', + title: 'logstash-*', + fields: [ + { + name: 'response', + type: 'number', + esTypes: ['integer'], + aggregatable: true, + filterable: true, + searchable: true, + }, + ], +}; + +const kqlQuery = { + query: 'response:200', + language: 'kuery', +}; + +describe('SearchBar', () => { + const SEARCH_BAR_ROOT = '.globalQueryBar'; + const FILTER_BAR = '.filterBar'; + const QUERY_BAR = '.queryBar'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Should render query bar when no options provided (in reality - timepicker)', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(1); + }); + + it('Should render empty when timepicker is off and no options provided', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(0); + }); + + it('Should render filter bar, when required fields are provided', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(1); + expect(component.find(QUERY_BAR).length).toBe(0); + }); + + it('Should NOT render filter bar, if disabled', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(0); + }); + + it('Should render query bar, when required fields are provided', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(1); + }); + + it('Should NOT render query bar, if disabled', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(0); + }); + + it('Should render query bar and filter bar', () => { + const component = mountWithIntl( + + ); + + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(1); + expect(component.find(QUERY_BAR).length).toBe(1); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx index 26a641b7bc153..84cd1f26b4256 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx @@ -24,9 +24,9 @@ import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; -import { IndexPattern } from 'ui/index_patterns'; import { Storage } from 'ui/storage'; +import { IndexPattern } from '../../../index_patterns'; import { Query, QueryBar } from '../../../query/query_bar'; import { FilterBar } from '../../../filter/filter_bar'; @@ -39,22 +39,26 @@ interface DateRange { * NgReact lib requires that changes to the props need to be made in the directive config as well * See [search_bar\directive\index.js] file */ -interface Props { - query: Query; - onQuerySubmit: (payload: { dateRange: DateRange; query: Query }) => void; - disableAutoFocus?: boolean; +export interface SearchBarProps { appName: string; - screenTitle: string; - indexPatterns: IndexPattern[]; - store: Storage; - filters: Filter[]; - onFiltersUpdated: (filters: Filter[]) => void; - showQueryBar: boolean; - showFilterBar: boolean; intl: InjectedIntl; + indexPatterns?: IndexPattern[]; + // Query bar + showQueryBar?: boolean; + showQueryInput?: boolean; + screenTitle?: string; + store?: Storage; + query?: Query; + onQuerySubmit?: (payload: { dateRange: DateRange; query?: Query }) => void; + // Filter bar + showFilterBar?: boolean; + filters?: Filter[]; + onFiltersUpdated?: (filters: Filter[]) => void; + // Date picker showDatePicker?: boolean; dateRangeFrom?: string; dateRangeTo?: string; + // Autorefresh isRefreshPaused?: boolean; refreshInterval?: number; showAutoRefreshOnly?: boolean; @@ -65,10 +69,12 @@ interface State { isFiltersVisible: boolean; } -class SearchBarUI extends Component { +class SearchBarUI extends Component { public static defaultProps = { showQueryBar: true, showFilterBar: true, + showDatePicker: true, + showAutoRefreshOnly: false, }; public filterBarRef: Element | null = null; @@ -78,6 +84,61 @@ class SearchBarUI extends Component { isFiltersVisible: true, }; + private getFilterLength() { + if (this.props.showFilterBar && this.props.filters) { + return this.props.filters.length; + } + } + + private getFilterUpdateFunction() { + if (this.props.showFilterBar && this.props.onFiltersUpdated) { + return this.props.onFiltersUpdated; + } + return (filters: Filter[]) => {}; + } + + private shouldRenderQueryBar() { + const showDatePicker = this.props.showDatePicker || this.props.showAutoRefreshOnly; + const showQueryInput = + this.props.showQueryInput && this.props.indexPatterns && this.props.query; + return this.props.showQueryBar && (showDatePicker || showQueryInput); + } + + private shouldRenderFilterBar() { + return this.props.showFilterBar && this.props.filters && this.props.indexPatterns; + } + + private getFilterTriggerButton() { + const filtersAppliedText = this.props.intl.formatMessage({ + id: 'data.search.searchBar.filtersButtonFiltersAppliedTitle', + defaultMessage: 'filters applied.', + }); + const clickToShowOrHideText = this.state.isFiltersVisible + ? this.props.intl.formatMessage({ + id: 'data.search.searchBar.filtersButtonClickToShowTitle', + defaultMessage: 'Select to hide', + }) + : this.props.intl.formatMessage({ + id: 'data.search.searchBar.filtersButtonClickToHideTitle', + defaultMessage: 'Select to show', + }); + + const filterCount = this.getFilterLength(); + return ( + + Filters + + ); + } + public setFilterBarHeight = () => { requestAnimationFrame(() => { const height = @@ -114,85 +175,62 @@ class SearchBarUI extends Component { } public render() { - const filtersAppliedText = this.props.intl.formatMessage({ - id: 'data.search.searchBar.filtersButtonFiltersAppliedTitle', - defaultMessage: 'filters applied.', - }); - const clickToShowOrHideText = this.state.isFiltersVisible - ? this.props.intl.formatMessage({ - id: 'data.search.searchBar.filtersButtonClickToShowTitle', - defaultMessage: 'Select to hide', - }) - : this.props.intl.formatMessage({ - id: 'data.search.searchBar.filtersButtonClickToHideTitle', - defaultMessage: 'Select to show', - }); - - const filterTriggerButton = ( - 0 ? this.props.filters.length : undefined} - aria-controls="GlobalFilterGroup" - aria-expanded={!!this.state.isFiltersVisible} - title={`${this.props.filters.length} ${filtersAppliedText} ${clickToShowOrHideText}`} - > - Filters - - ); - - const classes = classNames('globalFilterGroup__wrapper', { - 'globalFilterGroup__wrapper-isVisible': this.state.isFiltersVisible, - }); + let queryBar; + if (this.shouldRenderQueryBar()) { + queryBar = ( + + ); + } - return ( -
- {this.props.showQueryBar ? ( - - ) : ( - '' - )} - - {this.props.showFilterBar ? ( + let filterBar; + if (this.shouldRenderFilterBar()) { + const filterGroupClasses = classNames('globalFilterGroup__wrapper', { + 'globalFilterGroup__wrapper-isVisible': this.state.isFiltersVisible, + }); + filterBar = ( +
{ + this.filterBarWrapperRef = node; + }} + className={filterGroupClasses} + >
{ - this.filterBarWrapperRef = node; + this.filterBarRef = node; }} - className={classes} > -
{ - this.filterBarRef = node; - }} - > - -
+
- ) : ( - '' - )} +
+ ); + } + + return ( +
+ {queryBar} + {filterBar}
); } diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx index b181b43e0e527..357e28d181ae3 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx @@ -17,7 +17,7 @@ * under the License. */ -export { SearchBar } from './components'; +export * from './components'; // @ts-ignore export { setupDirective } from './directive'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 1e226ab804bdc..4b77a7e3733ca 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -3,30 +3,26 @@ ng-class="{'dshAppContainer--withMargins': model.useMargins}" > - - -
- -
- -
-
-
+ + - - -
- -
-

- - - -

-
- {{(hits || 0) | number:0}} - -
-
- - -
- -
-
-
+ + +
+
- - - -
- -
+
+
+ {{ ::'kbn.visualize.linkedToSearchInfoText' | i18n: { defaultMessage: 'Linked to Saved Search' } }} + - - -
- -
-
+ {{ savedVis.savedSearch.title }} + +   + + +
- +
+ + + + - - -
-
- - - - - -
-
-
+ + + +
diff --git a/src/legacy/ui/public/kbn_top_nav/_kbn_top_nav.scss b/src/legacy/ui/public/kbn_top_nav/_kbn_top_nav.scss index 8b58691cd18ca..47fd377681c9f 100644 --- a/src/legacy/ui/public/kbn_top_nav/_kbn_top_nav.scss +++ b/src/legacy/ui/public/kbn_top_nav/_kbn_top_nav.scss @@ -2,6 +2,10 @@ * 1. Make sure the timepicker is always one right, even if the main menu doesn't exist */ +kbn-top-nav { + z-index: 5; +} + .kbnTopNav { background-color: $euiPageBackgroundColor; border-bottom: $euiBorderThin; diff --git a/src/legacy/ui/public/kbn_top_nav/index.js b/src/legacy/ui/public/kbn_top_nav/index.js index 8a93972c4b226..4bca1972e9904 100644 --- a/src/legacy/ui/public/kbn_top_nav/index.js +++ b/src/legacy/ui/public/kbn_top_nav/index.js @@ -18,3 +18,4 @@ */ import './kbn_top_nav'; +import './kbn_top_nav2'; diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav2.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav2.js new file mode 100644 index 0000000000000..8c1feb922a25b --- /dev/null +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav2.js @@ -0,0 +1,120 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'ngreact'; +import { wrapInI18nContext } from 'ui/i18n'; +import { uiModules } from 'ui/modules'; +import { TopNavMenu } from '../../../core_plugins/kibana_react/public'; +import { Storage } from 'ui/storage'; + +const module = uiModules.get('kibana'); + +module.directive('kbnTopNavV2', () => { + return { + restrict: 'E', + template: '', + compile: (elem) => { + const child = document.createElement('kbn-top-nav-v2-helper'); + + // Copy attributes to the child directive + for (const attr of elem[0].attributes) { + child.setAttribute(attr.name, attr.value); + } + + // Add a special attribute that will change every time that one + // of the config array's disableButton function return value changes. + child.setAttribute('disabled-buttons', 'disabledButtons'); + + // Pass in storage + const localStorage = new Storage(window.localStorage); + child.setAttribute('store', 'store'); + + // Append helper directive + elem.append(child); + + const linkFn = ($scope, _, $attr) => { + $scope.store = localStorage; + + // Watch config changes + $scope.$watch(() => { + const config = $scope.$eval($attr.config); + return config.map((item) => { + // Copy key into id, as it's a reserved react propery. + // This is done for Angular directive backward compatibility. + // In React only id is recognized. + if (item.key && !item.id) { + item.id = item.key; + } + + // Watch the disableButton functions + if (typeof item.disableButton === 'function') { + return item.disableButton(); + } + return item.disableButton; + }); + }, (newVal) => { + $scope.disabledButtons = newVal; + }, + true); + }; + + return linkFn; + } + }; +}); + +module.directive('kbnTopNavV2Helper', (reactDirective) => { + return reactDirective( + wrapInI18nContext(TopNavMenu), + [ + ['name', { watchDepth: 'reference' }], + ['config', { watchDepth: 'value' }], + ['disabledButtons', { watchDepth: 'reference' }], + + ['query', { watchDepth: 'reference' }], + ['store', { watchDepth: 'reference' }], + ['intl', { watchDepth: 'reference' }], + ['store', { watchDepth: 'reference' }], + + ['onQuerySubmit', { watchDepth: 'reference' }], + ['onFiltersUpdated', { watchDepth: 'reference' }], + ['onRefreshChange', { watchDepth: 'reference' }], + + ['indexPatterns', { watchDepth: 'collection' }], + ['filters', { watchDepth: 'collection' }], + + // All modifiers default to true. + // Set to false to hide subcomponents. + 'showSearchBar', + 'showFilterBar', + 'showQueryBar', + 'showQueryInput', + 'showDatePicker', + + 'appName', + 'screenTitle', + 'dateRangeFrom', + 'dateRangeTo', + 'isRefreshPaused', + 'refreshInterval', + 'disableAutoFocus', + 'showAutoRefreshOnly', + ], + ); +}); diff --git a/src/legacy/ui/public/styles/_legacy/_base.scss b/src/legacy/ui/public/styles/_legacy/_base.scss index 2cf7e1e309046..aad90a99ac357 100644 --- a/src/legacy/ui/public/styles/_legacy/_base.scss +++ b/src/legacy/ui/public/styles/_legacy/_base.scss @@ -59,8 +59,8 @@ input[type="checkbox"], padding-bottom: $euiSizeS; } - > kbn-top-nav { - z-index: 5; + .globalQueryBar { + padding: 0px $euiSizeS $euiSizeS $euiSizeS; } > nav, diff --git a/test/functional/services/inspector.js b/test/functional/services/inspector.js index 393af9f7d5897..67d3c1113103b 100644 --- a/test/functional/services/inspector.js +++ b/test/functional/services/inspector.js @@ -30,7 +30,7 @@ export function InspectorProvider({ getService }) { return new class Inspector { async getIsEnabled() { - const ariaDisabled = await testSubjects.getAttribute('openInspectorButton', 'aria-disabled'); + const ariaDisabled = await testSubjects.getAttribute('openInspectorButton', 'disabled'); return ariaDisabled !== 'true'; } diff --git a/x-pack/legacy/plugins/maps/public/angular/map.html b/x-pack/legacy/plugins/maps/public/angular/map.html index c95117d906d16..13e6c038327c5 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map.html +++ b/x-pack/legacy/plugins/maps/public/angular/map.html @@ -1,30 +1,22 @@
- -
- -
- - -
-
-
-
- -
-
+ +
diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index 38ad432387d24..d386be39818ca 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -332,7 +332,8 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta timefilter.disableAutoRefreshSelector(); $scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar $scope.topNavMenu = [{ - key: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', { + id: 'full-screen', + label: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', { defaultMessage: `full screen` }), description: i18n.translate('xpack.maps.mapController.fullScreenDescription', { @@ -343,7 +344,8 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta store.dispatch(enableFullScreen()); } }, { - key: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', { + id: 'inspect', + label: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', { defaultMessage: `inspect` }), description: i18n.translate('xpack.maps.mapController.openInspectorDescription', { @@ -355,7 +357,8 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta Inspector.open(inspectorAdapters, {}); } }, ...(capabilities.get().maps.save ? [{ - key: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', { + id: 'save', + label: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', { defaultMessage: `save` }), description: i18n.translate('xpack.maps.mapController.saveMapDescription', { diff --git a/x-pack/legacy/plugins/maps/public/index.js b/x-pack/legacy/plugins/maps/public/index.js index 1afc4a2bbab3e..7e87fc7363ce5 100644 --- a/x-pack/legacy/plugins/maps/public/index.js +++ b/x-pack/legacy/plugins/maps/public/index.js @@ -22,7 +22,6 @@ import { capabilities } from 'ui/capabilities'; import chrome from 'ui/chrome'; import routes from 'ui/routes'; import 'ui/kbn_top_nav'; -import 'ui/angular-bootstrap'; // required for kbn-top-nav button tooltips import { uiModules } from 'ui/modules'; import { docTitle } from 'ui/doc_title'; import 'ui/autoload/styles'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 258252a30335c..87c577383b36c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1566,9 +1566,6 @@ "kbn.discover.notifications.notSavedSearchTitle": "検索「{savedSearchTitle}」は保存されませんでした。", "kbn.discover.notifications.savedSearchTitle": "検索「{savedSearchTitle}」が保存されました。", "kbn.discover.painlessError.painlessScriptedFieldErrorMessage": "Painless スクリプトのフィールド「{script}」のエラー.", - "kbn.discover.reloadSavedSearchAriaLabel": "保存された検索を再読み込みします", - "kbn.discover.reloadSavedSearchButton": "再読み込み", - "kbn.discover.reloadSavedSearchTooltip": "保存された検索を再読み込みします", "kbn.discover.rootBreadcrumb": "ディスカバリ", "kbn.discover.savedSearch.newSavedSearchTitle": "新しく保存された検索", "kbn.discover.savedSearch.savedObjectName": "保存された検索", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index bf6af4c68ecda..39be032e6381d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1566,9 +1566,6 @@ "kbn.discover.notifications.notSavedSearchTitle": "搜索 “{savedSearchTitle}” 未保存。", "kbn.discover.notifications.savedSearchTitle": "搜索 “{savedSearchTitle}” 已保存", "kbn.discover.painlessError.painlessScriptedFieldErrorMessage": "Painless 脚本字段 “{script}” 有错误。", - "kbn.discover.reloadSavedSearchAriaLabel": "重新加载已保存搜索", - "kbn.discover.reloadSavedSearchButton": "重新加载", - "kbn.discover.reloadSavedSearchTooltip": "重新加载已保存搜索", "kbn.discover.rootBreadcrumb": "Discover", "kbn.discover.savedSearch.newSavedSearchTitle": "新保存的搜索", "kbn.discover.savedSearch.savedObjectName": "已保存搜索",