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

[State Management] Move url state_hashing utils to kibana_utils #52280

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
KbnUrl,
SaveOptions,
SavedObjectFinder,
unhashUrl,
StateManagement,
} from './legacy_imports';
import { FilterStateManager, IndexPattern } from '../../../data/public';
import { Query, SavedQuery, IndexPatterns } from '../../../../../plugins/data/public';
Expand Down Expand Up @@ -747,7 +747,7 @@ export class DashboardAppController {
anchorElement,
allowEmbed: true,
allowShortUrl: !dashboardConfig.getHideWriteControls(),
shareableUrl: unhashUrl(window.location.href),
shareableUrl: StateManagement.Url.unhashUrl(window.location.href),
objectId: dash.id,
objectType: 'dashboard',
sharingData: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { StateManagement } from '../../../../../plugins/kibana_utils/public';
export { IInjector } from 'ui/chrome';
export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
migrateLegacyQuery,
RequestAdapter,
showSaveModal,
unhashUrl,
StateManagement,
stateMonitorFactory,
subscribeWithScope,
tabifyAggResponse,
Expand Down Expand Up @@ -330,7 +330,7 @@ function discoverController(
anchorElement,
allowEmbed: false,
allowShortUrl: uiCapabilities.discover.createShortUrl,
shareableUrl: unhashUrl(window.location.href),
shareableUrl: StateManagement.Url.unhashUrl(window.location.href),
objectId: savedSearch.id,
objectType: 'search',
sharingData: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { StateManagement } from '../../../../../plugins/kibana_utils/public';

// EXPORT types
export { Vis } from 'ui/vis';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
showSaveModal,
stateMonitorFactory,
subscribeWithScope,
unhashUrl,
StateManagement
} from '../kibana_services';

const {
Expand Down Expand Up @@ -248,7 +248,7 @@ function VisEditor(
anchorElement,
allowEmbed: true,
allowShortUrl: capabilities.visualize.createShortUrl,
shareableUrl: unhashUrl(window.location.href),
shareableUrl: StateManagement.Url.unhashUrl(window.location.href),
objectId: savedVis.id,
objectType: 'visualization',
sharingData: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { StateManagement } from '../../../../../plugins/kibana_utils/public';
export {
Container,
Embeddable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import chrome from 'ui/chrome';
import { hashUrl } from 'ui/state_management/state_hashing';
import { StateManagement } from '../../../../plugins/kibana_utils/public';
import uiRoutes from 'ui/routes';
import { fatalError } from 'ui/notify';

Expand All @@ -29,7 +29,7 @@ uiRoutes
url: function (AppState, globalState, $window) {
const redirectUrl = chrome.getInjected('redirectUrl');
try {
const hashedUrl = hashUrl(redirectUrl);
const hashedUrl = StateManagement.Url.hashUrl(redirectUrl);
const url = chrome.addBasePath(hashedUrl);

$window.location = url;
Expand Down
15 changes: 10 additions & 5 deletions src/legacy/ui/public/chrome/api/sub_url_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,23 @@

import url from 'url';

import {
unhashUrl,
} from '../../state_management/state_hashing';
import { StateManagement } from '../../../../../plugins/kibana_utils/public';
import { toastNotifications } from '../../notify/toasts';

export function registerSubUrlHooks(angularModule, internals) {
angularModule.run(($rootScope, Private, $location) => {
const subUrlRouteFilter = Private(SubUrlRouteFilterProvider);

function updateSubUrls() {
const urlWithHashes = window.location.href;
const urlWithStates = unhashUrl(urlWithHashes);
internals.trackPossibleSubUrl(urlWithStates);
let urlWithStates;
try {
urlWithStates = StateManagement.Url.unhashUrl(urlWithHashes);
} catch (e) {
toastNotifications.addDanger(e.message);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes small regression introduced in #51835,
When sharing hashed url directly it should show error toast instead of full page fatal error

}

internals.trackPossibleSubUrl(urlWithStates || urlWithHashes);
}

function onRouteChange($event) {
Expand Down
20 changes: 9 additions & 11 deletions src/legacy/ui/public/state_management/__tests__/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ import { toastNotifications } from '../../notify';
import * as FatalErrorNS from '../../notify/fatal_error';
import { StateProvider } from '../state';
import {
createStateHash,
isStateHash,
unhashQuery
} from '../state_hashing';
import { HashedItemStore } from '../../../../../plugins/kibana_utils/public';
StateManagement,
HashedItemStore
} from '../../../../../plugins/kibana_utils/public';
import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
import { EventsProvider } from '../../events';

Expand Down Expand Up @@ -58,7 +56,7 @@ describe('State Management', () => {
const hashedItemStore = new HashedItemStore(store);
const state = new State(param, initial, hashedItemStore);

const getUnhashedSearch = () => unhashQuery($location.search());
const getUnhashedSearch = () => StateManagement.Url.unhashQuery($location.search());

return { store, hashedItemStore, state, getUnhashedSearch };
};
Expand Down Expand Up @@ -248,7 +246,7 @@ describe('State Management', () => {
state.save();
const urlVal = $location.search()[state.getQueryParamName()];

expect(isStateHash(urlVal)).to.be(true);
expect(StateManagement.StateHash.isStateHash(urlVal)).to.be(true);
expect(hashedItemStore.getItem(urlVal)).to.eql(JSON.stringify({ foo: 'bar' }));
});

Expand All @@ -262,7 +260,7 @@ describe('State Management', () => {

const urlVal = $location.search()._s;
expect(urlVal).to.not.be(rison);
expect(isStateHash(urlVal)).to.be(true);
expect(StateManagement.StateHash.isStateHash(urlVal)).to.be(true);
expect(hashedItemStore.getItem(urlVal)).to.eql(JSON.stringify(obj));
});

Expand All @@ -278,7 +276,7 @@ describe('State Management', () => {

const { state } = setup({ storeInHash: true });
const search = $location.search();
const badHash = createStateHash('{"a": "b"}', () => null);
const badHash = StateManagement.StateHash.createStateHash('{"a": "b"}', () => null);

search[state.getQueryParamName()] = badHash;
$location.search(search);
Expand Down Expand Up @@ -315,10 +313,10 @@ describe('State Management', () => {
expect(state.translateHashToRison('(a:b)')).to.be('(a:b)');
expect(state.translateHashToRison('')).to.be('');

const existingHash = createStateHash('{"a": "b"}', () => null);
const existingHash = StateManagement.StateHash.createStateHash('{"a": "b"}', () => null);
hashedItemStore.setItem(existingHash, '{"a": "b"}');

const nonExistingHash = createStateHash('{"b": "c"}', () => null);
const nonExistingHash = StateManagement.StateHash.createStateHash('{"b": "c"}', () => null);

expect(state.translateHashToRison(existingHash)).to.be('(a:b)');
expect(state.translateHashToRison(nonExistingHash)).to.be('!n');
Expand Down
12 changes: 4 additions & 8 deletions src/legacy/ui/public/state_management/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, StateManagement } from '../../../../plugins/kibana_utils/public';

export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) {
const Events = Private(EventsProvider);
Expand Down Expand Up @@ -95,7 +91,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
return null;
}

if (isStateHash(urlVal)) {
if (StateManagement.StateHash.isStateHash(urlVal)) {
return this._parseStateHash(urlVal);
}

Expand Down Expand Up @@ -269,7 +265,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
* @return {string} rison
*/
State.prototype.translateHashToRison = function (stateHashOrRison) {
if (isStateHash(stateHashOrRison)) {
if (StateManagement.StateHash.isStateHash(stateHashOrRison)) {
return rison.encode(this._parseStateHash(stateHashOrRison));
}

Expand All @@ -292,7 +288,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon

// We need to strip out Angular-specific properties.
const json = angular.toJson(state);
const hash = createStateHash(json);
const hash = StateManagement.StateHash.createStateHash(json);
const isItemSet = this._hashedItemStore.setItem(hash, json);

if (isItemSet) {
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/kibana_utils/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ export * from './errors';
export * from './field_mapping';
export * from './storage';
export * from './storage/hashed_item_store';
import * as StateManagement from './state_management';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just

export * from './state_management';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We’ve been talking for awhile now about how static code is organized / exported from plugins, and two themes have frequently come up:

  1. We should be doing named exports from our index files (instead of export *), otherwise it is too easy to accidentally leak internal apis as part of the public contract.

  2. We should consider how we are “organizing” static exports. Core does this by prefixing most names with the name of the service they belong to. Another thing that has been tried recently is exporting the code under namespaces that match each service in a plugin.

So I think the motivation here is to make it more intuitive for folks consuming these utils to understand which ones are related.

That said, I have a few reservations with this PR that perhaps we should discuss further:

  1. On the whole I support the idea of namespacing the static exports. But I think how we go about this is something we should agree to some conventions on, because if things aren’t consistent we will be no better off than we are today. (e.g. do we want to namespace multiple levels deep? Or just one? do we capitalize? And all those similar nit picky questions)

  2. I’m not sure if we should create a designated state management grouping. I know this existed as a concept in the legacy world, but I don’t know if it is the right way to frame things in the new platform. The way I’ve been thinking about it is that syncState is basically an extension of the other store stuff, and the hash/unhash/encode/decode items are basic utils for manipulating urls. “State management” is now the job of apps.

TLDR - I guess what I am proposing is, instead of starting to change some of these imports now, perhaps we should re-export everything from legacy ui/state_management under their original names, until we can align on a design we think will work. (Otherwise we need to go back and change these again later). WDYT @Dosant @streamich ?

Maybe we should move this part of the discussion to the POC PR since it is ultimately design related? And perhaps cover the topic in our next app arch sync so we are aligned on a consistent approach? Since this effort isn’t super urgent, I’d rather take the time to make sure we sort these things out together.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And for the record, I know @Dosant has invested some time on this, so I am not opposed to merging as-is, as long as we are agreed we will revisit and make adjustments for consistency with whatever conventions the group decides on)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think right now for me best would be to revert to

export * from './state_management';

To be consistent with what we currently have.
And indeed we should discuss the approach separately all together.

export { StateManagement };
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@
* under the License.
*/

export { hashUrl, unhashUrl, hashQuery, unhashQuery } from './hash_unhash_url';
export { createStateHash, isStateHash } from './state_hash';
import * as Url from './url';
import * as StateHash from './state_hash';

export { Url, StateHash };
Original file line number Diff line number Diff line change
@@ -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 './state_hash';
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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@';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>;

Expand Down Expand Up @@ -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.',
})
Expand All @@ -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 ' +
Expand Down
20 changes: 20 additions & 0 deletions src/plugins/kibana_utils/public/state_management/url/index.ts
Original file line number Diff line number Diff line change
@@ -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';
24 changes: 24 additions & 0 deletions test/typings/encode_uri_query.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading