mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Enabled: - View/Manage/Create rollup jobs Disabled: - Create a rollup index pattern - Create rollup visualizations - Add rollup visualizations to dashboards - View raw rollup documents in Discover
This commit is contained in:
parent
5041605782
commit
30d69b365b
258 changed files with 12108 additions and 364 deletions
|
@ -21,6 +21,12 @@
|
|||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"typeMeta": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -278,11 +278,17 @@ function discoverController(
|
|||
.setField('highlightAll', true)
|
||||
.setField('version', true);
|
||||
|
||||
// Even when searching rollups, we want to use the default strategy so that we get back a
|
||||
// document-like response.
|
||||
$scope.searchSource.setPreferredSearchStrategyId('default');
|
||||
|
||||
// searchSource which applies time range
|
||||
const timeRangeSearchSource = savedSearch.searchSource.create();
|
||||
timeRangeSearchSource.setField('filter', () => {
|
||||
return timefilter.createFilter($scope.indexPattern);
|
||||
});
|
||||
if(isDefaultTypeIndexPattern($scope.indexPattern)) {
|
||||
timeRangeSearchSource.setField('filter', () => {
|
||||
return timefilter.createFilter($scope.indexPattern);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.searchSource.setParent(timeRangeSearchSource);
|
||||
|
||||
|
@ -393,7 +399,7 @@ function discoverController(
|
|||
$scope.opts = {
|
||||
// number of records to fetch, then paginate through
|
||||
sampleSize: config.get('discover:sampleSize'),
|
||||
timefield: $scope.indexPattern.timeFieldName,
|
||||
timefield: isDefaultTypeIndexPattern($scope.indexPattern) && $scope.indexPattern.timeFieldName,
|
||||
savedSearch: savedSearch,
|
||||
indexPatternList: $route.current.locals.ip.list,
|
||||
};
|
||||
|
|
|
@ -37,6 +37,7 @@ import 'uiExports/fieldFormatEditors';
|
|||
import 'uiExports/navbarExtensions';
|
||||
import 'uiExports/contextMenuActions';
|
||||
import 'uiExports/managementSections';
|
||||
import 'uiExports/indexManagement';
|
||||
import 'uiExports/devTools';
|
||||
import 'uiExports/docViews';
|
||||
import 'uiExports/embeddableFactories';
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
@import 'hacks';
|
||||
|
||||
// Core
|
||||
// Core
|
||||
@import 'management_app';
|
||||
@import 'sections/settings/advanced_settings';
|
||||
@import 'sections/settings/advanced_settings';
|
||||
@import 'sections/indices/index';
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
exports[`CreateIndexPatternWizard defaults to the loading state 1`] = `
|
||||
<div>
|
||||
<Header
|
||||
indexPatternName="name"
|
||||
isIncludingSystemIndices={false}
|
||||
onChangeIncludingSystemIndices={[Function]}
|
||||
showSystemIndices={false}
|
||||
/>
|
||||
<LoadingState />
|
||||
</div>
|
||||
|
@ -13,8 +15,10 @@ exports[`CreateIndexPatternWizard defaults to the loading state 1`] = `
|
|||
exports[`CreateIndexPatternWizard renders index pattern step when there are indices 1`] = `
|
||||
<div>
|
||||
<Header
|
||||
indexPatternName="name"
|
||||
isIncludingSystemIndices={false}
|
||||
onChangeIncludingSystemIndices={[Function]}
|
||||
showSystemIndices={false}
|
||||
/>
|
||||
<StepIndexPattern
|
||||
allIndices={
|
||||
|
@ -26,6 +30,16 @@ exports[`CreateIndexPatternWizard renders index pattern step when there are indi
|
|||
}
|
||||
esService={Object {}}
|
||||
goToNextStep={[Function]}
|
||||
indexPatternCreationType={
|
||||
Object {
|
||||
"checkIndicesForErrors": [Function],
|
||||
"getIndexPatternMappings": [Function],
|
||||
"getIndexPatternName": [Function],
|
||||
"getIndexPatternType": [Function],
|
||||
"getShowSystemIndices": [Function],
|
||||
"renderPrompt": [Function],
|
||||
}
|
||||
}
|
||||
initialQuery=""
|
||||
isIncludingSystemIndices={false}
|
||||
savedObjectsClient={Object {}}
|
||||
|
@ -36,8 +50,10 @@ exports[`CreateIndexPatternWizard renders index pattern step when there are indi
|
|||
exports[`CreateIndexPatternWizard renders the empty state when there are no indices 1`] = `
|
||||
<div>
|
||||
<Header
|
||||
indexPatternName="name"
|
||||
isIncludingSystemIndices={false}
|
||||
onChangeIncludingSystemIndices={[Function]}
|
||||
showSystemIndices={false}
|
||||
/>
|
||||
<EmptyState
|
||||
onRefresh={[Function]}
|
||||
|
@ -48,13 +64,25 @@ exports[`CreateIndexPatternWizard renders the empty state when there are no indi
|
|||
exports[`CreateIndexPatternWizard renders time field step when step is set to 2 1`] = `
|
||||
<div>
|
||||
<Header
|
||||
indexPatternName="name"
|
||||
isIncludingSystemIndices={false}
|
||||
onChangeIncludingSystemIndices={[Function]}
|
||||
showSystemIndices={false}
|
||||
/>
|
||||
<StepTimeField
|
||||
createIndexPattern={[Function]}
|
||||
goToPreviousStep={[Function]}
|
||||
indexPattern=""
|
||||
indexPatternCreationType={
|
||||
Object {
|
||||
"checkIndicesForErrors": [Function],
|
||||
"getIndexPatternMappings": [Function],
|
||||
"getIndexPatternName": [Function],
|
||||
"getIndexPatternType": [Function],
|
||||
"getShowSystemIndices": [Function],
|
||||
"renderPrompt": [Function],
|
||||
}
|
||||
}
|
||||
indexPatternsService={Object {}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -63,8 +91,10 @@ exports[`CreateIndexPatternWizard renders time field step when step is set to 2
|
|||
exports[`CreateIndexPatternWizard shows system indices even if there are no other indices if the include system indices is toggled 1`] = `
|
||||
<div>
|
||||
<Header
|
||||
indexPatternName="name"
|
||||
isIncludingSystemIndices={true}
|
||||
onChangeIncludingSystemIndices={[Function]}
|
||||
showSystemIndices={false}
|
||||
/>
|
||||
<StepIndexPattern
|
||||
allIndices={
|
||||
|
@ -76,6 +106,16 @@ exports[`CreateIndexPatternWizard shows system indices even if there are no othe
|
|||
}
|
||||
esService={Object {}}
|
||||
goToNextStep={[Function]}
|
||||
indexPatternCreationType={
|
||||
Object {
|
||||
"checkIndicesForErrors": [Function],
|
||||
"getIndexPatternMappings": [Function],
|
||||
"getIndexPatternName": [Function],
|
||||
"getIndexPatternType": [Function],
|
||||
"getShowSystemIndices": [Function],
|
||||
"renderPrompt": [Function],
|
||||
}
|
||||
}
|
||||
initialQuery=""
|
||||
isIncludingSystemIndices={true}
|
||||
savedObjectsClient={Object {}}
|
||||
|
|
|
@ -21,7 +21,14 @@ import React from 'react';
|
|||
import { shallow } from 'enzyme';
|
||||
|
||||
import { CreateIndexPatternWizard } from '../create_index_pattern_wizard';
|
||||
|
||||
const mockIndexPatternCreationType = {
|
||||
getIndexPatternType: () => 'default',
|
||||
getIndexPatternName: () => 'name',
|
||||
checkIndicesForErrors: () => false,
|
||||
getShowSystemIndices: () => false,
|
||||
renderPrompt: () => {},
|
||||
getIndexPatternMappings: () => { return {}; }
|
||||
};
|
||||
jest.mock('../components/step_index_pattern', () => ({ StepIndexPattern: 'StepIndexPattern' }));
|
||||
jest.mock('../components/step_time_field', () => ({ StepTimeField: 'StepTimeField' }));
|
||||
jest.mock('../components/header', () => ({ Header: 'Header' }));
|
||||
|
@ -44,6 +51,7 @@ const services = {
|
|||
config: {},
|
||||
changeUrl: () => {},
|
||||
scopeApply: () => {},
|
||||
indexPatternCreationType: mockIndexPatternCreationType,
|
||||
};
|
||||
|
||||
describe('CreateIndexPatternWizard', () => {
|
||||
|
@ -154,6 +162,7 @@ describe('CreateIndexPatternWizard', () => {
|
|||
cache: { clear }
|
||||
},
|
||||
changeUrl,
|
||||
indexPatternCreationType: mockIndexPatternCreationType
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -52,6 +52,7 @@ describe('CreateIndexPatternWizardRender', () => {
|
|||
savedObjectsClient: {},
|
||||
config: {},
|
||||
changeUrl: () => {},
|
||||
indexPatternCreationType: {},
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -10,9 +10,13 @@ exports[`Header should render normally 1`] = `
|
|||
>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create index pattern"
|
||||
defaultMessage="Create {indexPatternName}"
|
||||
id="kbn.management.createIndexPatternHeader"
|
||||
values={Object {}}
|
||||
values={
|
||||
Object {
|
||||
"indexPatternName": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
@ -46,23 +50,6 @@ exports[`Header should render normally 1`] = `
|
|||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
id="checkboxShowSystemIndices"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Include system indices"
|
||||
id="kbn.management.createIndexPattern.includeSystemIndicesToggleSwitchLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
|
@ -80,9 +67,13 @@ exports[`Header should render without including system indices 1`] = `
|
|||
>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create index pattern"
|
||||
defaultMessage="Create {indexPatternName}"
|
||||
id="kbn.management.createIndexPatternHeader"
|
||||
values={Object {}}
|
||||
values={
|
||||
Object {
|
||||
"indexPatternName": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
@ -116,23 +107,6 @@ exports[`Header should render without including system indices 1`] = `
|
|||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
id="checkboxShowSystemIndices"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Include system indices"
|
||||
id="kbn.management.createIndexPattern.includeSystemIndicesToggleSwitchLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import {
|
||||
EuiSpacer,
|
||||
|
@ -32,6 +32,9 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const Header = ({
|
||||
prompt,
|
||||
indexPatternName,
|
||||
showSystemIndices,
|
||||
isIncludingSystemIndices,
|
||||
onChangeIncludingSystemIndices,
|
||||
}) => (
|
||||
|
@ -41,7 +44,10 @@ export const Header = ({
|
|||
<h1>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPatternHeader"
|
||||
defaultMessage="Create index pattern"
|
||||
defaultMessage="Create {indexPatternName}"
|
||||
values={{
|
||||
indexPatternName
|
||||
}}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
@ -58,18 +64,30 @@ export const Header = ({
|
|||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.includeSystemIndicesToggleSwitchLabel"
|
||||
defaultMessage="Include system indices"
|
||||
/>}
|
||||
id="checkboxShowSystemIndices"
|
||||
checked={isIncludingSystemIndices}
|
||||
onChange={onChangeIncludingSystemIndices}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{
|
||||
showSystemIndices ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.includeSystemIndicesToggleSwitchLabel"
|
||||
defaultMessage="Include system indices"
|
||||
/>}
|
||||
id="checkboxShowSystemIndices"
|
||||
checked={isIncludingSystemIndices}
|
||||
onChange={onChangeIncludingSystemIndices}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null
|
||||
}
|
||||
</EuiFlexGroup>
|
||||
{
|
||||
prompt ? (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
{prompt}
|
||||
</Fragment>
|
||||
) : null
|
||||
}
|
||||
<EuiSpacer size="m"/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ Object {
|
|||
data-test-subj="createIndexPatternStep1Header"
|
||||
errors={
|
||||
Array [
|
||||
"An index pattern cannot contain spaces or the characters: {characterList}",
|
||||
"A {indexPatternName} cannot contain spaces or the characters: {characterList}",
|
||||
]
|
||||
}
|
||||
goToNextStep={[Function]}
|
||||
|
@ -19,38 +19,42 @@ Object {
|
|||
"i18n": Array [
|
||||
Array [
|
||||
Object {
|
||||
"defaultMessage": "An index pattern cannot contain spaces or the characters: {characterList}",
|
||||
"defaultMessage": "A {indexPatternName} cannot contain spaces or the characters: {characterList}",
|
||||
"id": "kbn.management.createIndexPattern.step.invalidCharactersErrorMessage",
|
||||
},
|
||||
Object {
|
||||
"characterList": "\\\\, /, ?, \\", <, >, |",
|
||||
"indexPatternName": "name",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"defaultMessage": "An index pattern cannot contain spaces or the characters: {characterList}",
|
||||
"defaultMessage": "A {indexPatternName} cannot contain spaces or the characters: {characterList}",
|
||||
"id": "kbn.management.createIndexPattern.step.invalidCharactersErrorMessage",
|
||||
},
|
||||
Object {
|
||||
"characterList": "\\\\, /, ?, \\", <, >, |",
|
||||
"indexPatternName": "name",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"defaultMessage": "An index pattern cannot contain spaces or the characters: {characterList}",
|
||||
"defaultMessage": "A {indexPatternName} cannot contain spaces or the characters: {characterList}",
|
||||
"id": "kbn.management.createIndexPattern.step.invalidCharactersErrorMessage",
|
||||
},
|
||||
Object {
|
||||
"characterList": "\\\\, /, ?, \\", <, >, |",
|
||||
"indexPatternName": "name",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"defaultMessage": "An index pattern cannot contain spaces or the characters: {characterList}",
|
||||
"defaultMessage": "A {indexPatternName} cannot contain spaces or the characters: {characterList}",
|
||||
"id": "kbn.management.createIndexPattern.step.invalidCharactersErrorMessage",
|
||||
},
|
||||
Object {
|
||||
"characterList": "\\\\, /, ?, \\", <, >, |",
|
||||
"indexPatternName": "name",
|
||||
},
|
||||
],
|
||||
],
|
||||
|
|
|
@ -25,7 +25,12 @@ import { Header } from '../components/header';
|
|||
jest.mock('../../../lib/ensure_minimum_time', () => ({
|
||||
ensureMinimumTime: async (promises) => Array.isArray(promises) ? await Promise.all(promises) : await promises
|
||||
}));
|
||||
|
||||
const mockIndexPatternCreationType = {
|
||||
getIndexPatternType: () => 'default',
|
||||
getIndexPatternName: () => 'name',
|
||||
checkIndicesForErrors: () => false,
|
||||
getShowSystemIndices: () => false
|
||||
};
|
||||
// If we don't mock this, Jest fails with the error `TypeError: Cannot redefine property: prototype
|
||||
// at Function.defineProperties`.
|
||||
jest.mock('ui/index_patterns', () => ({
|
||||
|
@ -39,7 +44,7 @@ jest.mock('ui/chrome', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../../../lib/get_indices', () => ({
|
||||
getIndices: (service, query) => {
|
||||
getIndices: (service, indexPatternCreationType, query) => {
|
||||
if (query.startsWith('e')) {
|
||||
return [
|
||||
{ name: 'es' },
|
||||
|
@ -67,6 +72,7 @@ const createComponent = props => {
|
|||
esService={esService}
|
||||
savedObjectsClient={savedObjectsClient}
|
||||
goToNextStep={goToNextStep}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,10 @@ exports[`IndicesList should change pages 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="1"
|
||||
|
@ -25,6 +29,10 @@ exports[`IndicesList should change pages 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
</EuiTableBody>
|
||||
</EuiTable>
|
||||
|
@ -134,6 +142,10 @@ exports[`IndicesList should change per page 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
</EuiTableBody>
|
||||
</EuiTable>
|
||||
|
@ -258,6 +270,10 @@ exports[`IndicesList should highlight the query in the matches 1`] = `
|
|||
bana
|
||||
</span>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="1"
|
||||
|
@ -268,6 +284,10 @@ exports[`IndicesList should highlight the query in the matches 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
</EuiTableBody>
|
||||
</EuiTable>
|
||||
|
@ -377,6 +397,10 @@ exports[`IndicesList should render normally 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="1"
|
||||
|
@ -387,6 +411,10 @@ exports[`IndicesList should render normally 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
</EuiTableBody>
|
||||
</EuiTable>
|
||||
|
@ -496,6 +524,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="1"
|
||||
|
@ -506,6 +538,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="2"
|
||||
|
@ -516,6 +552,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="3"
|
||||
|
@ -526,6 +566,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="4"
|
||||
|
@ -536,6 +580,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="5"
|
||||
|
@ -546,6 +594,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="6"
|
||||
|
@ -556,6 +608,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="7"
|
||||
|
@ -566,6 +622,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="8"
|
||||
|
@ -576,6 +636,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
kibana
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="9"
|
||||
|
@ -586,6 +650,10 @@ exports[`IndicesList updating props should render all new indices 1`] = `
|
|||
>
|
||||
es
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
align="left"
|
||||
textOnly={true}
|
||||
/>
|
||||
</EuiTableRow>
|
||||
</EuiTableBody>
|
||||
</EuiTable>
|
||||
|
|
|
@ -22,8 +22,8 @@ import { IndicesList } from '../indices_list';
|
|||
import { shallow } from 'enzyme';
|
||||
|
||||
const indices = [
|
||||
{ name: 'kibana' },
|
||||
{ name: 'es' }
|
||||
{ name: 'kibana', tags: [] },
|
||||
{ name: 'es', tags: [] }
|
||||
];
|
||||
|
||||
describe('IndicesList', () => {
|
||||
|
|
|
@ -22,6 +22,7 @@ import PropTypes from 'prop-types';
|
|||
import { PER_PAGE_INCREMENTS } from '../../../../constants';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
|
@ -185,6 +186,13 @@ export class IndicesList extends Component {
|
|||
<EuiTableRowCell>
|
||||
{this.highlightIndexName(index.name, queryWithoutWildcard)}
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell>
|
||||
{index.tags.map(tag => {
|
||||
return (
|
||||
<EuiBadge key={`index_${key}_tag_${tag.key}`} color="primary">{tag.name}</EuiBadge>
|
||||
);
|
||||
})}
|
||||
</EuiTableRowCell>
|
||||
</EuiTableRow>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -174,8 +174,8 @@ exports[`StatusMessage should show that system indices exist 1`] = `
|
|||
>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
defaultMessage="No Elasticsearch indices match your pattern. To view the matching system indices, toggle the switch in the upper right."
|
||||
id="kbn.management.createIndexPattern.step.status.noSystemIndicesWithPromptLabel"
|
||||
defaultMessage="No Elasticsearch indices match your pattern."
|
||||
id="kbn.management.createIndexPattern.step.status.noSystemIndicesLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</span>
|
||||
|
|
|
@ -35,6 +35,7 @@ export const StatusMessage = ({
|
|||
},
|
||||
isIncludingSystemIndices,
|
||||
query,
|
||||
showSystemIndicies,
|
||||
}) => {
|
||||
let statusIcon;
|
||||
let statusMessage;
|
||||
|
@ -58,7 +59,7 @@ export const StatusMessage = ({
|
|||
</span>
|
||||
);
|
||||
}
|
||||
else if (!isIncludingSystemIndices) {
|
||||
else if (!isIncludingSystemIndices && showSystemIndicies) {
|
||||
statusMessage = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
|
@ -70,7 +71,6 @@ export const StatusMessage = ({
|
|||
);
|
||||
}
|
||||
else {
|
||||
// This should never really happen but let's handle it just in case
|
||||
statusMessage = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -50,6 +50,7 @@ export class StepIndexPatternComponent extends Component {
|
|||
isIncludingSystemIndices: PropTypes.bool.isRequired,
|
||||
esService: PropTypes.object.isRequired,
|
||||
savedObjectsClient: PropTypes.object.isRequired,
|
||||
indexPatternCreationType: PropTypes.object.isRequired,
|
||||
goToNextStep: PropTypes.func.isRequired,
|
||||
initialQuery: PropTypes.string,
|
||||
}
|
||||
|
@ -60,6 +61,7 @@ export class StepIndexPatternComponent extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { indexPatternCreationType } = this.props;
|
||||
this.state = {
|
||||
partialMatchedIndices: [],
|
||||
exactMatchedIndices: [],
|
||||
|
@ -69,8 +71,10 @@ export class StepIndexPatternComponent extends Component {
|
|||
query: props.initialQuery,
|
||||
appendedWildcard: false,
|
||||
showingIndexPatternQueryErrors: false,
|
||||
indexPatternName: indexPatternCreationType.getIndexPatternName(),
|
||||
};
|
||||
|
||||
this.ILLEGAL_CHARACTERS = [...ILLEGAL_CHARACTERS];
|
||||
this.lastQuery = null;
|
||||
}
|
||||
|
||||
|
@ -93,7 +97,7 @@ export class StepIndexPatternComponent extends Component {
|
|||
}
|
||||
|
||||
fetchIndices = async (query) => {
|
||||
const { esService } = this.props;
|
||||
const { esService, indexPatternCreationType } = this.props;
|
||||
const { existingIndexPatterns } = this.state;
|
||||
|
||||
if (existingIndexPatterns.includes(query)) {
|
||||
|
@ -104,7 +108,7 @@ export class StepIndexPatternComponent extends Component {
|
|||
this.setState({ isLoadingIndices: true, indexPatternExists: false });
|
||||
|
||||
if (query.endsWith('*')) {
|
||||
const exactMatchedIndices = await ensureMinimumTime(getIndices(esService, query, MAX_SEARCH_SIZE));
|
||||
const exactMatchedIndices = await ensureMinimumTime(getIndices(esService, indexPatternCreationType, query, MAX_SEARCH_SIZE));
|
||||
// If the search changed, discard this state
|
||||
if (query !== this.lastQuery) {
|
||||
return;
|
||||
|
@ -117,8 +121,8 @@ export class StepIndexPatternComponent extends Component {
|
|||
partialMatchedIndices,
|
||||
exactMatchedIndices,
|
||||
] = await ensureMinimumTime([
|
||||
getIndices(esService, `${query}*`, MAX_SEARCH_SIZE),
|
||||
getIndices(esService, query, MAX_SEARCH_SIZE),
|
||||
getIndices(esService, indexPatternCreationType, `${query}*`, MAX_SEARCH_SIZE),
|
||||
getIndices(esService, indexPatternCreationType, query, MAX_SEARCH_SIZE),
|
||||
]);
|
||||
|
||||
// If the search changed, discard this state
|
||||
|
@ -167,6 +171,7 @@ export class StepIndexPatternComponent extends Component {
|
|||
}
|
||||
|
||||
renderStatusMessage(matchedIndices) {
|
||||
const { indexPatternCreationType } = this.props;
|
||||
const { query, isLoadingIndices, indexPatternExists, isIncludingSystemIndices } = this.state;
|
||||
|
||||
if (isLoadingIndices || indexPatternExists) {
|
||||
|
@ -176,6 +181,7 @@ export class StepIndexPatternComponent extends Component {
|
|||
return (
|
||||
<StatusMessage
|
||||
matchedIndices={matchedIndices}
|
||||
showSystemIndices={indexPatternCreationType.getShowSystemIndices()}
|
||||
isIncludingSystemIndices={isIncludingSystemIndices}
|
||||
query={query}
|
||||
/>
|
||||
|
@ -230,27 +236,31 @@ export class StepIndexPatternComponent extends Component {
|
|||
}
|
||||
|
||||
renderHeader({ exactMatchedIndices: indices }) {
|
||||
const { goToNextStep, intl } = this.props;
|
||||
const { query, showingIndexPatternQueryErrors, indexPatternExists } = this.state;
|
||||
const { goToNextStep, indexPatternCreationType, intl } = this.props;
|
||||
const { query, showingIndexPatternQueryErrors, indexPatternExists, indexPatternName } = this.state;
|
||||
|
||||
let containsErrors = false;
|
||||
const errors = [];
|
||||
const characterList = ILLEGAL_CHARACTERS.slice(0, ILLEGAL_CHARACTERS.length - 1).join(', ');
|
||||
const characterList = this.ILLEGAL_CHARACTERS.slice(0, this.ILLEGAL_CHARACTERS.length - 1).join(', ');
|
||||
const checkIndices = indexPatternCreationType.checkIndicesForErrors(indices);
|
||||
|
||||
if (!query || !query.length || query === '.' || query === '..') {
|
||||
// This is an error scenario but do not report an error
|
||||
containsErrors = true;
|
||||
}
|
||||
else if (containsIllegalCharacters(query, ILLEGAL_CHARACTERS)) {
|
||||
} else if (containsIllegalCharacters(query, ILLEGAL_CHARACTERS)) {
|
||||
const errorMessage = intl.formatMessage(
|
||||
{
|
||||
id: 'kbn.management.createIndexPattern.step.invalidCharactersErrorMessage',
|
||||
defaultMessage: 'An index pattern cannot contain spaces or the characters: {characterList}'
|
||||
defaultMessage: 'A {indexPatternName} cannot contain spaces or the characters: {characterList}'
|
||||
},
|
||||
{ characterList });
|
||||
{ characterList, indexPatternName }
|
||||
);
|
||||
|
||||
errors.push(errorMessage);
|
||||
containsErrors = true;
|
||||
} else if (checkIndices) {
|
||||
errors.push(...checkIndices);
|
||||
containsErrors = true;
|
||||
}
|
||||
|
||||
const isInputInvalid = showingIndexPatternQueryErrors && containsErrors && errors.length > 0;
|
||||
|
|
|
@ -63,9 +63,10 @@ exports[`StepTimeField should render a selected timeField 1`] = `
|
|||
>
|
||||
<Header
|
||||
indexPattern="ki*"
|
||||
indexPatternName="name"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
size="m"
|
||||
/>
|
||||
<TimeField
|
||||
fetchTimeFields={[Function]}
|
||||
|
@ -120,9 +121,10 @@ exports[`StepTimeField should render advanced options 1`] = `
|
|||
>
|
||||
<Header
|
||||
indexPattern="ki*"
|
||||
indexPatternName="name"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
size="m"
|
||||
/>
|
||||
<TimeField
|
||||
fetchTimeFields={[Function]}
|
||||
|
@ -166,9 +168,10 @@ exports[`StepTimeField should render advanced options with an index pattern id 1
|
|||
>
|
||||
<Header
|
||||
indexPattern="ki*"
|
||||
indexPatternName="name"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
size="m"
|
||||
/>
|
||||
<TimeField
|
||||
fetchTimeFields={[Function]}
|
||||
|
@ -212,9 +215,10 @@ exports[`StepTimeField should render normally 1`] = `
|
|||
>
|
||||
<Header
|
||||
indexPattern="ki*"
|
||||
indexPatternName="name"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
size="m"
|
||||
/>
|
||||
<TimeField
|
||||
fetchTimeFields={[Function]}
|
||||
|
@ -258,9 +262,10 @@ exports[`StepTimeField should render timeFields 1`] = `
|
|||
>
|
||||
<Header
|
||||
indexPattern="ki*"
|
||||
indexPatternName="name"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
size="m"
|
||||
/>
|
||||
<TimeField
|
||||
fetchTimeFields={[Function]}
|
||||
|
|
|
@ -30,6 +30,10 @@ jest.mock('../../../lib/extract_time_fields', () => ({
|
|||
extractTimeFields: fields => fields,
|
||||
}));
|
||||
|
||||
const mockIndexPatternCreationType = {
|
||||
getIndexPatternType: () => 'default',
|
||||
getIndexPatternName: () => 'name'
|
||||
};
|
||||
const noop = () => {};
|
||||
const indexPatternsService = {
|
||||
fieldsFetcher: {
|
||||
|
@ -45,6 +49,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -58,6 +63,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -78,6 +84,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -100,6 +107,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -128,6 +136,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -151,6 +160,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -174,6 +184,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -189,6 +200,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -207,6 +219,7 @@ describe('StepTimeField', () => {
|
|||
indexPatternsService={indexPatternsService}
|
||||
goToPreviousStep={noop}
|
||||
createIndexPattern={noop}
|
||||
indexPatternCreationType={mockIndexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -21,13 +21,14 @@ exports[`Header should render normally 1`] = `
|
|||
grow={true}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="You've defined {indexPattern} as your index pattern. Now you can specify some settings before we create it."
|
||||
defaultMessage="You've defined {indexPattern} as your {indexPatternName}. Now you can specify some settings before we create it."
|
||||
id="kbn.management.createIndexPattern.stepTimeLabel"
|
||||
values={
|
||||
Object {
|
||||
"indexPattern": <strong>
|
||||
ki*
|
||||
</strong>,
|
||||
"indexPatternName": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -29,6 +29,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
|
||||
export const Header = ({
|
||||
indexPattern,
|
||||
indexPatternName,
|
||||
}) => (
|
||||
<div>
|
||||
<EuiTitle size="s">
|
||||
|
@ -43,8 +44,11 @@ export const Header = ({
|
|||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.stepTimeLabel"
|
||||
defaultMessage="You've defined {indexPattern} as your index pattern. Now you can specify some settings before we create it."
|
||||
values={{ indexPattern: <strong>{indexPattern}</strong> }}
|
||||
defaultMessage="You've defined {indexPattern} as your {indexPatternName}. Now you can specify some settings before we create it."
|
||||
values={{
|
||||
indexPattern: <strong>{indexPattern}</strong>,
|
||||
indexPatternName,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
|
|
|
@ -43,11 +43,17 @@ export class StepTimeFieldComponent extends Component {
|
|||
indexPatternsService: PropTypes.object.isRequired,
|
||||
goToPreviousStep: PropTypes.func.isRequired,
|
||||
createIndexPattern: PropTypes.func.isRequired,
|
||||
indexPatternCreationType: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {
|
||||
getIndexPatternType,
|
||||
getIndexPatternName,
|
||||
} = props.indexPatternCreationType;
|
||||
|
||||
this.state = {
|
||||
timeFields: [],
|
||||
selectedTimeField: undefined,
|
||||
|
@ -56,6 +62,8 @@ export class StepTimeFieldComponent extends Component {
|
|||
isFetchingTimeFields: false,
|
||||
isCreating: false,
|
||||
indexPatternId: '',
|
||||
indexPatternType: getIndexPatternType(),
|
||||
indexPatternName: getIndexPatternName(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -65,9 +73,12 @@ export class StepTimeFieldComponent extends Component {
|
|||
|
||||
fetchTimeFields = async () => {
|
||||
const { indexPatternsService, indexPattern } = this.props;
|
||||
const { getFetchForWildcardOptions } = this.props.indexPatternCreationType;
|
||||
|
||||
this.setState({ isFetchingTimeFields: true });
|
||||
const fields = await ensureMinimumTime(indexPatternsService.fieldsFetcher.fetchForWildcard(indexPattern));
|
||||
const fields = await ensureMinimumTime(
|
||||
indexPatternsService.fieldsFetcher.fetchForWildcard(indexPattern, getFetchForWildcardOptions())
|
||||
);
|
||||
const timeFields = extractTimeFields(fields);
|
||||
|
||||
this.setState({ timeFields, isFetchingTimeFields: false });
|
||||
|
@ -113,6 +124,7 @@ export class StepTimeFieldComponent extends Component {
|
|||
indexPatternId,
|
||||
isCreating,
|
||||
isFetchingTimeFields,
|
||||
indexPatternName,
|
||||
} = this.state;
|
||||
|
||||
if (isCreating) {
|
||||
|
@ -156,8 +168,11 @@ export class StepTimeFieldComponent extends Component {
|
|||
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<Header indexPattern={indexPattern} />
|
||||
<EuiSpacer size="xs"/>
|
||||
<Header
|
||||
indexPattern={indexPattern}
|
||||
indexPatternName={indexPatternName}
|
||||
/>
|
||||
<EuiSpacer size="m"/>
|
||||
<TimeField
|
||||
isVisible={showTimeField}
|
||||
fetchTimeFields={this.fetchTimeFields}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { ensureMinimumTime } from './lib';
|
||||
import { StepIndexPattern } from './components/step_index_pattern';
|
||||
import { StepTimeField } from './components/step_time_field';
|
||||
import { Header } from './components/header';
|
||||
|
@ -28,7 +27,10 @@ import { LoadingState } from './components/loading_state';
|
|||
import { EmptyState } from './components/empty_state';
|
||||
|
||||
import { MAX_SEARCH_SIZE } from './constants';
|
||||
import { getIndices } from './lib/get_indices';
|
||||
import {
|
||||
ensureMinimumTime,
|
||||
getIndices,
|
||||
} from './lib';
|
||||
|
||||
export class CreateIndexPatternWizard extends Component {
|
||||
static propTypes = {
|
||||
|
@ -37,6 +39,7 @@ export class CreateIndexPatternWizard extends Component {
|
|||
es: PropTypes.object.isRequired,
|
||||
indexPatterns: PropTypes.object.isRequired,
|
||||
savedObjectsClient: PropTypes.object.isRequired,
|
||||
indexPatternCreationType: PropTypes.object.isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
changeUrl: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
|
@ -44,6 +47,7 @@ export class CreateIndexPatternWizard extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.indexPatternCreationType = this.props.services.indexPatternCreationType;
|
||||
this.state = {
|
||||
step: 1,
|
||||
indexPattern: '',
|
||||
|
@ -60,7 +64,7 @@ export class CreateIndexPatternWizard extends Component {
|
|||
fetchIndices = async () => {
|
||||
this.setState({ allIndices: [], isInitiallyLoadingIndices: true });
|
||||
const { services } = this.props;
|
||||
const allIndices = await ensureMinimumTime(getIndices(services.es, `*`, MAX_SEARCH_SIZE));
|
||||
const allIndices = await ensureMinimumTime(getIndices(services.es, this.indexPatternCreationType, `*`, MAX_SEARCH_SIZE)); //
|
||||
this.setState({ allIndices, isInitiallyLoadingIndices: false });
|
||||
}
|
||||
|
||||
|
@ -74,6 +78,7 @@ export class CreateIndexPatternWizard extends Component {
|
|||
id: indexPatternId,
|
||||
title: indexPattern,
|
||||
timeFieldName,
|
||||
...this.indexPatternCreationType.getIndexPatternMappings()
|
||||
});
|
||||
|
||||
const createdId = await emptyPattern.create();
|
||||
|
@ -105,8 +110,11 @@ export class CreateIndexPatternWizard extends Component {
|
|||
|
||||
return (
|
||||
<Header
|
||||
prompt={this.indexPatternCreationType.renderPrompt()}
|
||||
showSystemIndices={this.indexPatternCreationType.getShowSystemIndices()}
|
||||
isIncludingSystemIndices={isIncludingSystemIndices}
|
||||
onChangeIncludingSystemIndices={this.onChangeIncludingSystemIndices}
|
||||
indexPatternName={this.indexPatternCreationType.getIndexPatternName()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -138,6 +146,7 @@ export class CreateIndexPatternWizard extends Component {
|
|||
isIncludingSystemIndices={isIncludingSystemIndices}
|
||||
esService={services.es}
|
||||
savedObjectsClient={services.savedObjectsClient}
|
||||
indexPatternCreationType={this.indexPatternCreationType}
|
||||
goToNextStep={this.goToTimeFieldStep}
|
||||
/>
|
||||
);
|
||||
|
@ -151,6 +160,7 @@ export class CreateIndexPatternWizard extends Component {
|
|||
indexPatternsService={services.indexPatterns}
|
||||
goToPreviousStep={this.goToIndexPatternStep}
|
||||
createIndexPattern={this.createIndexPattern}
|
||||
indexPatternCreationType={this.indexPatternCreationType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
|||
import uiRoutes from 'ui/routes';
|
||||
import angularTemplate from './angular_template.html';
|
||||
import 'ui/index_patterns';
|
||||
import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation';
|
||||
|
||||
import { renderCreateIndexPatternWizard, destroyCreateIndexPatternWizard } from './render';
|
||||
|
||||
|
@ -29,13 +30,17 @@ uiRoutes.when('/management/kibana/index', {
|
|||
controller: function ($scope, $injector) {
|
||||
// Wait for the directives to execute
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const Private = $injector.get('Private');
|
||||
$scope.$$postDigest(() => {
|
||||
const $routeParams = $injector.get('$routeParams');
|
||||
const indexPatternCreationProvider = Private(IndexPatternCreationFactory)($routeParams.type);
|
||||
const indexPatternCreationType = indexPatternCreationProvider.getType();
|
||||
const services = {
|
||||
config: $injector.get('config'),
|
||||
es: $injector.get('es'),
|
||||
indexPatterns: $injector.get('indexPatterns'),
|
||||
savedObjectsClient: $injector.get('Private')(SavedObjectsClientProvider),
|
||||
savedObjectsClient: Private(SavedObjectsClientProvider),
|
||||
indexPatternCreationType,
|
||||
changeUrl: url => {
|
||||
$scope.$evalAsync(() => kbnUrl.changePath(url));
|
||||
},
|
||||
|
|
|
@ -21,6 +21,15 @@ import { getIndices } from '../get_indices';
|
|||
import successfulResponse from './api/get_indices.success.json';
|
||||
import errorResponse from './api/get_indices.error.json';
|
||||
import exceptionResponse from './api/get_indices.exception.json';
|
||||
const mockIndexPatternCreationType = {
|
||||
getIndexPatternType: () => 'default',
|
||||
getIndexPatternName: () => 'name',
|
||||
checkIndicesForErrors: () => false,
|
||||
getShowSystemIndices: () => false,
|
||||
renderPrompt: () => {},
|
||||
getIndexPatternMappings: () => { return {}; },
|
||||
getIndexTags: () => { return []; }
|
||||
};
|
||||
|
||||
describe('getIndices', () => {
|
||||
it('should work in a basic case', async () => {
|
||||
|
@ -28,20 +37,20 @@ describe('getIndices', () => {
|
|||
search: () => new Promise((resolve) => resolve(successfulResponse))
|
||||
};
|
||||
|
||||
const result = await getIndices(es, 'kibana', 1);
|
||||
const result = await getIndices(es, mockIndexPatternCreationType, 'kibana', 1);
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0].name).toBe('1');
|
||||
expect(result[1].name).toBe('2');
|
||||
});
|
||||
|
||||
it('should ignore ccs query-all', async () => {
|
||||
expect((await getIndices(null, '*:')).length).toBe(0);
|
||||
expect((await getIndices(null, mockIndexPatternCreationType, '*:')).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should ignore a single comma', async () => {
|
||||
expect((await getIndices(null, ',')).length).toBe(0);
|
||||
expect((await getIndices(null, ',*')).length).toBe(0);
|
||||
expect((await getIndices(null, ',foobar')).length).toBe(0);
|
||||
expect((await getIndices(null, mockIndexPatternCreationType, ',')).length).toBe(0);
|
||||
expect((await getIndices(null, mockIndexPatternCreationType, ',*')).length).toBe(0);
|
||||
expect((await getIndices(null, mockIndexPatternCreationType, ',foobar')).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should trim the input', async () => {
|
||||
|
@ -52,7 +61,7 @@ describe('getIndices', () => {
|
|||
}),
|
||||
};
|
||||
|
||||
await getIndices(es, 'kibana ', 1);
|
||||
await getIndices(es, mockIndexPatternCreationType, 'kibana ', 1);
|
||||
expect(index).toBe('kibana');
|
||||
});
|
||||
|
||||
|
@ -64,7 +73,7 @@ describe('getIndices', () => {
|
|||
}),
|
||||
};
|
||||
|
||||
await getIndices(es, 'kibana', 10);
|
||||
await getIndices(es, mockIndexPatternCreationType, 'kibana', 10);
|
||||
expect(limit).toBe(10);
|
||||
});
|
||||
|
||||
|
@ -74,7 +83,7 @@ describe('getIndices', () => {
|
|||
search: () => new Promise((resolve) => resolve(errorResponse))
|
||||
};
|
||||
|
||||
const result = await getIndices(es, 'kibana', 1);
|
||||
const result = await getIndices(es, mockIndexPatternCreationType, 'kibana', 1);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
|
||||
|
@ -83,7 +92,7 @@ describe('getIndices', () => {
|
|||
search: () => { throw new Error('Fail'); }
|
||||
};
|
||||
|
||||
await expect(getIndices(es, 'kibana', 1)).rejects.toThrow();
|
||||
await expect(getIndices(es, mockIndexPatternCreationType, 'kibana', 1)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle index_not_found_exception errors gracefully', async () => {
|
||||
|
@ -91,12 +100,12 @@ describe('getIndices', () => {
|
|||
search: () => new Promise((resolve, reject) => reject(exceptionResponse))
|
||||
};
|
||||
|
||||
const result = await getIndices(es, 'kibana', 1);
|
||||
const result = await getIndices(es, mockIndexPatternCreationType, 'kibana', 1);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should throw an exception if no limit is provided', async () => {
|
||||
await expect(getIndices({}, 'kibana')).rejects.toThrow();
|
||||
await expect(getIndices({}, mockIndexPatternCreationType, 'kibana')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { get, sortBy } from 'lodash';
|
||||
|
||||
export async function getIndices(es, rawPattern, limit) {
|
||||
export async function getIndices(es, indexPatternCreationType, rawPattern, limit) {
|
||||
const pattern = rawPattern.trim();
|
||||
|
||||
// Searching for `*:` fails for CCS environments. The search request
|
||||
|
@ -57,7 +57,7 @@ export async function getIndices(es, rawPattern, limit) {
|
|||
size: limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -67,11 +67,18 @@ export async function getIndices(es, rawPattern, limit) {
|
|||
return [];
|
||||
}
|
||||
|
||||
return sortBy(response.aggregations.indices.buckets.map(bucket => {
|
||||
return {
|
||||
name: bucket.key
|
||||
};
|
||||
}), 'name');
|
||||
return sortBy(
|
||||
response.aggregations.indices.buckets.map(bucket => {
|
||||
return bucket.key;
|
||||
})
|
||||
.map((indexName) => {
|
||||
return {
|
||||
name: indexName,
|
||||
tags: indexPatternCreationType.getIndexTags(indexName)
|
||||
};
|
||||
})
|
||||
, 'name'
|
||||
);
|
||||
}
|
||||
catch (err) {
|
||||
const type = get(err, 'body.error.caused_by.type');
|
||||
|
|
|
@ -15,15 +15,22 @@
|
|||
delete="removePattern()"
|
||||
></kbn-management-index-header>
|
||||
|
||||
<p ng-if="indexPattern.timeFieldName" class="kuiText kuiVerticalRhythm">
|
||||
<span class="label label-success">
|
||||
<span class="kuiIcon fa-clock-o"></span>
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterHeader"
|
||||
i18n-default-message="Time Filter field name: {timeFieldName}"
|
||||
i18n-values="{ timeFieldName: indexPattern.timeFieldName }"></span>
|
||||
<p class="kuiText kuiVerticalRhythm" ng-if="::(indexPattern.timeFieldName || (indexPattern.tags && indexPattern.tags.length))">
|
||||
<span ng-if="::indexPattern.timeFieldName">
|
||||
<span class="label label-success">
|
||||
<span class="kuiIcon fa-clock-o"></span>
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterHeader"
|
||||
i18n-default-message="Time Filter field name: {timeFieldName}"
|
||||
i18n-values="{ timeFieldName: indexPattern.timeFieldName }"></span>
|
||||
</span>
|
||||
|
||||
</span>
|
||||
<span ng-repeat="tag in indexPattern.tags">
|
||||
<span class="label label-info">{{tag.name}}</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
|
||||
<p class="kuiText kuiVerticalRhythm">
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterLabel.timeFilterDetail"
|
||||
i18n-default-message="This page lists every field in the {indexPatternTitle} index and the field's associated core type as recorded by Elasticsearch. To change a field type, use the Elasticsearch"
|
||||
|
|
|
@ -27,6 +27,7 @@ import uiRoutes from 'ui/routes';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import template from './edit_index_pattern.html';
|
||||
import { FieldWildcardProvider } from 'ui/field_wildcard';
|
||||
import { IndexPatternListFactory } from 'ui/management/index_pattern_list';
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { SourceFiltersTable } from './source_filters_table';
|
||||
|
@ -135,6 +136,7 @@ function updateIndexedFieldsTable($scope, $state) {
|
|||
$scope.kbnUrl.redirectToRoute(obj, route);
|
||||
$scope.$apply();
|
||||
},
|
||||
getFieldInfo: $scope.getFieldInfo,
|
||||
}}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
|
@ -184,11 +186,14 @@ uiModules.get('apps/management')
|
|||
$scope, $location, $route, config, indexPatterns, Private, AppState, docTitle, confirmModal) {
|
||||
const $state = $scope.state = new AppState();
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
const indexPatternListProvider = Private(IndexPatternListFactory)();
|
||||
|
||||
$scope.fieldWildcardMatcher = fieldWildcardMatcher;
|
||||
$scope.editSectionsProvider = Private(IndicesEditSectionsProvider);
|
||||
$scope.kbnUrl = Private(KbnUrlProvider);
|
||||
$scope.indexPattern = $route.current.locals.indexPattern;
|
||||
$scope.indexPattern.tags = indexPatternListProvider.getIndexPatternTags($scope.indexPattern);
|
||||
$scope.getFieldInfo = indexPatternListProvider.getFieldInfo;
|
||||
docTitle.change($scope.indexPattern.title);
|
||||
|
||||
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
|
||||
|
@ -196,7 +201,7 @@ uiModules.get('apps/management')
|
|||
});
|
||||
|
||||
$scope.$watch('indexPattern.fields', function () {
|
||||
$scope.editSections = $scope.editSectionsProvider($scope.indexPattern);
|
||||
$scope.editSections = $scope.editSectionsProvider($scope.indexPattern, indexPatternListProvider);
|
||||
$scope.refreshFilters();
|
||||
$scope.fields = $scope.indexPattern.getNonScriptedFields();
|
||||
updateIndexedFieldsTable($scope, $state);
|
||||
|
|
|
@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
export function IndicesEditSectionsProvider() {
|
||||
|
||||
return function (indexPattern) {
|
||||
return function (indexPattern, indexPatternListProvider) {
|
||||
const fieldCount = _.countBy(indexPattern.fields, function (field) {
|
||||
return (field.scripted) ? 'scripted' : 'indexed';
|
||||
});
|
||||
|
@ -33,22 +33,28 @@ export function IndicesEditSectionsProvider() {
|
|||
sourceFilters: indexPattern.sourceFilters ? indexPattern.sourceFilters.length : 0,
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.fieldsHeader', { defaultMessage: 'Fields' }),
|
||||
index: 'indexedFields',
|
||||
count: fieldCount.indexed
|
||||
},
|
||||
{
|
||||
const editSections = [];
|
||||
|
||||
editSections.push({
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.fieldsHeader', { defaultMessage: 'Fields' }),
|
||||
index: 'indexedFields',
|
||||
count: fieldCount.indexed
|
||||
});
|
||||
|
||||
if(indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) {
|
||||
editSections.push({
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.scriptedHeader', { defaultMessage: 'Scripted fields' }),
|
||||
index: 'scriptedFields',
|
||||
count: fieldCount.scripted
|
||||
},
|
||||
{
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.sourceHeader', { defaultMessage: 'Source filters' }),
|
||||
index: 'sourceFilters',
|
||||
count: fieldCount.sourceFilters
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
editSections.push({
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.sourceHeader', { defaultMessage: 'Source filters' }),
|
||||
index: 'sourceFilters',
|
||||
count: fieldCount.sourceFilters
|
||||
});
|
||||
|
||||
return editSections;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = `
|
|||
"excluded": false,
|
||||
"format": undefined,
|
||||
"indexPattern": undefined,
|
||||
"info": undefined,
|
||||
"name": "Elastic",
|
||||
"routes": undefined,
|
||||
"searchable": true,
|
||||
|
@ -45,6 +46,7 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = `
|
|||
"excluded": false,
|
||||
"format": undefined,
|
||||
"indexPattern": undefined,
|
||||
"info": undefined,
|
||||
"name": "timestamp",
|
||||
"routes": undefined,
|
||||
"type": "date",
|
||||
|
@ -73,6 +75,7 @@ exports[`IndexedFieldsTable should render normally 1`] = `
|
|||
"excluded": false,
|
||||
"format": undefined,
|
||||
"indexPattern": undefined,
|
||||
"info": undefined,
|
||||
"name": "Elastic",
|
||||
"routes": undefined,
|
||||
"searchable": true,
|
||||
|
@ -82,6 +85,7 @@ exports[`IndexedFieldsTable should render normally 1`] = `
|
|||
"excluded": false,
|
||||
"format": undefined,
|
||||
"indexPattern": undefined,
|
||||
"info": undefined,
|
||||
"name": "timestamp",
|
||||
"routes": undefined,
|
||||
"type": "date",
|
||||
|
@ -91,6 +95,7 @@ exports[`IndexedFieldsTable should render normally 1`] = `
|
|||
"excluded": false,
|
||||
"format": undefined,
|
||||
"indexPattern": undefined,
|
||||
"info": undefined,
|
||||
"name": "conflictingField",
|
||||
"routes": undefined,
|
||||
"type": "conflict",
|
||||
|
|
|
@ -97,16 +97,19 @@ exports[`Table should render normally 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"displayName": "Elastic",
|
||||
"info": Object {},
|
||||
"name": "Elastic",
|
||||
"searchable": true,
|
||||
},
|
||||
Object {
|
||||
"displayName": "timestamp",
|
||||
"info": Object {},
|
||||
"name": "timestamp",
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"displayName": "conflictingField",
|
||||
"info": Object {},
|
||||
"name": "conflictingField",
|
||||
"type": "conflict",
|
||||
},
|
||||
|
|
|
@ -28,9 +28,9 @@ const indexPattern = {
|
|||
};
|
||||
|
||||
const items = [
|
||||
{ name: 'Elastic', displayName: 'Elastic', searchable: true },
|
||||
{ name: 'timestamp', displayName: 'timestamp', type: 'date' },
|
||||
{ name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' },
|
||||
{ name: 'Elastic', displayName: 'Elastic', searchable: true, info: {} },
|
||||
{ name: 'timestamp', displayName: 'timestamp', type: 'date', info: {} },
|
||||
{ name: 'conflictingField', displayName: 'conflictingField', type: 'conflict', info: {} },
|
||||
];
|
||||
|
||||
describe('Table', () => {
|
||||
|
@ -55,7 +55,7 @@ describe('Table', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const tableCell = shallow(component.prop('columns')[0].render('Elastic'));
|
||||
const tableCell = shallow(component.prop('columns')[0].render('Elastic', items[0]));
|
||||
expect(tableCell).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -68,7 +68,7 @@ describe('Table', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const tableCell = shallow(component.prop('columns')[0].render('timestamp', true));
|
||||
const tableCell = shallow(component.prop('columns')[0].render('timestamp', items[1]));
|
||||
expect(tableCell).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -94,7 +94,7 @@ describe('Table', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const tableCell = shallow(component.prop('columns')[3].render(false));
|
||||
const tableCell = shallow(component.prop('columns')[3].render(false, items[2]));
|
||||
expect(tableCell).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -39,13 +39,19 @@ export class TableComponent extends PureComponent {
|
|||
return value ? <EuiIcon type="dot" color="secondary" aria-label={label}/> : <span/>;
|
||||
}
|
||||
|
||||
renderFieldName(name, isTimeField) {
|
||||
renderFieldName(name, field) {
|
||||
const { indexPattern } = this.props;
|
||||
const { intl } = this.props;
|
||||
const label = intl.formatMessage({
|
||||
id: 'kbn.management.editIndexPattern.fields.table.primaryTimeAria',
|
||||
|
||||
const infoLabel = intl.formatMessage({
|
||||
id: 'kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel',
|
||||
defaultMessage: 'Additional field information'
|
||||
});
|
||||
const timeLabel = intl.formatMessage({
|
||||
id: 'kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel',
|
||||
defaultMessage: 'Primary time field'
|
||||
});
|
||||
const content = intl.formatMessage({
|
||||
const timeContent = intl.formatMessage({
|
||||
id: 'kbn.management.editIndexPattern.fields.table.primaryTimeTooltip',
|
||||
defaultMessage: 'This field represents the time that events occurred.'
|
||||
});
|
||||
|
@ -53,17 +59,28 @@ export class TableComponent extends PureComponent {
|
|||
return (
|
||||
<span>
|
||||
{name}
|
||||
{isTimeField ? (
|
||||
{field.info && field.info.length ? (
|
||||
<span>
|
||||
|
||||
<EuiIconTip
|
||||
type="questionInCircle"
|
||||
color="primary"
|
||||
aria-label={infoLabel}
|
||||
content={field.info.map((info, i) => <div key={i}>{info}</div>)}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
{indexPattern.timeFieldName === name ? (
|
||||
<span>
|
||||
|
||||
<EuiIconTip
|
||||
type="clock"
|
||||
color="primary"
|
||||
aria-label={label}
|
||||
content={content}
|
||||
aria-label={timeLabel}
|
||||
content={timeContent}
|
||||
/>
|
||||
</span>
|
||||
) : ''}
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -98,7 +115,7 @@ export class TableComponent extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { indexPattern, items, editField, intl } = this.props;
|
||||
const { items, editField, intl } = this.props;
|
||||
|
||||
const pagination = {
|
||||
initialPageSize: 10,
|
||||
|
@ -111,8 +128,8 @@ export class TableComponent extends PureComponent {
|
|||
name: intl.formatMessage({ id: 'kbn.management.editIndexPattern.fields.table.nameHeader', defaultMessage: 'Name' }),
|
||||
dataType: 'string',
|
||||
sortable: true,
|
||||
render: (value) => {
|
||||
return this.renderFieldName(value, indexPattern.timeFieldName === value);
|
||||
render: (value, field) => {
|
||||
return this.renderFieldName(value, field);
|
||||
},
|
||||
width: '38%',
|
||||
'data-test-subj': 'indexedFieldName',
|
||||
|
|
|
@ -36,6 +36,7 @@ export class IndexedFieldsTable extends Component {
|
|||
indexedFieldTypeFilter: PropTypes.string,
|
||||
helpers: PropTypes.shape({
|
||||
redirectToRoute: PropTypes.func.isRequired,
|
||||
getFieldInfo: PropTypes.func,
|
||||
}),
|
||||
fieldWildcardMatcher: PropTypes.func.isRequired,
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ export class IndexedFieldsTable extends Component {
|
|||
}
|
||||
|
||||
mapFields(fields) {
|
||||
const { indexPattern, fieldWildcardMatcher } = this.props;
|
||||
const { indexPattern, fieldWildcardMatcher, helpers } = this.props;
|
||||
const sourceFilters = indexPattern.sourceFilters && indexPattern.sourceFilters.map(f => f.value);
|
||||
const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters || []);
|
||||
|
||||
|
@ -70,6 +71,7 @@ export class IndexedFieldsTable extends Component {
|
|||
indexPattern: field.indexPattern,
|
||||
format: getFieldFormat(indexPattern, field.name),
|
||||
excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false,
|
||||
info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field.name),
|
||||
};
|
||||
}) || [];
|
||||
}
|
||||
|
|
|
@ -1,52 +1,7 @@
|
|||
<div class="euiPage">
|
||||
<div class="col-md-2 sidebar-container" role="region" aria-label="{{::'kbn.management.editIndexPatternAria' | i18n: { defaultMessage: 'Index patterns' } }}">
|
||||
<div class="sidebar-list">
|
||||
<div class="sidebar-item-title full-title">
|
||||
<h5 data-test-subj="createIndexPatternParent">
|
||||
<a
|
||||
ng-if="editingId"
|
||||
href="#/management/kibana/index"
|
||||
class="kuiButton kuiButton--primary kuiButton--small"
|
||||
data-test-subj="createIndexPatternButton"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiIcon fa-plus"></span>
|
||||
<span i18n-id="kbn.management.editIndexPattern.createIndexButton"
|
||||
i18n-default-message="Create Index Pattern"></span>
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
<li
|
||||
ng-if="!defaultIndex"
|
||||
class="sidebar-item"
|
||||
>
|
||||
<div class="sidebar-item-title full-title">
|
||||
<div class="euiText euiText--extraSmall">
|
||||
<div class="euiBadge euiBadge--warning"
|
||||
i18n-id="kbn.management.editIndexPattern.createIndex.warningHeader"
|
||||
i18n-default-message="Warning"></div>
|
||||
<p i18n-id="kbn.management.editIndexPattern.createIndex.warningLabel"
|
||||
i18n-default-message="No default index pattern. You must select or create one to continue."></p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li
|
||||
ng-repeat="pattern in indexPatternList | orderBy:['-default','title'] track by pattern.id"
|
||||
class="sidebar-item"
|
||||
>
|
||||
<a
|
||||
href="{{::pattern.url}}"
|
||||
class="euiLink euiLink--primary show"
|
||||
data-test-subj="indexPatternLink"
|
||||
>
|
||||
<div class="{{::pattern.class}}">
|
||||
<i aria-hidden="true" ng-if="pattern.default" class="fa fa-star"></i>
|
||||
<span ng-bind="::pattern.title"></span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="indexPatternListReact" role="region" aria-label="{{'kbn.management.editIndexPatternLiveRegionAriaLabel' | i18n: { defaultMessage: 'Index patterns' } }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
|
||||
import { management } from 'ui/management';
|
||||
import { IndexPatternListFactory } from 'ui/management/index_pattern_list';
|
||||
import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation';
|
||||
import './create_index_pattern_wizard';
|
||||
import './edit_index_pattern';
|
||||
import uiRoutes from 'ui/routes';
|
||||
|
@ -27,13 +29,45 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
|||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { IndexPatternList } from './index_pattern_list';
|
||||
|
||||
const INDEX_PATTERN_LIST_DOM_ELEMENT_ID = 'indexPatternListReact';
|
||||
|
||||
export function updateIndexPatternList(
|
||||
$scope,
|
||||
indexPatternCreationOptions,
|
||||
defaultIndex,
|
||||
indexPatterns,
|
||||
) {
|
||||
const node = document.getElementById(INDEX_PATTERN_LIST_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<IndexPatternList
|
||||
indexPatternCreationOptions={indexPatternCreationOptions}
|
||||
defaultIndex={defaultIndex}
|
||||
indexPatterns={indexPatterns}
|
||||
/>,
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
export const destroyIndexPatternList = () => {
|
||||
const node = document.getElementById(INDEX_PATTERN_LIST_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
};
|
||||
|
||||
const indexPatternsResolutions = {
|
||||
indexPatterns: function (Private) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
fields: ['title', 'type'],
|
||||
perPage: 10000
|
||||
}).then(response => response.savedObjects);
|
||||
}
|
||||
|
@ -52,28 +86,55 @@ uiRoutes
|
|||
|
||||
// wrapper directive, which sets some global stuff up like the left nav
|
||||
uiModules.get('apps/management')
|
||||
.directive('kbnManagementIndices', function ($route, config, kbnUrl) {
|
||||
.directive('kbnManagementIndices', function ($route, config, kbnUrl, Private) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
template: indexTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.editingId = $route.current.params.indexPatternId;
|
||||
config.bindToScope($scope, 'defaultIndex');
|
||||
link: async function ($scope) {
|
||||
const indexPatternListProvider = Private(IndexPatternListFactory)();
|
||||
const indexPatternCreationProvider = Private(IndexPatternCreationFactory)();
|
||||
const indexPatternCreationOptions = await indexPatternCreationProvider.getIndexPatternCreationOptions((url) => {
|
||||
$scope.$evalAsync(() => kbnUrl.change(url));
|
||||
});
|
||||
|
||||
$scope.$watch('defaultIndex', function () {
|
||||
const renderList = () => {
|
||||
$scope.indexPatternList = $route.current.locals.indexPatterns.map(pattern => {
|
||||
const id = pattern.id;
|
||||
const tags = indexPatternListProvider.getIndexPatternTags(pattern, $scope.defaultIndex === id);
|
||||
|
||||
return {
|
||||
id: id,
|
||||
title: pattern.get('title'),
|
||||
url: kbnUrl.eval('#/management/kibana/indices/{{id}}', { id: id }),
|
||||
class: 'sidebar-item-title ' + ($scope.editingId === id ? 'active' : ''),
|
||||
default: $scope.defaultIndex === id
|
||||
active: $scope.editingId === id,
|
||||
default: $scope.defaultIndex === id,
|
||||
tag: tags && tags.length ? tags[0] : null,
|
||||
};
|
||||
});
|
||||
});
|
||||
}).sort((a, b) => {
|
||||
if(a.default) {
|
||||
return -1;
|
||||
}
|
||||
if(b.default) {
|
||||
return 1;
|
||||
}
|
||||
if(a.title < b.title) {
|
||||
return -1;
|
||||
}
|
||||
if(a.title > b.title) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}) || [];
|
||||
|
||||
updateIndexPatternList($scope, indexPatternCreationOptions, $scope.defaultIndex, $scope.indexPatternList);
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', destroyIndexPatternList);
|
||||
$scope.editingId = $route.current.params.indexPatternId;
|
||||
$scope.$watch('defaultIndex', () => renderList());
|
||||
config.bindToScope($scope, 'defaultIndex');
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#indexPatternListReact {
|
||||
.indexPatternList__headerWrapper {
|
||||
padding-bottom: $euiSizeS;
|
||||
}
|
||||
|
||||
.euiButtonEmpty__content {
|
||||
justify-content: left;
|
||||
padding: 0;
|
||||
|
||||
span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiPopover,
|
||||
EuiContextMenuPanel,
|
||||
EuiContextMenuItem,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export class CreateButton extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
text: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
})),
|
||||
}
|
||||
|
||||
togglePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
|
||||
closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { options, children } = this.props;
|
||||
const { isPopoverOpen } = this.state;
|
||||
|
||||
if(!options || !options.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(options.length === 1) {
|
||||
return (
|
||||
<EuiButton
|
||||
data-test-subj="createIndexPatternButton"
|
||||
fill={true}
|
||||
size={'s'}
|
||||
onClick={options[0].onClick}
|
||||
>
|
||||
{children}
|
||||
</EuiButton>
|
||||
);
|
||||
}
|
||||
|
||||
const button = (
|
||||
<EuiButton
|
||||
data-test-subj="createIndexPatternButton"
|
||||
fill={true}
|
||||
size="s"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={this.togglePopover}
|
||||
>
|
||||
{children}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
if(options.length > 1) {
|
||||
return (
|
||||
<EuiPopover
|
||||
id="singlePanel"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
items={options.map(option => {
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key={option.text}
|
||||
onClick={option.onClick}
|
||||
data-test-subj={option.testSubj}
|
||||
>
|
||||
<EuiDescriptionList style={{ whiteSpace: 'nowrap' }}>
|
||||
<EuiDescriptionListTitle>
|
||||
{option.text}
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{option.description}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { CreateButton } from './create_button';
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { CreateButton } from '../create_button';
|
||||
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const Header = ({
|
||||
indexPatternCreationOptions
|
||||
}) => (
|
||||
<I18nProvider>
|
||||
<CreateButton options={indexPatternCreationOptions}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.indexPatternList.header.createIndexPatternButtonLabel"
|
||||
defaultMessage="Create index pattern"
|
||||
/>
|
||||
</CreateButton>
|
||||
</I18nProvider>
|
||||
);
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { Header } from './header';
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { List } from './list';
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiBadge,
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export class List extends Component {
|
||||
static propTypes = {
|
||||
indexPatterns: PropTypes.array,
|
||||
defaultIndex: PropTypes.string,
|
||||
}
|
||||
|
||||
renderList() {
|
||||
const { indexPatterns } = this.props;
|
||||
return indexPatterns && indexPatterns.length ? (
|
||||
<div>
|
||||
{
|
||||
indexPatterns.map(pattern => {
|
||||
return (
|
||||
<div key={pattern.id} >
|
||||
<EuiButtonEmpty size="xs" href={pattern.url} data-test-subj="indexPatternLink">
|
||||
{pattern.default ? <Fragment><i aria-label="Default index pattern" className="fa fa-star" /> </Fragment> : ''}
|
||||
{pattern.active ? <strong>{pattern.title}</strong> : pattern.title} {pattern.tag ? (
|
||||
<Fragment key={pattern.tag.key}>
|
||||
{<EuiBadge color={pattern.tag.color || 'primary'}>{pattern.tag.name}</EuiBadge> }
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer size="xs"/>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
renderNoDefaultMessage() {
|
||||
const { defaultIndex } = this.props;
|
||||
return !defaultIndex ? (
|
||||
<div className="indexPatternList__headerWrapper">
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="s"
|
||||
iconType="alert"
|
||||
title="No default index pattern. You must select or create one to continue."
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderNoDefaultMessage()}
|
||||
{this.renderList()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { IndexPatternList } from './index_pattern_list';
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
|
||||
import { Header } from './components/header';
|
||||
import { List } from './components/list';
|
||||
|
||||
export const IndexPatternList = ({
|
||||
indexPatternCreationOptions,
|
||||
defaultIndex,
|
||||
indexPatterns
|
||||
}) => (
|
||||
<Fragment>
|
||||
<div className="indexPatternList__headerWrapper" data-test-subj="createIndexPatternParent">
|
||||
<Header indexPatternCreationOptions={indexPatternCreationOptions} />
|
||||
</div>
|
||||
<List indexPatterns={indexPatterns} defaultIndex={defaultIndex} />
|
||||
</Fragment>
|
||||
);
|
|
@ -53,15 +53,27 @@ function addJsonFieldToIndexPattern(target, sourceString, fieldName, indexName)
|
|||
async function importIndexPattern(doc, indexPatterns, overwriteAll) {
|
||||
// TODO: consolidate this is the code in create_index_pattern_wizard.js
|
||||
const emptyPattern = await indexPatterns.get();
|
||||
const { title, timeFieldName, fields, fieldFormatMap, sourceFilters } = doc._source;
|
||||
const {
|
||||
title,
|
||||
timeFieldName,
|
||||
fields,
|
||||
fieldFormatMap,
|
||||
sourceFilters,
|
||||
type,
|
||||
typeMeta,
|
||||
} = doc._source;
|
||||
const importedIndexPattern = {
|
||||
id: doc._id,
|
||||
title,
|
||||
timeFieldName
|
||||
timeFieldName,
|
||||
};
|
||||
if (type) {
|
||||
importedIndexPattern.type = type;
|
||||
}
|
||||
addJsonFieldToIndexPattern(importedIndexPattern, fields, 'fields', title);
|
||||
addJsonFieldToIndexPattern(importedIndexPattern, fieldFormatMap, 'fieldFormatMap', title);
|
||||
addJsonFieldToIndexPattern(importedIndexPattern, sourceFilters, 'sourceFilters', title);
|
||||
addJsonFieldToIndexPattern(importedIndexPattern, typeMeta, 'typeMeta', title);
|
||||
Object.assign(emptyPattern, importedIndexPattern);
|
||||
|
||||
const newId = await emptyPattern.create(true, !overwriteAll);
|
||||
|
@ -128,14 +140,11 @@ export async function resolveIndexPatternConflicts(
|
|||
|
||||
export async function saveObjects(objs, overwriteAll) {
|
||||
let importCount = 0;
|
||||
await awaitEachItemInParallel(
|
||||
objs,
|
||||
async obj => {
|
||||
if (await saveObject(obj, overwriteAll)) {
|
||||
importCount++;
|
||||
}
|
||||
await awaitEachItemInParallel(objs, async obj => {
|
||||
if (await saveObject(obj, overwriteAll)) {
|
||||
importCount++;
|
||||
}
|
||||
);
|
||||
});
|
||||
return importCount;
|
||||
}
|
||||
|
||||
|
@ -143,12 +152,7 @@ export async function saveObject(obj, overwriteAll) {
|
|||
return await obj.save({ confirmOverwrite: !overwriteAll });
|
||||
}
|
||||
|
||||
export async function resolveSavedSearches(
|
||||
savedSearches,
|
||||
services,
|
||||
indexPatterns,
|
||||
overwriteAll
|
||||
) {
|
||||
export async function resolveSavedSearches(savedSearches, services, indexPatterns, overwriteAll) {
|
||||
let importCount = 0;
|
||||
await awaitEachItemInParallel(savedSearches, async searchDoc => {
|
||||
const obj = await getSavedObject(searchDoc, services);
|
||||
|
@ -163,12 +167,7 @@ export async function resolveSavedSearches(
|
|||
return importCount;
|
||||
}
|
||||
|
||||
export async function resolveSavedObjects(
|
||||
savedObjects,
|
||||
overwriteAll,
|
||||
services,
|
||||
indexPatterns
|
||||
) {
|
||||
export async function resolveSavedObjects(savedObjects, overwriteAll, services, indexPatterns) {
|
||||
const docTypes = groupByType(savedObjects);
|
||||
|
||||
// Keep track of how many we actually import because the user
|
||||
|
@ -177,19 +176,20 @@ export async function resolveSavedObjects(
|
|||
// Keep a record of any objects which fail to import for unknown reasons.
|
||||
const failedImports = [];
|
||||
// Start with the index patterns since everything is dependent on them
|
||||
await awaitEachItemInParallel(
|
||||
docTypes.indexPatterns,
|
||||
async indexPatternDoc => {
|
||||
try {
|
||||
const importedIndexPatternId = await importIndexPattern(indexPatternDoc, indexPatterns, overwriteAll);
|
||||
if (importedIndexPatternId) {
|
||||
importedObjectCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
failedImports.push({ indexPatternDoc, error });
|
||||
await awaitEachItemInParallel(docTypes.indexPatterns, async indexPatternDoc => {
|
||||
try {
|
||||
const importedIndexPatternId = await importIndexPattern(
|
||||
indexPatternDoc,
|
||||
indexPatterns,
|
||||
overwriteAll
|
||||
);
|
||||
if (importedIndexPatternId) {
|
||||
importedObjectCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
failedImports.push({ indexPatternDoc, error });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// We want to do the same for saved searches, but we want to keep them separate because they need
|
||||
// to be applied _first_ because other saved objects can be dependent on those saved searches existing
|
||||
|
|
|
@ -67,14 +67,16 @@ export function ArgValueSuggestionsProvider(Private, indexPatterns) {
|
|||
const search = partial ? `${partial}*` : '*';
|
||||
const resp = await savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
fields: ['title', 'type'],
|
||||
search: `${search}`,
|
||||
search_fields: ['title'],
|
||||
perPage: 25
|
||||
});
|
||||
return resp.savedObjects.map(savedObject => {
|
||||
return { name: savedObject.attributes.title };
|
||||
});
|
||||
return resp.savedObjects
|
||||
.filter(savedObject => !savedObject.get('type'))
|
||||
.map(savedObject => {
|
||||
return { name: savedObject.attributes.title };
|
||||
});
|
||||
},
|
||||
metric: async function (partial, functionArgs) {
|
||||
if (!partial || !partial.includes(':')) {
|
||||
|
|
|
@ -27,7 +27,7 @@ export const createFieldsForWildcardRoute = pre => ({
|
|||
validate: {
|
||||
query: Joi.object().keys({
|
||||
pattern: Joi.string().required(),
|
||||
meta_fields: Joi.array().items(Joi.string()).default([])
|
||||
meta_fields: Joi.array().items(Joi.string()).default([]),
|
||||
}).default()
|
||||
},
|
||||
handler(req, reply) {
|
||||
|
|
|
@ -32,7 +32,7 @@ export class IndexPatternsService {
|
|||
* Get a list of field objects for an index pattern that may contain wildcards
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* @property {String} options.pattern The moment compatible time pattern
|
||||
* @property {String} options.pattern The index pattern
|
||||
* @property {Number} options.metaFields The list of underscore prefixed fields that should
|
||||
* be left in the field list (all others are removed).
|
||||
* @return {Promise<Array<Fields>>}
|
||||
|
|
|
@ -104,6 +104,11 @@ export const dateHistogramBucketAgg = new BucketAggType({
|
|||
default: null,
|
||||
write: _.noop,
|
||||
},
|
||||
{
|
||||
name: 'useNormalizedEsInterval',
|
||||
default: true,
|
||||
write: _.noop,
|
||||
},
|
||||
{
|
||||
name: 'interval',
|
||||
type: 'optioned',
|
||||
|
@ -124,8 +129,8 @@ export const dateHistogramBucketAgg = new BucketAggType({
|
|||
write: function (agg, output, aggs) {
|
||||
setBounds(agg, true);
|
||||
agg.buckets.setInterval(getInterval(agg));
|
||||
|
||||
const interval = agg.buckets.getInterval();
|
||||
const { useNormalizedEsInterval } = agg.params;
|
||||
const interval = agg.buckets.getInterval(useNormalizedEsInterval);
|
||||
output.bucketInterval = interval;
|
||||
output.params.interval = interval.expression;
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import '../../validate_date_interval';
|
||||
import chrome from 'ui/chrome';
|
||||
import { BucketAggType } from './_bucket_agg_type';
|
||||
|
@ -95,6 +97,12 @@ export const histogramBucketAgg = new BucketAggType({
|
|||
min: _.get(resp, 'aggregations.minAgg.value'),
|
||||
max: _.get(resp, 'aggregations.maxAgg.value')
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toastNotifications.addWarning(i18n.translate('common.ui.aggTypes.histogram.missingMaxMinValuesWarning', {
|
||||
// eslint-disable-next-line max-len
|
||||
defaultMessage: 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.'
|
||||
}));
|
||||
});
|
||||
},
|
||||
write: function (aggConfig, output) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<div class="visEditorAgg__formRow--flex">
|
||||
<div ng-if="agg.type.params.byName.order" class="form-group">
|
||||
<div ng-if="agg.type.params.byName.order && aggParam.options" class="form-group">
|
||||
<label for="visEditorOrderByOrder{{agg.id}}">Order</label>
|
||||
<select
|
||||
id="visEditorOrderByOrder{{agg.id}}"
|
||||
|
|
|
@ -49,6 +49,7 @@ describe('callClient', () => {
|
|||
_flatten: () => ({}),
|
||||
requestIsStopped: () => {},
|
||||
getField: () => 'indexPattern',
|
||||
getPreferredSearchStrategyId: () => undefined,
|
||||
...overrideSource
|
||||
};
|
||||
|
||||
|
@ -134,7 +135,7 @@ describe('callClient', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it(`still resolves the promise in spite of the failure`, () => {
|
||||
it(`still bubbles up the failure`, () => {
|
||||
const searchRequestFail = createSearchRequest('fail', {
|
||||
source: {
|
||||
getField: () => ({ type: 'fail' }),
|
||||
|
@ -144,22 +145,7 @@ describe('callClient', () => {
|
|||
searchRequests = [ searchRequestFail ];
|
||||
|
||||
return callClient(searchRequests).then(results => {
|
||||
expect(results).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the errorHandler provided to the searchRequest`, () => {
|
||||
const errorHandlerSpy = sinon.spy();
|
||||
const searchRequestFail = createSearchRequest('fail', {
|
||||
source: {
|
||||
getField: () => ({ type: 'fail' }),
|
||||
},
|
||||
}, errorHandlerSpy);
|
||||
|
||||
searchRequests = [ searchRequestFail ];
|
||||
|
||||
return callClient(searchRequests).then(() => {
|
||||
sinon.assert.calledOnce(errorHandlerSpy);
|
||||
expect(results).to.eql([{ error: new Error('Search failed') }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -185,20 +171,6 @@ describe('callClient', () => {
|
|||
expect(whenAbortedSpy.callCount).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls searchRequest.handleFailure() with the SearchError that's thrown`, async () => {
|
||||
esShouldError = true;
|
||||
const searchRequest = createSearchRequest(1);
|
||||
|
||||
const handleFailureSpy = sinon.spy();
|
||||
searchRequest.handleFailure = handleFailureSpy;
|
||||
searchRequests = [ searchRequest ];
|
||||
|
||||
return callClient(searchRequests).then(() => {
|
||||
sinon.assert.calledOnce(handleFailureSpy);
|
||||
expect(handleFailureSpy.args[0][0].name).to.be('SearchError');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('aborting at different points in the request lifecycle:', () => {
|
||||
|
@ -348,18 +320,24 @@ describe('callClient', () => {
|
|||
searchRequestA = createSearchRequest('a', {
|
||||
source: {
|
||||
getField: () => ({ type: 'a' }),
|
||||
getSearchStrategyForSearchRequest: () => {},
|
||||
getPreferredSearchStrategyId: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
searchRequestB = createSearchRequest('b', {
|
||||
source: {
|
||||
getField: () => ({ type: 'b' }),
|
||||
getSearchStrategyForSearchRequest: () => {},
|
||||
getPreferredSearchStrategyId: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
searchRequestA2 = createSearchRequest('a2', {
|
||||
source: {
|
||||
getField: () => ({ type: 'a' }),
|
||||
getSearchStrategyForSearchRequest: () => {},
|
||||
getPreferredSearchStrategyId: () => {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -161,7 +161,7 @@ export function CallClientProvider(Private, Promise, es, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
const segregatedResponses = await Promise.all(abortableSearches.map(({ searching }) => searching));
|
||||
const segregatedResponses = await Promise.all(abortableSearches.map(({ searching }) => searching.catch((e) => [{ error: e }])));
|
||||
|
||||
// Assigning searchRequests to strategies means that the responses come back in a different
|
||||
// order than the original searchRequests. So we'll put them back in order so that we can
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import { toastNotifications } from '../../notify';
|
||||
import { RequestFailure } from '../../errors';
|
||||
import { RequestStatus } from './req_status';
|
||||
import { SearchError } from '../search_strategy/search_error';
|
||||
|
||||
export function CallResponseHandlersProvider(Private, Promise) {
|
||||
const ABORTED = RequestStatus.ABORTED;
|
||||
|
@ -58,7 +59,7 @@ export function CallResponseHandlersProvider(Private, Promise) {
|
|||
if (searchRequest.filterError(response)) {
|
||||
return progress();
|
||||
} else {
|
||||
return searchRequest.handleFailure(new RequestFailure(null, response));
|
||||
return searchRequest.handleFailure(response.error instanceof SearchError ? response.error : new RequestFailure(null, response));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,11 @@ export function FetchNowProvider(Private, Promise) {
|
|||
return startRequests(searchRequests)
|
||||
.then(function () {
|
||||
replaceAbortedRequests();
|
||||
return callClient(searchRequests);
|
||||
return callClient(searchRequests)
|
||||
.catch(() => {
|
||||
// Silently swallow errors that result from search requests so the consumer can surface
|
||||
// them as notifications instead of courier forcing fatal errors.
|
||||
});
|
||||
})
|
||||
.then(function (responses) {
|
||||
replaceAbortedRequests();
|
||||
|
|
|
@ -123,7 +123,8 @@ export function SearchRequestProvider(Promise) {
|
|||
|
||||
handleFailure(error) {
|
||||
this.success = false;
|
||||
this.resp = error && error.resp;
|
||||
this.resp = error;
|
||||
this.resp = (error && error.resp) || error;
|
||||
return this.errorHandler(this, error);
|
||||
}
|
||||
|
||||
|
|
1
src/ui/public/courier/index.d.ts
vendored
1
src/ui/public/courier/index.d.ts
vendored
|
@ -18,3 +18,4 @@
|
|||
*/
|
||||
|
||||
export * from './search_source';
|
||||
export * from './search_strategy';
|
||||
|
|
|
@ -34,4 +34,5 @@ export {
|
|||
hasSearchStategyForIndexPattern,
|
||||
isDefaultTypeIndexPattern,
|
||||
SearchError,
|
||||
getSearchErrorType,
|
||||
} from './search_strategy';
|
||||
|
|
|
@ -130,6 +130,7 @@ export function SearchSourceProvider(Promise, Private, config) {
|
|||
constructor(initialFields) {
|
||||
this._id = _.uniqueId('data_source');
|
||||
|
||||
this._searchStrategyId = undefined;
|
||||
this._fields = parseInitialFields(initialFields);
|
||||
this._parent = undefined;
|
||||
|
||||
|
@ -164,6 +165,14 @@ export function SearchSourceProvider(Promise, Private, config) {
|
|||
* PUBLIC API
|
||||
*****/
|
||||
|
||||
setPreferredSearchStrategyId(searchStrategyId) {
|
||||
this._searchStrategyId = searchStrategyId;
|
||||
}
|
||||
|
||||
getPreferredSearchStrategyId() {
|
||||
return this._searchStrategyId;
|
||||
}
|
||||
|
||||
setFields(newFields) {
|
||||
this._fields = newFields;
|
||||
return this;
|
||||
|
|
|
@ -33,24 +33,23 @@ function getAllFetchParams(searchRequests, Promise) {
|
|||
}
|
||||
|
||||
async function serializeAllFetchParams(fetchParams, searchRequests, serializeFetchParams) {
|
||||
const searcRequestsWithFetchParams = [];
|
||||
const searchRequestsWithFetchParams = [];
|
||||
const failedSearchRequests = [];
|
||||
|
||||
// Gather the fetch param responses from all the successful requests.
|
||||
fetchParams.forEach((result, index) => {
|
||||
if (result.resolved) {
|
||||
searcRequestsWithFetchParams.push(result.resolved);
|
||||
searchRequestsWithFetchParams.push(result.resolved);
|
||||
} else {
|
||||
const searchRequest = searchRequests[index];
|
||||
|
||||
// TODO: All strategies will need to implement this.
|
||||
searchRequest.handleFailure(result.rejected);
|
||||
failedSearchRequests.push(searchRequest);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
serializedFetchParams: await serializeFetchParams(searcRequestsWithFetchParams),
|
||||
serializedFetchParams: await serializeFetchParams(searchRequestsWithFetchParams),
|
||||
failedSearchRequests,
|
||||
};
|
||||
}
|
||||
|
|
20
src/ui/public/courier/search_strategy/index.d.ts
vendored
Normal file
20
src/ui/public/courier/search_strategy/index.d.ts
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { SearchError, getSearchErrorType } from './search_error';
|
|
@ -25,4 +25,4 @@ export {
|
|||
|
||||
export { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
|
||||
|
||||
export { SearchError } from './search_error';
|
||||
export { SearchError, getSearchErrorType } from './search_error';
|
||||
|
|
|
@ -19,5 +19,5 @@
|
|||
|
||||
export const isDefaultTypeIndexPattern = indexPattern => {
|
||||
// Default index patterns don't have `type` defined.
|
||||
return indexPattern.type == null;
|
||||
return !indexPattern.type;
|
||||
};
|
||||
|
|
21
src/ui/public/courier/search_strategy/search_error.d.ts
vendored
Normal file
21
src/ui/public/courier/search_strategy/search_error.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type SearchError = any;
|
||||
export type getSearchErrorType = any;
|
|
@ -18,13 +18,14 @@
|
|||
*/
|
||||
|
||||
export class SearchError extends Error {
|
||||
constructor({ status, title, message, path }) {
|
||||
constructor({ status, title, message, path, type }) {
|
||||
super(message);
|
||||
this.name = 'SearchError';
|
||||
this.status = status;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
|
||||
// captureStackTrace is only available in the V8 engine, so any browser using
|
||||
// a different JS engine won't have access to this method.
|
||||
|
@ -37,3 +38,10 @@ export class SearchError extends Error {
|
|||
Object.setPrototypeOf(this, SearchError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export function getSearchErrorType({ message }) {
|
||||
const msg = message.toLowerCase();
|
||||
if(msg.indexOf('unsupported query') > -1) {
|
||||
return 'UNSUPPORTED_QUERY';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,31 @@ export const addSearchStrategy = searchStrategy => {
|
|||
searchStrategies.push(searchStrategy);
|
||||
};
|
||||
|
||||
const getSearchStrategy = indexPattern => {
|
||||
const getSearchStrategyByViability = indexPattern => {
|
||||
return searchStrategies.find(searchStrategy => {
|
||||
return searchStrategy.isViable(indexPattern);
|
||||
});
|
||||
};
|
||||
|
||||
const getSearchStrategyById = searchStrategyId => {
|
||||
return searchStrategies.find(searchStrategy => {
|
||||
return searchStrategy.id === searchStrategyId;
|
||||
});
|
||||
};
|
||||
|
||||
const getSearchStrategyForSearchRequest = searchRequest => {
|
||||
// Allow the searchSource to declare the correct strategy with which to execute its searches.
|
||||
const preferredSearchStrategyId = searchRequest.source.getPreferredSearchStrategyId();
|
||||
if (preferredSearchStrategyId != null) {
|
||||
return getSearchStrategyById(preferredSearchStrategyId);
|
||||
}
|
||||
|
||||
// Otherwise try to match it to a strategy.
|
||||
const indexPattern = searchRequest.source.getField('index');
|
||||
return getSearchStrategyByViability(indexPattern);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Build a structure like this:
|
||||
*
|
||||
|
@ -52,9 +71,7 @@ export const assignSearchRequestsToSearchStrategies = searchRequests => {
|
|||
const searchStrategyById = {};
|
||||
|
||||
searchRequests.forEach(searchRequest => {
|
||||
const indexPattern = searchRequest.source.getField('index');
|
||||
const matchingSearchStrategy = getSearchStrategy(indexPattern);
|
||||
|
||||
const matchingSearchStrategy = getSearchStrategyForSearchRequest(searchRequest);
|
||||
const { id } = matchingSearchStrategy;
|
||||
let searchStrategyWithRequest = searchStrategyById[id];
|
||||
|
||||
|
@ -76,5 +93,5 @@ export const assignSearchRequestsToSearchStrategies = searchRequests => {
|
|||
};
|
||||
|
||||
export const hasSearchStategyForIndexPattern = indexPattern => {
|
||||
return Boolean(getSearchStrategy(indexPattern));
|
||||
return Boolean(getSearchStrategyByViability(indexPattern));
|
||||
};
|
||||
|
|
|
@ -45,22 +45,22 @@ describe('SearchStrategyRegistry', () => {
|
|||
|
||||
const searchRequest0 = {
|
||||
id: 0,
|
||||
source: { getField: () => 'b' },
|
||||
source: { getField: () => 'b', getPreferredSearchStrategyId: () => {} },
|
||||
};
|
||||
|
||||
const searchRequest1 = {
|
||||
id: 1,
|
||||
source: { getField: () => 'a' },
|
||||
source: { getField: () => 'a', getPreferredSearchStrategyId: () => {} },
|
||||
};
|
||||
|
||||
const searchRequest2 = {
|
||||
id: 2,
|
||||
source: { getField: () => 'a' },
|
||||
source: { getField: () => 'a', getPreferredSearchStrategyId: () => {} },
|
||||
};
|
||||
|
||||
const searchRequest3 = {
|
||||
id: 3,
|
||||
source: { getField: () => 'b' },
|
||||
source: { getField: () => 'b', getPreferredSearchStrategyId: () => {} },
|
||||
};
|
||||
|
||||
const searchRequests = [ searchRequest0, searchRequest1, searchRequest2, searchRequest3];
|
||||
|
|
|
@ -79,7 +79,9 @@ export function IndexPatternProvider(Private, config, Promise, confirmModalPromi
|
|||
_deserialize(map = '{}') {
|
||||
return _.mapValues(angular.fromJson(map), deserializeFieldFormatMap);
|
||||
}
|
||||
}
|
||||
},
|
||||
type: 'keyword',
|
||||
typeMeta: 'json',
|
||||
});
|
||||
|
||||
function serializeFieldFormatMap(flat, format, field) {
|
||||
|
|
|
@ -25,7 +25,10 @@ export function createFieldsFetcher(apiClient, config) {
|
|||
return this.fetchForTimePattern(indexPattern.title, interval);
|
||||
}
|
||||
|
||||
return this.fetchForWildcard(indexPattern.title);
|
||||
return this.fetchForWildcard(indexPattern.title, {
|
||||
type: indexPattern.type,
|
||||
params: indexPattern.typeMeta && indexPattern.typeMeta.params,
|
||||
});
|
||||
}
|
||||
|
||||
fetchForTimePattern(indexPatternId) {
|
||||
|
@ -36,10 +39,12 @@ export function createFieldsFetcher(apiClient, config) {
|
|||
});
|
||||
}
|
||||
|
||||
fetchForWildcard(indexPatternId) {
|
||||
fetchForWildcard(indexPatternId, options = {}) {
|
||||
return apiClient.getFieldsForWildcard({
|
||||
pattern: indexPatternId,
|
||||
metaFields: config.get('metaFields'),
|
||||
type: options.type,
|
||||
params: options.params || {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,8 @@ export function createIndexPatternsApiClient($http, basePath) {
|
|||
const {
|
||||
pattern,
|
||||
metaFields,
|
||||
type,
|
||||
params,
|
||||
} = options;
|
||||
|
||||
const url = getUrl(['_fields_for_wildcard'], {
|
||||
|
@ -92,7 +94,23 @@ export function createIndexPatternsApiClient($http, basePath) {
|
|||
meta_fields: metaFields,
|
||||
});
|
||||
|
||||
return request('GET', url).then(resp => resp.fields);
|
||||
// Fetch fields normally, and then if the index pattern is a specific type,
|
||||
// pass the retrieved field information to the type-specific fields API for
|
||||
// further processing
|
||||
return request('GET', url).then(resp => {
|
||||
if(type) {
|
||||
const typeUrl = getUrl([type, '_fields_for_wildcard'], {
|
||||
pattern,
|
||||
fields: resp.fields,
|
||||
meta_fields: metaFields,
|
||||
params: JSON.stringify(params),
|
||||
});
|
||||
|
||||
return request('GET', typeUrl).then(typeResp => typeResp.fields);
|
||||
} else {
|
||||
return resp.fields;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
22
src/ui/public/indices/constants/index.js
Normal file
22
src/ui/public/indices/constants/index.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/index_patterns';
|
||||
|
||||
export const INDEX_ILLEGAL_CHARACTERS_VISIBLE = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.concat(',');
|
22
src/ui/public/indices/index.js
Normal file
22
src/ui/public/indices/index.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
INDEX_ILLEGAL_CHARACTERS_VISIBLE,
|
||||
} from './constants';
|
|
@ -17,42 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ManagementSection } from './section';
|
||||
|
||||
export {
|
||||
PAGE_TITLE_COMPONENT,
|
||||
PAGE_SUBTITLE_COMPONENT,
|
||||
PAGE_FOOTER_COMPONENT,
|
||||
} from '../../../core_plugins/kibana/public/management/sections/settings/components/default_component_registry';
|
||||
|
||||
export { registerSettingsComponent } from '../../../core_plugins/kibana/public/management/sections/settings/components/component_registry';
|
||||
|
||||
export { Field } from '../../../core_plugins/kibana/public/management/sections/settings/components/field/field';
|
||||
|
||||
export const management = new ManagementSection('management', {
|
||||
display: 'Management'
|
||||
});
|
||||
|
||||
// TODO: where should this live?
|
||||
management.register('data', {
|
||||
display: 'Connect Data',
|
||||
order: 0
|
||||
});
|
||||
|
||||
management.register('elasticsearch', {
|
||||
display: 'Elasticsearch',
|
||||
order: 20,
|
||||
icon: 'logoElasticsearch'
|
||||
});
|
||||
|
||||
management.register('kibana', {
|
||||
display: 'Kibana',
|
||||
order: 30,
|
||||
icon: 'logoKibana',
|
||||
});
|
||||
|
||||
management.register('logstash', {
|
||||
display: 'Logstash',
|
||||
order: 30,
|
||||
icon: 'logoLogstash',
|
||||
});
|
||||
export { management } from './sections_register';
|
||||
|
|
23
src/ui/public/management/index_pattern_creation/index.js
Normal file
23
src/ui/public/management/index_pattern_creation/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 './register';
|
||||
export { IndexPatternCreationFactory } from './index_pattern_creation';
|
||||
export { IndexPatternCreationConfig } from './index_pattern_creation_config';
|
||||
export { IndexPatternCreationConfigRegistry } from './index_pattern_creation_config_registry';
|
|
@ -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 { IndexPatternCreationConfigRegistry } from './index_pattern_creation_config_registry';
|
||||
|
||||
class IndexPatternCreation {
|
||||
constructor(registry, httpClient, type) {
|
||||
this._registry = registry;
|
||||
this._allTypes = this._registry.inOrder.map(Plugin => new Plugin({ httpClient }));
|
||||
this._setCurrentType(type);
|
||||
}
|
||||
|
||||
_setCurrentType = (type) => {
|
||||
const index = type ? this._registry.inOrder.findIndex(Plugin => Plugin.key === type) : -1;
|
||||
this._currentType = index > -1 && this._allTypes[index] ? this._allTypes[index] : null;
|
||||
}
|
||||
|
||||
getType = () => {
|
||||
return this._currentType || null;
|
||||
}
|
||||
|
||||
getIndexPatternCreationOptions = async (urlHandler) => {
|
||||
const options = [];
|
||||
await Promise.all(this._allTypes.map(async type => {
|
||||
const option = type.getIndexPatternCreationOption ? await type.getIndexPatternCreationOption(urlHandler) : null;
|
||||
if(option) {
|
||||
options.push(option);
|
||||
}
|
||||
}));
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
export const IndexPatternCreationFactory = (Private, $http) => {
|
||||
return (type = 'default') => {
|
||||
const indexPatternCreationRegistry = Private(IndexPatternCreationConfigRegistry);
|
||||
const indexPatternCreationProvider = new IndexPatternCreation(indexPatternCreationRegistry, $http, type);
|
||||
return indexPatternCreationProvider;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
const indexPatternTypeName = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultTypeName',
|
||||
{ defaultMessage: 'index pattern' });
|
||||
|
||||
const indexPatternButtonText = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonText',
|
||||
{ defaultMessage: 'Standard index pattern' });
|
||||
|
||||
const indexPatternButtonDescription = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonDescription',
|
||||
{ defaultMessage: 'Can perform full aggregations against any data' });
|
||||
|
||||
export class IndexPatternCreationConfig {
|
||||
static key = 'default';
|
||||
|
||||
constructor({
|
||||
type = undefined,
|
||||
name = indexPatternTypeName,
|
||||
showSystemIndices = true,
|
||||
httpClient = null,
|
||||
}) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.showSystemIndices = showSystemIndices;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
async getIndexPatternCreationOption(urlHandler) {
|
||||
return {
|
||||
text: indexPatternButtonText,
|
||||
description: indexPatternButtonDescription,
|
||||
testSubj: `createStandardIndexPatternButton`,
|
||||
onClick: () => {
|
||||
urlHandler('/management/kibana/index');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getIndexPatternType = () => {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
getIndexPatternName = () => {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getShowSystemIndices = () => {
|
||||
return this.showSystemIndices;
|
||||
}
|
||||
|
||||
getIndexTags() {
|
||||
return [];
|
||||
}
|
||||
|
||||
checkIndicesForErrors = () => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getIndexPatternMappings = () => {
|
||||
return {};
|
||||
}
|
||||
|
||||
renderPrompt = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
getFetchForWildcardOptions = () => {
|
||||
return {};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { uiRegistry } from 'ui/registry/_registry';
|
||||
|
||||
export const IndexPatternCreationConfigRegistry = uiRegistry({
|
||||
name: 'indexPatternCreation',
|
||||
index: ['name'],
|
||||
order: ['order'],
|
||||
});
|
23
src/ui/public/management/index_pattern_creation/register.js
Normal file
23
src/ui/public/management/index_pattern_creation/register.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { IndexPatternCreationConfig } from './index_pattern_creation_config';
|
||||
import { IndexPatternCreationConfigRegistry } from './index_pattern_creation_config_registry';
|
||||
|
||||
IndexPatternCreationConfigRegistry.register(() => IndexPatternCreationConfig);
|
23
src/ui/public/management/index_pattern_list/index.js
Normal file
23
src/ui/public/management/index_pattern_list/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 './register';
|
||||
export { IndexPatternListFactory } from './index_pattern_list';
|
||||
export { IndexPatternListConfig } from './index_pattern_list_config';
|
||||
export { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry';
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry';
|
||||
|
||||
class IndexPatternList {
|
||||
constructor(registry) {
|
||||
this._plugins = registry.inOrder.map(Plugin => new Plugin());
|
||||
}
|
||||
|
||||
getIndexPatternTags = (indexPattern) => {
|
||||
return this._plugins.reduce((tags, plugin) => {
|
||||
return plugin.getIndexPatternTags ? tags.concat(plugin.getIndexPatternTags(indexPattern)) : tags;
|
||||
}, []);
|
||||
}
|
||||
|
||||
getFieldInfo = (indexPattern, field) => {
|
||||
return this._plugins.reduce((info, plugin) => {
|
||||
return plugin.getFieldInfo ? info.concat(plugin.getFieldInfo(indexPattern, field)) : info;
|
||||
}, []);
|
||||
}
|
||||
|
||||
areScriptedFieldsEnabled = (indexPattern) => {
|
||||
return this._plugins.every((plugin) => {
|
||||
return plugin.areScriptedFieldsEnabled ? plugin.areScriptedFieldsEnabled(indexPattern) : true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const IndexPatternListFactory = (Private) => {
|
||||
return function () {
|
||||
const indexPatternListRegistry = Private(IndexPatternListConfigRegistry);
|
||||
const indexPatternListProvider = new IndexPatternList(indexPatternListRegistry);
|
||||
return indexPatternListProvider;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class IndexPatternListConfig {
|
||||
static key = 'default';
|
||||
|
||||
getIndexPatternTags = () => {
|
||||
return [];
|
||||
}
|
||||
|
||||
getFieldInfo = () => {
|
||||
return [];
|
||||
}
|
||||
|
||||
areScriptedFieldsEnabled = () => {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { uiRegistry } from 'ui/registry/_registry';
|
||||
|
||||
export const IndexPatternListConfigRegistry = uiRegistry({
|
||||
name: 'indexPatternList',
|
||||
index: ['name'],
|
||||
order: ['order'],
|
||||
});
|
23
src/ui/public/management/index_pattern_list/register.js
Normal file
23
src/ui/public/management/index_pattern_list/register.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { IndexPatternListConfig } from './index_pattern_list_config';
|
||||
import { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry';
|
||||
|
||||
IndexPatternListConfigRegistry.register(() => IndexPatternListConfig);
|
47
src/ui/public/management/sections_register.js
Normal file
47
src/ui/public/management/sections_register.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { ManagementSection } from './section';
|
||||
|
||||
export const management = new ManagementSection('management', {
|
||||
display: 'Management'
|
||||
});
|
||||
|
||||
management.register('data', {
|
||||
display: 'Connect Data',
|
||||
order: 0
|
||||
});
|
||||
|
||||
management.register('elasticsearch', {
|
||||
display: 'Elasticsearch',
|
||||
order: 20,
|
||||
icon: 'logoElasticsearch',
|
||||
});
|
||||
|
||||
management.register('kibana', {
|
||||
display: 'Kibana',
|
||||
order: 30,
|
||||
icon: 'logoKibana',
|
||||
});
|
||||
|
||||
management.register('logstash', {
|
||||
display: 'Logstash',
|
||||
order: 30,
|
||||
icon: 'logoLogstash',
|
||||
});
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import dateMath from '@kbn/datemath';
|
||||
import { parseEsInterval } from 'ui/utils/parse_es_interval';
|
||||
|
||||
const unitsDesc = dateMath.unitsDesc;
|
||||
const largeMax = unitsDesc.indexOf('M');
|
||||
|
@ -30,7 +31,7 @@ const largeMax = unitsDesc.indexOf('M');
|
|||
* @param {moment.duration} duration
|
||||
* @return {object}
|
||||
*/
|
||||
export function calcEsInterval(duration) {
|
||||
export function convertDurationToNormalizedEsInterval(duration) {
|
||||
for (let i = 0; i < unitsDesc.length; i++) {
|
||||
const unit = unitsDesc[i];
|
||||
const val = duration.as(unit);
|
||||
|
@ -59,3 +60,12 @@ export function calcEsInterval(duration) {
|
|||
expression: ms + 'ms'
|
||||
};
|
||||
}
|
||||
|
||||
export function convertIntervalToEsInterval(interval) {
|
||||
const { value, unit } = parseEsInterval(interval);
|
||||
return {
|
||||
value,
|
||||
unit,
|
||||
expression: interval,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,7 +22,10 @@ import moment from 'moment';
|
|||
import chrome from 'ui/chrome';
|
||||
import { parseInterval } from '../utils/parse_interval';
|
||||
import { calcAutoInterval } from './calc_auto_interval';
|
||||
import { calcEsInterval } from './calc_es_interval';
|
||||
import {
|
||||
convertDurationToNormalizedEsInterval,
|
||||
convertIntervalToEsInterval,
|
||||
} from './calc_es_interval';
|
||||
import { fieldFormats } from '../registry/field_formats';
|
||||
|
||||
const config = chrome.getUiSettingsClient();
|
||||
|
@ -152,6 +155,10 @@ TimeBuckets.prototype.getDuration = function () {
|
|||
* @param {object|string|moment.duration} input - see desc
|
||||
*/
|
||||
TimeBuckets.prototype.setInterval = function (input) {
|
||||
// Preserve the original units because they're lost when the interval is converted to a
|
||||
// moment duration object.
|
||||
this.originalInterval = input;
|
||||
|
||||
let interval = input;
|
||||
|
||||
// selection object -> val
|
||||
|
@ -215,7 +222,7 @@ TimeBuckets.prototype.setInterval = function (input) {
|
|||
*
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
TimeBuckets.prototype.getInterval = function () {
|
||||
TimeBuckets.prototype.getInterval = function (useNormalizedEsInterval = true) {
|
||||
const self = this;
|
||||
const duration = self.getDuration();
|
||||
return decorateInterval(maybeScaleInterval(readInterval()));
|
||||
|
@ -253,7 +260,9 @@ TimeBuckets.prototype.getInterval = function () {
|
|||
|
||||
// append some TimeBuckets specific props to the interval
|
||||
function decorateInterval(interval) {
|
||||
const esInterval = calcEsInterval(interval);
|
||||
const esInterval = useNormalizedEsInterval
|
||||
? convertDurationToNormalizedEsInterval(interval)
|
||||
: convertIntervalToEsInterval(self.originalInterval);
|
||||
interval.esValue = esInterval.value;
|
||||
interval.esUnit = esInterval.unit;
|
||||
interval.expression = esInterval.expression;
|
||||
|
@ -341,12 +350,12 @@ TimeBuckets.__cached__ = function (self) {
|
|||
|
||||
function cachedGetter(prop) {
|
||||
return {
|
||||
value: function cachedGetter() {
|
||||
value: function cachedGetter(...rest) {
|
||||
if (cache.hasOwnProperty(prop)) {
|
||||
return cache[prop];
|
||||
}
|
||||
|
||||
return cache[prop] = self[prop]();
|
||||
return cache[prop] = self[prop](...rest);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
22
src/ui/public/utils/parse_es_interval/index.ts
Normal file
22
src/ui/public/utils/parse_es_interval/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { parseEsInterval } from './parse_es_interval';
|
||||
export { InvalidEsCalendarIntervalError } from './invalid_es_calendar_interval_error';
|
||||
export { InvalidEsIntervalFormatError } from './invalid_es_interval_format_error';
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class InvalidEsCalendarIntervalError extends Error {
|
||||
constructor(
|
||||
public readonly interval: string,
|
||||
public readonly value: number,
|
||||
public readonly unit: string,
|
||||
public readonly type: string
|
||||
) {
|
||||
super(`Invalid calendar interval: ${interval}, value must be 1`);
|
||||
|
||||
this.name = 'InvalidEsCalendarIntervalError';
|
||||
this.value = value;
|
||||
this.unit = unit;
|
||||
this.type = type;
|
||||
|
||||
// captureStackTrace is only available in the V8 engine, so any browser using
|
||||
// a different JS engine won't have access to this method.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, InvalidEsCalendarIntervalError);
|
||||
}
|
||||
|
||||
// Babel doesn't support traditional `extends` syntax for built-in classes.
|
||||
// https://babeljs.io/docs/en/caveats/#classes
|
||||
Object.setPrototypeOf(this, InvalidEsCalendarIntervalError.prototype);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class InvalidEsIntervalFormatError extends Error {
|
||||
constructor(public readonly interval: string) {
|
||||
super(`Invalid interval format: ${interval}`);
|
||||
this.name = 'InvalidEsIntervalFormatError';
|
||||
|
||||
// captureStackTrace is only available in the V8 engine, so any browser using
|
||||
// a different JS engine won't have access to this method.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, InvalidEsIntervalFormatError);
|
||||
}
|
||||
|
||||
// Babel doesn't support traditional `extends` syntax for built-in classes.
|
||||
// https://babeljs.io/docs/en/caveats/#classes
|
||||
Object.setPrototypeOf(this, InvalidEsIntervalFormatError.prototype);
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { InvalidEsCalendarIntervalError } from './invalid_es_calendar_interval_error';
|
||||
import { InvalidEsIntervalFormatError } from './invalid_es_interval_format_error';
|
||||
import { parseEsInterval } from './parse_es_interval';
|
||||
|
||||
describe('parseEsInterval', () => {
|
||||
|
@ -39,16 +41,29 @@ describe('parseEsInterval', () => {
|
|||
expect(parseEsInterval('7d')).toEqual({ value: 7, unit: 'd', type: 'fixed' });
|
||||
});
|
||||
|
||||
it('should throw an error for intervals containing calendar unit and multiple value', () => {
|
||||
expect(() => parseEsInterval('4w')).toThrowError();
|
||||
expect(() => parseEsInterval('12M')).toThrowError();
|
||||
expect(() => parseEsInterval('10y')).toThrowError();
|
||||
it('should throw a InvalidEsCalendarIntervalError for intervals containing calendar unit and multiple value', () => {
|
||||
const intervals = ['4w', '12M', '10y'];
|
||||
expect.assertions(intervals.length);
|
||||
|
||||
intervals.forEach(interval => {
|
||||
try {
|
||||
parseEsInterval(interval);
|
||||
} catch (error) {
|
||||
expect(error instanceof InvalidEsCalendarIntervalError).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error for invalid interval formats', () => {
|
||||
expect(() => parseEsInterval('1')).toThrowError();
|
||||
expect(() => parseEsInterval('h')).toThrowError();
|
||||
expect(() => parseEsInterval('0m')).toThrowError();
|
||||
expect(() => parseEsInterval('0.5h')).toThrowError();
|
||||
it('should throw a InvalidEsIntervalFormatError for invalid interval formats', () => {
|
||||
const intervals = ['1', 'h', '0m', '0.5h'];
|
||||
expect.assertions(intervals.length);
|
||||
|
||||
intervals.forEach(interval => {
|
||||
try {
|
||||
parseEsInterval(interval);
|
||||
} catch (error) {
|
||||
expect(error instanceof InvalidEsIntervalFormatError).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -16,8 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import dateMath from '@kbn/datemath';
|
||||
|
||||
import { InvalidEsCalendarIntervalError } from './invalid_es_calendar_interval_error';
|
||||
import { InvalidEsIntervalFormatError } from './invalid_es_interval_format_error';
|
||||
|
||||
const ES_INTERVAL_STRING_REGEX = new RegExp(
|
||||
'^([1-9][0-9]*)\\s*(' + dateMath.units.join('|') + ')$'
|
||||
);
|
||||
|
@ -47,7 +51,7 @@ export function parseEsInterval(interval: string): { value: number; unit: string
|
|||
.match(ES_INTERVAL_STRING_REGEX);
|
||||
|
||||
if (!matches) {
|
||||
throw Error(`Invalid interval format: ${interval}`);
|
||||
throw new InvalidEsIntervalFormatError(interval);
|
||||
}
|
||||
|
||||
const value = matches && parseFloat(matches[1]);
|
||||
|
@ -55,7 +59,7 @@ export function parseEsInterval(interval: string): { value: number; unit: string
|
|||
const type = unit && dateMath.unitsMap[unit].type;
|
||||
|
||||
if (type === 'calendar' && value !== 1) {
|
||||
throw Error(`Invalid calendar interval: ${interval}, value must be 1`);
|
||||
throw new InvalidEsCalendarIntervalError(interval, value, unit, type);
|
||||
}
|
||||
|
||||
return {
|
|
@ -0,0 +1,17 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VisualizationRequestError should render according to snapshot 1`] = `
|
||||
<div
|
||||
class="visualize-error visualize-chart"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--extraSmall visualize-request-error"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--danger"
|
||||
>
|
||||
Request error
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -65,7 +65,15 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.top { align-self: flext-start; }
|
||||
.top { align-self: flex-start; }
|
||||
.item { }
|
||||
.bottom { align-self: flext-end; }
|
||||
.bottom { align-self: flex-end; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent large request errors from overflowing the container
|
||||
*/
|
||||
.visualize-request-error {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
|
|
@ -79,6 +79,13 @@ describe('<Visualization/>', () => {
|
|||
expect(wrapper.text()).toBe('No results found');
|
||||
});
|
||||
|
||||
it('should display error message when there is a request error that should be shown and no data', () => {
|
||||
const errorVis = { ...vis, requestError: { message: 'Request error' }, showRequestError: true };
|
||||
const data = null;
|
||||
const wrapper = render(<Visualization vis={errorVis} visData={data} listenOnChange={true} uiState={uiState} />);
|
||||
expect(wrapper.text()).toBe('Request error');
|
||||
});
|
||||
|
||||
it('should render chart when data is present', () => {
|
||||
const wrapper = render(<Visualization vis={vis} visData={visData} uiState={uiState} listenOnChange={true} />);
|
||||
expect(wrapper.text()).not.toBe('No results found');
|
||||
|
|
|
@ -25,6 +25,7 @@ import { memoizeLast } from '../../utils/memoize';
|
|||
import { Vis } from '../../vis';
|
||||
import { VisualizationChart } from './visualization_chart';
|
||||
import { VisualizationNoResults } from './visualization_noresults';
|
||||
import { VisualizationRequestError } from './visualization_requesterror';
|
||||
|
||||
import './visualization.less';
|
||||
|
||||
|
@ -37,6 +38,12 @@ function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
|
|||
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
|
||||
}
|
||||
|
||||
function shouldShowRequestErrorMessage(vis: Vis, visData: any): boolean {
|
||||
const requestError = get(vis, 'requestError');
|
||||
const showRequestError = get(vis, 'showRequestError');
|
||||
return Boolean(!visData && requestError && showRequestError);
|
||||
}
|
||||
|
||||
interface VisualizationProps {
|
||||
listenOnChange: boolean;
|
||||
onInit?: () => void;
|
||||
|
@ -63,10 +70,13 @@ export class Visualization extends React.Component<VisualizationProps> {
|
|||
const { vis, visData, onInit, uiState } = this.props;
|
||||
|
||||
const noResults = this.showNoResultsMessage(vis, visData);
|
||||
const requestError = shouldShowRequestErrorMessage(vis, visData);
|
||||
|
||||
return (
|
||||
<div className="visualization">
|
||||
{noResults ? (
|
||||
{requestError ? (
|
||||
<VisualizationRequestError onInit={onInit} error={vis.requestError} />
|
||||
) : noResults ? (
|
||||
<VisualizationNoResults onInit={onInit} />
|
||||
) : (
|
||||
<VisualizationChart vis={vis} visData={visData} onInit={onInit} uiState={uiState} />
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { render } from 'enzyme';
|
||||
import { VisualizationRequestError } from './visualization_requesterror';
|
||||
|
||||
describe('VisualizationRequestError', () => {
|
||||
it('should render according to snapshot', () => {
|
||||
const wrapper = render(<VisualizationRequestError error="Request error" />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should set html when error is an object', () => {
|
||||
const wrapper = render(<VisualizationRequestError error={{ message: 'Request error' }} />);
|
||||
expect(wrapper.text()).toBe('Request error');
|
||||
});
|
||||
|
||||
it('should set html when error is a string', () => {
|
||||
const wrapper = render(<VisualizationRequestError error="Request error" />);
|
||||
expect(wrapper.text()).toBe('Request error');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { SearchError } from 'ui/courier';
|
||||
import { dispatchRenderComplete } from '../../render_complete';
|
||||
|
||||
interface VisualizationRequestErrorProps {
|
||||
onInit?: () => void;
|
||||
error: SearchError | string;
|
||||
}
|
||||
|
||||
export class VisualizationRequestError extends React.Component<VisualizationRequestErrorProps> {
|
||||
private containerDiv = React.createRef<HTMLDivElement>();
|
||||
|
||||
public render() {
|
||||
const { error } = this.props;
|
||||
const errorMessage = (error && error.message) || error;
|
||||
|
||||
return (
|
||||
<div className="visualize-error visualize-chart" ref={this.containerDiv}>
|
||||
<EuiText className="visualize-request-error" color="danger" size="xs">
|
||||
{errorMessage}
|
||||
</EuiText>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.afterRender();
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.afterRender();
|
||||
}
|
||||
|
||||
private afterRender() {
|
||||
if (this.props.onInit) {
|
||||
this.props.onInit();
|
||||
}
|
||||
if (this.containerDiv.current) {
|
||||
dispatchRenderComplete(this.containerDiv.current);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,6 +69,8 @@ export class VisualizeDataLoader {
|
|||
|
||||
public async fetch(params: RequestHandlerParams): Promise<any> {
|
||||
this.vis.filters = { timeRange: params.timeRange };
|
||||
this.vis.requestError = undefined;
|
||||
this.vis.showRequestError = false;
|
||||
|
||||
try {
|
||||
// searchSource is only there for courier request handler
|
||||
|
@ -95,6 +97,7 @@ export class VisualizeDataLoader {
|
|||
} catch (e) {
|
||||
params.searchSource.cancelQueued();
|
||||
this.vis.requestError = e;
|
||||
this.vis.showRequestError = e.type && e.type === 'UNSUPPORTED_QUERY';
|
||||
if (isTermSizeZeroError(e)) {
|
||||
return toastNotifications.addDanger(
|
||||
`Your visualization ('${this.vis.title}') has an error: it has a term ` +
|
||||
|
|
|
@ -48,6 +48,7 @@ export {
|
|||
navbarExtensions,
|
||||
contextMenuActions,
|
||||
managementSections,
|
||||
indexManagement,
|
||||
devTools,
|
||||
docViews,
|
||||
hacks,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue