diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts new file mode 100644 index 0000000000000..5723a596f95d5 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts @@ -0,0 +1,86 @@ +/* + * 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 { createHashHistory } from 'history'; +import { + createStateContainer, + syncState, + createKbnUrlStateStorage, +} from '../../../../../../../../plugins/kibana_utils/public'; + +interface IEditIndexPatternState { + tab: string; +} + +/** + * Create state container with sync config for tab navigation specific for edit_index_pattern page + */ +export function createEditIndexPatternPageStateContainer({ + defaultTab, + useHashedUrl, +}: { + defaultTab: string; + useHashedUrl: boolean; +}) { + const history = createHashHistory(); + // query param to store app state at + const stateStorageKey = '_a'; + // default app state, when there is no initial state in the url + const defaultState = { + tab: defaultTab, + }; + const kbnUrlStateStorage = createKbnUrlStateStorage({ + useHash: useHashedUrl, + history, + }); + // extract starting app state from URL and use it as starting app state in state container + const initialStateFromUrl = kbnUrlStateStorage.get(stateStorageKey); + const stateContainer = createStateContainer( + { + ...defaultState, + ...initialStateFromUrl, + }, + { + setTab: (state: IEditIndexPatternState) => (tab: string) => ({ ...state, tab }), + }, + { + tab: (state: IEditIndexPatternState) => () => state.tab, + } + ); + + const { start, stop } = syncState({ + storageKey: stateStorageKey, + stateContainer: { + ...stateContainer, + // state syncing utility requires state containers to handle "null" + set: state => state && stateContainer.set(state), + }, + stateStorage: kbnUrlStateStorage, + }); + + // makes sure initial url is the same as initial state (this is not really required) + kbnUrlStateStorage.set(stateStorageKey, stateContainer.getState(), { replace: true }); + + return { + startSyncingState: start, + stopSyncingState: stop, + setCurrentTab: (newTab: string) => stateContainer.transitions.setTab(newTab), + getCurrentTab: () => stateContainer.selectors.tab(), + }; +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/tabs.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/tabs.tsx index 339d750065287..7d56d21791989 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/tabs.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/tabs/tabs.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useCallback, useEffect, Fragment } from 'react'; +import React, { useState, useCallback, useEffect, Fragment, useMemo } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiFlexGroup, @@ -33,10 +33,12 @@ 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 } from '../constants'; import { SourceFiltersTable } from '../source_filters_table'; import { IndexedFieldsTable } from '../indexed_fields_table'; import { ScriptedFieldsTable } from '../scripted_fields_table'; -import { getTabs, getTabIdFromURL, getPath, convertToEuiSelectOption } from './utils'; +import { getTabs, getPath, convertToEuiSelectOption } from './utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; @@ -64,6 +66,9 @@ export function Tabs({ config, indexPattern, fields, services, history, location const [scriptedFieldLanguageFilter, setScriptedFieldLanguageFilter] = useState(''); const [indexedFieldTypes, setIndexedFieldType] = useState([]); const [scriptedFieldLanguages, setScriptedFieldLanguages] = useState([]); + const [syncingStateFunc, setSyncingStateFunc] = useState({ + getCurrentTab: () => TAB_INDEXED_FIELDS, + }); const refreshFilters = useCallback(() => { const tempIndexedFieldTypes: string[] = []; @@ -88,117 +93,175 @@ export function Tabs({ config, indexPattern, fields, services, history, location refreshFilters(); }, [indexPattern, indexPattern.fields, refreshFilters]); - const fieldWildcardMatcherDecorated = (filters: string[]) => - fieldWildcardMatcher(filters, config.get('metaFields')); - - const getFilterSection = (type: string) => { - return ( - - - setFieldFilter(e.target.value)} - data-test-subj="indexPatternFieldFilter" - aria-label={filterAriaLabel} - /> - - {type === 'indexedFields' && indexedFieldTypes.length > 0 && ( - - setIndexedFieldTypeFilter(e.target.value)} - data-test-subj="indexedFieldTypeFilterDropdown" - /> - - )} - {type === 'scriptedFields' && scriptedFieldLanguages.length > 0 && ( - - setScriptedFieldLanguageFilter(e.target.value)} - data-test-subj="indexedFieldTypeFilterDropdown" + const fieldWildcardMatcherDecorated = useCallback( + (filters: string[]) => fieldWildcardMatcher(filters, config.get('metaFields')), + [config] + ); + + const getFilterSection = useCallback( + (type: string) => { + return ( + + + setFieldFilter(e.target.value)} + data-test-subj="indexPatternFieldFilter" + aria-label={filterAriaLabel} /> - )} - - ); - }; + {type === 'indexedFields' && indexedFieldTypes.length > 0 && ( + + setIndexedFieldTypeFilter(e.target.value)} + data-test-subj="indexedFieldTypeFilterDropdown" + /> + + )} + {type === 'scriptedFields' && scriptedFieldLanguages.length > 0 && ( + + setScriptedFieldLanguageFilter(e.target.value)} + data-test-subj="indexedFieldTypeFilterDropdown" + /> + + )} + + ); + }, + [ + fieldFilter, + indexedFieldTypeFilter, + indexedFieldTypes, + scriptedFieldLanguageFilter, + scriptedFieldLanguages, + ] + ); - const getContent = (type: string) => { - switch (type) { - case 'indexedFields': - return ( - - - {getFilterSection(type)} - - { - history.push(getPath(field)); - }, - getFieldInfo: services.indexPatternManagement.list.getFieldInfo, - }} - /> - - ); - case 'scriptedFields': - return ( - - - {getFilterSection(type)} - - { - history.push(getPath(field)); - }, - }} - onRemoveField={refreshFilters} - /> - - ); - case 'sourceFilters': - return ( - - - {getFilterSection(type)} - - - - ); - } - }; + const getContent = useCallback( + (type: string) => { + switch (type) { + case 'indexedFields': + return ( + + + {getFilterSection(type)} + + { + history.push(getPath(field)); + }, + getFieldInfo: services.indexPatternManagement.list.getFieldInfo, + }} + /> + + ); + case 'scriptedFields': + return ( + + + {getFilterSection(type)} + + { + history.push(getPath(field)); + }, + }} + onRemoveField={refreshFilters} + /> + + ); + case 'sourceFilters': + return ( + + + {getFilterSection(type)} + + + + ); + } + }, + [ + fieldFilter, + fieldWildcardMatcherDecorated, + fields, + getFilterSection, + history, + indexPattern, + indexedFieldTypeFilter, + refreshFilters, + scriptedFieldLanguageFilter, + services.indexPatternManagement.list.getFieldInfo, + ] + ); - const euiTabs: EuiTabbedContentTab[] = getTabs( - indexPattern, - fieldFilter, - services.indexPatternManagement.list - ).map((tab: Pick) => { - return { - ...tab, - content: getContent(tab.id), - }; - }); + const euiTabs: EuiTabbedContentTab[] = useMemo( + () => + getTabs(indexPattern, fieldFilter, services.indexPatternManagement.list).map( + (tab: Pick) => { + 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, + }); - const tabId = getTabIdFromURL(location.search); - const selectedTab = euiTabs.find(tab => tab.id === tabId) || euiTabs[0]; + startSyncingState(); + setSyncingStateFunc({ + setCurrentTab, + getCurrentTab, + }); + setSelectedTabId(getCurrentTab()); + + return () => { + stopSyncingState(); + }; + }, [config]); - return ; + return ( + tab.id === selectedTabId)} + onTabClick={tab => { + setSelectedTabId(tab.id); + syncingStateFunc.setCurrentTab(tab.id); + }} + /> + ); }