Skip to content

Commit

Permalink
Short URL browser client (#121886)
Browse files Browse the repository at this point in the history
  • Loading branch information
vadimkibana authored Feb 2, 2022
1 parent 22d87fc commit 28ba010
Show file tree
Hide file tree
Showing 29 changed files with 905 additions and 427 deletions.
9 changes: 0 additions & 9 deletions src/plugins/share/common/short_url_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,4 @@

export const GOTO_PREFIX = '/goto';

export const getUrlIdFromGotoRoute = (path: string) =>
path.match(new RegExp(`${GOTO_PREFIX}/(.*)$`))?.[1];

export const getGotoPath = (urlId: string) => `${GOTO_PREFIX}/${urlId}`;

export const GETTER_PREFIX = '/api/short_url';

export const getUrlPath = (urlId: string) => `${GETTER_PREFIX}/${urlId}`;

export const CREATE_PATH = '/api/shorten_url';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { SerializableRecord } from '@kbn/utility-types';
import type { KibanaLocation, LocatorDefinition } from '../../url_service';

export const SHORT_URL_REDIRECT_LOCATOR = 'SHORT_URL_REDIRECT_LOCATOR';

export interface ShortUrlRedirectLocatorParams extends SerializableRecord {
slug: string;
}

/**
* Locator that points to a frontend short URL redirect app by slug.
*/
export class ShortUrlRedirectLocatorDefinition
implements LocatorDefinition<ShortUrlRedirectLocatorParams>
{
public readonly id = SHORT_URL_REDIRECT_LOCATOR;

public async getLocation(params: ShortUrlRedirectLocatorParams): Promise<KibanaLocation> {
const { slug } = params;

return {
app: 'r',
path: 's/' + slug,
state: {},
};
}
}
7 changes: 4 additions & 3 deletions src/plugins/share/common/url_service/locators/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ export interface LocatorPublic<P extends SerializableRecord> extends Persistable
/**
* Returns a URL as a string.
*
* @deprecated Use `getRedirectUrl` instead. `getRedirectUrl` will preserve
* the location state, whereas the `getUrl` just return the URL without
* the location state.
* You may want to use `getRedirectUrl` instead. `getRedirectUrl` will
* preserve the location state, whereas the `getUrl` just returns the URL
* without the location state. Use this method if you know you don't need
* remember the location state and version of the URL locator.
*
* @param params URL locator parameters.
* @param getUrlParams URL construction parameters.
Expand Down
11 changes: 6 additions & 5 deletions src/plugins/share/common/url_service/short_urls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import type { LocatorPublic, ILocatorClient, LocatorData } from '../locators';
* request and the current Kibana version. On the client, the Short URL Service
* needs no dependencies.
*/
export interface IShortUrlClientFactory<D> {
get(dependencies: D): IShortUrlClient;
export interface IShortUrlClientFactory<D, Client extends IShortUrlClient = IShortUrlClient> {
get(dependencies: D): Client;
}

export type IShortUrlClientFactoryProvider<D> = (params: {
locators: ILocatorClient;
}) => IShortUrlClientFactory<D>;
export type IShortUrlClientFactoryProvider<
D,
ShortUrlClient extends IShortUrlClient = IShortUrlClient
> = (params: { locators: ILocatorClient }) => IShortUrlClientFactory<D, ShortUrlClient>;

/**
* CRUD-like API for short URLs.
Expand Down
19 changes: 13 additions & 6 deletions src/plugins/share/common/url_service/url_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,31 @@
*/

import { LocatorClient, LocatorClientDependencies } from './locators';
import { IShortUrlClientFactoryProvider, IShortUrlClientFactory } from './short_urls';
import {
IShortUrlClientFactoryProvider,
IShortUrlClientFactory,
IShortUrlClient,
} from './short_urls';

export interface UrlServiceDependencies<D = unknown> extends LocatorClientDependencies {
shortUrls: IShortUrlClientFactoryProvider<D>;
export interface UrlServiceDependencies<
D = unknown,
ShortUrlClient extends IShortUrlClient = IShortUrlClient
> extends LocatorClientDependencies {
shortUrls: IShortUrlClientFactoryProvider<D, ShortUrlClient>;
}

/**
* Common URL Service client interface for server-side and client-side.
*/
export class UrlService<D = unknown> {
export class UrlService<D = unknown, ShortUrlClient extends IShortUrlClient = IShortUrlClient> {
/**
* Client to work with locators.
*/
public readonly locators: LocatorClient;

public readonly shortUrls: IShortUrlClientFactory<D>;
public readonly shortUrls: IShortUrlClientFactory<D, ShortUrlClient>;

constructor(protected readonly deps: UrlServiceDependencies<D>) {
constructor(protected readonly deps: UrlServiceDependencies<D, ShortUrlClient>) {
this.locators = new LocatorClient(deps);
this.shortUrls = deps.shortUrls({
locators: this.locators,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions src/plugins/share/public/components/share_context_menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,19 @@

import { ShareMenuItem } from '../types';

jest.mock('../lib/url_shortener', () => ({}));

import React from 'react';
import { shallow } from 'enzyme';

import { ShareContextMenu } from './share_context_menu';
import { ShareContextMenu, ShareContextMenuProps } from './share_context_menu';

const defaultProps = {
const defaultProps: ShareContextMenuProps = {
allowEmbed: true,
allowShortUrl: false,
shareMenuItems: [],
sharingData: null,
isDirty: false,
onClose: () => {},
basePath: '',
post: () => Promise.resolve({} as any),
objectType: 'dashboard',
urlService: {} as any,
};

test('should render context menu panel when there are more than one panel', () => {
Expand Down
15 changes: 6 additions & 9 deletions src/plugins/share/public/components/share_context_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import { I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { EuiContextMenu, EuiContextMenuPanelDescriptor } from '@elastic/eui';

import { HttpStart } from 'kibana/public';
import type { Capabilities } from 'src/core/public';

import { UrlPanelContent } from './url_panel_content';
import { ShareMenuItem, ShareContextMenuPanelItem, UrlParamExtension } from '../types';
import { AnonymousAccessServiceContract } from '../../common/anonymous_access';
import type { BrowserUrlService } from '../types';

interface Props {
export interface ShareContextMenuProps {
allowEmbed: boolean;
allowShortUrl: boolean;
objectId?: string;
Expand All @@ -28,14 +28,13 @@ interface Props {
shareMenuItems: ShareMenuItem[];
sharingData: any;
onClose: () => void;
basePath: string;
post: HttpStart['post'];
embedUrlParamExtensions?: UrlParamExtension[];
anonymousAccess?: AnonymousAccessServiceContract;
showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean;
urlService: BrowserUrlService;
}

export class ShareContextMenu extends Component<Props> {
export class ShareContextMenu extends Component<ShareContextMenuProps> {
public render() {
const { panels, initialPanelId } = this.getPanels();
return (
Expand Down Expand Up @@ -63,11 +62,10 @@ export class ShareContextMenu extends Component<Props> {
allowShortUrl={this.props.allowShortUrl}
objectId={this.props.objectId}
objectType={this.props.objectType}
basePath={this.props.basePath}
post={this.props.post}
shareableUrl={this.props.shareableUrl}
anonymousAccess={this.props.anonymousAccess}
showPublicUrlSwitch={this.props.showPublicUrlSwitch}
urlService={this.props.urlService}
/>
),
};
Expand All @@ -93,12 +91,11 @@ export class ShareContextMenu extends Component<Props> {
isEmbedded
objectId={this.props.objectId}
objectType={this.props.objectType}
basePath={this.props.basePath}
post={this.props.post}
shareableUrl={this.props.shareableUrl}
urlParamExtensions={this.props.embedUrlParamExtensions}
anonymousAccess={this.props.anonymousAccess}
showPublicUrlSwitch={this.props.showPublicUrlSwitch}
urlService={this.props.urlService}
/>
),
};
Expand Down
58 changes: 37 additions & 21 deletions src/plugins/share/public/components/url_panel_content.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,46 @@

import { EuiCopy, EuiRadioGroup, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';

jest.mock('../lib/url_shortener', () => ({ shortenUrl: jest.fn() }));

import React from 'react';
import { shallow } from 'enzyme';

import { ExportUrlAsType, UrlPanelContent } from './url_panel_content';
import { ExportUrlAsType, UrlPanelContent, UrlPanelContentProps } from './url_panel_content';
import { act } from 'react-dom/test-utils';
import { shortenUrl } from '../lib/url_shortener';

const defaultProps = {
const createFromLongUrl = jest.fn(async () => ({
url: 'http://localhost/short/url',
data: {} as any,
locator: {} as any,
params: {} as any,
}));

const defaultProps: UrlPanelContentProps = {
allowShortUrl: true,
objectType: 'dashboard',
basePath: '',
post: () => Promise.resolve({} as any),
urlService: {
locators: {} as any,
shortUrls: {
get: () =>
({
createFromLongUrl,
create: async () => {
throw new Error('not implemented');
},
createWithLocator: async () => {
throw new Error('not implemented');
},
get: async () => {
throw new Error('not implemented');
},
resolve: async () => {
throw new Error('not implemented');
},
delete: async () => {
throw new Error('not implemented');
},
} as any),
},
} as any,
};

describe('share url panel content', () => {
Expand Down Expand Up @@ -61,10 +87,6 @@ describe('share url panel content', () => {

describe('short url', () => {
test('should generate short url and put it in copy button', async () => {
const shortenUrlMock = shortenUrl as jest.Mock;
shortenUrlMock.mockReset();
shortenUrlMock.mockResolvedValue('http://localhost/short/url');

const component = shallow(
<UrlPanelContent
{...defaultProps}
Expand All @@ -77,9 +99,8 @@ describe('share url panel content', () => {
target: { checked: true },
} as unknown as EuiSwitchEvent);
});
expect(shortenUrlMock).toHaveBeenCalledWith(
'http://localhost:5601/app/myapp#/?_g=()&_a=()',
expect.anything()
expect(createFromLongUrl).toHaveBeenCalledWith(
'http://localhost:5601/app/myapp#/?_g=()&_a=()'
);
expect(component.find(EuiCopy).prop('textToCopy')).toContain('http://localhost/short/url');
});
Expand Down Expand Up @@ -151,10 +172,6 @@ describe('share url panel content', () => {
});

test('should generate short url with embed flag and put it in copy button', async () => {
const shortenUrlMock = shortenUrl as jest.Mock;
shortenUrlMock.mockReset();
shortenUrlMock.mockResolvedValue('http://localhost/short/url');

const component = shallow(
<UrlPanelContent
{...defaultProps}
Expand All @@ -168,9 +185,8 @@ describe('share url panel content', () => {
target: { checked: true },
} as unknown as EuiSwitchEvent);
});
expect(shortenUrlMock).toHaveBeenCalledWith(
'http://localhost:5601/app/myapp#/?embed=true&_g=()&_a=()',
expect.anything()
expect(createFromLongUrl).toHaveBeenCalledWith(
'http://localhost:5601/app/myapp#/?embed=true&_g=()&_a=()'
);
expect(component.find(EuiCopy).prop('textToCopy')).toContain('http://localhost/short/url');
});
Expand Down
Loading

0 comments on commit 28ba010

Please sign in to comment.