mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Index pattern management UI -> TypeScript and New Platform Ready (edit_index_pattern) (#64184)
* draft * Converted edit_index_pattern to React. Created 'tab' component. * Some fixes * returned state_container * Fixed tests and translation * Some refactoring * Fixed tests * rermove unused translations * update snapshots * Some refactoring Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Matt Kime <matt@mattki.me>
This commit is contained in:
parent
4eb971c8c3
commit
66074f9042
17 changed files with 861 additions and 792 deletions
|
@ -6,156 +6,7 @@
|
|||
role="region"
|
||||
aria-label="{{::'kbn.management.editIndexPattern.detailsAria' | i18n: { defaultMessage: 'Index pattern details' } }}"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div id="reactIndexHeader"></div>
|
||||
|
||||
<div class="euiSpacer euiSpacer--s"></div>
|
||||
<p ng-if="::(indexPattern.timeFieldName || (indexPattern.tags && indexPattern.tags.length))">
|
||||
<span ng-if="::indexPattern.timeFieldName">
|
||||
<span class="euiBadge euiBadge--warning">
|
||||
<span class="euiBadge__content">
|
||||
<span class="euiBadge__text">
|
||||
<span
|
||||
i18n-id="kbn.management.editIndexPattern.timeFilterHeader"
|
||||
i18n-default-message="Time Filter field name: {timeFieldName}"
|
||||
i18n-values="{ timeFieldName: indexPattern.timeFieldName }">
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-repeat="tag in indexPattern.tags">
|
||||
<span class="euiBadge euiBadge--hollow">
|
||||
<span class="euiBadge__content">
|
||||
<span class="euiBadge__text">
|
||||
{{tag.name}}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<div class="euiText">
|
||||
<p>
|
||||
<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"
|
||||
i18n-values="{ html_indexPatternTitle: '<strong>' + indexPattern.title + '</strong>' }"></span>
|
||||
<a target="_blank" class="euiLink euiLink--primary" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink"
|
||||
i18n-default-message="Mapping API"></span>
|
||||
<i aria-hidden="true" class="fa-link fa"></i>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div
|
||||
ng-if="conflictFields.length"
|
||||
class="kuiInfoPanel kuiInfoPanel--warning kuiVerticalRhythm"
|
||||
>
|
||||
<div class="kuiInfoPanelHeader">
|
||||
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--warning fa-bolt"></span>
|
||||
<span class="kuiInfoPanelHeader__title"
|
||||
i18n-id="kbn.management.editIndexPattern.mappingConflictHeader"
|
||||
i18n-default-message="Mapping conflict"></span>
|
||||
</div>
|
||||
|
||||
<div class="kuiInfoPanelBody">
|
||||
<div class="kuiInfoPanelBody__message">
|
||||
<span i18n-id="kbn.management.editIndexPattern.mappingConflictLabel"
|
||||
i18n-default-message="{conflictFieldsLength, plural, one {A field is} other {# fields are}} defined as several types (string, integer, etc) across the indices that match this pattern. You may still be able to use these conflict fields in parts of Kibana, but they will be unavailable for functions that require Kibana to know their type. Correcting this issue will require reindexing your data."
|
||||
i18n-values="{ conflictFieldsLength: conflictFields.length }"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="kuiTabs kuiVerticalRhythm">
|
||||
<button
|
||||
class="kuiTab"
|
||||
ng-repeat="editSection in editSections"
|
||||
ng-class="{ 'kuiTab-isSelected': getCurrentTab() === editSection.index }"
|
||||
ng-click="setCurrentTab(editSection.index)"
|
||||
data-test-subj="tab-{{ editSection.index }}"
|
||||
>
|
||||
{{ editSection.title }}
|
||||
<span
|
||||
data-test-subj="tab-count-{{ editSection.index }}"
|
||||
aria-label="{{:: editSection.count + ' ' + editSection.title}}"
|
||||
>
|
||||
<span ng-if="editSection.count != editSection.totalCount">
|
||||
({{ editSection.count }} / {{ editSection.totalCount }})
|
||||
</span>
|
||||
<span ng-if="editSection.count == editSection.totalCount">
|
||||
({{ editSection.count }})
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Field Filters -->
|
||||
<form role="form" class="kuiFieldGroup kuiVerticalRhythm">
|
||||
<div class="kuiFieldGroupSection kuiFieldGroupSection--wide">
|
||||
<div class="kuiSearchInput">
|
||||
<div class="kuiSearchInput__icon kuiIcon fa-search"></div>
|
||||
<input
|
||||
class="kuiSearchInput__input"
|
||||
type="text"
|
||||
aria-label="{{::'kbn.management.editIndexPattern.fields.filterAria' | i18n: {defaultMessage: 'Filter'} }}"
|
||||
ng-model="fieldFilter"
|
||||
placeholder="{{::'kbn.management.editIndexPattern.fields.filterPlaceholder' | i18n: {defaultMessage: 'Filter'} }}"
|
||||
data-test-subj="indexPatternFieldFilter"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="kuiFieldGroupSection"
|
||||
ng-if="getCurrentTab() == 'indexedFields' && indexedFieldTypes.length > 0"
|
||||
>
|
||||
<select
|
||||
data-test-subj="indexedFieldTypeFilterDropdown"
|
||||
class="kuiSelect"
|
||||
ng-model="indexedFieldTypeFilter"
|
||||
ng-change="changeFilter('indexedFieldTypeFilter', indexedFieldTypeFilter)"
|
||||
ng-options="o for o in indexedFieldTypes"
|
||||
>
|
||||
<option value=""
|
||||
i18n-id="kbn.management.editIndexPattern.fields.allTypesDropDown"
|
||||
i18n-default-message="All field types"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="kuiFieldGroupSection"
|
||||
ng-if="getCurrentTab() == 'scriptedFields' && scriptedFieldLanguages.length > 0"
|
||||
>
|
||||
<select
|
||||
data-test-subj="scriptedFieldLanguageFilterDropdown"
|
||||
class="kuiSelect"
|
||||
ng-model="scriptedFieldLanguageFilter"
|
||||
ng-change="changeFilter('scriptedFieldLanguageFilter', scriptedFieldLanguageFilter)"
|
||||
ng-options="o for o in scriptedFieldLanguages"
|
||||
>
|
||||
<option value=""
|
||||
i18n-id="kbn.management.editIndexPattern.fields.allLangsDropDown"
|
||||
i18n-default-message="All languages"></option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="kuiVerticalRhythm">
|
||||
<div id="reactIndexedFieldsTable"></div>
|
||||
|
||||
<div id="reactScriptedFieldsTable"></div>
|
||||
|
||||
<div id="reactSourceFiltersTable"></div>
|
||||
</div>
|
||||
<div id="reactEditIndexPattern"></div>
|
||||
</div>
|
||||
</div>
|
||||
</kbn-management-app>
|
||||
|
|
|
@ -1,511 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { IndexHeader } from './index_header';
|
||||
import { CreateEditField } from './create_edit_field';
|
||||
import { docTitle } from 'ui/doc_title';
|
||||
import { KbnUrlProvider } from 'ui/url';
|
||||
import { IndicesEditSectionsProvider } from './edit_sections';
|
||||
import { fatalError, toastNotifications } from 'ui/notify';
|
||||
import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import template from './edit_index_pattern.html';
|
||||
import createEditFieldtemplate from './create_edit_field.html';
|
||||
import { fieldWildcardMatcher } from '../../../../../../../../plugins/kibana_utils/public';
|
||||
import { subscribeWithScope } from '../../../../../../../../plugins/kibana_legacy/public';
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { SourceFiltersTable } from './source_filters_table';
|
||||
import { IndexedFieldsTable } from './indexed_fields_table';
|
||||
import { ScriptedFieldsTable } from './scripted_fields_table';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import {
|
||||
getEditBreadcrumbs,
|
||||
getEditFieldBreadcrumbs,
|
||||
getCreateFieldBreadcrumbs,
|
||||
} from '../breadcrumbs';
|
||||
import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from './constants';
|
||||
import { createEditIndexPatternPageStateContainer } from './edit_index_pattern_state_container';
|
||||
|
||||
const REACT_SOURCE_FILTERS_DOM_ELEMENT_ID = 'reactSourceFiltersTable';
|
||||
const REACT_INDEXED_FIELDS_DOM_ELEMENT_ID = 'reactIndexedFieldsTable';
|
||||
const REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID = 'reactScriptedFieldsTable';
|
||||
const REACT_INDEX_HEADER_DOM_ELEMENT_ID = 'reactIndexHeader';
|
||||
|
||||
const EDIT_FIELD_PATH = '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}';
|
||||
|
||||
function updateSourceFiltersTable($scope) {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<SourceFiltersTable
|
||||
indexPattern={$scope.indexPattern}
|
||||
filterFilter={$scope.fieldFilter}
|
||||
fieldWildcardMatcher={$scope.fieldWildcardMatcher}
|
||||
onAddOrRemoveFilter={() => {
|
||||
$scope.editSections = $scope.editSectionsProvider(
|
||||
$scope.indexPattern,
|
||||
$scope.fieldFilter,
|
||||
$scope.indexPatternListProvider
|
||||
);
|
||||
$scope.refreshFilters();
|
||||
$scope.$apply();
|
||||
}}
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function destroySourceFiltersTable() {
|
||||
const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
function updateScriptedFieldsTable($scope) {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<ScriptedFieldsTable
|
||||
indexPattern={$scope.indexPattern}
|
||||
fieldFilter={$scope.fieldFilter}
|
||||
scriptedFieldLanguageFilter={$scope.scriptedFieldLanguageFilter}
|
||||
helpers={{
|
||||
redirectToRoute: field => {
|
||||
$scope.kbnUrl.changePath(EDIT_FIELD_PATH, field);
|
||||
$scope.$apply();
|
||||
},
|
||||
getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route),
|
||||
}}
|
||||
onRemoveField={() => {
|
||||
$scope.editSections = $scope.editSectionsProvider(
|
||||
$scope.indexPattern,
|
||||
$scope.fieldFilter,
|
||||
$scope.indexPatternListProvider
|
||||
);
|
||||
$scope.refreshFilters();
|
||||
$scope.$apply();
|
||||
}}
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function destroyScriptedFieldsTable() {
|
||||
const node = document.getElementById(REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
function updateIndexedFieldsTable($scope) {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<IndexedFieldsTable
|
||||
fields={$scope.fields}
|
||||
indexPattern={$scope.indexPattern}
|
||||
fieldFilter={$scope.fieldFilter}
|
||||
fieldWildcardMatcher={$scope.fieldWildcardMatcher}
|
||||
indexedFieldTypeFilter={$scope.indexedFieldTypeFilter}
|
||||
helpers={{
|
||||
redirectToRoute: field => {
|
||||
$scope.kbnUrl.changePath(EDIT_FIELD_PATH, field);
|
||||
$scope.$apply();
|
||||
},
|
||||
getFieldInfo: $scope.getFieldInfo,
|
||||
}}
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function destroyIndexedFieldsTable() {
|
||||
const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
function destroyIndexHeader() {
|
||||
const node = document.getElementById(REACT_INDEX_HEADER_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
function renderIndexHeader($scope, config) {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_INDEX_HEADER_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<IndexHeader
|
||||
indexPattern={$scope.indexPattern}
|
||||
setDefault={$scope.setDefaultPattern}
|
||||
refreshFields={$scope.refreshFields}
|
||||
deleteIndexPattern={$scope.removePattern}
|
||||
defaultIndex={config.get('defaultIndex')}
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function handleTabChange($scope, newTab) {
|
||||
destroyIndexedFieldsTable();
|
||||
destroySourceFiltersTable();
|
||||
destroyScriptedFieldsTable();
|
||||
updateTables($scope, newTab);
|
||||
}
|
||||
|
||||
function updateTables($scope, currentTab) {
|
||||
switch (currentTab) {
|
||||
case TAB_SCRIPTED_FIELDS:
|
||||
return updateScriptedFieldsTable($scope);
|
||||
case TAB_INDEXED_FIELDS:
|
||||
return updateIndexedFieldsTable($scope);
|
||||
case TAB_SOURCE_FILTERS:
|
||||
return updateSourceFiltersTable($scope);
|
||||
}
|
||||
}
|
||||
|
||||
uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', {
|
||||
template,
|
||||
k7Breadcrumbs: getEditBreadcrumbs,
|
||||
resolve: {
|
||||
indexPattern: function($route, Promise, redirectWhenMissing) {
|
||||
const { indexPatterns } = npStart.plugins.data;
|
||||
return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch(
|
||||
redirectWhenMissing('/management/kibana/index_patterns')
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
uiModules
|
||||
.get('apps/management')
|
||||
.controller('managementIndexPatternsEdit', function(
|
||||
$scope,
|
||||
$location,
|
||||
$route,
|
||||
Promise,
|
||||
config,
|
||||
Private
|
||||
) {
|
||||
const {
|
||||
startSyncingState,
|
||||
stopSyncingState,
|
||||
setCurrentTab,
|
||||
getCurrentTab,
|
||||
state$,
|
||||
} = createEditIndexPatternPageStateContainer({
|
||||
useHashedUrl: config.get('state:storeInSessionStorage'),
|
||||
defaultTab: TAB_INDEXED_FIELDS,
|
||||
});
|
||||
|
||||
$scope.getCurrentTab = getCurrentTab;
|
||||
$scope.setCurrentTab = setCurrentTab;
|
||||
|
||||
const stateChangedSub = subscribeWithScope(
|
||||
$scope,
|
||||
state$,
|
||||
{
|
||||
next: ({ tab }) => {
|
||||
handleTabChange($scope, tab);
|
||||
},
|
||||
},
|
||||
fatalError
|
||||
);
|
||||
|
||||
handleTabChange($scope, getCurrentTab()); // setup initial tab depending on initial tab state
|
||||
|
||||
startSyncingState(); // starts syncing state between state container and url
|
||||
|
||||
const destroyState = () => {
|
||||
stateChangedSub.unsubscribe();
|
||||
stopSyncingState();
|
||||
};
|
||||
|
||||
$scope.fieldWildcardMatcher = (...args) =>
|
||||
fieldWildcardMatcher(...args, config.get('metaFields'));
|
||||
$scope.editSectionsProvider = Private(IndicesEditSectionsProvider);
|
||||
$scope.kbnUrl = Private(KbnUrlProvider);
|
||||
$scope.indexPattern = $route.current.locals.indexPattern;
|
||||
$scope.indexPatternListProvider = npStart.plugins.indexPatternManagement.list;
|
||||
$scope.indexPattern.tags = npStart.plugins.indexPatternManagement.list.getIndexPatternTags(
|
||||
$scope.indexPattern,
|
||||
$scope.indexPattern.id === config.get('defaultIndex')
|
||||
);
|
||||
$scope.getFieldInfo = npStart.plugins.indexPatternManagement.list.getFieldInfo;
|
||||
docTitle.change($scope.indexPattern.title);
|
||||
|
||||
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
|
||||
return pattern.id !== $scope.indexPattern.id;
|
||||
});
|
||||
|
||||
$scope.$watch('indexPattern.fields', function() {
|
||||
$scope.editSections = $scope.editSectionsProvider(
|
||||
$scope.indexPattern,
|
||||
$scope.fieldFilter,
|
||||
npStart.plugins.indexPatternManagement.list
|
||||
);
|
||||
$scope.refreshFilters();
|
||||
$scope.fields = $scope.indexPattern.getNonScriptedFields();
|
||||
});
|
||||
|
||||
$scope.refreshFilters = function() {
|
||||
const indexedFieldTypes = [];
|
||||
const scriptedFieldLanguages = [];
|
||||
$scope.indexPattern.fields.forEach(field => {
|
||||
if (field.scripted) {
|
||||
scriptedFieldLanguages.push(field.lang);
|
||||
} else {
|
||||
indexedFieldTypes.push(field.type);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.indexedFieldTypes = _.unique(indexedFieldTypes);
|
||||
$scope.scriptedFieldLanguages = _.unique(scriptedFieldLanguages);
|
||||
};
|
||||
|
||||
$scope.changeFilter = function(filter, val) {
|
||||
$scope[filter] = val || ''; // null causes filter to check for null explicitly
|
||||
};
|
||||
|
||||
$scope.$watchCollection('indexPattern.fields', function() {
|
||||
$scope.conflictFields = $scope.indexPattern.fields.filter(field => field.type === 'conflict');
|
||||
});
|
||||
|
||||
$scope.refreshFields = function() {
|
||||
const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', {
|
||||
defaultMessage: 'This action resets the popularity counter of each field.',
|
||||
});
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', {
|
||||
defaultMessage: 'Refresh',
|
||||
}),
|
||||
title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', {
|
||||
defaultMessage: 'Refresh field list?',
|
||||
}),
|
||||
};
|
||||
|
||||
npStart.core.overlays
|
||||
.openConfirm(confirmMessage, confirmModalOptions)
|
||||
.then(async isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
await $scope.indexPattern.init(true);
|
||||
$scope.fields = $scope.indexPattern.getNonScriptedFields();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removePattern = function() {
|
||||
function doRemove() {
|
||||
if ($scope.indexPattern.id === config.get('defaultIndex')) {
|
||||
config.remove('defaultIndex');
|
||||
|
||||
if (otherPatterns.length) {
|
||||
config.set('defaultIndex', otherPatterns[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.resolve($scope.indexPattern.destroy())
|
||||
.then(function() {
|
||||
$location.url('/management/kibana/index_patterns');
|
||||
})
|
||||
.catch(fatalError);
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', {
|
||||
defaultMessage: 'Delete',
|
||||
}),
|
||||
title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', {
|
||||
defaultMessage: 'Delete index pattern?',
|
||||
}),
|
||||
};
|
||||
|
||||
npStart.core.overlays.openConfirm('', confirmModalOptions).then(isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
doRemove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setDefaultPattern = function() {
|
||||
config.set('defaultIndex', $scope.indexPattern.id);
|
||||
};
|
||||
|
||||
$scope.setIndexPatternsTimeField = function(field) {
|
||||
if (field.type !== 'date') {
|
||||
const errorMessage = i18n.translate('kbn.management.editIndexPattern.notDateErrorMessage', {
|
||||
defaultMessage: 'That field is a {fieldType} not a date.',
|
||||
values: { fieldType: field.type },
|
||||
});
|
||||
toastNotifications.addDanger(errorMessage);
|
||||
return;
|
||||
}
|
||||
$scope.indexPattern.timeFieldName = field.name;
|
||||
return $scope.indexPattern.save();
|
||||
};
|
||||
|
||||
$scope.$watch('fieldFilter', () => {
|
||||
$scope.editSections = $scope.editSectionsProvider(
|
||||
$scope.indexPattern,
|
||||
$scope.fieldFilter,
|
||||
npStart.plugins.indexPatternManagement.list
|
||||
);
|
||||
|
||||
if ($scope.fieldFilter === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateTables($scope, getCurrentTab());
|
||||
});
|
||||
|
||||
$scope.$watch('indexedFieldTypeFilter', () => {
|
||||
if ($scope.indexedFieldTypeFilter !== undefined && getCurrentTab() === TAB_INDEXED_FIELDS) {
|
||||
updateIndexedFieldsTable($scope);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('scriptedFieldLanguageFilter', () => {
|
||||
if (
|
||||
$scope.scriptedFieldLanguageFilter !== undefined &&
|
||||
getCurrentTab() === TAB_SCRIPTED_FIELDS
|
||||
) {
|
||||
updateScriptedFieldsTable($scope);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
destroyIndexedFieldsTable();
|
||||
destroyScriptedFieldsTable();
|
||||
destroySourceFiltersTable();
|
||||
destroyIndexHeader();
|
||||
destroyState();
|
||||
});
|
||||
|
||||
renderIndexHeader($scope, config);
|
||||
});
|
||||
|
||||
// routes for create edit field. Will be removed after migartion all component to react.
|
||||
const REACT_FIELD_EDITOR_ID = 'reactFieldEditor';
|
||||
const renderCreateEditField = ($scope, $route, getConfig, $http, fieldFormatEditors) => {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_FIELD_EDITOR_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<HashRouter>
|
||||
<I18nContext>
|
||||
<CreateEditField
|
||||
indexPattern={$route.current.locals.indexPattern}
|
||||
mode={$route.current.mode}
|
||||
fieldName={$route.current.params.fieldName}
|
||||
fieldFormatEditors={fieldFormatEditors}
|
||||
getConfig={getConfig}
|
||||
services={{
|
||||
http: $http,
|
||||
notifications: npStart.core.notifications,
|
||||
docTitle: npStart.core.chrome.docTitle,
|
||||
}}
|
||||
/>
|
||||
</I18nContext>
|
||||
</HashRouter>,
|
||||
node
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const destroyCreateEditField = () => {
|
||||
const node = document.getElementById(REACT_FIELD_EDITOR_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
};
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/index_patterns/:indexPatternId/field/:fieldName*', {
|
||||
mode: 'edit',
|
||||
k7Breadcrumbs: getEditFieldBreadcrumbs,
|
||||
})
|
||||
.when('/management/kibana/index_patterns/:indexPatternId/create-field/', {
|
||||
mode: 'create',
|
||||
k7Breadcrumbs: getCreateFieldBreadcrumbs,
|
||||
})
|
||||
.defaults(/management\/kibana\/index_patterns\/[^\/]+\/(field|create-field)(\/|$)/, {
|
||||
template: createEditFieldtemplate,
|
||||
mapBreadcrumbs($route, breadcrumbs) {
|
||||
const { indexPattern } = $route.current.locals;
|
||||
return breadcrumbs.map(crumb => {
|
||||
if (crumb.id !== indexPattern.id) {
|
||||
return crumb;
|
||||
}
|
||||
|
||||
return {
|
||||
...crumb,
|
||||
display: indexPattern.title,
|
||||
};
|
||||
});
|
||||
},
|
||||
resolve: {
|
||||
indexPattern: function($route, Promise, redirectWhenMissing) {
|
||||
const { indexPatterns } = npStart.plugins.data;
|
||||
return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch(
|
||||
redirectWhenMissing('/management/kibana/index_patterns')
|
||||
);
|
||||
},
|
||||
},
|
||||
controllerAs: 'fieldSettings',
|
||||
controller: function FieldEditorPageController($scope, $route, $http, Private, config) {
|
||||
const getConfig = (...args) => config.get(...args);
|
||||
const fieldFormatEditors = Private(RegistryFieldFormatEditorsProvider);
|
||||
|
||||
renderCreateEditField($scope, $route, getConfig, $http, fieldFormatEditors);
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
destroyCreateEditField();
|
||||
});
|
||||
},
|
||||
});
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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 { filter } from 'lodash';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiBadge,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
EuiIcon,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { IndexPattern, IndexPatternField } from '../../../../../../../../plugins/data/public';
|
||||
import {
|
||||
ChromeDocTitle,
|
||||
NotificationsStart,
|
||||
OverlayStart,
|
||||
} from '../../../../../../../../core/public';
|
||||
import { IndexPatternManagementStart } from '../../../../../../../../plugins/index_pattern_management/public';
|
||||
import { Tabs } from './tabs';
|
||||
import { IndexHeader } from './index_header';
|
||||
|
||||
interface EditIndexPatternProps extends RouteComponentProps {
|
||||
indexPattern: IndexPattern;
|
||||
indexPatterns: IndexPattern[];
|
||||
config: Record<string, any>;
|
||||
services: {
|
||||
notifications: NotificationsStart;
|
||||
docTitle: ChromeDocTitle;
|
||||
overlays: OverlayStart;
|
||||
indexPatternManagement: IndexPatternManagementStart;
|
||||
};
|
||||
}
|
||||
|
||||
const mappingAPILink = i18n.translate(
|
||||
'kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink',
|
||||
{
|
||||
defaultMessage: 'Mapping API',
|
||||
}
|
||||
);
|
||||
|
||||
const mappingConflictHeader = i18n.translate(
|
||||
'kbn.management.editIndexPattern.mappingConflictHeader',
|
||||
{
|
||||
defaultMessage: 'Mapping conflict',
|
||||
}
|
||||
);
|
||||
|
||||
const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', {
|
||||
defaultMessage: 'This action resets the popularity counter of each field.',
|
||||
});
|
||||
|
||||
const confirmModalOptionsRefresh = {
|
||||
confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', {
|
||||
defaultMessage: 'Refresh',
|
||||
}),
|
||||
title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', {
|
||||
defaultMessage: 'Refresh field list?',
|
||||
}),
|
||||
};
|
||||
|
||||
const confirmModalOptionsDelete = {
|
||||
confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', {
|
||||
defaultMessage: 'Delete',
|
||||
}),
|
||||
title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', {
|
||||
defaultMessage: 'Delete index pattern?',
|
||||
}),
|
||||
};
|
||||
|
||||
export const EditIndexPattern = withRouter(
|
||||
({ indexPattern, indexPatterns, config, services, history, location }: EditIndexPatternProps) => {
|
||||
const [fields, setFields] = useState<IndexPatternField[]>(indexPattern.getNonScriptedFields());
|
||||
const [conflictedFields, setConflictedFields] = useState<IndexPatternField[]>(
|
||||
indexPattern.fields.filter(field => field.type === 'conflict')
|
||||
);
|
||||
const [defaultIndex, setDefaultIndex] = useState<string>(config.get('defaultIndex'));
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setFields(indexPattern.getNonScriptedFields());
|
||||
setConflictedFields(indexPattern.fields.filter(field => field.type === 'conflict'));
|
||||
}, [indexPattern, indexPattern.fields]);
|
||||
|
||||
useEffect(() => {
|
||||
const indexPatternTags =
|
||||
services.indexPatternManagement.list.getIndexPatternTags(
|
||||
indexPattern,
|
||||
indexPattern.id === defaultIndex
|
||||
) || [];
|
||||
setTags(indexPatternTags);
|
||||
}, [defaultIndex, indexPattern, services.indexPatternManagement.list]);
|
||||
|
||||
const setDefaultPattern = useCallback(() => {
|
||||
config.set('defaultIndex', indexPattern.id);
|
||||
setDefaultIndex(indexPattern.id || '');
|
||||
}, [config, indexPattern.id]);
|
||||
|
||||
const refreshFields = () => {
|
||||
services.overlays
|
||||
.openConfirm(confirmMessage, confirmModalOptionsRefresh)
|
||||
.then(async isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
await indexPattern.init(true);
|
||||
setFields(indexPattern.getNonScriptedFields());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removePattern = () => {
|
||||
function doRemove() {
|
||||
if (indexPattern.id === defaultIndex) {
|
||||
config.remove('defaultIndex');
|
||||
const otherPatterns = filter(indexPatterns, pattern => {
|
||||
return pattern.id !== indexPattern.id;
|
||||
});
|
||||
|
||||
if (otherPatterns.length) {
|
||||
config.set('defaultIndex', otherPatterns[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.resolve(indexPattern.destroy()).then(function() {
|
||||
history.push('/management/kibana/index_patterns');
|
||||
});
|
||||
}
|
||||
|
||||
services.overlays.openConfirm('', confirmModalOptionsDelete).then(isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
doRemove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const timeFilterHeader = i18n.translate('kbn.management.editIndexPattern.timeFilterHeader', {
|
||||
defaultMessage: "Time Filter field name: '{timeFieldName}'",
|
||||
values: { timeFieldName: indexPattern.timeFieldName },
|
||||
});
|
||||
|
||||
const mappingConflictLabel = i18n.translate(
|
||||
'kbn.management.editIndexPattern.mappingConflictLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'{conflictFieldsLength, plural, one {A field is} other {# fields are}} defined as several types (string, integer, etc) across the indices that match this pattern. You may still be able to use these conflict fields in parts of Kibana, but they will be unavailable for functions that require Kibana to know their type. Correcting this issue will require reindexing your data.',
|
||||
values: { conflictFieldsLength: conflictedFields.length },
|
||||
}
|
||||
);
|
||||
|
||||
services.docTitle.change(indexPattern.title);
|
||||
|
||||
const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<IndexHeader
|
||||
indexPattern={indexPattern}
|
||||
setDefault={setDefaultPattern}
|
||||
refreshFields={refreshFields}
|
||||
deleteIndexPattern={removePattern}
|
||||
defaultIndex={defaultIndex}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
{showTagsSection && (
|
||||
<EuiFlexGroup wrap>
|
||||
{Boolean(indexPattern.timeFieldName) && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="warning">{timeFilterHeader}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{tags.map((tag: any) => (
|
||||
<EuiFlexItem grow={false} key={tag.key}>
|
||||
<EuiBadge color="hollow">{tag.name}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.editIndexPattern.timeFilterLabel.timeFilterDetail"
|
||||
defaultMessage="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"
|
||||
values={{ indexPatternTitle: <strong>{indexPattern.title}</strong> }}
|
||||
/>{' '}
|
||||
<EuiLink
|
||||
href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html"
|
||||
target="_blank"
|
||||
>
|
||||
{mappingAPILink}
|
||||
<EuiIcon type="link" />
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
{conflictedFields.length > 0 && (
|
||||
<EuiCallOut title={mappingConflictHeader} color="warning" iconType="alert">
|
||||
<p>{mappingConflictLabel}</p>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<Tabs
|
||||
indexPattern={indexPattern}
|
||||
fields={fields}
|
||||
config={config}
|
||||
services={{
|
||||
indexPatternManagement: services.indexPatternManagement,
|
||||
}}
|
||||
history={history}
|
||||
location={location}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -25,7 +25,7 @@ import {
|
|||
} from '../../../../../../../../plugins/kibana_utils/public';
|
||||
|
||||
interface IEditIndexPatternState {
|
||||
tab: string; // TODO: type those 3 tabs with enum, when edit_index_pattern.js migrated to ts
|
||||
tab: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +38,6 @@ export function createEditIndexPatternPageStateContainer({
|
|||
defaultTab: string;
|
||||
useHashedUrl: boolean;
|
||||
}) {
|
||||
// until angular is used as shell - use hash history
|
||||
const history = createHashHistory();
|
||||
// query param to store app state at
|
||||
const stateStorageKey = '_a';
|
||||
|
@ -78,12 +77,10 @@ export function createEditIndexPatternPageStateContainer({
|
|||
// makes sure initial url is the same as initial state (this is not really required)
|
||||
kbnUrlStateStorage.set(stateStorageKey, stateContainer.getState(), { replace: true });
|
||||
|
||||
// expose api needed for Controller
|
||||
return {
|
||||
startSyncingState: start,
|
||||
stopSyncingState: stop,
|
||||
setCurrentTab: (newTab: string) => stateContainer.transitions.setTab(newTab),
|
||||
getCurrentTab: () => stateContainer.selectors.tab(),
|
||||
state$: stateContainer.state$,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
function filterBy(items, key, filter) {
|
||||
const lowercaseFilter = (filter || '').toLowerCase();
|
||||
return items.filter(item => item[key].toLowerCase().includes(lowercaseFilter));
|
||||
}
|
||||
|
||||
function getCounts(fields, sourceFilters, fieldFilter = '') {
|
||||
const fieldCount = _.countBy(filterBy(fields, 'name', fieldFilter), function(field) {
|
||||
return field.scripted ? 'scripted' : 'indexed';
|
||||
});
|
||||
|
||||
_.defaults(fieldCount, {
|
||||
indexed: 0,
|
||||
scripted: 0,
|
||||
sourceFilters: sourceFilters ? filterBy(sourceFilters, 'value', fieldFilter).length : 0,
|
||||
});
|
||||
|
||||
return fieldCount;
|
||||
}
|
||||
|
||||
export function IndicesEditSectionsProvider() {
|
||||
return function(indexPattern, fieldFilter, indexPatternListProvider) {
|
||||
const totalCount = getCounts(indexPattern.fields, indexPattern.sourceFilters);
|
||||
const filteredCount = getCounts(indexPattern.fields, indexPattern.sourceFilters, fieldFilter);
|
||||
|
||||
const editSections = [];
|
||||
|
||||
editSections.push({
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.fieldsHeader', {
|
||||
defaultMessage: 'Fields',
|
||||
}),
|
||||
index: 'indexedFields',
|
||||
count: filteredCount.indexed,
|
||||
totalCount: totalCount.indexed,
|
||||
});
|
||||
|
||||
if (indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) {
|
||||
editSections.push({
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.scriptedHeader', {
|
||||
defaultMessage: 'Scripted fields',
|
||||
}),
|
||||
index: 'scriptedFields',
|
||||
count: filteredCount.scripted,
|
||||
totalCount: totalCount.scripted,
|
||||
});
|
||||
}
|
||||
|
||||
editSections.push({
|
||||
title: i18n.translate('kbn.management.editIndexPattern.tabs.sourceHeader', {
|
||||
defaultMessage: 'Source filters',
|
||||
}),
|
||||
index: 'sourceFilters',
|
||||
count: filteredCount.sourceFilters,
|
||||
totalCount: totalCount.sourceFilters,
|
||||
});
|
||||
|
||||
return editSections;
|
||||
};
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<div class="actions">
|
||||
<a
|
||||
data-test-subj="indexPatternFieldEditButton"
|
||||
ng-href="{{ kbnUrl.getRouteHref(field, 'edit') }}"
|
||||
aria-label="{{::'kbn.management.editIndexPattern.editFieldButton' | i18n: { defaultMessage: 'Edit' } }}"
|
||||
class="kuiButton kuiButton--basic kuiButton--small"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiIcon fa-pencil"></span>
|
||||
</a>
|
||||
|
||||
<button
|
||||
ng-if="field.scripted"
|
||||
ng-click="remove(field)"
|
||||
class="kuiButton kuiButton--danger kuiButton--small"
|
||||
aria-label="{{::'kbn.management.editIndexPattern.deleteFieldButton' | i18n: { defaultMessage: 'Delete' } }}"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiIcon fa-trash"></span>
|
||||
</button>
|
||||
</div>
|
|
@ -17,4 +17,159 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './edit_index_pattern';
|
||||
import React from 'react';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import template from './edit_index_pattern.html';
|
||||
import createEditFieldtemplate from './create_edit_field.html';
|
||||
import {
|
||||
getEditBreadcrumbs,
|
||||
getEditFieldBreadcrumbs,
|
||||
getCreateFieldBreadcrumbs,
|
||||
} from '../breadcrumbs';
|
||||
import { EditIndexPattern } from './edit_index_pattern';
|
||||
import { CreateEditField } from './create_edit_field';
|
||||
|
||||
const REACT_EDIT_INDEX_PATTERN_DOM_ELEMENT_ID = 'reactEditIndexPattern';
|
||||
|
||||
function destroyEditIndexPattern() {
|
||||
const node = document.getElementById(REACT_EDIT_INDEX_PATTERN_DOM_ELEMENT_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
}
|
||||
|
||||
function renderEditIndexPattern($scope, config, $route) {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_EDIT_INDEX_PATTERN_DOM_ELEMENT_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<HashRouter>
|
||||
<I18nContext>
|
||||
<EditIndexPattern
|
||||
indexPattern={$route.current.locals.indexPattern}
|
||||
indexPatterns={$route.current.locals.indexPatterns}
|
||||
config={config}
|
||||
services={{
|
||||
notifications: npStart.core.notifications,
|
||||
docTitle: npStart.core.chrome.docTitle,
|
||||
overlays: npStart.core.overlays,
|
||||
indexPatternManagement: npStart.plugins.indexPatternManagement,
|
||||
}}
|
||||
/>
|
||||
</I18nContext>
|
||||
</HashRouter>,
|
||||
node
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', {
|
||||
template,
|
||||
k7Breadcrumbs: getEditBreadcrumbs,
|
||||
resolve: {
|
||||
indexPattern: function($route, Promise, redirectWhenMissing) {
|
||||
const { indexPatterns } = npStart.plugins.data;
|
||||
return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch(
|
||||
redirectWhenMissing('/management/kibana/index_patterns')
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
uiModules
|
||||
.get('apps/management')
|
||||
.controller('managementIndexPatternsEdit', function($scope, $route, config) {
|
||||
$scope.$on('$destroy', () => {
|
||||
destroyEditIndexPattern();
|
||||
});
|
||||
|
||||
renderEditIndexPattern($scope, config, $route);
|
||||
});
|
||||
|
||||
// routes for create edit field. Will be removed after migartion all component to react.
|
||||
const REACT_FIELD_EDITOR_ID = 'reactFieldEditor';
|
||||
const renderCreateEditField = ($scope, $route, getConfig, $http, fieldFormatEditors) => {
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_FIELD_EDITOR_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<HashRouter>
|
||||
<I18nContext>
|
||||
<CreateEditField
|
||||
indexPattern={$route.current.locals.indexPattern}
|
||||
mode={$route.current.mode}
|
||||
fieldName={$route.current.params.fieldName}
|
||||
fieldFormatEditors={fieldFormatEditors}
|
||||
getConfig={getConfig}
|
||||
services={{
|
||||
http: $http,
|
||||
notifications: npStart.core.notifications,
|
||||
docTitle: npStart.core.chrome.docTitle,
|
||||
}}
|
||||
/>
|
||||
</I18nContext>
|
||||
</HashRouter>,
|
||||
node
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const destroyCreateEditField = () => {
|
||||
const node = document.getElementById(REACT_FIELD_EDITOR_ID);
|
||||
node && unmountComponentAtNode(node);
|
||||
};
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/index_patterns/:indexPatternId/field/:fieldName*', {
|
||||
mode: 'edit',
|
||||
k7Breadcrumbs: getEditFieldBreadcrumbs,
|
||||
})
|
||||
.when('/management/kibana/index_patterns/:indexPatternId/create-field/', {
|
||||
mode: 'create',
|
||||
k7Breadcrumbs: getCreateFieldBreadcrumbs,
|
||||
})
|
||||
.defaults(/management\/kibana\/index_patterns\/[^\/]+\/(field|create-field)(\/|$)/, {
|
||||
template: createEditFieldtemplate,
|
||||
mapBreadcrumbs($route, breadcrumbs) {
|
||||
const { indexPattern } = $route.current.locals;
|
||||
return breadcrumbs.map(crumb => {
|
||||
if (crumb.id !== indexPattern.id) {
|
||||
return crumb;
|
||||
}
|
||||
|
||||
return {
|
||||
...crumb,
|
||||
display: indexPattern.title,
|
||||
};
|
||||
});
|
||||
},
|
||||
resolve: {
|
||||
indexPattern: function($route, Promise, redirectWhenMissing) {
|
||||
const { indexPatterns } = npStart.plugins.data;
|
||||
return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch(
|
||||
redirectWhenMissing('/management/kibana/index_patterns')
|
||||
);
|
||||
},
|
||||
},
|
||||
controllerAs: 'fieldSettings',
|
||||
controller: function FieldEditorPageController($scope, $route, $http, Private, config) {
|
||||
const getConfig = (...args) => config.get(...args);
|
||||
const fieldFormatEditors = Private(RegistryFieldFormatEditorsProvider);
|
||||
|
||||
renderCreateEditField($scope, $route, getConfig, $http, fieldFormatEditors);
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
destroyCreateEditField();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -92,7 +92,7 @@ export function IndexHeader({
|
|||
<EuiButtonIcon
|
||||
color="text"
|
||||
onClick={setDefault}
|
||||
iconType="starFilledSpace"
|
||||
iconType="starFilled"
|
||||
aria-label={setDefaultAriaLabel}
|
||||
data-test-subj="setDefaultIndexPatternButton"
|
||||
/>
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import { createSelector } from 'reselect';
|
||||
import { IndexPatternField, IIndexPattern } from '../../../../../../../../../plugins/data/public';
|
||||
import {
|
||||
IndexPatternField,
|
||||
IIndexPattern,
|
||||
IFieldType,
|
||||
} from '../../../../../../../../../plugins/data/public';
|
||||
import { Table } from './components/table';
|
||||
import { getFieldFormat } from './lib';
|
||||
import { IndexedFieldItem } from './types';
|
||||
|
@ -31,7 +35,7 @@ interface IndexedFieldsTableProps {
|
|||
indexedFieldTypeFilter?: string;
|
||||
helpers: {
|
||||
redirectToRoute: (obj: any) => void;
|
||||
getFieldInfo: (indexPattern: IIndexPattern, field: string) => string[];
|
||||
getFieldInfo: (indexPattern: IIndexPattern, field: IFieldType) => string[];
|
||||
};
|
||||
fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean;
|
||||
}
|
||||
|
@ -76,7 +80,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),
|
||||
info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field),
|
||||
};
|
||||
})) ||
|
||||
[]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
addScriptedFieldUrl="#"
|
||||
addScriptedFieldUrl="http://localhost/#/management/kibana/index_patterns/undefined/create-field/"
|
||||
/>
|
||||
<call-outs
|
||||
deprecatedLangsInUse={
|
||||
|
@ -45,7 +45,7 @@ exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = `
|
|||
exports[`ScriptedFieldsTable should filter based on the query bar 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
addScriptedFieldUrl="#"
|
||||
addScriptedFieldUrl="http://localhost/#/management/kibana/index_patterns/undefined/create-field/"
|
||||
/>
|
||||
<call-outs
|
||||
deprecatedLangsInUse={Array []}
|
||||
|
@ -78,7 +78,7 @@ exports[`ScriptedFieldsTable should filter based on the query bar 1`] = `
|
|||
exports[`ScriptedFieldsTable should hide the table if there are no scripted fields 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
addScriptedFieldUrl="#"
|
||||
addScriptedFieldUrl="http://localhost/#/management/kibana/index_patterns/undefined/create-field/"
|
||||
/>
|
||||
<call-outs
|
||||
deprecatedLangsInUse={Array []}
|
||||
|
@ -103,7 +103,7 @@ exports[`ScriptedFieldsTable should hide the table if there are no scripted fiel
|
|||
exports[`ScriptedFieldsTable should render normally 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
addScriptedFieldUrl="#"
|
||||
addScriptedFieldUrl="http://localhost/#/management/kibana/index_patterns/undefined/create-field/"
|
||||
/>
|
||||
<call-outs
|
||||
deprecatedLangsInUse={Array []}
|
||||
|
@ -141,7 +141,7 @@ exports[`ScriptedFieldsTable should render normally 1`] = `
|
|||
exports[`ScriptedFieldsTable should show a delete modal 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
addScriptedFieldUrl="#"
|
||||
addScriptedFieldUrl="http://localhost/#/management/kibana/index_patterns/undefined/create-field/"
|
||||
/>
|
||||
<call-outs
|
||||
deprecatedLangsInUse={Array []}
|
||||
|
|
|
@ -37,7 +37,7 @@ interface ScriptedFieldsTableProps {
|
|||
scriptedFieldLanguageFilter?: string;
|
||||
helpers: {
|
||||
redirectToRoute: Function;
|
||||
getRouteHref: Function;
|
||||
getRouteHref?: Function;
|
||||
};
|
||||
onRemoveField?: () => void;
|
||||
}
|
||||
|
@ -136,14 +136,19 @@ export class ScriptedFieldsTable extends Component<
|
|||
};
|
||||
|
||||
render() {
|
||||
const { helpers, indexPattern } = this.props;
|
||||
const { indexPattern } = this.props;
|
||||
const { fieldToDelete, deprecatedLangsInUse } = this.state;
|
||||
|
||||
const items = this.getFilteredItems();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header addScriptedFieldUrl={helpers.getRouteHref(indexPattern, 'addField')} />
|
||||
<Header
|
||||
addScriptedFieldUrl={`${window.location.origin +
|
||||
window.location.pathname}#/management/kibana/index_patterns/${
|
||||
indexPattern.id
|
||||
}/create-field/`}
|
||||
/>
|
||||
|
||||
<CallOuts
|
||||
deprecatedLangsInUse={deprecatedLangsInUse}
|
||||
|
|
|
@ -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 { Tabs } from './tabs';
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback, useEffect, Fragment, useMemo } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTabbedContent,
|
||||
EuiTabbedContentTab,
|
||||
EuiSpacer,
|
||||
EuiFieldSearch,
|
||||
EuiSelect,
|
||||
EuiSelectOption,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { fieldWildcardMatcher } from '../../../../../../../../../plugins/kibana_utils/public';
|
||||
import { IndexPatternManagementStart } from '../../../../../../../../../plugins/index_pattern_management/public';
|
||||
import { IndexPattern, IndexPatternField } from '../../../../../../../../../plugins/data/public';
|
||||
import { createEditIndexPatternPageStateContainer } from '../edit_index_pattern_state_container';
|
||||
import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../constants';
|
||||
import { SourceFiltersTable } from '../source_filters_table';
|
||||
import { IndexedFieldsTable } from '../indexed_fields_table';
|
||||
import { ScriptedFieldsTable } from '../scripted_fields_table';
|
||||
import { getTabs, getPath, convertToEuiSelectOption } from './utils';
|
||||
|
||||
interface TabsProps extends Pick<RouteComponentProps, 'history' | 'location'> {
|
||||
indexPattern: IndexPattern;
|
||||
config: Record<string, any>;
|
||||
fields: IndexPatternField[];
|
||||
services: {
|
||||
indexPatternManagement: IndexPatternManagementStart;
|
||||
};
|
||||
}
|
||||
|
||||
const filterAriaLabel = i18n.translate('kbn.management.editIndexPattern.fields.filterAria', {
|
||||
defaultMessage: 'Filter',
|
||||
});
|
||||
|
||||
const filterPlaceholder = i18n.translate(
|
||||
'kbn.management.editIndexPattern.fields.filterPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Filter',
|
||||
}
|
||||
);
|
||||
|
||||
export function Tabs({ config, indexPattern, fields, services, history, location }: TabsProps) {
|
||||
const [fieldFilter, setFieldFilter] = useState<string>('');
|
||||
const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState<string>('');
|
||||
const [scriptedFieldLanguageFilter, setScriptedFieldLanguageFilter] = useState<string>('');
|
||||
const [indexedFieldTypes, setIndexedFieldType] = useState<EuiSelectOption[]>([]);
|
||||
const [scriptedFieldLanguages, setScriptedFieldLanguages] = useState<EuiSelectOption[]>([]);
|
||||
const [syncingStateFunc, setSyncingStateFunc] = useState<any>({
|
||||
getCurrentTab: () => TAB_INDEXED_FIELDS,
|
||||
});
|
||||
|
||||
const refreshFilters = useCallback(() => {
|
||||
const tempIndexedFieldTypes: string[] = [];
|
||||
const tempScriptedFieldLanguages: string[] = [];
|
||||
indexPattern.fields.forEach(field => {
|
||||
if (field.scripted) {
|
||||
if (field.lang) {
|
||||
tempScriptedFieldLanguages.push(field.lang);
|
||||
}
|
||||
} else {
|
||||
tempIndexedFieldTypes.push(field.type);
|
||||
}
|
||||
});
|
||||
|
||||
setIndexedFieldType(convertToEuiSelectOption(tempIndexedFieldTypes, 'indexedFiledTypes'));
|
||||
setScriptedFieldLanguages(
|
||||
convertToEuiSelectOption(tempScriptedFieldLanguages, 'scriptedFieldLanguages')
|
||||
);
|
||||
}, [indexPattern]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFilters();
|
||||
}, [indexPattern, indexPattern.fields, refreshFilters]);
|
||||
|
||||
const fieldWildcardMatcherDecorated = useCallback(
|
||||
(filters: string[]) => fieldWildcardMatcher(filters, config.get('metaFields')),
|
||||
[config]
|
||||
);
|
||||
|
||||
const getFilterSection = useCallback(
|
||||
(type: string) => {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiFieldSearch
|
||||
placeholder={filterPlaceholder}
|
||||
value={fieldFilter}
|
||||
onChange={e => setFieldFilter(e.target.value)}
|
||||
data-test-subj="indexPatternFieldFilter"
|
||||
aria-label={filterAriaLabel}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{type === TAB_INDEXED_FIELDS && indexedFieldTypes.length > 0 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
options={indexedFieldTypes}
|
||||
value={indexedFieldTypeFilter}
|
||||
onChange={e => setIndexedFieldTypeFilter(e.target.value)}
|
||||
data-test-subj="indexedFieldTypeFilterDropdown"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{type === TAB_SCRIPTED_FIELDS && scriptedFieldLanguages.length > 0 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
options={scriptedFieldLanguages}
|
||||
value={scriptedFieldLanguageFilter}
|
||||
onChange={e => setScriptedFieldLanguageFilter(e.target.value)}
|
||||
data-test-subj="scriptedFieldLanguageFilterDropdown"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
[
|
||||
fieldFilter,
|
||||
indexedFieldTypeFilter,
|
||||
indexedFieldTypes,
|
||||
scriptedFieldLanguageFilter,
|
||||
scriptedFieldLanguages,
|
||||
]
|
||||
);
|
||||
|
||||
const getContent = useCallback(
|
||||
(type: string) => {
|
||||
switch (type) {
|
||||
case TAB_INDEXED_FIELDS:
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
{getFilterSection(type)}
|
||||
<EuiSpacer size="m" />
|
||||
<IndexedFieldsTable
|
||||
fields={fields}
|
||||
indexPattern={indexPattern}
|
||||
fieldFilter={fieldFilter}
|
||||
fieldWildcardMatcher={fieldWildcardMatcherDecorated}
|
||||
indexedFieldTypeFilter={indexedFieldTypeFilter}
|
||||
helpers={{
|
||||
redirectToRoute: (field: IndexPatternField) => {
|
||||
history.push(getPath(field));
|
||||
},
|
||||
getFieldInfo: services.indexPatternManagement.list.getFieldInfo,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
case TAB_SCRIPTED_FIELDS:
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
{getFilterSection(type)}
|
||||
<EuiSpacer size="m" />
|
||||
<ScriptedFieldsTable
|
||||
indexPattern={indexPattern}
|
||||
fieldFilter={fieldFilter}
|
||||
scriptedFieldLanguageFilter={scriptedFieldLanguageFilter}
|
||||
helpers={{
|
||||
redirectToRoute: (field: IndexPatternField) => {
|
||||
history.push(getPath(field));
|
||||
},
|
||||
}}
|
||||
onRemoveField={refreshFilters}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
case TAB_SOURCE_FILTERS:
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
{getFilterSection(type)}
|
||||
<EuiSpacer size="m" />
|
||||
<SourceFiltersTable
|
||||
indexPattern={indexPattern}
|
||||
filterFilter={fieldFilter}
|
||||
fieldWildcardMatcher={fieldWildcardMatcherDecorated}
|
||||
onAddOrRemoveFilter={refreshFilters}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
fieldFilter,
|
||||
fieldWildcardMatcherDecorated,
|
||||
fields,
|
||||
getFilterSection,
|
||||
history,
|
||||
indexPattern,
|
||||
indexedFieldTypeFilter,
|
||||
refreshFilters,
|
||||
scriptedFieldLanguageFilter,
|
||||
services.indexPatternManagement.list.getFieldInfo,
|
||||
]
|
||||
);
|
||||
|
||||
const euiTabs: EuiTabbedContentTab[] = useMemo(
|
||||
() =>
|
||||
getTabs(indexPattern, fieldFilter, services.indexPatternManagement.list).map(
|
||||
(tab: Pick<EuiTabbedContentTab, 'name' | 'id'>) => {
|
||||
return {
|
||||
...tab,
|
||||
content: getContent(tab.id),
|
||||
};
|
||||
}
|
||||
),
|
||||
[fieldFilter, getContent, indexPattern, services.indexPatternManagement.list]
|
||||
);
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(euiTabs[0].id);
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
startSyncingState,
|
||||
stopSyncingState,
|
||||
setCurrentTab,
|
||||
getCurrentTab,
|
||||
} = createEditIndexPatternPageStateContainer({
|
||||
useHashedUrl: config.get('state:storeInSessionStorage'),
|
||||
defaultTab: TAB_INDEXED_FIELDS,
|
||||
});
|
||||
|
||||
startSyncingState();
|
||||
setSyncingStateFunc({
|
||||
setCurrentTab,
|
||||
getCurrentTab,
|
||||
});
|
||||
setSelectedTabId(getCurrentTab());
|
||||
|
||||
return () => {
|
||||
stopSyncingState();
|
||||
};
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
tabs={euiTabs}
|
||||
selectedTab={euiTabs.find(tab => tab.id === selectedTabId)}
|
||||
onTabClick={tab => {
|
||||
setSelectedTabId(tab.id);
|
||||
syncingStateFunc.setCurrentTab(tab.id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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 { Dictionary, countBy, defaults, unique } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IndexPattern, IndexPatternField } from '../../../../../../../../../plugins/data/public';
|
||||
import { IndexPatternManagementStart } from '../../../../../../../../../plugins/index_pattern_management/public';
|
||||
import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../constants';
|
||||
|
||||
function filterByName(items: IndexPatternField[], filter: string) {
|
||||
const lowercaseFilter = (filter || '').toLowerCase();
|
||||
return items.filter(item => item.name.toLowerCase().includes(lowercaseFilter));
|
||||
}
|
||||
|
||||
function getCounts(
|
||||
fields: IndexPatternField[],
|
||||
sourceFilters: {
|
||||
excludes: string[];
|
||||
},
|
||||
fieldFilter = ''
|
||||
) {
|
||||
const fieldCount = countBy(filterByName(fields, fieldFilter), function(field) {
|
||||
return field.scripted ? 'scripted' : 'indexed';
|
||||
});
|
||||
|
||||
defaults(fieldCount, {
|
||||
indexed: 0,
|
||||
scripted: 0,
|
||||
sourceFilters: sourceFilters.excludes
|
||||
? sourceFilters.excludes.filter(value =>
|
||||
value.toLowerCase().includes(fieldFilter.toLowerCase())
|
||||
).length
|
||||
: 0,
|
||||
});
|
||||
|
||||
return fieldCount;
|
||||
}
|
||||
|
||||
function getTitle(type: string, filteredCount: Dictionary<number>, totalCount: Dictionary<number>) {
|
||||
let title = '';
|
||||
switch (type) {
|
||||
case 'indexed':
|
||||
title = i18n.translate('kbn.management.editIndexPattern.tabs.fieldsHeader', {
|
||||
defaultMessage: 'Fields',
|
||||
});
|
||||
break;
|
||||
case 'scripted':
|
||||
title = i18n.translate('kbn.management.editIndexPattern.tabs.scriptedHeader', {
|
||||
defaultMessage: 'Scripted fields',
|
||||
});
|
||||
break;
|
||||
case 'sourceFilters':
|
||||
title = i18n.translate('kbn.management.editIndexPattern.tabs.sourceHeader', {
|
||||
defaultMessage: 'Source filters',
|
||||
});
|
||||
break;
|
||||
}
|
||||
const count = ` (${
|
||||
filteredCount[type] === totalCount[type]
|
||||
? filteredCount[type]
|
||||
: filteredCount[type] + ' / ' + totalCount[type]
|
||||
})`;
|
||||
return title + count;
|
||||
}
|
||||
|
||||
export function getTabs(
|
||||
indexPattern: IndexPattern,
|
||||
fieldFilter: string,
|
||||
indexPatternListProvider: IndexPatternManagementStart['list']
|
||||
) {
|
||||
const totalCount = getCounts(indexPattern.fields, indexPattern.getSourceFiltering());
|
||||
const filteredCount = getCounts(
|
||||
indexPattern.fields,
|
||||
indexPattern.getSourceFiltering(),
|
||||
fieldFilter
|
||||
);
|
||||
|
||||
const tabs = [];
|
||||
|
||||
tabs.push({
|
||||
name: getTitle('indexed', filteredCount, totalCount),
|
||||
id: TAB_INDEXED_FIELDS,
|
||||
});
|
||||
|
||||
if (indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) {
|
||||
tabs.push({
|
||||
name: getTitle('scripted', filteredCount, totalCount),
|
||||
id: TAB_SCRIPTED_FIELDS,
|
||||
});
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
name: getTitle('sourceFilters', filteredCount, totalCount),
|
||||
id: TAB_SOURCE_FILTERS,
|
||||
});
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
export function getPath(field: IndexPatternField) {
|
||||
return `/management/kibana/index_patterns/${field.indexPattern?.id}/field/${field.name}`;
|
||||
}
|
||||
|
||||
const allTypesDropDown = i18n.translate('kbn.management.editIndexPattern.fields.allTypesDropDown', {
|
||||
defaultMessage: 'All field types',
|
||||
});
|
||||
|
||||
const allLangsDropDown = i18n.translate('kbn.management.editIndexPattern.fields.allLangsDropDown', {
|
||||
defaultMessage: 'All languages',
|
||||
});
|
||||
|
||||
export function convertToEuiSelectOption(options: string[], type: string) {
|
||||
const euiOptions =
|
||||
options.length > 0
|
||||
? [
|
||||
{
|
||||
value: '',
|
||||
text: type === 'scriptedFieldLanguages' ? allLangsDropDown : allTypesDropDown,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
return euiOptions.concat(
|
||||
unique(options).map(option => {
|
||||
return {
|
||||
value: option,
|
||||
text: option,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
|
@ -206,15 +206,17 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
|
||||
async getFieldsTabCount() {
|
||||
return retry.try(async () => {
|
||||
const text = await testSubjects.getVisibleText('tab-count-indexedFields');
|
||||
return text.replace(/\((.*)\)/, '$1');
|
||||
const indexedFieldsTab = await find.byCssSelector('#indexedFields .euiTab__content');
|
||||
const text = await indexedFieldsTab.getVisibleText();
|
||||
return text.split(/[()]/)[1];
|
||||
});
|
||||
}
|
||||
|
||||
async getScriptedFieldsTabCount() {
|
||||
return await retry.try(async () => {
|
||||
const theText = await testSubjects.getVisibleText('tab-count-scriptedFields');
|
||||
return theText.replace(/\((.*)\)/, '$1');
|
||||
const scriptedFieldsTab = await find.byCssSelector('#scriptedFields .euiTab__content');
|
||||
const text = await scriptedFieldsTab.getVisibleText();
|
||||
return text.split(/[()]/)[1];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -241,13 +243,13 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
|
||||
async setFieldTypeFilter(type: string) {
|
||||
await find.clickByCssSelector(
|
||||
'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]'
|
||||
'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[value="' + type + '"]'
|
||||
);
|
||||
}
|
||||
|
||||
async setScriptedFieldLanguageFilter(language: string) {
|
||||
await find.clickByCssSelector(
|
||||
'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' +
|
||||
'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[value="' +
|
||||
language +
|
||||
'"]'
|
||||
);
|
||||
|
@ -412,17 +414,17 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
|
||||
async clickFieldsTab() {
|
||||
log.debug('click Fields tab');
|
||||
await testSubjects.click('tab-indexFields');
|
||||
await find.clickByCssSelector('#indexedFields');
|
||||
}
|
||||
|
||||
async clickScriptedFieldsTab() {
|
||||
log.debug('click Scripted Fields tab');
|
||||
await testSubjects.click('tab-scriptedFields');
|
||||
await find.clickByCssSelector('#scriptedFields');
|
||||
}
|
||||
|
||||
async clickSourceFiltersTab() {
|
||||
log.debug('click Source Filters tab');
|
||||
await testSubjects.click('tab-sourceFilters');
|
||||
await find.clickByCssSelector('#sourceFilters');
|
||||
}
|
||||
|
||||
async editScriptedField(name: string) {
|
||||
|
|
|
@ -2186,10 +2186,8 @@
|
|||
"kbn.management.createIndexPatternHeader": "{indexPatternName} の作成",
|
||||
"kbn.management.createIndexPatternLabel": "Kibana は、可視化などを目的に Elasticsearch インデックスからデータを取得するために、インデックスパターンを使用します。",
|
||||
"kbn.management.editIndexPattern.deleteButton": "削除",
|
||||
"kbn.management.editIndexPattern.deleteFieldButton": "削除",
|
||||
"kbn.management.editIndexPattern.deleteHeader": "インデックスパターンを削除しますか?",
|
||||
"kbn.management.editIndexPattern.detailsAria": "インデックスパターンの詳細",
|
||||
"kbn.management.editIndexPattern.editFieldButton": "編集",
|
||||
"kbn.management.editIndexPattern.fields.allLangsDropDown": "すべての言語",
|
||||
"kbn.management.editIndexPattern.fields.allTypesDropDown": "すべてのフィールドタイプ",
|
||||
"kbn.management.editIndexPattern.fields.filterAria": "フィルター",
|
||||
|
@ -2215,7 +2213,6 @@
|
|||
"kbn.management.editIndexPattern.fields.table.typeHeader": "タイプ",
|
||||
"kbn.management.editIndexPattern.mappingConflictHeader": "マッピングの矛盾",
|
||||
"kbn.management.editIndexPattern.mappingConflictLabel": "{conflictFieldsLength, plural, one {フィールドが} other {# フィールドが}}このパターンと一致するインデックスの間で異なるタイプ (文字列、整数など) に定義されています。これらの矛盾したフィールドは Kibana の一部で使用できますが、Kibana がタイプを把握しなければならない機能には使用できません。この問題を修正するにはデータのレンダリングが必要です。",
|
||||
"kbn.management.editIndexPattern.notDateErrorMessage": "このフィールドは日付ではなく {fieldType} です。",
|
||||
"kbn.management.editIndexPattern.refreshAria": "フィールドリストを再度読み込みます",
|
||||
"kbn.management.editIndexPattern.refreshButton": "更新",
|
||||
"kbn.management.editIndexPattern.refreshHeader": "フィールドリストを更新しますか?",
|
||||
|
|
|
@ -2187,10 +2187,8 @@
|
|||
"kbn.management.createIndexPatternHeader": "创建 {indexPatternName}",
|
||||
"kbn.management.createIndexPatternLabel": "Kibana 使用索引模式从 Elasticsearch 索引中检索数据,以实现诸如可视化等功能。",
|
||||
"kbn.management.editIndexPattern.deleteButton": "删除",
|
||||
"kbn.management.editIndexPattern.deleteFieldButton": "删除",
|
||||
"kbn.management.editIndexPattern.deleteHeader": "删除索引模式?",
|
||||
"kbn.management.editIndexPattern.detailsAria": "索引模式详细信息",
|
||||
"kbn.management.editIndexPattern.editFieldButton": "编辑",
|
||||
"kbn.management.editIndexPattern.fields.allLangsDropDown": "所有语言",
|
||||
"kbn.management.editIndexPattern.fields.allTypesDropDown": "所有字段类型",
|
||||
"kbn.management.editIndexPattern.fields.filterAria": "筛选",
|
||||
|
@ -2216,7 +2214,6 @@
|
|||
"kbn.management.editIndexPattern.fields.table.typeHeader": "类型",
|
||||
"kbn.management.editIndexPattern.mappingConflictHeader": "映射冲突",
|
||||
"kbn.management.editIndexPattern.mappingConflictLabel": "匹配此模式的各个索引中{conflictFieldsLength, plural, one {一个字段已} other {# 个字段已}}定义为若干类型(字符串、整数等)。您仍能够在 Kibana 的各个部分中使用这些冲突类型,但它们将无法用于需要 Kibana 知道其类型的函数。要解决此问题,需要重新索引您的数据。",
|
||||
"kbn.management.editIndexPattern.notDateErrorMessage": "该字段是{fieldType},不是日期。",
|
||||
"kbn.management.editIndexPattern.refreshAria": "重新加载字段列表",
|
||||
"kbn.management.editIndexPattern.refreshButton": "刷新",
|
||||
"kbn.management.editIndexPattern.refreshHeader": "刷新字段列表?",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue