mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Shorten url: Unification across Explore and Dashboards (#28434)
* WIP: Unify short url for dashboards and explore * Add tests * Update * Address feedback, move createShortUrl to buildUrl
This commit is contained in:
parent
8f4be08b00
commit
a0932f4d2a
29
public/app/core/utils/shortLinks.test.ts
Normal file
29
public/app/core/utils/shortLinks.test.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { createShortLink, createAndCopyShortLink } from './shortLinks';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
getBackendSrv: () => {
|
||||||
|
return {
|
||||||
|
post: () => {
|
||||||
|
return Promise.resolve({ url: 'www.short.com' });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
appSubUrl: '',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('createShortLink', () => {
|
||||||
|
it('creates short link', async () => {
|
||||||
|
const shortUrl = await createShortLink('www.verylonglinkwehavehere.com');
|
||||||
|
expect(shortUrl).toBe('www.short.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createAndCopyShortLink', () => {
|
||||||
|
it('copies short link to clipboard', async () => {
|
||||||
|
document.execCommand = jest.fn();
|
||||||
|
await createAndCopyShortLink('www.verylonglinkwehavehere.com');
|
||||||
|
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||||
|
});
|
||||||
|
});
|
36
public/app/core/utils/shortLinks.ts
Normal file
36
public/app/core/utils/shortLinks.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import memoizeOne from 'memoize-one';
|
||||||
|
import { getBackendSrv, config } from '@grafana/runtime';
|
||||||
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
|
import { copyStringToClipboard } from './explore';
|
||||||
|
|
||||||
|
function buildHostUrl() {
|
||||||
|
return `${window.location.protocol}//${window.location.host}${config.appSubUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRelativeURLPath(url: string) {
|
||||||
|
let path = url.replace(buildHostUrl(), '');
|
||||||
|
return path.startsWith('/') ? path.substring(1, path.length) : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createShortLink = memoizeOne(async function(path: string) {
|
||||||
|
try {
|
||||||
|
const shortLink = await getBackendSrv().post(`/api/short-urls`, {
|
||||||
|
path: getRelativeURLPath(path),
|
||||||
|
});
|
||||||
|
return shortLink.url;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error when creating shortened link: ', err);
|
||||||
|
appEvents.emit(AppEvents.alertError, ['Error generating shortened link']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createAndCopyShortLink = async (path: string) => {
|
||||||
|
const shortLink = await createShortLink(path);
|
||||||
|
if (shortLink) {
|
||||||
|
copyStringToClipboard(shortLink);
|
||||||
|
appEvents.emit(AppEvents.alertSuccess, ['Shortened link copied to clipboard']);
|
||||||
|
} else {
|
||||||
|
appEvents.emit(AppEvents.alertError, ['Error generating shortened link']);
|
||||||
|
}
|
||||||
|
};
|
@ -111,64 +111,70 @@ describe('ShareModal', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate share url absolute time', () => {
|
it('should generate share url absolute time', async () => {
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
expect(state?.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&viewPanel=22');
|
expect(state?.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&viewPanel=22');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate render url', () => {
|
it('should generate render url', async () => {
|
||||||
mockLocationHref('http://dashboards.grafana.com/d/abcdefghi/my-dash');
|
mockLocationHref('http://dashboards.grafana.com/d/abcdefghi/my-dash');
|
||||||
ctx.mount({
|
ctx.mount({
|
||||||
panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } },
|
panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
|
const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
|
||||||
const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
|
const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
|
||||||
expect(state?.imageUrl).toContain(base + params);
|
expect(state?.imageUrl).toContain(base + params);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate render url for scripted dashboard', () => {
|
it('should generate render url for scripted dashboard', async () => {
|
||||||
mockLocationHref('http://dashboards.grafana.com/dashboard/script/my-dash.js');
|
mockLocationHref('http://dashboards.grafana.com/dashboard/script/my-dash.js');
|
||||||
ctx.mount({
|
ctx.mount({
|
||||||
panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } },
|
panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
const base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
|
const base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
|
||||||
const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
|
const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
|
||||||
expect(state?.imageUrl).toContain(base + params);
|
expect(state?.imageUrl).toContain(base + params);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove panel id when no panel in scope', () => {
|
it('should remove panel id when no panel in scope', async () => {
|
||||||
ctx.mount({
|
ctx.mount({
|
||||||
panel: undefined,
|
panel: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
expect(state?.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1');
|
expect(state?.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add theme when specified', () => {
|
it('should add theme when specified', async () => {
|
||||||
ctx.wrapper?.setProps({ panel: undefined });
|
ctx.wrapper?.setProps({ panel: undefined });
|
||||||
ctx.wrapper?.setState({ selectedTheme: { label: 'light', value: 'light' } });
|
ctx.wrapper?.setState({ selectedTheme: { label: 'light', value: 'light' } });
|
||||||
|
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
expect(state?.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
|
expect(state?.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove editPanel from image url when is first param in querystring', () => {
|
it('should remove editPanel from image url when is first param in querystring', async () => {
|
||||||
mockLocationHref('http://server/#!/test?editPanel=1');
|
mockLocationHref('http://server/#!/test?editPanel=1');
|
||||||
ctx.mount({
|
ctx.mount({
|
||||||
panel: { id: 1, options: {}, fieldConfig: { defaults: {}, overrides: [] } },
|
panel: { id: 1, options: {}, fieldConfig: { defaults: {}, overrides: [] } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
expect(state?.shareUrl).toContain('?editPanel=1&from=1000&to=2000&orgId=1');
|
expect(state?.shareUrl).toContain('?editPanel=1&from=1000&to=2000&orgId=1');
|
||||||
expect(state?.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
|
expect(state?.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include template variables in url', () => {
|
it('should include template variables in url', async () => {
|
||||||
mockLocationHref('http://server/#!/test');
|
mockLocationHref('http://server/#!/test');
|
||||||
fillVariableValuesForUrlMock = (params: any) => {
|
fillVariableValuesForUrlMock = (params: any) => {
|
||||||
params['var-app'] = 'mupp';
|
params['var-app'] = 'mupp';
|
||||||
@ -177,6 +183,7 @@ describe('ShareModal', () => {
|
|||||||
ctx.mount();
|
ctx.mount();
|
||||||
ctx.wrapper?.setState({ includeTemplateVars: true });
|
ctx.wrapper?.setState({ includeTemplateVars: true });
|
||||||
|
|
||||||
|
await ctx.wrapper?.instance().buildUrl();
|
||||||
const state = ctx.wrapper?.state();
|
const state = ctx.wrapper?.state();
|
||||||
expect(state?.shareUrl).toContain(
|
expect(state?.shareUrl).toContain(
|
||||||
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
|
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
|
||||||
|
@ -3,9 +3,8 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
|||||||
import { LegacyForms, ClipboardButton, Icon, InfoBox, Input } from '@grafana/ui';
|
import { LegacyForms, ClipboardButton, Icon, InfoBox, Input } from '@grafana/ui';
|
||||||
const { Select, Switch } = LegacyForms;
|
const { Select, Switch } = LegacyForms;
|
||||||
import { SelectableValue, PanelModel, AppEvents } from '@grafana/data';
|
import { SelectableValue, PanelModel, AppEvents } from '@grafana/data';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
|
||||||
import { DashboardModel } from 'app/features/dashboard/state';
|
import { DashboardModel } from 'app/features/dashboard/state';
|
||||||
import { buildImageUrl, buildShareUrl, getRelativeURLPath } from './utils';
|
import { buildImageUrl, buildShareUrl } from './utils';
|
||||||
import { appEvents } from 'app/core/core';
|
import { appEvents } from 'app/core/core';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
|
||||||
@ -58,22 +57,20 @@ export class ShareLink extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUrl = () => {
|
buildUrl = async () => {
|
||||||
const { panel } = this.props;
|
const { panel } = this.props;
|
||||||
const { useCurrentTimeRange, includeTemplateVars, useShortUrl, selectedTheme } = this.state;
|
const { useCurrentTimeRange, includeTemplateVars, useShortUrl, selectedTheme } = this.state;
|
||||||
|
|
||||||
const shareUrl = buildShareUrl(useCurrentTimeRange, includeTemplateVars, selectedTheme.value, panel);
|
const shareUrl = await buildShareUrl(
|
||||||
|
useCurrentTimeRange,
|
||||||
|
includeTemplateVars,
|
||||||
|
selectedTheme.value,
|
||||||
|
panel,
|
||||||
|
useShortUrl
|
||||||
|
);
|
||||||
const imageUrl = buildImageUrl(useCurrentTimeRange, includeTemplateVars, selectedTheme.value, panel);
|
const imageUrl = buildImageUrl(useCurrentTimeRange, includeTemplateVars, selectedTheme.value, panel);
|
||||||
|
|
||||||
if (useShortUrl) {
|
this.setState({ shareUrl, imageUrl });
|
||||||
getBackendSrv()
|
|
||||||
.post(`/api/short-urls`, {
|
|
||||||
path: getRelativeURLPath(shareUrl),
|
|
||||||
})
|
|
||||||
.then(res => this.setState({ shareUrl: res.url, imageUrl }));
|
|
||||||
} else {
|
|
||||||
this.setState({ shareUrl, imageUrl });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onUseCurrentTimeRangeChange = () => {
|
onUseCurrentTimeRangeChange = () => {
|
||||||
@ -126,11 +123,11 @@ export class ShareLink extends PureComponent<Props, State> {
|
|||||||
checked={includeTemplateVars}
|
checked={includeTemplateVars}
|
||||||
onChange={this.onIncludeTemplateVarsChange}
|
onChange={this.onIncludeTemplateVarsChange}
|
||||||
/>
|
/>
|
||||||
<Switch labelClass="width-12" label="Shorten URL" checked={useShortUrl} onChange={this.onUrlShorten} />
|
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<label className="gf-form-label width-12">Theme</label>
|
<label className="gf-form-label width-12">Theme</label>
|
||||||
<Select width={10} options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
|
<Select width={10} options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
|
||||||
</div>
|
</div>
|
||||||
|
<Switch labelClass="width-12" label="Shorten URL" checked={useShortUrl} onChange={this.onUrlShorten} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { getTemplateSrv } from 'app/features/templating/template_srv';
|
import { getTemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import { createShortLink } from 'app/core/utils/shortLinks';
|
||||||
import { PanelModel, dateTime, urlUtil } from '@grafana/data';
|
import { PanelModel, dateTime, urlUtil } from '@grafana/data';
|
||||||
|
|
||||||
export function buildParams(
|
export function buildParams(
|
||||||
@ -38,15 +39,6 @@ export function buildParams(
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildHostUrl() {
|
|
||||||
return `${window.location.protocol}//${window.location.host}${config.appSubUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRelativeURLPath(url: string) {
|
|
||||||
let p = url.replace(buildHostUrl(), '');
|
|
||||||
return p.startsWith('/') ? p.substring(1, p.length) : p;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildBaseUrl() {
|
export function buildBaseUrl() {
|
||||||
let baseUrl = window.location.href;
|
let baseUrl = window.location.href;
|
||||||
const queryStart = baseUrl.indexOf('?');
|
const queryStart = baseUrl.indexOf('?');
|
||||||
@ -58,16 +50,20 @@ export function buildBaseUrl() {
|
|||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildShareUrl(
|
export async function buildShareUrl(
|
||||||
useCurrentTimeRange: boolean,
|
useCurrentTimeRange: boolean,
|
||||||
includeTemplateVars: boolean,
|
includeTemplateVars: boolean,
|
||||||
selectedTheme?: string,
|
selectedTheme?: string,
|
||||||
panel?: PanelModel
|
panel?: PanelModel,
|
||||||
|
shortenUrl?: boolean
|
||||||
) {
|
) {
|
||||||
const baseUrl = buildBaseUrl();
|
const baseUrl = buildBaseUrl();
|
||||||
const params = buildParams(useCurrentTimeRange, includeTemplateVars, selectedTheme, panel);
|
const params = buildParams(useCurrentTimeRange, includeTemplateVars, selectedTheme, panel);
|
||||||
|
const shareUrl = urlUtil.appendQueryToUrl(baseUrl, urlUtil.toUrlParams(params));
|
||||||
return urlUtil.appendQueryToUrl(baseUrl, urlUtil.toUrlParams(params));
|
if (shortenUrl) {
|
||||||
|
return await createShortLink(shareUrl);
|
||||||
|
}
|
||||||
|
return shareUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildSoloUrl(
|
export function buildSoloUrl(
|
||||||
@ -113,11 +109,6 @@ export function buildIframeHtml(
|
|||||||
return '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
|
return '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildShortUrl(uid: string) {
|
|
||||||
const hostUrl = buildHostUrl();
|
|
||||||
return `${hostUrl}/goto/${uid}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLocalTimeZone() {
|
export function getLocalTimeZone() {
|
||||||
const utcOffset = '&tz=UTC' + encodeURIComponent(dateTime().format('Z'));
|
const utcOffset = '&tz=UTC' + encodeURIComponent(dateTime().format('Z'));
|
||||||
|
|
||||||
|
@ -7,11 +7,10 @@ import { css } from 'emotion';
|
|||||||
|
|
||||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||||
import { Icon, IconButton, LegacyForms, SetInterval, Tooltip } from '@grafana/ui';
|
import { Icon, IconButton, LegacyForms, SetInterval, Tooltip } from '@grafana/ui';
|
||||||
import { DataQuery, RawTimeRange, TimeRange, TimeZone, AppEvents } from '@grafana/data';
|
import { DataQuery, RawTimeRange, TimeRange, TimeZone } from '@grafana/data';
|
||||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||||
import { StoreState } from 'app/types/store';
|
import { StoreState } from 'app/types/store';
|
||||||
import { copyStringToClipboard } from 'app/core/utils/explore';
|
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
|
||||||
import appEvents from 'app/core/app_events';
|
|
||||||
import {
|
import {
|
||||||
cancelQueries,
|
cancelQueries,
|
||||||
changeDatasource,
|
changeDatasource,
|
||||||
@ -27,7 +26,6 @@ import { getTimeZone } from '../profile/state/selectors';
|
|||||||
import { updateTimeZoneForSession } from '../profile/state/reducers';
|
import { updateTimeZoneForSession } from '../profile/state/reducers';
|
||||||
import { getDashboardSrv } from '../dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from '../dashboard/services/DashboardSrv';
|
||||||
import kbn from '../../core/utils/kbn';
|
import kbn from '../../core/utils/kbn';
|
||||||
import { createShortLink } from './utils/links';
|
|
||||||
import { ExploreTimeControls } from './ExploreTimeControls';
|
import { ExploreTimeControls } from './ExploreTimeControls';
|
||||||
import { LiveTailButton } from './LiveTailButton';
|
import { LiveTailButton } from './LiveTailButton';
|
||||||
import { ResponsiveButton } from './ResponsiveButton';
|
import { ResponsiveButton } from './ResponsiveButton';
|
||||||
@ -155,16 +153,6 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
|||||||
return datasourceName ? exploreDatasources.find(datasource => datasource.name === datasourceName) : undefined;
|
return datasourceName ? exploreDatasources.find(datasource => datasource.name === datasourceName) : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
copyAndSaveShortLink = async () => {
|
|
||||||
const shortLink = await createShortLink(window.location.href);
|
|
||||||
if (shortLink) {
|
|
||||||
copyStringToClipboard(shortLink);
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Shortened link copied to clipboard']);
|
|
||||||
} else {
|
|
||||||
appEvents.emit(AppEvents.alertError, ['Error generating shortened link']);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
@ -277,7 +265,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
|||||||
) : null}
|
) : null}
|
||||||
<div className={'explore-toolbar-content-item'}>
|
<div className={'explore-toolbar-content-item'}>
|
||||||
<Tooltip content={'Copy shortened link'} placement="bottom">
|
<Tooltip content={'Copy shortened link'} placement="bottom">
|
||||||
<button className={'btn navbar-button'} onClick={this.copyAndSaveShortLink}>
|
<button className={'btn navbar-button'} onClick={() => createAndCopyShortLink(window.location.href)}>
|
||||||
<Icon name="share-alt" />
|
<Icon name="share-alt" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -7,7 +7,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
|
|||||||
import { GrafanaTheme, AppEvents, DataSourceApi } from '@grafana/data';
|
import { GrafanaTheme, AppEvents, DataSourceApi } from '@grafana/data';
|
||||||
import { RichHistoryQuery, ExploreId } from 'app/types/explore';
|
import { RichHistoryQuery, ExploreId } from 'app/types/explore';
|
||||||
import { createUrlFromRichHistory, createQueryText } from 'app/core/utils/richHistory';
|
import { createUrlFromRichHistory, createQueryText } from 'app/core/utils/richHistory';
|
||||||
import { createShortLink } from '../../explore/utils/links';
|
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
|
||||||
import { copyStringToClipboard } from 'app/core/utils/explore';
|
import { copyStringToClipboard } from 'app/core/utils/explore';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { StoreState, CoreEvents } from 'app/types';
|
import { StoreState, CoreEvents } from 'app/types';
|
||||||
@ -178,15 +178,9 @@ export function RichHistoryCard(props: Props) {
|
|||||||
appEvents.emit(AppEvents.alertSuccess, ['Query copied to clipboard']);
|
appEvents.emit(AppEvents.alertSuccess, ['Query copied to clipboard']);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreateLink = async () => {
|
const onCreateShortLink = async () => {
|
||||||
const link = createUrlFromRichHistory(query);
|
const link = createUrlFromRichHistory(query);
|
||||||
const shortLink = await createShortLink(link);
|
await createAndCopyShortLink(link);
|
||||||
if (shortLink) {
|
|
||||||
copyStringToClipboard(shortLink);
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Shortened link copied to clipboard']);
|
|
||||||
} else {
|
|
||||||
appEvents.emit(AppEvents.alertError, ['Error generating shortened link']);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteQuery = () => {
|
const onDeleteQuery = () => {
|
||||||
@ -261,7 +255,9 @@ export function RichHistoryCard(props: Props) {
|
|||||||
title={query.comment?.length > 0 ? 'Edit comment' : 'Add comment'}
|
title={query.comment?.length > 0 ? 'Edit comment' : 'Add comment'}
|
||||||
/>
|
/>
|
||||||
<IconButton name="copy" onClick={onCopyQuery} title="Copy query to clipboard" />
|
<IconButton name="copy" onClick={onCopyQuery} title="Copy query to clipboard" />
|
||||||
{!isRemoved && <IconButton name="share-alt" onClick={onCreateLink} title="Copy shortened link to clipboard" />}
|
{!isRemoved && (
|
||||||
|
<IconButton name="share-alt" onClick={onCreateShortLink} title="Copy shortened link to clipboard" />
|
||||||
|
)}
|
||||||
<IconButton name="trash-alt" title={'Delete query'} onClick={onDeleteQuery} />
|
<IconButton name="trash-alt" title={'Delete query'} onClick={onDeleteQuery} />
|
||||||
<IconButton
|
<IconButton
|
||||||
name={query.starred ? 'favorite' : 'star'}
|
name={query.starred ? 'favorite' : 'star'}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import memoizeOne from 'memoize-one';
|
|
||||||
import { splitOpen } from '../state/actions';
|
import { splitOpen } from '../state/actions';
|
||||||
import { Field, LinkModel, TimeRange, mapInternalLinkToExplore } from '@grafana/data';
|
import { Field, LinkModel, TimeRange, mapInternalLinkToExplore } from '@grafana/data';
|
||||||
import { getLinkSrv } from '../../panel/panellinks/link_srv';
|
import { getLinkSrv } from '../../panel/panellinks/link_srv';
|
||||||
import { getDataSourceSrv, getTemplateSrv, getBackendSrv, config } from '@grafana/runtime';
|
import { getDataSourceSrv, getTemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get links from the field of a dataframe and in addition check if there is associated
|
* Get links from the field of a dataframe and in addition check if there is associated
|
||||||
@ -60,23 +59,3 @@ function getTitleFromHref(href: string): string {
|
|||||||
}
|
}
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildHostUrl() {
|
|
||||||
return `${window.location.protocol}//${window.location.host}${config.appSubUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRelativeURLPath(url: string) {
|
|
||||||
let path = url.replace(buildHostUrl(), '');
|
|
||||||
return path.startsWith('/') ? path.substring(1, path.length) : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createShortLink = memoizeOne(async function(path: string) {
|
|
||||||
try {
|
|
||||||
const shortUrl = await getBackendSrv().post(`/api/short-urls`, {
|
|
||||||
path: getRelativeURLPath(path),
|
|
||||||
});
|
|
||||||
return shortUrl.url;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error when creating shortened link: ', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
Loading…
Reference in New Issue
Block a user