mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Discover] Migrate discover.html Angular template to React (#75784)
Co-authored-by: Andrea Del Rio <delrio.andre@gmail.com>
This commit is contained in:
parent
dd8db10253
commit
0dc0f1f34c
47 changed files with 841 additions and 969 deletions
|
@ -5,6 +5,11 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dscAppContainer {
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
discover-app {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -17,9 +22,12 @@ discover-app {
|
|||
|
||||
// SASSTODO: replace the z-index value with a variable
|
||||
.dscWrapper {
|
||||
padding-left: $euiSizeXL;
|
||||
padding-right: $euiSizeS;
|
||||
padding-left: 21px;
|
||||
z-index: 1;
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
padding-left: $euiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
@include euiPanel('.dscWrapper__content');
|
||||
|
@ -104,14 +112,51 @@ discover-app {
|
|||
top: $euiSizeXS;
|
||||
}
|
||||
|
||||
[fixed-scroll] {
|
||||
.dscTableFixedScroll {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0;
|
||||
|
||||
+ .fixed-scroll-scroller {
|
||||
+ .dscTableFixedScroll__scroller {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.dscCollapsibleSidebar {
|
||||
position: relative;
|
||||
z-index: $euiZLevel1;
|
||||
|
||||
.dscCollapsibleSidebar__collapseButton {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -$euiSizeXL + 4;
|
||||
cursor: pointer;
|
||||
z-index: -1;
|
||||
min-height: $euiSizeM;
|
||||
min-width: $euiSizeM;
|
||||
padding: $euiSizeXS * .5;
|
||||
}
|
||||
|
||||
&.closed {
|
||||
width: 0 !important;
|
||||
border-right-width: 0;
|
||||
border-left-width: 0;
|
||||
.dscCollapsibleSidebar__collapseButton {
|
||||
right: -$euiSizeL + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
.dscCollapsibleSidebar {
|
||||
&.closed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dscCollapsibleSidebar__collapseButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,132 +167,6 @@ Array [
|
|||
]
|
||||
`;
|
||||
|
||||
exports[`DiscoverNoResults props shardFailures doesn't render failures list when there are no failures 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>,
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero dscNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOut euiCallOut--warning"
|
||||
data-test-subj="discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOutHeader"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="euiCallOutHeader__icon"
|
||||
data-euiicon-type="help"
|
||||
/>
|
||||
<span
|
||||
class="euiCallOutHeader__title"
|
||||
>
|
||||
No results match your search criteria
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DiscoverNoResults props shardFailures renders failures list when there are failures 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>,
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero dscNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOut euiCallOut--warning"
|
||||
data-test-subj="discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOutHeader"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="euiCallOutHeader__icon"
|
||||
data-euiicon-type="help"
|
||||
/>
|
||||
<span
|
||||
class="euiCallOutHeader__title"
|
||||
>
|
||||
No results match your search criteria
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h3>
|
||||
Address shard failures
|
||||
</h3>
|
||||
<p>
|
||||
The following shard failures occurred:
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
class="euiText euiText--extraSmall"
|
||||
>
|
||||
<strong>
|
||||
Index ‘A’
|
||||
</strong>
|
||||
, shard ‘1’
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div>
|
||||
<pre>
|
||||
<code>
|
||||
{"reason":"Awful error"}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--l"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="euiText euiText--extraSmall"
|
||||
>
|
||||
<strong>
|
||||
Index ‘B’
|
||||
</strong>
|
||||
, shard ‘2’
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div>
|
||||
<pre>
|
||||
<code>
|
||||
{"reason":"Bad error"}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DiscoverNoResults props timeFieldName renders time range feedback 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
@import 'no_results';
|
||||
@import 'histogram';
|
||||
@import './collapsible_sidebar/index';
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
// SASSTODO: Can't rename main class
|
||||
// because it's also the name of the angular directive
|
||||
.collapsible-sidebar {
|
||||
position: relative;
|
||||
z-index: $kbnDiscoverSidebarDepth;
|
||||
|
||||
.kbnCollapsibleSidebar__collapseButton {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -21px;
|
||||
cursor: pointer;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&.closed {
|
||||
width: 0 !important;
|
||||
border-right-width: 0;
|
||||
border-left-width: 0;
|
||||
|
||||
> * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.kbnCollapsibleSidebar__collapseButton {
|
||||
visibility: visible;
|
||||
|
||||
.chevron-cont:before {
|
||||
content: "\F138";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
.collapsible-sidebar {
|
||||
&.closed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kbnCollapsibleSidebar__collapseButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* 1. The local nav contains tooltips which should pop over the filter bar.
|
||||
* 2. The filter and local nav components should always appear above the dashboard grid items.
|
||||
* 3. The filter and local nav components should always appear above the discover content.
|
||||
* 4. The sidebar collapser button should appear above the main Discover content but below the top elements.
|
||||
* 5. Dragged panels in dashboard should always appear above other panels.
|
||||
*/
|
||||
$kbnFilterBarDepth: 4; /* 1 */
|
||||
$kbnLocalNavDepth: 5; /* 1 */
|
||||
$kbnDashboardGridDepth: 1; /* 2 */
|
||||
$kbnDashboardDraggingGridDepth: 2; /* 5 */
|
||||
$kbnDiscoverWrapperDepth: 1; /* 3 */
|
||||
$kbnDiscoverSidebarDepth: 2; /* 4 */
|
|
@ -1,2 +0,0 @@
|
|||
@import 'depth';
|
||||
@import 'collapsible_sidebar';
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { IScope } from 'angular';
|
||||
|
||||
interface LazyScope extends IScope {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function CollapsibleSidebarProvider() {
|
||||
// simply a list of all of all of angulars .col-md-* classes except 12
|
||||
const listOfWidthClasses = _.times(11, function (i) {
|
||||
return 'col-md-' + i;
|
||||
});
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
link: ($scope: LazyScope, $elem: any) => {
|
||||
let isCollapsed = false;
|
||||
const $collapser = $(
|
||||
`<button
|
||||
data-test-subj="collapseSideBarButton"
|
||||
type="button"
|
||||
aria-expanded="true"
|
||||
aria-label="Toggle sidebar"
|
||||
class="kuiCollapseButton kbnCollapsibleSidebar__collapseButton"
|
||||
></button>`
|
||||
);
|
||||
// If the collapsable element has an id, also set aria-controls
|
||||
if ($elem.attr('id')) {
|
||||
$collapser.attr('aria-controls', $elem.attr('id'));
|
||||
}
|
||||
const $icon = $('<span class="kuiIcon fa-chevron-circle-left"></span>');
|
||||
$collapser.append($icon);
|
||||
const $siblings = $elem.siblings();
|
||||
|
||||
const siblingsClass = listOfWidthClasses.reduce((prev: string, className: string) => {
|
||||
if (prev) return prev;
|
||||
return $siblings.hasClass(className) && className;
|
||||
}, '');
|
||||
|
||||
// If there is are only two elements we can assume the other one will take 100% of the width.
|
||||
const hasSingleSibling = $siblings.length === 1 && siblingsClass;
|
||||
|
||||
$collapser.on('click', function () {
|
||||
if (isCollapsed) {
|
||||
isCollapsed = false;
|
||||
$elem.removeClass('closed');
|
||||
$icon.addClass('fa-chevron-circle-left');
|
||||
$icon.removeClass('fa-chevron-circle-right');
|
||||
$collapser.attr('aria-expanded', 'true');
|
||||
} else {
|
||||
isCollapsed = true;
|
||||
$elem.addClass('closed');
|
||||
$icon.removeClass('fa-chevron-circle-left');
|
||||
$icon.addClass('fa-chevron-circle-right');
|
||||
$collapser.attr('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
if (hasSingleSibling) {
|
||||
$siblings.toggleClass(siblingsClass + ' col-md-12');
|
||||
}
|
||||
|
||||
if ($scope.toggleSidebar) $scope.toggleSidebar();
|
||||
});
|
||||
|
||||
$collapser.appendTo($elem);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -21,7 +21,7 @@ import _ from 'lodash';
|
|||
// Debounce service, angularized version of lodash debounce
|
||||
// borrowed heavily from https://github.com/shahata/angular-debounce
|
||||
|
||||
export function DebounceProviderTimeout($timeout) {
|
||||
export function createDebounceProviderTimeout($timeout) {
|
||||
return function (func, wait, options) {
|
||||
let timeout;
|
||||
let args;
|
||||
|
@ -66,7 +66,3 @@ export function DebounceProviderTimeout($timeout) {
|
|||
return debounce;
|
||||
};
|
||||
}
|
||||
|
||||
export function DebounceProvider(debounce) {
|
||||
return debounce;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import 'angular-sanitize';
|
|||
import 'angular-route';
|
||||
|
||||
// @ts-ignore
|
||||
import { DebounceProvider } from './index';
|
||||
import { createDebounceProviderTimeout } from './debounce';
|
||||
import { coreMock } from '../../../../../../../core/public/mocks';
|
||||
import { initializeInnerAngularModule } from '../../../../get_inner_angular';
|
||||
import { navigationPluginMock } from '../../../../../../navigation/public/mocks';
|
||||
|
@ -33,7 +33,6 @@ import { initAngularBootstrap } from '../../../../../../kibana_legacy/public';
|
|||
|
||||
describe('debounce service', function () {
|
||||
let debounce: (fn: () => void, timeout: number, options?: any) => any;
|
||||
let debounceFromProvider: (fn: () => void, timeout: number, options?: any) => any;
|
||||
let $timeout: ITimeoutService;
|
||||
let spy: SinonSpy;
|
||||
|
||||
|
@ -51,22 +50,17 @@ describe('debounce service', function () {
|
|||
|
||||
angular.mock.module('app/discover');
|
||||
|
||||
angular.mock.inject(
|
||||
($injector: auto.IInjectorService, _$timeout_: ITimeoutService, Private: any) => {
|
||||
$timeout = _$timeout_;
|
||||
angular.mock.inject(($injector: auto.IInjectorService, _$timeout_: ITimeoutService) => {
|
||||
$timeout = _$timeout_;
|
||||
|
||||
debounce = $injector.get('debounce');
|
||||
debounceFromProvider = Private(DebounceProvider);
|
||||
}
|
||||
);
|
||||
debounce = createDebounceProviderTimeout($timeout);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have a cancel method', function () {
|
||||
const bouncer = debounce(() => {}, 100);
|
||||
const bouncerFromProvider = debounceFromProvider(() => {}, 100);
|
||||
|
||||
expect(bouncer).toHaveProperty('cancel');
|
||||
expect(bouncerFromProvider).toHaveProperty('cancel');
|
||||
});
|
||||
|
||||
describe('delayed execution', function () {
|
||||
|
@ -77,7 +71,6 @@ describe('debounce service', function () {
|
|||
|
||||
it('should delay execution', function () {
|
||||
const bouncer = debounce(spy, 100);
|
||||
const bouncerFromProvider = debounceFromProvider(spy, 100);
|
||||
|
||||
bouncer();
|
||||
sinon.assert.notCalled(spy);
|
||||
|
@ -85,16 +78,10 @@ describe('debounce service', function () {
|
|||
sinon.assert.calledOnce(spy);
|
||||
|
||||
spy.resetHistory();
|
||||
|
||||
bouncerFromProvider();
|
||||
sinon.assert.notCalled(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledOnce(spy);
|
||||
});
|
||||
|
||||
it('should fire on leading edge', function () {
|
||||
const bouncer = debounce(spy, 100, { leading: true });
|
||||
const bouncerFromProvider = debounceFromProvider(spy, 100, { leading: true });
|
||||
|
||||
bouncer();
|
||||
sinon.assert.calledOnce(spy);
|
||||
|
@ -102,19 +89,10 @@ describe('debounce service', function () {
|
|||
sinon.assert.calledTwice(spy);
|
||||
|
||||
spy.resetHistory();
|
||||
|
||||
bouncerFromProvider();
|
||||
sinon.assert.calledOnce(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledTwice(spy);
|
||||
});
|
||||
|
||||
it('should only fire on leading edge', function () {
|
||||
const bouncer = debounce(spy, 100, { leading: true, trailing: false });
|
||||
const bouncerFromProvider = debounceFromProvider(spy, 100, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
});
|
||||
|
||||
bouncer();
|
||||
sinon.assert.calledOnce(spy);
|
||||
|
@ -122,17 +100,11 @@ describe('debounce service', function () {
|
|||
sinon.assert.calledOnce(spy);
|
||||
|
||||
spy.resetHistory();
|
||||
|
||||
bouncerFromProvider();
|
||||
sinon.assert.calledOnce(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledOnce(spy);
|
||||
});
|
||||
|
||||
it('should reset delayed execution', function () {
|
||||
const cancelSpy = sinon.spy($timeout, 'cancel');
|
||||
const bouncer = debounce(spy, 100);
|
||||
const bouncerFromProvider = debounceFromProvider(spy, 100);
|
||||
|
||||
bouncer();
|
||||
sandbox.clock.tick(1);
|
||||
|
@ -145,15 +117,6 @@ describe('debounce service', function () {
|
|||
|
||||
spy.resetHistory();
|
||||
cancelSpy.resetHistory();
|
||||
|
||||
bouncerFromProvider();
|
||||
sandbox.clock.tick(1);
|
||||
|
||||
bouncerFromProvider();
|
||||
sinon.assert.notCalled(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledOnce(spy);
|
||||
sinon.assert.calledOnce(cancelSpy);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -161,7 +124,6 @@ describe('debounce service', function () {
|
|||
it('should cancel the $timeout', function () {
|
||||
const cancelSpy = sinon.spy($timeout, 'cancel');
|
||||
const bouncer = debounce(spy, 100);
|
||||
const bouncerFromProvider = debounceFromProvider(spy, 100);
|
||||
|
||||
bouncer();
|
||||
bouncer.cancel();
|
||||
|
@ -170,12 +132,6 @@ describe('debounce service', function () {
|
|||
$timeout.verifyNoPendingTasks();
|
||||
|
||||
cancelSpy.resetHistory();
|
||||
|
||||
bouncerFromProvider();
|
||||
bouncerFromProvider.cancel();
|
||||
sinon.assert.calledOnce(cancelSpy);
|
||||
// throws if pending timeouts
|
||||
$timeout.verifyNoPendingTasks();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './debounce';
|
||||
|
||||
export { DebounceProvider } from './debounce';
|
||||
export { createDebounceProviderTimeout } from './debounce';
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import { DebounceProvider } from './debounce';
|
||||
import { createDebounceProviderTimeout } from './debounce';
|
||||
|
||||
const SCROLLER_HEIGHT = 20;
|
||||
|
||||
|
@ -28,124 +28,128 @@ const SCROLLER_HEIGHT = 20;
|
|||
* to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar
|
||||
* might be waaaay down the page, like the doc table on Discover.
|
||||
*/
|
||||
export function FixedScrollProvider(Private) {
|
||||
const debounce = Private(DebounceProvider);
|
||||
|
||||
export function FixedScrollProvider($timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, $el) {
|
||||
let $window = $(window);
|
||||
let $scroller = $('<div class="fixed-scroll-scroller">').height(SCROLLER_HEIGHT);
|
||||
|
||||
/**
|
||||
* Remove the listeners bound in listen()
|
||||
* @type {function}
|
||||
*/
|
||||
let unlisten = _.noop;
|
||||
|
||||
/**
|
||||
* Listen for scroll events on the $scroller and the $el, sets unlisten()
|
||||
*
|
||||
* unlisten must be called before calling or listen() will throw an Error
|
||||
*
|
||||
* Since the browser emits "scroll" events after setting scrollLeft
|
||||
* the listeners also prevent tug-of-war
|
||||
*
|
||||
* @throws {Error} If unlisten was not called first
|
||||
* @return {undefined}
|
||||
*/
|
||||
function listen() {
|
||||
if (unlisten !== _.noop) {
|
||||
throw new Error(
|
||||
'fixedScroll listeners were not cleaned up properly before re-listening!'
|
||||
);
|
||||
}
|
||||
|
||||
let blockTo;
|
||||
function bind($from, $to) {
|
||||
function handler() {
|
||||
if (blockTo === $to) return (blockTo = null);
|
||||
$to.scrollLeft((blockTo = $from).scrollLeft());
|
||||
}
|
||||
|
||||
$from.on('scroll', handler);
|
||||
return function () {
|
||||
$from.off('scroll', handler);
|
||||
};
|
||||
}
|
||||
|
||||
unlisten = _.flow(bind($el, $scroller), bind($scroller, $el), function () {
|
||||
unlisten = _.noop;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert DOM changes and event listeners
|
||||
* @return {undefined}
|
||||
*/
|
||||
function cleanUp() {
|
||||
unlisten();
|
||||
$scroller.detach();
|
||||
$el.css('padding-bottom', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the DOM and attach event listeners based on need.
|
||||
* Is called many times to re-setup, must be idempotent
|
||||
* @return {undefined}
|
||||
*/
|
||||
function setup() {
|
||||
cleanUp();
|
||||
|
||||
const containerWidth = $el.width();
|
||||
const contentWidth = $el.prop('scrollWidth');
|
||||
const containerHorizOverflow = contentWidth - containerWidth;
|
||||
|
||||
const elTop = $el.offset().top - $window.scrollTop();
|
||||
const elBottom = elTop + $el.height();
|
||||
const windowVertOverflow = elBottom - $window.height();
|
||||
|
||||
const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0;
|
||||
if (!requireScroller) return;
|
||||
|
||||
// push the content away from the scroller
|
||||
$el.css('padding-bottom', SCROLLER_HEIGHT);
|
||||
|
||||
// fill the scroller with a dummy element that mimics the content
|
||||
$scroller
|
||||
.width(containerWidth)
|
||||
.html($('<div>').css({ width: contentWidth, height: SCROLLER_HEIGHT }))
|
||||
.insertAfter($el);
|
||||
|
||||
// listen for scroll events
|
||||
listen();
|
||||
}
|
||||
|
||||
let width;
|
||||
let scrollWidth;
|
||||
function checkWidth() {
|
||||
const newScrollWidth = $el.prop('scrollWidth');
|
||||
const newWidth = $el.width();
|
||||
|
||||
if (scrollWidth !== newScrollWidth || width !== newWidth) {
|
||||
$scope.$apply(setup);
|
||||
|
||||
scrollWidth = newScrollWidth;
|
||||
width = newWidth;
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedCheckWidth = debounce(checkWidth, 100, {
|
||||
invokeApply: false,
|
||||
});
|
||||
$scope.$watch(debouncedCheckWidth);
|
||||
|
||||
// cleanup when the scope is destroyed
|
||||
$scope.$on('$destroy', function () {
|
||||
cleanUp();
|
||||
debouncedCheckWidth.cancel();
|
||||
$scroller = $window = null;
|
||||
});
|
||||
return createFixedScroll($scope, $timeout)($el);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createFixedScroll($scope, $timeout) {
|
||||
const debounce = createDebounceProviderTimeout($timeout);
|
||||
return function (el) {
|
||||
const $el = typeof el.css === 'function' ? el : $(el);
|
||||
let $window = $(window);
|
||||
let $scroller = $('<div class="dscTableFixedScroll__scroller">').height(SCROLLER_HEIGHT);
|
||||
|
||||
/**
|
||||
* Remove the listeners bound in listen()
|
||||
* @type {function}
|
||||
*/
|
||||
let unlisten = _.noop;
|
||||
|
||||
/**
|
||||
* Listen for scroll events on the $scroller and the $el, sets unlisten()
|
||||
*
|
||||
* unlisten must be called before calling or listen() will throw an Error
|
||||
*
|
||||
* Since the browser emits "scroll" events after setting scrollLeft
|
||||
* the listeners also prevent tug-of-war
|
||||
*
|
||||
* @throws {Error} If unlisten was not called first
|
||||
* @return {undefined}
|
||||
*/
|
||||
function listen() {
|
||||
if (unlisten !== _.noop) {
|
||||
throw new Error('fixedScroll listeners were not cleaned up properly before re-listening!');
|
||||
}
|
||||
|
||||
let blockTo;
|
||||
function bind($from, $to) {
|
||||
function handler() {
|
||||
if (blockTo === $to) return (blockTo = null);
|
||||
$to.scrollLeft((blockTo = $from).scrollLeft());
|
||||
}
|
||||
|
||||
$from.on('scroll', handler);
|
||||
return function () {
|
||||
$from.off('scroll', handler);
|
||||
};
|
||||
}
|
||||
|
||||
unlisten = _.flow(bind($el, $scroller), bind($scroller, $el), function () {
|
||||
unlisten = _.noop;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert DOM changes and event listeners
|
||||
* @return {undefined}
|
||||
*/
|
||||
function cleanUp() {
|
||||
unlisten();
|
||||
$scroller.detach();
|
||||
$el.css('padding-bottom', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the DOM and attach event listeners based on need.
|
||||
* Is called many times to re-setup, must be idempotent
|
||||
* @return {undefined}
|
||||
*/
|
||||
function setup() {
|
||||
cleanUp();
|
||||
|
||||
const containerWidth = $el.width();
|
||||
const contentWidth = $el.prop('scrollWidth');
|
||||
const containerHorizOverflow = contentWidth - containerWidth;
|
||||
|
||||
const elTop = $el.offset().top - $window.scrollTop();
|
||||
const elBottom = elTop + $el.height();
|
||||
const windowVertOverflow = elBottom - $window.height();
|
||||
|
||||
const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0;
|
||||
if (!requireScroller) return;
|
||||
|
||||
// push the content away from the scroller
|
||||
$el.css('padding-bottom', SCROLLER_HEIGHT);
|
||||
|
||||
// fill the scroller with a dummy element that mimics the content
|
||||
$scroller
|
||||
.width(containerWidth)
|
||||
.html($('<div>').css({ width: contentWidth, height: SCROLLER_HEIGHT }))
|
||||
.insertAfter($el);
|
||||
|
||||
// listen for scroll events
|
||||
listen();
|
||||
}
|
||||
|
||||
let width;
|
||||
let scrollWidth;
|
||||
function checkWidth() {
|
||||
const newScrollWidth = $el.prop('scrollWidth');
|
||||
const newWidth = $el.width();
|
||||
|
||||
if (scrollWidth !== newScrollWidth || width !== newWidth) {
|
||||
$scope.$apply(setup);
|
||||
|
||||
scrollWidth = newScrollWidth;
|
||||
width = newWidth;
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedCheckWidth = debounce(checkWidth, 100, {
|
||||
invokeApply: false,
|
||||
});
|
||||
$scope.$watch(debouncedCheckWidth);
|
||||
|
||||
function destroy() {
|
||||
cleanUp();
|
||||
debouncedCheckWidth.cancel();
|
||||
$scroller = $window = null;
|
||||
}
|
||||
return destroy;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,17 +23,12 @@ import $ from 'jquery';
|
|||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { PrivateProvider, initAngularBootstrap } from '../../../../../kibana_legacy/public';
|
||||
import { initAngularBootstrap } from '../../../../../kibana_legacy/public';
|
||||
import { FixedScrollProvider } from './fixed_scroll';
|
||||
import { DebounceProviderTimeout } from './debounce/debounce';
|
||||
|
||||
const testModuleName = 'fixedScroll';
|
||||
|
||||
angular
|
||||
.module(testModuleName, [])
|
||||
.provider('Private', PrivateProvider)
|
||||
.service('debounce', ['$timeout', DebounceProviderTimeout])
|
||||
.directive('fixedScroll', FixedScrollProvider);
|
||||
angular.module(testModuleName, []).directive('fixedScroll', FixedScrollProvider);
|
||||
|
||||
describe('FixedScroll directive', function () {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
@ -127,7 +122,7 @@ describe('FixedScroll directive', function () {
|
|||
return {
|
||||
$container: $el,
|
||||
$content: $content,
|
||||
$scroller: $parent.find('.fixed-scroll-scroller'),
|
||||
$scroller: $parent.find('.dscTableFixedScroll__scroller'),
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -24,7 +24,6 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiCodeBlock,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -37,72 +36,12 @@ import { getServices } from '../../../kibana_services';
|
|||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
export class DiscoverNoResults extends Component {
|
||||
static propTypes = {
|
||||
shardFailures: PropTypes.array,
|
||||
timeFieldName: PropTypes.string,
|
||||
queryLanguage: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { shardFailures, timeFieldName, queryLanguage } = this.props;
|
||||
|
||||
let shardFailuresMessage;
|
||||
|
||||
if (shardFailures && shardFailures.length) {
|
||||
const failures = shardFailures.map((failure, index) => (
|
||||
<div key={`${failure.index}${failure.shard}${failure.reason}`}>
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
id="discover.noResults.indexFailureShardText"
|
||||
defaultMessage="{index}, shard {failureShard}"
|
||||
values={{
|
||||
index: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="discover.noResults.indexFailureIndexText"
|
||||
defaultMessage="Index {failureIndex}"
|
||||
values={{
|
||||
failureIndex: `‘${failure.index}’`,
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
failureShard: `‘${failure.shard}’`,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiCodeBlock paddingSize="s">{JSON.stringify(failure.reason)}</EuiCodeBlock>
|
||||
|
||||
{index < shardFailures.length - 1 ? <EuiSpacer size="l" /> : undefined}
|
||||
</div>
|
||||
));
|
||||
|
||||
shardFailuresMessage = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiText>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="discover.noResults.addressShardFailuresTitle"
|
||||
defaultMessage="Address shard failures"
|
||||
/>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="discover.noResults.shardFailuresDescription"
|
||||
defaultMessage="The following shard failures occurred:"
|
||||
/>
|
||||
</p>
|
||||
|
||||
{failures}
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
const { timeFieldName, queryLanguage } = this.props;
|
||||
|
||||
let timeFieldMessage;
|
||||
|
||||
|
@ -264,8 +203,6 @@ export class DiscoverNoResults extends Component {
|
|||
iconType="help"
|
||||
data-test-subj="discoverNoResults"
|
||||
/>
|
||||
|
||||
{shardFailuresMessage}
|
||||
{timeFieldMessage}
|
||||
{luceneQueryMessage}
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -42,35 +42,6 @@ beforeEach(() => {
|
|||
|
||||
describe('DiscoverNoResults', () => {
|
||||
describe('props', () => {
|
||||
describe('shardFailures', () => {
|
||||
test('renders failures list when there are failures', () => {
|
||||
const shardFailures = [
|
||||
{
|
||||
index: 'A',
|
||||
shard: '1',
|
||||
reason: { reason: 'Awful error' },
|
||||
},
|
||||
{
|
||||
index: 'B',
|
||||
shard: '2',
|
||||
reason: { reason: 'Bad error' },
|
||||
},
|
||||
];
|
||||
|
||||
const component = renderWithIntl(<DiscoverNoResults shardFailures={shardFailures} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(`doesn't render failures list when there are no failures`, () => {
|
||||
const shardFailures = [];
|
||||
|
||||
const component = renderWithIntl(<DiscoverNoResults shardFailures={shardFailures} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('timeFieldName', () => {
|
||||
test('renders time range feedback', () => {
|
||||
const component = renderWithIntl(<DiscoverNoResults timeFieldName="awesome_time_field" />);
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
<discover-app class="app-container" data-fetch-counter="{{fetchCounter}}">
|
||||
<h1 class="euiScreenReaderOnly">{{screenTitle}}</h1>
|
||||
|
||||
<!-- Local nav. -->
|
||||
<kbn-top-nav
|
||||
app-name="'discover'"
|
||||
config="topNavMenu"
|
||||
set-menu-mount-point="setHeaderActionMenu"
|
||||
index-patterns="[indexPattern]"
|
||||
on-query-submit="handleRefresh"
|
||||
on-saved-query-id-change="updateSavedQueryId"
|
||||
saved-query-id="state.savedQuery"
|
||||
screen-title="screenTitle"
|
||||
show-date-picker="indexPattern.isTimeBased()"
|
||||
show-save-query="showSaveQuery"
|
||||
show-search-bar="true"
|
||||
use-default-behaviors="true"
|
||||
>
|
||||
</kbn-top-nav>
|
||||
|
||||
<main class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-2 dscSidebar__container collapsible-sidebar" id="discover-sidebar" data-test-subj="discover-sidebar">
|
||||
<div class="dscFieldChooser">
|
||||
<discover-sidebar
|
||||
columns="state.columns"
|
||||
field-counts="fieldCounts"
|
||||
hits="rows"
|
||||
index-pattern-list="opts.indexPatternList"
|
||||
on-add-field="addColumn"
|
||||
on-add-filter="filterQuery"
|
||||
on-remove-field="removeColumn"
|
||||
selected-index-pattern="searchSource.getField('index')"
|
||||
set-index-pattern="setIndexPattern"
|
||||
>
|
||||
</discover-sidebar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dscWrapper col-md-10">
|
||||
<discover-no-results
|
||||
ng-show="resultState === 'none'"
|
||||
shard-failures="failures"
|
||||
time-field-name="opts.timefield"
|
||||
query-language="state.query.language"
|
||||
get-doc-link="getDocLink"
|
||||
></discover-no-results>
|
||||
|
||||
<discover-uninitialized
|
||||
ng-show="resultState === 'uninitialized'"
|
||||
on-refresh="fetch"
|
||||
></discover-uninitialized>
|
||||
|
||||
<!-- loading -->
|
||||
<div ng-show="resultState === 'loading'">
|
||||
<discover-fetch-error
|
||||
ng-show="fetchError"
|
||||
fetch-error="fetchError"
|
||||
></discover-fetch-error>
|
||||
|
||||
<loading-spinner ng-hide="fetchError" class="dscOverlay"></loading-spinner>
|
||||
</div>
|
||||
|
||||
<div class="dscWrapper__content" ng-show="resultState === 'ready'">
|
||||
<!-- result -->
|
||||
<div class="dscResults">
|
||||
<skip-bottom-button on-click="onSkipBottomButtonClick"></skip-bottom-button>
|
||||
|
||||
<hits-counter
|
||||
hits="hits || 0"
|
||||
show-reset-button="opts.savedSearch.id"
|
||||
on-reset-query="resetQuery"
|
||||
>
|
||||
</hits-counter>
|
||||
|
||||
<section
|
||||
aria-label="{{::'discover.histogramOfFoundDocumentsAriaLabel' | i18n: {defaultMessage: 'Histogram of found documents'} }}"
|
||||
class="dscTimechart"
|
||||
ng-if="opts.timefield"
|
||||
>
|
||||
<timechart-header
|
||||
from="toMoment(timeRange.from)"
|
||||
to="toMoment(timeRange.to)"
|
||||
options="intervalOptions"
|
||||
on-change-interval="changeInterval"
|
||||
state-interval="state.interval"
|
||||
show-scaled-info="bucketInterval.scaled"
|
||||
bucket-interval-description="bucketInterval.description"
|
||||
bucket-interval-scale="bucketInterval.scale"
|
||||
>
|
||||
</timechart-header>
|
||||
|
||||
<discover-histogram
|
||||
class="dscHistogram"
|
||||
ng-show="vis && rows.length !== 0"
|
||||
chart-data="histogramData"
|
||||
timefilter-update-handler="timefilterUpdateHandler"
|
||||
watch-depth="reference"
|
||||
data-test-subj="discoverChart"
|
||||
></discover-histogram>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="dscTable"
|
||||
fixed-scroll
|
||||
aria-labelledby="documentsAriaLabel"
|
||||
>
|
||||
<h2 class="euiScreenReaderOnly"
|
||||
id="documentsAriaLabel"
|
||||
i18n-id="discover.documentsAriaLabel"
|
||||
i18n-default-message="Documents"
|
||||
></h2>
|
||||
<doc-table
|
||||
hits="rows"
|
||||
index-pattern="indexPattern"
|
||||
sorting="state.sort"
|
||||
columns="state.columns"
|
||||
infinite-scroll="true"
|
||||
filter="filterQuery"
|
||||
data-shared-item
|
||||
data-title="{{opts.savedSearch.lastSavedTitle}}"
|
||||
data-description="{{opts.savedSearch.description}}"
|
||||
data-test-subj="discoverDocTable"
|
||||
minimum-visible-rows="minimumVisibleRows"
|
||||
render-complete
|
||||
on-add-column="addColumn"
|
||||
on-change-sort-order="setSortOrder"
|
||||
on-move-column="moveColumn"
|
||||
on-remove-column="removeColumn"
|
||||
></doc-table>
|
||||
|
||||
<a tabindex="0" id="discoverBottomMarker">​</a>
|
||||
|
||||
<div
|
||||
ng-if="rows.length == opts.sampleSize"
|
||||
class="dscTable__footer"
|
||||
data-test-subj="discoverDocTableFooter"
|
||||
>
|
||||
<span
|
||||
i18n-id="discover.howToSeeOtherMatchingDocumentsDescription"
|
||||
i18n-default-message="These are the first {sampleSize} documents matching
|
||||
your search, refine your search to see others. "
|
||||
i18n-values="{
|
||||
sampleSize: opts.sampleSize,
|
||||
}"
|
||||
></span>
|
||||
<a
|
||||
kbn-accessible-click
|
||||
ng-click="scrollToTop()"
|
||||
i18n-id="discover.backToTopLinkText"
|
||||
i18n-default-message="Back to top."
|
||||
></a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</discover-app>
|
|
@ -29,12 +29,11 @@ import { getState, splitState } from './discover_state';
|
|||
import { RequestAdapter } from '../../../../inspector/public';
|
||||
import { SavedObjectSaveModal, showSaveModal } from '../../../../saved_objects/public';
|
||||
import { getSortArray, getSortForSearchSource } from './doc_table';
|
||||
import { createFixedScroll } from './directives/fixed_scroll';
|
||||
import * as columnActions from './doc_table/actions/columns';
|
||||
|
||||
import indexTemplate from './discover.html';
|
||||
import indexTemplateLegacy from './discover_legacy.html';
|
||||
import { showOpenSearchPanel } from '../components/top_nav/show_open_search_panel';
|
||||
import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util';
|
||||
import '../components/fetch_error';
|
||||
import { getPainlessError } from './get_painless_error';
|
||||
import { discoverResponseHandler } from './response_handler';
|
||||
import {
|
||||
|
@ -71,7 +70,6 @@ import {
|
|||
indexPatterns as indexPatternsUtils,
|
||||
connectToQueryState,
|
||||
syncQueryStateWithUrl,
|
||||
search,
|
||||
} from '../../../../data/public';
|
||||
import { getIndexPatternId } from '../helpers/get_index_pattern_id';
|
||||
import { addFatalError } from '../../../../kibana_legacy/public';
|
||||
|
@ -115,7 +113,7 @@ app.config(($routeProvider) => {
|
|||
};
|
||||
const discoverRoute = {
|
||||
...defaults,
|
||||
template: indexTemplate,
|
||||
template: indexTemplateLegacy,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
savedObjects: function ($route, Promise) {
|
||||
|
@ -308,18 +306,10 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
|
|||
mode: 'absolute',
|
||||
});
|
||||
};
|
||||
$scope.intervalOptions = search.aggs.intervalOptions;
|
||||
$scope.minimumVisibleRows = 50;
|
||||
$scope.fetchStatus = fetchStatuses.UNINITIALIZED;
|
||||
$scope.showSaveQuery = uiCapabilities.discover.saveQuery;
|
||||
|
||||
$scope.$watch(
|
||||
() => uiCapabilities.discover.saveQuery,
|
||||
(newCapability) => {
|
||||
$scope.showSaveQuery = newCapability;
|
||||
}
|
||||
);
|
||||
|
||||
let abortController;
|
||||
$scope.$on('$destroy', () => {
|
||||
if (abortController) abortController.abort();
|
||||
|
@ -471,7 +461,6 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
|
|||
];
|
||||
};
|
||||
$scope.topNavMenu = getTopNavLinks();
|
||||
$scope.setHeaderActionMenu = getHeaderActionMenuMounter();
|
||||
|
||||
$scope.searchSource
|
||||
.setField('index', $scope.indexPattern)
|
||||
|
@ -515,8 +504,6 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
|
|||
]);
|
||||
}
|
||||
|
||||
$scope.screenTitle = savedSearch.title;
|
||||
|
||||
const getFieldCounts = async () => {
|
||||
// the field counts aren't set until we have the data back,
|
||||
// so we wait for the fetch to be done before proceeding
|
||||
|
@ -612,6 +599,9 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
|
|||
timefield: getTimeField(),
|
||||
savedSearch: savedSearch,
|
||||
indexPatternList: $route.current.locals.savedObjects.ip.list,
|
||||
config: config,
|
||||
fixedScroll: createFixedScroll($scope, $timeout),
|
||||
setHeaderActionMenu: getHeaderActionMenuMounter(),
|
||||
};
|
||||
|
||||
const shouldSearchOnPageLoad = () => {
|
||||
|
@ -771,6 +761,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
|
|||
if (!init.complete) return;
|
||||
$scope.fetchCounter++;
|
||||
$scope.fetchError = undefined;
|
||||
$scope.minimumVisibleRows = 50;
|
||||
if (!validateTimeRange(timefilter.getTime(), toastNotifications)) {
|
||||
$scope.resultState = 'none';
|
||||
return;
|
||||
|
@ -868,9 +859,6 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise
|
|||
tabifiedData,
|
||||
getDimensions($scope.vis.data.aggs.aggs, $scope.timeRange)
|
||||
);
|
||||
if ($scope.vis.data.aggs.aggs[1]) {
|
||||
$scope.bucketInterval = $scope.vis.data.aggs.aggs[1].buckets.getInterval();
|
||||
}
|
||||
$scope.updateTime();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<discover-app>
|
||||
<discover-legacy
|
||||
add-column="addColumn"
|
||||
fetch="fetch"
|
||||
fetch-counter="fetchCounter"
|
||||
fetch-error="fetchError"
|
||||
field-counts="fieldCounts"
|
||||
histogram-data="histogramData"
|
||||
hits="hits"
|
||||
index-pattern="indexPattern"
|
||||
minimum-visible-rows="minimumVisibleRows"
|
||||
on-add-filter="filterQuery"
|
||||
on-move-column="moveColumn"
|
||||
on-change-interval="changeInterval"
|
||||
on-remove-column="removeColumn"
|
||||
on-set-columns="setColumns"
|
||||
on-skip-bottom-button-click="onSkipBottomButtonClick"
|
||||
on-sort="setSortOrder"
|
||||
opts="opts"
|
||||
reset-query="resetQuery"
|
||||
result-state="resultState"
|
||||
rows="rows"
|
||||
saved-search="savedSearch"
|
||||
search-source="searchSource"
|
||||
set-index-pattern="setIndexPattern"
|
||||
show-save-query="showSaveQuery"
|
||||
state="state"
|
||||
time-filter-update-handler="timefilterUpdateHandler"
|
||||
time-range="timeRange"
|
||||
top-nav-menu="topNavMenu"
|
||||
update-query="handleRefresh"
|
||||
update-saved-query-id="updateSavedQueryId"
|
||||
vis="vis"
|
||||
>
|
||||
</discover-legacy>
|
||||
</discover-app>
|
|
@ -55,6 +55,10 @@ export interface AppState {
|
|||
* Array of the used sorting [[field,direction],...]
|
||||
*/
|
||||
sort?: string[][];
|
||||
/**
|
||||
* id of the used saved query
|
||||
*/
|
||||
savedQuery?: string;
|
||||
}
|
||||
|
||||
interface GetStateParams {
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 angular, { auto, ICompileService, IScope } from 'angular';
|
||||
import { render } from 'react-dom';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { getServices, IIndexPattern } from '../../../kibana_services';
|
||||
import { IndexPatternField } from '../../../../../data/common/index_patterns';
|
||||
export type AngularScope = IScope;
|
||||
|
||||
export interface AngularDirective {
|
||||
template: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles and injects the give angular template into the given dom node
|
||||
* returns a function to cleanup the injected angular element
|
||||
*/
|
||||
export async function injectAngularElement(
|
||||
domNode: Element,
|
||||
template: string,
|
||||
scopeProps: any,
|
||||
getInjector: () => Promise<auto.IInjectorService>
|
||||
): Promise<() => void> {
|
||||
const $injector = await getInjector();
|
||||
const rootScope: AngularScope = $injector.get('$rootScope');
|
||||
const $compile: ICompileService = $injector.get('$compile');
|
||||
const newScope = Object.assign(rootScope.$new(), scopeProps);
|
||||
|
||||
const $target = angular.element(domNode);
|
||||
const $element = angular.element(template);
|
||||
|
||||
newScope.$apply(() => {
|
||||
const linkFn = $compile($element);
|
||||
$target.empty().append($element);
|
||||
linkFn(newScope);
|
||||
});
|
||||
|
||||
return () => {
|
||||
newScope.$destroy();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given legacy angular directive to a render function
|
||||
* for usage in a react component. Note that the rendering is async
|
||||
*/
|
||||
export function convertDirectiveToRenderFn(
|
||||
directive: AngularDirective,
|
||||
getInjector: () => Promise<auto.IInjectorService>
|
||||
) {
|
||||
return (domNode: Element, props: any) => {
|
||||
let rejected = false;
|
||||
|
||||
const cleanupFnPromise = injectAngularElement(domNode, directive.template, props, getInjector);
|
||||
cleanupFnPromise.catch(() => {
|
||||
rejected = true;
|
||||
render(<div>error</div>, domNode);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (!rejected) {
|
||||
// for cleanup
|
||||
// http://roubenmeschian.com/rubo/?p=51
|
||||
cleanupFnPromise.then((cleanup) => cleanup());
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface DocTableLegacyProps {
|
||||
columns: string[];
|
||||
searchDescription?: string;
|
||||
searchTitle?: string;
|
||||
onFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
rows: Array<Record<string, unknown>>;
|
||||
indexPattern: IIndexPattern;
|
||||
minimumVisibleRows: number;
|
||||
onAddColumn: (column: string) => void;
|
||||
onSort: (sort: string[][]) => void;
|
||||
onMoveColumn: (columns: string, newIdx: number) => void;
|
||||
onRemoveColumn: (column: string) => void;
|
||||
sort?: string[][];
|
||||
}
|
||||
|
||||
export function DocTableLegacy(renderProps: DocTableLegacyProps) {
|
||||
const renderFn = convertDirectiveToRenderFn(
|
||||
{
|
||||
template: `<doc-table
|
||||
columns="columns"
|
||||
data-description="{{searchDescription}}"
|
||||
data-shared-item
|
||||
data-test-subj="discoverDocTable"
|
||||
data-title="{{searchTitle}}"
|
||||
filter="onFilter"
|
||||
hits="rows"
|
||||
index-pattern="indexPattern"
|
||||
infinite-scroll="true"
|
||||
minimum-visible-rows="minimumVisibleRows"
|
||||
on-add-column="onAddColumn"
|
||||
on-change-sort-order="onSort"
|
||||
on-move-column="onMoveColumn"
|
||||
on-remove-column="onRemoveColumn"
|
||||
render-complete
|
||||
sorting="sort"></doc_table>`,
|
||||
},
|
||||
() => getServices().getEmbeddableInjector()
|
||||
);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (ref && ref.current) {
|
||||
return renderFn(ref.current, renderProps);
|
||||
}
|
||||
}, [renderFn, renderProps]);
|
||||
return <div ref={ref} />;
|
||||
}
|
|
@ -50,10 +50,6 @@ export function createDocTableDirective(pagerFactory: any, $filter: any) {
|
|||
inspectorAdapters: '=?',
|
||||
},
|
||||
link: ($scope: LazyScope, $el: JQuery) => {
|
||||
$scope.$watch('minimumVisibleRows', (minimumVisibleRows: number) => {
|
||||
$scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50);
|
||||
});
|
||||
|
||||
$scope.persist = {
|
||||
sorting: $scope.sorting,
|
||||
columns: $scope.columns,
|
||||
|
@ -77,7 +73,7 @@ export function createDocTableDirective(pagerFactory: any, $filter: any) {
|
|||
if (!hits) return;
|
||||
|
||||
// Reset infinite scroll limit
|
||||
$scope.limit = 50;
|
||||
$scope.limit = $scope.minimumVisibleRows || 50;
|
||||
|
||||
if (hits.length === 0) {
|
||||
dispatchRenderComplete($el[0]);
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { DiscoverLegacy } from './discover_legacy';
|
||||
|
||||
export function createDiscoverLegacyDirective(reactDirective: any) {
|
||||
return reactDirective(DiscoverLegacy, [
|
||||
['addColumn', { watchDepth: 'reference' }],
|
||||
['fetch', { watchDepth: 'reference' }],
|
||||
['fetchCounter', { watchDepth: 'reference' }],
|
||||
['fetchError', { watchDepth: 'reference' }],
|
||||
['fieldCounts', { watchDepth: 'reference' }],
|
||||
['histogramData', { watchDepth: 'reference' }],
|
||||
['hits', { watchDepth: 'reference' }],
|
||||
['indexPattern', { watchDepth: 'reference' }],
|
||||
['minimumVisibleRows', { watchDepth: 'reference' }],
|
||||
['onAddFilter', { watchDepth: 'reference' }],
|
||||
['onChangeInterval', { watchDepth: 'reference' }],
|
||||
['onMoveColumn', { watchDepth: 'reference' }],
|
||||
['onRemoveColumn', { watchDepth: 'reference' }],
|
||||
['onSetColumns', { watchDepth: 'reference' }],
|
||||
['onSkipBottomButtonClick', { watchDepth: 'reference' }],
|
||||
['onSort', { watchDepth: 'reference' }],
|
||||
['opts', { watchDepth: 'reference' }],
|
||||
['resetQuery', { watchDepth: 'reference' }],
|
||||
['resultState', { watchDepth: 'reference' }],
|
||||
['rows', { watchDepth: 'reference' }],
|
||||
['savedSearch', { watchDepth: 'reference' }],
|
||||
['searchSource', { watchDepth: 'reference' }],
|
||||
['setIndexPattern', { watchDepth: 'reference' }],
|
||||
['showSaveQuery', { watchDepth: 'reference' }],
|
||||
['state', { watchDepth: 'reference' }],
|
||||
['timefilterUpdateHandler', { watchDepth: 'reference' }],
|
||||
['timeRange', { watchDepth: 'reference' }],
|
||||
['topNavMenu', { watchDepth: 'reference' }],
|
||||
['updateQuery', { watchDepth: 'reference' }],
|
||||
['updateSavedQueryId', { watchDepth: 'reference' }],
|
||||
['vis', { watchDepth: 'reference' }],
|
||||
]);
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* 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, { useState, useCallback, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { IUiSettingsClient, MountPoint } from 'kibana/public';
|
||||
import { HitsCounter } from './hits_counter';
|
||||
import { TimechartHeader } from './timechart_header';
|
||||
import { DiscoverSidebar } from './sidebar';
|
||||
import { getServices, IIndexPattern } from '../../kibana_services';
|
||||
// @ts-ignore
|
||||
import { DiscoverNoResults } from '../angular/directives/no_results';
|
||||
import { DiscoverUninitialized } from '../angular/directives/uninitialized';
|
||||
import { DiscoverHistogram } from '../angular/directives/histogram';
|
||||
import { LoadingSpinner } from './loading_spinner/loading_spinner';
|
||||
import { DiscoverFetchError, FetchError } from './fetch_error/fetch_error';
|
||||
import { DocTableLegacy } from '../angular/doc_table/create_doc_table_react';
|
||||
import { SkipBottomButton } from './skip_bottom_button';
|
||||
import {
|
||||
IndexPatternField,
|
||||
search,
|
||||
ISearchSource,
|
||||
TimeRange,
|
||||
Query,
|
||||
IndexPatternAttributes,
|
||||
} from '../../../../data/public';
|
||||
import { Chart } from '../angular/helpers/point_series';
|
||||
import { AppState } from '../angular/discover_state';
|
||||
import { SavedSearch } from '../../saved_searches';
|
||||
|
||||
import { SavedObject } from '../../../../../core/types';
|
||||
import { Vis } from '../../../../visualizations/public';
|
||||
import { TopNavMenuData } from '../../../../navigation/public';
|
||||
|
||||
export interface DiscoverLegacyProps {
|
||||
addColumn: (column: string) => void;
|
||||
fetch: () => void;
|
||||
fetchCounter: number;
|
||||
fetchError: FetchError;
|
||||
fieldCounts: Record<string, number>;
|
||||
histogramData: Chart;
|
||||
hits: number;
|
||||
indexPattern: IIndexPattern;
|
||||
minimumVisibleRows: number;
|
||||
onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
onChangeInterval: (interval: string) => void;
|
||||
onMoveColumn: (columns: string, newIdx: number) => void;
|
||||
onRemoveColumn: (column: string) => void;
|
||||
onSetColumns: (columns: string[]) => void;
|
||||
onSkipBottomButtonClick: () => void;
|
||||
onSort: (sort: string[][]) => void;
|
||||
opts: {
|
||||
savedSearch: SavedSearch;
|
||||
config: IUiSettingsClient;
|
||||
indexPatternList: Array<SavedObject<IndexPatternAttributes>>;
|
||||
timefield: string;
|
||||
sampleSize: number;
|
||||
fixedScroll: (el: HTMLElement) => void;
|
||||
setHeaderActionMenu: (menuMount: MountPoint | undefined) => void;
|
||||
};
|
||||
resetQuery: () => void;
|
||||
resultState: string;
|
||||
rows: Array<Record<string, unknown>>;
|
||||
searchSource: ISearchSource;
|
||||
setIndexPattern: (id: string) => void;
|
||||
showSaveQuery: boolean;
|
||||
state: AppState;
|
||||
timefilterUpdateHandler: (ranges: { from: number; to: number }) => void;
|
||||
timeRange?: { from: string; to: string };
|
||||
topNavMenu: TopNavMenuData[];
|
||||
updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void;
|
||||
updateSavedQueryId: (savedQueryId?: string) => void;
|
||||
vis?: Vis;
|
||||
}
|
||||
|
||||
export function DiscoverLegacy({
|
||||
addColumn,
|
||||
fetch,
|
||||
fetchCounter,
|
||||
fetchError,
|
||||
fieldCounts,
|
||||
histogramData,
|
||||
hits,
|
||||
indexPattern,
|
||||
minimumVisibleRows,
|
||||
onAddFilter,
|
||||
onChangeInterval,
|
||||
onMoveColumn,
|
||||
onRemoveColumn,
|
||||
onSkipBottomButtonClick,
|
||||
onSort,
|
||||
opts,
|
||||
resetQuery,
|
||||
resultState,
|
||||
rows,
|
||||
searchSource,
|
||||
setIndexPattern,
|
||||
showSaveQuery,
|
||||
state,
|
||||
timefilterUpdateHandler,
|
||||
timeRange,
|
||||
topNavMenu,
|
||||
updateQuery,
|
||||
updateSavedQueryId,
|
||||
vis,
|
||||
}: DiscoverLegacyProps) {
|
||||
const [isSidebarClosed, setIsSidebarClosed] = useState(false);
|
||||
const { TopNavMenu } = getServices().navigation.ui;
|
||||
const { savedSearch, indexPatternList } = opts;
|
||||
const bucketAggConfig = vis?.data?.aggs?.aggs[1];
|
||||
const bucketInterval =
|
||||
bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig)
|
||||
? bucketAggConfig.buckets?.getInterval()
|
||||
: undefined;
|
||||
const [fixedScrollEl, setFixedScrollEl] = useState<HTMLElement | undefined>();
|
||||
|
||||
useEffect(() => (fixedScrollEl ? opts.fixedScroll(fixedScrollEl) : undefined), [
|
||||
fixedScrollEl,
|
||||
opts,
|
||||
]);
|
||||
const fixedScrollRef = useCallback(
|
||||
(node: HTMLElement) => {
|
||||
if (node !== null) {
|
||||
setFixedScrollEl(node);
|
||||
}
|
||||
},
|
||||
[setFixedScrollEl]
|
||||
);
|
||||
const sidebarClassName = classNames({
|
||||
closed: isSidebarClosed,
|
||||
});
|
||||
|
||||
const mainSectionClassName = classNames({
|
||||
'col-md-10': !isSidebarClosed,
|
||||
'col-md-12': isSidebarClosed,
|
||||
});
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<div className="dscAppContainer" data-fetch-counter={fetchCounter}>
|
||||
<h1 className="euiScreenReaderOnly">{savedSearch.title}</h1>
|
||||
<TopNavMenu
|
||||
appName="discover"
|
||||
config={topNavMenu}
|
||||
indexPatterns={[indexPattern]}
|
||||
onQuerySubmit={updateQuery}
|
||||
onSavedQueryIdChange={updateSavedQueryId}
|
||||
query={state.query}
|
||||
setMenuMountPoint={opts.setHeaderActionMenu}
|
||||
savedQueryId={state.savedQuery}
|
||||
screenTitle={savedSearch.title}
|
||||
showDatePicker={indexPattern.isTimeBased()}
|
||||
showSaveQuery={showSaveQuery}
|
||||
showSearchBar={true}
|
||||
useDefaultBehaviors={true}
|
||||
/>
|
||||
<main className="container-fluid">
|
||||
<div className="row">
|
||||
<div
|
||||
className={`col-md-2 dscSidebar__container dscCollapsibleSidebar ${sidebarClassName}`}
|
||||
id="discover-sidebar"
|
||||
data-test-subj="discover-sidebar"
|
||||
>
|
||||
{!isSidebarClosed && (
|
||||
<div className="dscFieldChooser">
|
||||
<DiscoverSidebar
|
||||
columns={state.columns || []}
|
||||
fieldCounts={fieldCounts}
|
||||
hits={rows}
|
||||
indexPatternList={indexPatternList}
|
||||
onAddField={addColumn}
|
||||
onAddFilter={onAddFilter}
|
||||
onRemoveField={onRemoveColumn}
|
||||
selectedIndexPattern={searchSource && searchSource.getField('index')}
|
||||
setIndexPattern={setIndexPattern}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<EuiButtonIcon
|
||||
iconType={isSidebarClosed ? 'menuRight' : 'menuLeft'}
|
||||
iconSize="m"
|
||||
size="s"
|
||||
onClick={() => setIsSidebarClosed(!isSidebarClosed)}
|
||||
data-test-subj="collapseSideBarButton"
|
||||
aria-controls="discover-sidebar"
|
||||
aria-expanded={isSidebarClosed ? 'false' : 'true'}
|
||||
aria-label="Toggle sidebar"
|
||||
className="dscCollapsibleSidebar__collapseButton"
|
||||
/>
|
||||
</div>
|
||||
<div className={`dscWrapper ${mainSectionClassName}`}>
|
||||
{resultState === 'none' && (
|
||||
<DiscoverNoResults
|
||||
timeFieldName={opts.timefield}
|
||||
queryLanguage={state.query ? state.query.language : ''}
|
||||
/>
|
||||
)}
|
||||
{resultState === 'uninitialized' && <DiscoverUninitialized onRefresh={fetch} />}
|
||||
{/* @TODO: Solved in the Angular way to satisfy functional test - should be improved*/}
|
||||
<span style={{ display: resultState !== 'loading' ? 'none' : '' }}>
|
||||
{fetchError && <DiscoverFetchError fetchError={fetchError} />}
|
||||
<div className="dscOverlay" style={{ display: fetchError ? 'none' : '' }}>
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
</span>
|
||||
{resultState === 'ready' && (
|
||||
<div className="dscWrapper__content">
|
||||
<SkipBottomButton onClick={onSkipBottomButtonClick} />
|
||||
<HitsCounter
|
||||
hits={hits > 0 ? hits : 0}
|
||||
showResetButton={!!(savedSearch && savedSearch.id)}
|
||||
onResetQuery={resetQuery}
|
||||
/>
|
||||
{opts.timefield && (
|
||||
<TimechartHeader
|
||||
dateFormat={opts.config.get('dateFormat')}
|
||||
timeRange={timeRange}
|
||||
options={search.aggs.intervalOptions}
|
||||
onChangeInterval={onChangeInterval}
|
||||
stateInterval={state.interval || ''}
|
||||
bucketInterval={bucketInterval}
|
||||
/>
|
||||
)}
|
||||
|
||||
{opts.timefield && (
|
||||
<section
|
||||
aria-label={i18n.translate('discover.histogramOfFoundDocumentsAriaLabel', {
|
||||
defaultMessage: 'Histogram of found documents',
|
||||
})}
|
||||
className="dscTimechart"
|
||||
>
|
||||
{vis && rows.length !== 0 && (
|
||||
<div className="dscHistogram" data-test-subj="discoverChart">
|
||||
<DiscoverHistogram
|
||||
chartData={histogramData}
|
||||
timefilterUpdateHandler={timefilterUpdateHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="dscResults">
|
||||
<section
|
||||
className="dscTable dscTableFixedScroll"
|
||||
aria-labelledby="documentsAriaLabel"
|
||||
ref={fixedScrollRef}
|
||||
>
|
||||
<h2 className="euiScreenReaderOnly" id="documentsAriaLabel">
|
||||
<FormattedMessage
|
||||
id="discover.documentsAriaLabel"
|
||||
defaultMessage="Documents"
|
||||
/>
|
||||
</h2>
|
||||
{rows && rows.length && (
|
||||
<div className="dscDiscover">
|
||||
<DocTableLegacy
|
||||
columns={state.columns || []}
|
||||
indexPattern={indexPattern}
|
||||
minimumVisibleRows={minimumVisibleRows}
|
||||
rows={rows}
|
||||
sort={state.sort || []}
|
||||
searchDescription={opts.savedSearch.description}
|
||||
searchTitle={opts.savedSearch.lastSavedTitle}
|
||||
onAddColumn={addColumn}
|
||||
onFilter={onAddFilter}
|
||||
onMoveColumn={onMoveColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onSort={onSort}
|
||||
/>
|
||||
<a tabIndex={0} id="discoverBottomMarker">
|
||||
​
|
||||
</a>
|
||||
{rows.length === opts.sampleSize && (
|
||||
<div
|
||||
className="dscTable__footer"
|
||||
data-test-subj="discoverDocTableFooter"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="discover.howToSeeOtherMatchingDocumentsDescription"
|
||||
defaultMessage="These are the first {sampleSize} documents matching
|
||||
your search, refine your search to see others."
|
||||
values={{ sampleSize: opts.sampleSize }}
|
||||
/>
|
||||
|
||||
<EuiButtonEmpty onClick={() => window.scrollTo(0, 0)}>
|
||||
<FormattedMessage
|
||||
id="discover.backToTopLinkText"
|
||||
defaultMessage="Back to top."
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
|
@ -20,18 +20,20 @@ import './fetch_error.scss';
|
|||
import React, { Fragment } from 'react';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui';
|
||||
import { getAngularModule, getServices } from '../../../kibana_services';
|
||||
import { getServices } from '../../../kibana_services';
|
||||
|
||||
interface Props {
|
||||
fetchError: {
|
||||
lang: string;
|
||||
script: string;
|
||||
message: string;
|
||||
error: string;
|
||||
};
|
||||
export interface FetchError {
|
||||
lang: string;
|
||||
script: string;
|
||||
message: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const DiscoverFetchError = ({ fetchError }: Props) => {
|
||||
interface Props {
|
||||
fetchError: FetchError;
|
||||
}
|
||||
|
||||
export const DiscoverFetchError = ({ fetchError }: Props) => {
|
||||
if (!fetchError) {
|
||||
return null;
|
||||
}
|
||||
|
@ -92,9 +94,3 @@ const DiscoverFetchError = ({ fetchError }: Props) => {
|
|||
</I18nProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export function createFetchErrorDirective(reactDirective: any) {
|
||||
return reactDirective(DiscoverFetchError);
|
||||
}
|
||||
|
||||
getAngularModule().directive('discoverFetchError', createFetchErrorDirective);
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* 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 { HitsCounter } from './hits_counter';
|
||||
|
||||
export function createHitsCounterDirective(reactDirective: any) {
|
||||
return reactDirective(HitsCounter, [
|
||||
['hits', { watchDepth: 'reference' }],
|
||||
['showResetButton', { watchDepth: 'reference' }],
|
||||
['onResetQuery', { watchDepth: 'reference' }],
|
||||
]);
|
||||
}
|
|
@ -18,4 +18,3 @@
|
|||
*/
|
||||
|
||||
export { HitsCounter } from './hits_counter';
|
||||
export { createHitsCounterDirective } from './hits_counter_directive';
|
||||
|
|
|
@ -18,24 +18,18 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { EuiLoadingSpinner, EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export function LoadingSpinner() {
|
||||
return (
|
||||
<I18nProvider>
|
||||
<>
|
||||
<EuiTitle size="s" data-test-subj="loadingSpinnerText">
|
||||
<h2>
|
||||
<FormattedMessage id="discover.searchingTitle" defaultMessage="Searching" />
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiLoadingSpinner size="l" data-test-subj="loadingSpinner" />
|
||||
</>
|
||||
</I18nProvider>
|
||||
<>
|
||||
<EuiTitle size="s" data-test-subj="loadingSpinnerText">
|
||||
<h2>
|
||||
<FormattedMessage id="discover.searchingTitle" defaultMessage="Searching" />
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiLoadingSpinner size="l" data-test-subj="loadingSpinner" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function createLoadingSpinnerDirective(reactDirective: any) {
|
||||
return reactDirective(LoadingSpinner);
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export interface DiscoverSidebarProps {
|
|||
/**
|
||||
* Currently selected index pattern
|
||||
*/
|
||||
selectedIndexPattern: IndexPattern;
|
||||
selectedIndexPattern?: IndexPattern;
|
||||
/**
|
||||
* Callback function to select another index pattern
|
||||
*/
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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 { DiscoverSidebar } from './discover_sidebar';
|
||||
|
||||
export function createDiscoverSidebarDirective(reactDirective: any) {
|
||||
return reactDirective(DiscoverSidebar, [
|
||||
['columns', { watchDepth: 'reference' }],
|
||||
['fieldCounts', { watchDepth: 'reference' }],
|
||||
['hits', { watchDepth: 'reference' }],
|
||||
['indexPatternList', { watchDepth: 'reference' }],
|
||||
['onAddField', { watchDepth: 'reference' }],
|
||||
['onAddFilter', { watchDepth: 'reference' }],
|
||||
['onRemoveField', { watchDepth: 'reference' }],
|
||||
['selectedIndexPattern', { watchDepth: 'reference' }],
|
||||
['setIndexPattern', { watchDepth: 'reference' }],
|
||||
]);
|
||||
}
|
|
@ -18,4 +18,3 @@
|
|||
*/
|
||||
|
||||
export { DiscoverSidebar } from './discover_sidebar';
|
||||
export { createDiscoverSidebarDirective } from './discover_sidebar_directive';
|
||||
|
|
|
@ -25,8 +25,11 @@ export function getDetails(
|
|||
field: IndexPatternField,
|
||||
hits: Array<Record<string, unknown>>,
|
||||
columns: string[],
|
||||
indexPattern: IndexPattern
|
||||
indexPattern?: IndexPattern
|
||||
) {
|
||||
if (!indexPattern) {
|
||||
return {};
|
||||
}
|
||||
const details = {
|
||||
...fieldCalculator.getFieldValueCounts({
|
||||
hits,
|
||||
|
|
|
@ -20,8 +20,8 @@ import { difference } from 'lodash';
|
|||
import { IndexPattern, IndexPatternField } from 'src/plugins/data/public';
|
||||
|
||||
export function getIndexPatternFieldList(
|
||||
indexPattern: IndexPattern,
|
||||
fieldCounts: Record<string, number>
|
||||
indexPattern?: IndexPattern,
|
||||
fieldCounts?: Record<string, number>
|
||||
) {
|
||||
if (!indexPattern || !fieldCounts) return [];
|
||||
|
||||
|
|
|
@ -18,4 +18,3 @@
|
|||
*/
|
||||
|
||||
export { SkipBottomButton } from './skip_bottom_button';
|
||||
export { createSkipBottomButtonDirective } from './skip_bottom_button_directive';
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* 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 { SkipBottomButton } from './skip_bottom_button';
|
||||
|
||||
export function createSkipBottomButtonDirective(reactDirective: any) {
|
||||
return reactDirective(SkipBottomButton, [['onClick', { watchDepth: 'reference' }]]);
|
||||
}
|
|
@ -18,4 +18,3 @@
|
|||
*/
|
||||
|
||||
export { TimechartHeader } from './timechart_header';
|
||||
export { createTimechartHeaderDirective } from './timechart_header_directive';
|
||||
|
|
|
@ -29,8 +29,10 @@ describe('timechart header', function () {
|
|||
|
||||
beforeAll(() => {
|
||||
props = {
|
||||
from: 'May 14, 2020 @ 11:05:13.590',
|
||||
to: 'May 14, 2020 @ 11:20:13.590',
|
||||
timeRange: {
|
||||
from: 'May 14, 2020 @ 11:05:13.590',
|
||||
to: 'May 14, 2020 @ 11:20:13.590',
|
||||
},
|
||||
stateInterval: 's',
|
||||
options: [
|
||||
{
|
||||
|
@ -47,9 +49,11 @@ describe('timechart header', function () {
|
|||
},
|
||||
],
|
||||
onChangeInterval: jest.fn(),
|
||||
showScaledInfo: undefined,
|
||||
bucketIntervalDescription: 'second',
|
||||
bucketIntervalScale: undefined,
|
||||
bucketInterval: {
|
||||
scaled: undefined,
|
||||
description: 'second',
|
||||
scale: undefined,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -58,8 +62,8 @@ describe('timechart header', function () {
|
|||
expect(component.find(EuiIconTip).length).toBe(0);
|
||||
});
|
||||
|
||||
it('TimechartHeader renders an info text by providing the showScaledInfo property', () => {
|
||||
props.showScaledInfo = true;
|
||||
it('TimechartHeader renders an info when bucketInterval.scale is set to true', () => {
|
||||
props.bucketInterval!.scaled = true;
|
||||
component = mountWithIntl(<TimechartHeader {...props} />);
|
||||
expect(component.find(EuiIconTip).length).toBe(1);
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -27,16 +27,28 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
|
||||
export interface TimechartHeaderProps {
|
||||
/**
|
||||
* the query from date string
|
||||
* Format of date to be displayed
|
||||
*/
|
||||
from: string;
|
||||
dateFormat?: string;
|
||||
/**
|
||||
* the query to date string
|
||||
* Interval for the buckets of the recent request
|
||||
*/
|
||||
to: string;
|
||||
bucketInterval?: {
|
||||
scaled?: boolean;
|
||||
description?: string;
|
||||
scale?: number;
|
||||
};
|
||||
/**
|
||||
* Range of dates to be displayed
|
||||
*/
|
||||
timeRange?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
/**
|
||||
* Interval Options
|
||||
*/
|
||||
|
@ -49,31 +61,29 @@ export interface TimechartHeaderProps {
|
|||
* selected interval
|
||||
*/
|
||||
stateInterval: string;
|
||||
/**
|
||||
* displays the scaled info of the interval
|
||||
*/
|
||||
showScaledInfo: boolean | undefined;
|
||||
/**
|
||||
* scaled info description
|
||||
*/
|
||||
bucketIntervalDescription: string;
|
||||
/**
|
||||
* bucket interval scale
|
||||
*/
|
||||
bucketIntervalScale: number | undefined;
|
||||
}
|
||||
|
||||
export function TimechartHeader({
|
||||
from,
|
||||
to,
|
||||
bucketInterval,
|
||||
dateFormat,
|
||||
timeRange,
|
||||
options,
|
||||
onChangeInterval,
|
||||
stateInterval,
|
||||
showScaledInfo,
|
||||
bucketIntervalDescription,
|
||||
bucketIntervalScale,
|
||||
}: TimechartHeaderProps) {
|
||||
const [interval, setInterval] = useState(stateInterval);
|
||||
const toMoment = useCallback(
|
||||
(datetime: string) => {
|
||||
if (!datetime) {
|
||||
return '';
|
||||
}
|
||||
if (!dateFormat) {
|
||||
return datetime;
|
||||
}
|
||||
return moment(datetime).format(dateFormat);
|
||||
},
|
||||
[dateFormat]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(stateInterval);
|
||||
|
@ -84,6 +94,10 @@ export function TimechartHeader({
|
|||
onChangeInterval(e.target.value);
|
||||
};
|
||||
|
||||
if (!timeRange || !bucketInterval) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiFlexGroup gutterSize="s" responsive justifyContent="center" alignItems="center">
|
||||
|
@ -95,7 +109,7 @@ export function TimechartHeader({
|
|||
delay="long"
|
||||
>
|
||||
<EuiText data-test-subj="discoverIntervalDateRange" size="s">
|
||||
{`${from} - ${to} ${
|
||||
{`${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${
|
||||
interval !== 'auto'
|
||||
? i18n.translate('discover.timechartHeader.timeIntervalSelect.per', {
|
||||
defaultMessage: 'per',
|
||||
|
@ -125,7 +139,7 @@ export function TimechartHeader({
|
|||
value={interval}
|
||||
onChange={handleIntervalChange}
|
||||
append={
|
||||
showScaledInfo ? (
|
||||
bucketInterval.scaled ? (
|
||||
<EuiIconTip
|
||||
id="discoverIntervalIconTip"
|
||||
content={i18n.translate('discover.bucketIntervalTooltip', {
|
||||
|
@ -133,14 +147,14 @@ export function TimechartHeader({
|
|||
'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}.',
|
||||
values: {
|
||||
bucketsDescription:
|
||||
bucketIntervalScale && bucketIntervalScale > 1
|
||||
bucketInterval!.scale && bucketInterval!.scale > 1
|
||||
? i18n.translate('discover.bucketIntervalTooltip.tooLargeBucketsText', {
|
||||
defaultMessage: 'buckets that are too large',
|
||||
})
|
||||
: i18n.translate('discover.bucketIntervalTooltip.tooManyBucketsText', {
|
||||
defaultMessage: 'too many buckets',
|
||||
}),
|
||||
bucketIntervalDescription,
|
||||
bucketIntervalDescription: bucketInterval.description,
|
||||
},
|
||||
})}
|
||||
color="warning"
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 { TimechartHeader } from './timechart_header';
|
||||
|
||||
export function createTimechartHeaderDirective(reactDirective: any) {
|
||||
return reactDirective(TimechartHeader, [
|
||||
['from', { watchDepth: 'reference' }],
|
||||
['to', { watchDepth: 'reference' }],
|
||||
['options', { watchDepth: 'reference' }],
|
||||
['onChangeInterval', { watchDepth: 'reference' }],
|
||||
['stateInterval', { watchDepth: 'reference' }],
|
||||
['showScaledInfo', { watchDepth: 'reference' }],
|
||||
['bucketIntervalDescription', { watchDepth: 'reference' }],
|
||||
['bucketIntervalScale', { watchDepth: 'reference' }],
|
||||
]);
|
||||
}
|
|
@ -44,6 +44,7 @@ import { createSavedSearchesLoader, SavedSearch } from './saved_searches';
|
|||
import { getHistory } from './kibana_services';
|
||||
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
||||
import { UrlForwardingStart } from '../../url_forwarding/public';
|
||||
import { NavigationPublicPluginStart } from '../../navigation/public';
|
||||
|
||||
export interface DiscoverServices {
|
||||
addBasePath: (path: string) => string;
|
||||
|
@ -58,6 +59,7 @@ export interface DiscoverServices {
|
|||
indexPatterns: IndexPatternsContract;
|
||||
inspector: InspectorPublicPluginStart;
|
||||
metadata: { branch: string };
|
||||
navigation: NavigationPublicPluginStart;
|
||||
share?: SharePluginStart;
|
||||
kibanaLegacy: KibanaLegacyStart;
|
||||
urlForwarding: UrlForwardingStart;
|
||||
|
@ -65,6 +67,7 @@ export interface DiscoverServices {
|
|||
toastNotifications: ToastsStart;
|
||||
getSavedSearchById: (id: string) => Promise<SavedSearch>;
|
||||
getSavedSearchUrlById: (id: string) => Promise<string>;
|
||||
getEmbeddableInjector: any;
|
||||
uiSettings: IUiSettingsClient;
|
||||
visualizations: VisualizationsStart;
|
||||
}
|
||||
|
@ -72,7 +75,8 @@ export interface DiscoverServices {
|
|||
export async function buildServices(
|
||||
core: CoreStart,
|
||||
plugins: DiscoverStartPlugins,
|
||||
context: PluginInitializerContext
|
||||
context: PluginInitializerContext,
|
||||
getEmbeddableInjector: any
|
||||
): Promise<DiscoverServices> {
|
||||
const services: SavedObjectKibanaServices = {
|
||||
savedObjectsClient: core.savedObjects.client,
|
||||
|
@ -92,6 +96,7 @@ export async function buildServices(
|
|||
docLinks: core.docLinks,
|
||||
theme: plugins.charts.theme,
|
||||
filterManager: plugins.data.query.filterManager,
|
||||
getEmbeddableInjector,
|
||||
getSavedSearchById: async (id: string) => savedObjectService.get(id),
|
||||
getSavedSearchUrlById: async (id: string) => savedObjectService.urlFor(id),
|
||||
history: getHistory,
|
||||
|
@ -100,6 +105,7 @@ export async function buildServices(
|
|||
metadata: {
|
||||
branch: context.env.packageInfo.branch,
|
||||
},
|
||||
navigation: plugins.navigation,
|
||||
share: plugins.share,
|
||||
kibanaLegacy: plugins.kibanaLegacy,
|
||||
urlForwarding: plugins.urlForwarding,
|
||||
|
|
|
@ -40,16 +40,10 @@ import { createTableRowDirective } from './application/angular/doc_table/compone
|
|||
import { createPagerFactory } from './application/angular/doc_table/lib/pager/pager_factory';
|
||||
import { createInfiniteScrollDirective } from './application/angular/doc_table/infinite_scroll';
|
||||
import { createDocViewerDirective } from './application/angular/doc_viewer';
|
||||
import { CollapsibleSidebarProvider } from './application/angular/directives/collapsible_sidebar/collapsible_sidebar';
|
||||
// @ts-ignore
|
||||
import { FixedScrollProvider } from './application/angular/directives/fixed_scroll';
|
||||
// @ts-ignore
|
||||
import { DebounceProviderTimeout } from './application/angular/directives/debounce/debounce';
|
||||
import { createRenderCompleteDirective } from './application/angular/directives/render_complete';
|
||||
import {
|
||||
initAngularBootstrap,
|
||||
configureAppAngularModule,
|
||||
KbnAccessibleClickProvider,
|
||||
PrivateProvider,
|
||||
PromiseServiceCreator,
|
||||
registerListenEventListener,
|
||||
|
@ -57,14 +51,10 @@ import {
|
|||
createTopNavDirective,
|
||||
createTopNavHelper,
|
||||
} from '../../kibana_legacy/public';
|
||||
import { createDiscoverSidebarDirective } from './application/components/sidebar';
|
||||
import { createHitsCounterDirective } from '././application/components/hits_counter';
|
||||
import { createLoadingSpinnerDirective } from '././application/components/loading_spinner/loading_spinner';
|
||||
import { createTimechartHeaderDirective } from './application/components/timechart_header';
|
||||
import { createContextErrorMessageDirective } from './application/components/context_error_message';
|
||||
import { DiscoverStartPlugins } from './plugin';
|
||||
import { getScopedHistory } from './kibana_services';
|
||||
import { createSkipBottomButtonDirective } from './application/components/skip_bottom_button';
|
||||
import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive';
|
||||
|
||||
/**
|
||||
* returns the main inner angular module, it contains all the parts of Angular Discover
|
||||
|
@ -88,11 +78,9 @@ export function getInnerAngularModule(
|
|||
export function getInnerAngularModuleEmbeddable(
|
||||
name: string,
|
||||
core: CoreStart,
|
||||
deps: DiscoverStartPlugins,
|
||||
context: PluginInitializerContext
|
||||
deps: DiscoverStartPlugins
|
||||
) {
|
||||
const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data, true);
|
||||
return module;
|
||||
return initializeInnerAngularModule(name, core, deps.navigation, deps.data, true);
|
||||
}
|
||||
|
||||
let initialized = false;
|
||||
|
@ -129,8 +117,7 @@ export function initializeInnerAngularModule(
|
|||
])
|
||||
.config(watchMultiDecorator)
|
||||
.directive('icon', (reactDirective) => reactDirective(EuiIcon))
|
||||
.directive('renderComplete', createRenderCompleteDirective)
|
||||
.service('debounce', ['$timeout', DebounceProviderTimeout]);
|
||||
.directive('renderComplete', createRenderCompleteDirective);
|
||||
}
|
||||
|
||||
return angular
|
||||
|
@ -149,18 +136,9 @@ export function initializeInnerAngularModule(
|
|||
])
|
||||
.config(watchMultiDecorator)
|
||||
.run(registerListenEventListener)
|
||||
.directive('icon', (reactDirective) => reactDirective(EuiIcon))
|
||||
.directive('kbnAccessibleClick', KbnAccessibleClickProvider)
|
||||
.directive('collapsibleSidebar', CollapsibleSidebarProvider)
|
||||
.directive('fixedScroll', FixedScrollProvider)
|
||||
.directive('renderComplete', createRenderCompleteDirective)
|
||||
.directive('discoverSidebar', createDiscoverSidebarDirective)
|
||||
.directive('skipBottomButton', createSkipBottomButtonDirective)
|
||||
.directive('hitsCounter', createHitsCounterDirective)
|
||||
.directive('loadingSpinner', createLoadingSpinnerDirective)
|
||||
.directive('timechartHeader', createTimechartHeaderDirective)
|
||||
.directive('contextErrorMessage', createContextErrorMessageDirective)
|
||||
.service('debounce', ['$timeout', DebounceProviderTimeout]);
|
||||
.directive('discoverLegacy', createDiscoverLegacyDirective)
|
||||
.directive('contextErrorMessage', createContextErrorMessageDirective);
|
||||
}
|
||||
|
||||
function createLocalPromiseModule() {
|
||||
|
|
|
@ -327,7 +327,12 @@ export class DiscoverPlugin
|
|||
if (this.servicesInitialized) {
|
||||
return { core, plugins };
|
||||
}
|
||||
const services = await buildServices(core, plugins, this.initializerContext);
|
||||
const services = await buildServices(
|
||||
core,
|
||||
plugins,
|
||||
this.initializerContext,
|
||||
this.getEmbeddableInjector
|
||||
);
|
||||
setServices(services);
|
||||
this.servicesInitialized = true;
|
||||
|
||||
|
@ -380,12 +385,7 @@ export class DiscoverPlugin
|
|||
const { core, plugins } = await this.initializeServices();
|
||||
getServices().kibanaLegacy.loadFontAwesome();
|
||||
const { getInnerAngularModuleEmbeddable } = await import('./get_inner_angular');
|
||||
getInnerAngularModuleEmbeddable(
|
||||
embeddableAngularName,
|
||||
core,
|
||||
plugins,
|
||||
this.initializerContext
|
||||
);
|
||||
getInnerAngularModuleEmbeddable(embeddableAngularName, core, plugins);
|
||||
const mountpoint = document.createElement('div');
|
||||
this.embeddableInjector = angular.bootstrap(mountpoint, [embeddableAngularName]);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ export interface SavedSearch {
|
|||
columns: string[];
|
||||
sort: SortOrder[];
|
||||
destroy: () => void;
|
||||
lastSavedTitle?: string;
|
||||
}
|
||||
export interface SavedSearchLoader {
|
||||
get: (id: string) => Promise<SavedSearch>;
|
||||
|
|
|
@ -254,7 +254,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
}
|
||||
|
||||
public async getSidebarWidth() {
|
||||
const sidebar = await find.byCssSelector('.sidebar-list');
|
||||
const sidebar = await testSubjects.find('discover-sidebar');
|
||||
return await sidebar.getAttribute('clientWidth');
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ filter-bar,
|
|||
/* hide unusable controls */
|
||||
discover-app .dscTimechart,
|
||||
discover-app .dscSidebar__container,
|
||||
discover-app .kbnCollapsibleSidebar__collapseButton,
|
||||
discover-app .dscCollapsibleSidebar__collapseButton,
|
||||
discover-app .discover-table-footer {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ filter-bar,
|
|||
/* hide unusable controls */
|
||||
discover-app .dscTimechart,
|
||||
discover-app .dscSidebar__container,
|
||||
discover-app .kbnCollapsibleSidebar__collapseButton,
|
||||
discover-app .dscCollapsibleSidebar__collapseButton,
|
||||
discover-app .discover-table-footer {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -1430,10 +1430,7 @@
|
|||
"discover.localMenu.saveTitle": "保存",
|
||||
"discover.localMenu.shareSearchDescription": "検索を共有します",
|
||||
"discover.localMenu.shareTitle": "共有",
|
||||
"discover.noResults.addressShardFailuresTitle": "シャードエラーの解決",
|
||||
"discover.noResults.expandYourTimeRangeTitle": "時間範囲を拡大",
|
||||
"discover.noResults.indexFailureIndexText": "インデックス {failureIndex}",
|
||||
"discover.noResults.indexFailureShardText": "{index}、シャード {failureShard}",
|
||||
"discover.noResults.queryMayNotMatchTitle": "1つ以上の表示されているインデックスに日付フィールドが含まれています。クエリが現在の時間範囲のデータと一致しないか、現在選択された時間範囲にデータが全く存在しない可能性があります。データが存在する時間範囲に変えることができます。",
|
||||
"discover.noResults.searchExamples.400to499StatusCodeExampleTitle": "400-499のすべてのステータスコードを検索",
|
||||
"discover.noResults.searchExamples.400to499StatusCodeWithPhpExtensionExampleTitle": "400-499のphp拡張子のステータスコードを検索",
|
||||
|
@ -1444,7 +1441,6 @@
|
|||
"discover.noResults.searchExamples.queryStringSyntaxLinkText": "クエリ文字列の構文",
|
||||
"discover.noResults.searchExamples.refineYourQueryTitle": "クエリの調整",
|
||||
"discover.noResults.searchExamples.statusField200StatusCodeExampleTitle": "ステータスフィールドの200を検索",
|
||||
"discover.noResults.shardFailuresDescription": "次のシャードエラーが発生しました。",
|
||||
"discover.notifications.invalidTimeRangeText": "指定された時間範囲が無効です。(開始:'{from}'、終了:'{to}')",
|
||||
"discover.notifications.invalidTimeRangeTitle": "無効な時間範囲",
|
||||
"discover.notifications.notSavedSearchTitle": "検索「{savedSearchTitle}」は保存されませんでした。",
|
||||
|
|
|
@ -1431,10 +1431,7 @@
|
|||
"discover.localMenu.saveTitle": "保存",
|
||||
"discover.localMenu.shareSearchDescription": "共享搜索",
|
||||
"discover.localMenu.shareTitle": "共享",
|
||||
"discover.noResults.addressShardFailuresTitle": "解决分片错误",
|
||||
"discover.noResults.expandYourTimeRangeTitle": "展开时间范围",
|
||||
"discover.noResults.indexFailureIndexText": "索引 {failureIndex}",
|
||||
"discover.noResults.indexFailureShardText": "{index},分片 {failureShard}",
|
||||
"discover.noResults.queryMayNotMatchTitle": "您正在查看的一个或多个索引包含日期字段。您的查询在当前时间范围内可能不匹配任何数据,也可能在当前选定的时间范围内没有任何数据。您可以尝试将时间范围更改为包含数据的时间范围。",
|
||||
"discover.noResults.searchExamples.400to499StatusCodeExampleTitle": "查找所有介于 400-499 之间的状态代码",
|
||||
"discover.noResults.searchExamples.400to499StatusCodeWithPhpExtensionExampleTitle": "查找状态代码 400-499 以及扩展名 php",
|
||||
|
@ -1445,7 +1442,6 @@
|
|||
"discover.noResults.searchExamples.queryStringSyntaxLinkText": "查询字符串语法",
|
||||
"discover.noResults.searchExamples.refineYourQueryTitle": "优化您的查询",
|
||||
"discover.noResults.searchExamples.statusField200StatusCodeExampleTitle": "在状态字段中查找 200",
|
||||
"discover.noResults.shardFailuresDescription": "发生了以下分片错误:",
|
||||
"discover.notifications.invalidTimeRangeText": "提供的时间范围无效。(起始:“{from}”,结束:“{to}”)",
|
||||
"discover.notifications.invalidTimeRangeTitle": "时间范围无效",
|
||||
"discover.notifications.notSavedSearchTitle": "搜索“{savedSearchTitle}”未保存。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue