Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Index pattern management UI -> TypeScript and New Platform Ready (edit_index_pattern) #64184

Merged
merged 11 commits into from
Apr 24, 2020
Next Next commit
draft
  • Loading branch information
VladLasitsa committed Apr 21, 2020
commit aa3aa5675012fcd28c962b64090d7b0971bb7ffb
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* 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 React, { useEffect, useState, useCallback } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
// @ts-ignore
import { FieldEditor } from 'ui/field_editor';

import {
EuiFlexGroup,
EuiFlexItem,
EuiTabbedContent,
EuiTabbedContentTab,
EuiSpacer,
EuiBadge,
EuiText,
EuiLink,
EuiIcon,
EuiCallOut,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { IndexHeader } from './index_header';
import { IndexPattern, IndexPatternField } from '../../../../../../../../plugins/data/public';
import { ChromeDocTitle, NotificationsStart } from '../../../../../../../../core/public';

// @ts-ignore
import { getTabs } from './edit_sections';

interface EditIndexPatternProps extends RouteComponentProps {
indexPattern: IndexPattern;
mode?: string;
fieldName?: string;
fieldFormatEditors: any;
config: Record<string, any>;
services: {
notifications: NotificationsStart;
docTitle: ChromeDocTitle;
indexPatternManagement: Record<string, any>;
http: Function;
};
}

const mappingAPILink = i18n.translate(
'kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink',
{
defaultMessage: 'Mapping API',
}
);

const mappingConflictHeader = i18n.translate(
'kbn.management.editIndexPattern.mappingConflictHeader',
{
defaultMessage: 'Mapping conflict',
}
);

export const EditIndexPattern = withRouter(
({
indexPattern,
mode,
fieldName,
fieldFormatEditors,
config,
services,
history,
}: EditIndexPatternProps) => {
const [fieldFilter, setFieldFilter] = useState<string>('');
const [tabs, setTabs] = useState<EuiTabbedContentTab[]>(
getTabs(indexPattern, fieldFilter, services.indexPatternManagement.list)
);
const [selectedTab, setSelectedTab] = useState<EuiTabbedContentTab>(tabs[0]);
const [fields, setFields] = useState<IndexPatternField[]>(indexPattern.getNonScriptedFields());
const [conflictedFields, setConflictedFields] = useState<IndexPatternField[]>(
indexPattern.fields.filter(field => field.type === 'conflict')
);
indexPattern.tags =
services.indexPatternManagement.list.getIndexPatternTags(
indexPattern,
indexPattern.id === config.get('defaultIndex')
) || [];

const refreshFilters = useCallback(() => {
const indexedFieldTypes = [];
const scriptedFieldLanguages = [];
indexPattern.fields.forEach(field => {
if (field.scripted) {
scriptedFieldLanguages.push(field.lang);
} else {
indexedFieldTypes.push(field.type);
}
});

indexedFieldTypes = _.unique(indexedFieldTypes);
scriptedFieldLanguages = _.unique(scriptedFieldLanguages);
}, [indexPattern]);

useEffect(() => {
refreshFilters();
setFields(indexPattern.getNonScriptedFields());
setConflictedFields(indexPattern.fields.filter(field => field.type === 'conflict'));
}, [indexPattern, refreshFilters]);

const onTabClick = (tab: EuiTabbedContentTab) => {
setSelectedTab(tab);
};

const timeFilterHeader = i18n.translate('kbn.management.editIndexPattern.timeFilterHeader', {
defaultMessage: "Time Filter field name: '{timeFieldName}'",
values: { timeFieldName: indexPattern.timeFieldName },
});

const timeFilterDetail = i18n.translate(
'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: { html_indexPatternTitle: '<strong>' + indexPattern.title + '</strong>' },
}
);

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 },
}
);

return (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<IndexHeader indexPattern={indexPattern} defaultIndex={config.get('defaultIndex')} />
<EuiSpacer size="s" />
{indexPattern.timeFieldName ||
(indexPattern.tags && indexPattern.tags.length && (
<EuiFlexGroup wrap responsive={false} gutterSize="xs">
{indexPattern.timeFieldName && (
<EuiFlexItem grow={false}>
<EuiBadge color="warning">{timeFilterHeader}</EuiBadge>
</EuiFlexItem>
)}
{indexPattern.tags.map(tag => (
<EuiFlexItem grow={false} key={tag.id}>
<EuiBadge color="hollow">{tag.name}</EuiBadge>
</EuiFlexItem>
))}
</EuiFlexGroup>
))}
<EuiSpacer size="m" />
<EuiText grow={false}>
<p>
{timeFilterDetail}
<EuiLink
href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html"
target="_blank"
>
{mappingAPILink}
<EuiIcon type="link" />
</EuiLink>
</p>
</EuiText>
<EuiSpacer size="m" />
{conflictedFields.length && (
<EuiCallOut title={mappingConflictHeader} color="warning" iconType="alert">
<p>{mappingConflictLabel}</p>
</EuiCallOut>
)}
</EuiFlexItem>
<EuiFlexItem>
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={selectedTab}
autoFocus="selected"
onTabClick={onTabClick}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
);
Original file line number Diff line number Diff line change
@@ -78,3 +78,54 @@ export function IndicesEditSectionsProvider() {
return editSections;
};
}

function getTitle(type, filteredCount, totalCount) {
let translateKey = '';
switch (type) {
case 'indexed':
translateKey = 'kbn.management.editIndexPattern.tabs.fieldsHeader';
break;
case 'scripted':
translateKey = 'kbn.management.editIndexPattern.tabs.scriptedHeader';
break;
case 'sourceFilters':
translateKey = 'kbn.management.editIndexPattern.tabs.sourceHeader';
break;
}
const count = `(${
filteredCount[type] === totalCount[type]
? filteredCount[type]
: filteredCount[type] + ' / ' + totalCount[type]
})`;
return (
i18n.translate(translateKey, {
defaultMessage: 'Fields',
}) + count
);
}

export function getTabs(indexPattern, fieldFilter, indexPatternListProvider) {
const totalCount = getCounts(indexPattern.fields, indexPattern.sourceFilters);
const filteredCount = getCounts(indexPattern.fields, indexPattern.sourceFilters, fieldFilter);

const tabs = [];

tabs.push({
name: getTitle('indexed', filteredCount, totalCount),
id: 'indexedFields',
});

if (indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) {
tabs.push({
name: getTitle('scripted', filteredCount, totalCount),
id: 'scriptedFields',
});
}

tabs.push({
name: getTitle('sourceFilters', filteredCount, totalCount),
id: 'sourceFilters',
});

return tabs;
}