From 881c836f9489a122cb1d1e2e0d32b4d9c2289f91 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 6 Dec 2019 13:20:29 +0100 Subject: [PATCH] [State Management] Move url state_hashing utils to kibana_utils (#52280) Part of #44151, Continuation of #51835, Just moves existing state related url utils to kibana_utils plugin Also fixes small regression introduced in #51835, When sharing hashed url directly it should show error toast instead of full page fatal error --- .i18nrc.json | 1 + .../kibana/public/dashboard/legacy_imports.ts | 2 +- .../kibana/public/discover/kibana_services.ts | 2 +- .../kibana/public/visualize/editor/editor.js | 2 +- .../public/visualize/kibana_services.ts | 2 +- .../public/index.js | 2 +- .../ui/public/chrome/api/sub_url_hooks.js | 15 ++++--- .../state_management/__tests__/state.js | 6 +-- .../ui/public/state_management/state.js | 6 +-- src/plugins/kibana_utils/public/index.ts | 2 + .../state_management/state_hash}/index.ts | 3 +- .../state_hash}/state_hash.test.ts | 5 +-- .../state_hash}/state_hash.ts | 2 +- .../url}/hash_unhash_url.test.ts | 5 +-- .../state_management/url}/hash_unhash_url.ts | 8 ++-- .../public/state_management/url/index.ts | 20 ++++++++++ test/typings/encode_uri_query.d.ts | 24 ++++++++++++ test/typings/rison_node.d.ts | 39 +++++++++++++++++++ .../translations/translations/ja-JP.json | 4 +- .../translations/translations/zh-CN.json | 2 + 20 files changed, 120 insertions(+), 32 deletions(-) rename src/{legacy/ui/public/state_management/state_hashing => plugins/kibana_utils/public/state_management/state_hash}/index.ts (85%) rename src/{legacy/ui/public/state_management/state_hashing => plugins/kibana_utils/public/state_management/state_hash}/state_hash.test.ts (91%) rename src/{legacy/ui/public/state_management/state_hashing => plugins/kibana_utils/public/state_management/state_hash}/state_hash.ts (96%) rename src/{legacy/ui/public/state_management/state_hashing => plugins/kibana_utils/public/state_management/url}/hash_unhash_url.test.ts (97%) rename src/{legacy/ui/public/state_management/state_hashing => plugins/kibana_utils/public/state_management/url}/hash_unhash_url.ts (94%) create mode 100644 src/plugins/kibana_utils/public/state_management/url/index.ts create mode 100644 test/typings/encode_uri_query.d.ts create mode 100644 test/typings/rison_node.d.ts diff --git a/.i18nrc.json b/.i18nrc.json index e5ba6762da154..fac9b9ce53184 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -19,6 +19,7 @@ "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", + "kibana_utils": "src/plugins/kibana_utils", "navigation": "src/legacy/core_plugins/navigation", "newsfeed": "src/plugins/newsfeed", "regionMap": "src/legacy/core_plugins/region_map", diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index 7c3c389330887..b0f09f0cf9745 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -63,6 +63,6 @@ export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule } from 'ui/legacy_compat'; export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 0d9dab96d6120..43a0afa83dfe4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -78,7 +78,7 @@ export { tabifyAggResponse } from 'ui/agg_response/tabify'; // @ts-ignore export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; // EXPORT types export { Vis } from 'ui/vis'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 2cf2584810741..c985e1a00655f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -45,7 +45,7 @@ import { showSaveModal, stateMonitorFactory, subscribeWithScope, - unhashUrl, + unhashUrl } from '../kibana_services'; const { diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 6477d1941c205..40d36dab227fa 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -103,7 +103,7 @@ export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { Container, Embeddable, diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js index 1aa7bce2af699..7fbf715ac3616 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js @@ -18,7 +18,7 @@ */ import chrome from 'ui/chrome'; -import { hashUrl } from 'ui/state_management/state_hashing'; +import { hashUrl } from '../../../../plugins/kibana_utils/public'; import uiRoutes from 'ui/routes'; import { fatalError } from 'ui/notify'; diff --git a/src/legacy/ui/public/chrome/api/sub_url_hooks.js b/src/legacy/ui/public/chrome/api/sub_url_hooks.js index e38a1f4b19e56..3ff262f546e3c 100644 --- a/src/legacy/ui/public/chrome/api/sub_url_hooks.js +++ b/src/legacy/ui/public/chrome/api/sub_url_hooks.js @@ -19,9 +19,8 @@ import url from 'url'; -import { - unhashUrl, -} from '../../state_management/state_hashing'; +import { unhashUrl } from '../../../../../plugins/kibana_utils/public'; +import { toastNotifications } from '../../notify/toasts'; export function registerSubUrlHooks(angularModule, internals) { angularModule.run(($rootScope, Private, $location) => { @@ -29,8 +28,14 @@ export function registerSubUrlHooks(angularModule, internals) { function updateSubUrls() { const urlWithHashes = window.location.href; - const urlWithStates = unhashUrl(urlWithHashes); - internals.trackPossibleSubUrl(urlWithStates); + let urlWithStates; + try { + urlWithStates = unhashUrl(urlWithHashes); + } catch (e) { + toastNotifications.addDanger(e.message); + } + + internals.trackPossibleSubUrl(urlWithStates || urlWithHashes); } function onRouteChange($event) { diff --git a/src/legacy/ui/public/state_management/__tests__/state.js b/src/legacy/ui/public/state_management/__tests__/state.js index 6f6f74c9d2bec..475d7c44a5f5a 100644 --- a/src/legacy/ui/public/state_management/__tests__/state.js +++ b/src/legacy/ui/public/state_management/__tests__/state.js @@ -26,11 +26,11 @@ import { toastNotifications } from '../../notify'; import * as FatalErrorNS from '../../notify/fatal_error'; import { StateProvider } from '../state'; import { + unhashQuery, createStateHash, isStateHash, - unhashQuery -} from '../state_hashing'; -import { HashedItemStore } from '../../../../../plugins/kibana_utils/public'; + HashedItemStore +} from '../../../../../plugins/kibana_utils/public'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; import { EventsProvider } from '../../events'; diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index 359dfa5749611..27186b4249978 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -35,11 +35,7 @@ import { fatalError, toastNotifications } from '../notify'; import './config_provider'; import { createLegacyClass } from '../utils/legacy_class'; import { callEach } from '../utils/function'; -import { hashedItemStore } from '../../../../plugins/kibana_utils/public'; -import { - createStateHash, - isStateHash -} from './state_hashing'; +import { hashedItemStore, isStateHash, createStateHash } from '../../../../plugins/kibana_utils/public'; export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 22ac720246d4b..c5c129eca8fd3 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -28,3 +28,5 @@ export * from './errors'; export * from './field_mapping'; export * from './storage'; export * from './storage/hashed_item_store'; +export * from './state_management/state_hash'; +export * from './state_management/url'; diff --git a/src/legacy/ui/public/state_management/state_hashing/index.ts b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts similarity index 85% rename from src/legacy/ui/public/state_management/state_hashing/index.ts rename to src/plugins/kibana_utils/public/state_management/state_hash/index.ts index 6225202f90978..0e52c4c55872d 100644 --- a/src/legacy/ui/public/state_management/state_hashing/index.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export { hashUrl, unhashUrl, hashQuery, unhashQuery } from './hash_unhash_url'; -export { createStateHash, isStateHash } from './state_hash'; +export * from './state_hash'; diff --git a/src/legacy/ui/public/state_management/state_hashing/state_hash.test.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts similarity index 91% rename from src/legacy/ui/public/state_management/state_hashing/state_hash.test.ts rename to src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts index 83a94e37785c4..cccb74acaf1e5 100644 --- a/src/legacy/ui/public/state_management/state_hashing/state_hash.test.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts @@ -18,9 +18,8 @@ */ import { encode as encodeRison } from 'rison-node'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { mockStorage } from '../../../../../plugins/kibana_utils/public/storage/hashed_item_store/mock'; -import { createStateHash, isStateHash } from '../state_hashing'; +import { mockStorage } from '../../storage/hashed_item_store/mock'; +import { createStateHash, isStateHash } from './state_hash'; describe('stateHash', () => { beforeEach(() => { diff --git a/src/legacy/ui/public/state_management/state_hashing/state_hash.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts similarity index 96% rename from src/legacy/ui/public/state_management/state_hashing/state_hash.ts rename to src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts index b3574876bafae..a3eb5272b112d 100644 --- a/src/legacy/ui/public/state_management/state_hashing/state_hash.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts @@ -18,7 +18,7 @@ */ import { Sha256 } from '../../../../../core/public/utils'; -import { hashedItemStore } from '../../../../../plugins/kibana_utils/public'; +import { hashedItemStore } from '../../storage/hashed_item_store'; // This prefix is used to identify hash strings that have been encoded in the URL. const HASH_PREFIX = 'h@'; diff --git a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.test.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts similarity index 97% rename from src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.test.ts rename to src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts index afbe86a4b4d12..a85158acddefd 100644 --- a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts @@ -17,9 +17,8 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { mockStorage } from '../../../../../plugins/kibana_utils/public/storage/hashed_item_store/mock'; -import { HashedItemStore } from '../../../../../plugins/kibana_utils/public'; +import { mockStorage } from '../../storage/hashed_item_store/mock'; +import { HashedItemStore } from '../../storage/hashed_item_store'; import { hashUrl, unhashUrl } from './hash_unhash_url'; describe('hash unhash url', () => { diff --git a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts similarity index 94% rename from src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.ts rename to src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts index 7142683c25115..872e7953f938b 100644 --- a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.ts +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts @@ -22,8 +22,8 @@ import rison, { RisonObject } from 'rison-node'; import { stringify as stringifyQueryString } from 'querystring'; import encodeUriQuery from 'encode-uri-query'; import { format as formatUrl, parse as parseUrl } from 'url'; -import { hashedItemStore } from '../../../../../plugins/kibana_utils/public'; -import { createStateHash, isStateHash } from './state_hash'; +import { hashedItemStore } from '../../storage/hashed_item_store'; +import { createStateHash, isStateHash } from '../state_hash'; export type IParsedUrlQuery = Record; @@ -98,7 +98,7 @@ export function retrieveState(stateHash: string): RisonObject { const json = hashedItemStore.getItem(stateHash); const throwUnableToRestoreUrlError = () => { throw new Error( - i18n.translate('common.ui.stateManagement.unableToRestoreUrlErrorMessage', { + i18n.translate('kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage', { defaultMessage: 'Unable to completely restore the URL, be sure to use the share functionality.', }) @@ -125,7 +125,7 @@ export function persistState(state: RisonObject): string { if (isItemSet) return hash; // If we ran out of space trying to persist the state, notify the user. const message = i18n.translate( - 'common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage', + 'kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage', { defaultMessage: 'Kibana is unable to store history items in your session ' + diff --git a/src/plugins/kibana_utils/public/state_management/url/index.ts b/src/plugins/kibana_utils/public/state_management/url/index.ts new file mode 100644 index 0000000000000..30c5696233db7 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/index.ts @@ -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 * from './hash_unhash_url'; diff --git a/test/typings/encode_uri_query.d.ts b/test/typings/encode_uri_query.d.ts new file mode 100644 index 0000000000000..4bfc554624446 --- /dev/null +++ b/test/typings/encode_uri_query.d.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +declare module 'encode-uri-query' { + function encodeUriQuery(query: string, usePercentageSpace?: boolean): string; + // eslint-disable-next-line import/no-default-export + export default encodeUriQuery; +} diff --git a/test/typings/rison_node.d.ts b/test/typings/rison_node.d.ts new file mode 100644 index 0000000000000..2592c36e8ae9a --- /dev/null +++ b/test/typings/rison_node.d.ts @@ -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. + */ + +declare module 'rison-node' { + export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface RisonArray extends Array {} + + export interface RisonObject { + [key: string]: RisonValue; + } + + export const decode: (input: string) => RisonValue; + + // eslint-disable-next-line @typescript-eslint/camelcase + export const decode_object: (input: string) => RisonObject; + + export const encode: (input: Input) => string; + + // eslint-disable-next-line @typescript-eslint/camelcase + export const encode_object: (input: Input) => string; +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c24f04697e3e5..b9fc90f947e08 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -676,6 +676,8 @@ "kibana-react.savedObjects.saveModal.saveButtonLabel": "保存", "kibana-react.savedObjects.saveModal.saveTitle": "{objectType} を保存", "kibana-react.savedObjects.saveModal.titleLabel": "タイトル", + "kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", + "kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", "inspector.closeButton": "インスペクターを閉じる", "inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です", "inspector.reqTimestampKey": "リクエストのタイムスタンプ", @@ -12750,4 +12752,4 @@ "xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6de1bc2135351..19207007ee714 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -677,6 +677,8 @@ "kibana-react.savedObjects.saveModal.saveButtonLabel": "保存", "kibana-react.savedObjects.saveModal.saveTitle": "保存 {objectType}", "kibana-react.savedObjects.saveModal.titleLabel": "标题", + "kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", + "kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", "inspector.closeButton": "关闭检查器", "inspector.reqTimestampDescription": "记录请求启动的时间", "inspector.reqTimestampKey": "请求时间戳",