mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Migrate share registry (#50137)
This commit is contained in:
parent
c0d2b29e8b
commit
600862ef64
46 changed files with 1584 additions and 1206 deletions
|
@ -6,6 +6,7 @@
|
|||
"dashboardEmbeddableContainer": "src/plugins/dashboard_embeddable_container",
|
||||
"data": ["src/legacy/core_plugins/data", "src/plugins/data"],
|
||||
"embeddableApi": "src/plugins/embeddable",
|
||||
"share": "src/plugins/share",
|
||||
"esUi": "src/plugins/es_ui_shared",
|
||||
"expressions": "src/plugins/expressions",
|
||||
"inputControl": "src/legacy/core_plugins/input_control_vis",
|
||||
|
|
|
@ -14,9 +14,7 @@ However, these docs will be kept up-to-date to reflect the current implementatio
|
|||
[float]
|
||||
[[reporting-nav-bar-extensions]]
|
||||
=== Share menu extensions
|
||||
X-Pack uses the `ShareContextMenuExtensionsRegistryProvider` to register actions in the share menu.
|
||||
|
||||
This integration will likely be changing in the near future as we move towards a unified actions abstraction across {kib}.
|
||||
X-Pack uses the `share` plugin of the Kibana platform to register actions in the share menu.
|
||||
|
||||
[float]
|
||||
=== Generate job URL
|
||||
|
|
|
@ -1130,7 +1130,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy';
|
|||
| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives |
|
||||
| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives |
|
||||
| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. |
|
||||
| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. |
|
||||
| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. |
|
||||
| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. |
|
||||
| `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | |
|
||||
|
@ -1142,6 +1142,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy';
|
|||
| `ui/registry/feature_catalogue | `feature_catalogue.register` | Must add `feature_catalogue` as a dependency in your kibana.json. |
|
||||
| `ui/registry/vis_types` | `visualizations.types` | -- |
|
||||
| `ui/vis` | `visualizations.types` | -- |
|
||||
| `ui/share` | `share` | `showShareContextMenu` is now called `toggleShareContextMenu`, `ShareContextMenuExtensionsRegistryProvider` is now called `register` |
|
||||
| `ui/vis/vis_factory` | `visualizations.types` | -- |
|
||||
| `ui/vis/vis_filters` | `visualizations.filters` | -- |
|
||||
| `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code |
|
||||
|
|
|
@ -35,7 +35,6 @@ import { docTitle } from 'ui/doc_title/doc_title';
|
|||
|
||||
import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
|
||||
import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share';
|
||||
import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
|
||||
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
@ -55,6 +54,7 @@ import { SaveOptions } from 'ui/saved_objects/saved_object';
|
|||
import { capabilities } from 'ui/capabilities';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { unhashUrl } from 'ui/state_management/state_hashing';
|
||||
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
||||
import { Query } from '../../../../../plugins/data/public';
|
||||
import { start as data } from '../../../data/public/legacy';
|
||||
|
@ -131,7 +131,6 @@ export class DashboardAppController {
|
|||
}) {
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
const getUnhashableStates = Private(getUnhashableStatesProvider);
|
||||
const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider);
|
||||
|
||||
let lastReloadRequestTime = 0;
|
||||
|
||||
|
@ -758,14 +757,13 @@ export class DashboardAppController {
|
|||
});
|
||||
};
|
||||
navActions[TopNavIds.SHARE] = anchorElement => {
|
||||
showShareContextMenu({
|
||||
npStart.plugins.share.toggleShareContextMenu({
|
||||
anchorElement,
|
||||
allowEmbed: true,
|
||||
allowShortUrl: !dashboardConfig.getHideWriteControls(),
|
||||
getUnhashableStates,
|
||||
shareableUrl: unhashUrl(window.location.href, getUnhashableStates()),
|
||||
objectId: dash.id,
|
||||
objectType: 'dashboard',
|
||||
shareContextMenuExtensions: shareContextMenuExtensions.raw,
|
||||
sharingData: {
|
||||
title: dash.title,
|
||||
},
|
||||
|
|
|
@ -50,7 +50,7 @@ import {
|
|||
migrateLegacyQuery,
|
||||
RequestAdapter,
|
||||
showSaveModal,
|
||||
showShareContextMenu,
|
||||
unhashUrl,
|
||||
stateMonitorFactory,
|
||||
subscribeWithScope,
|
||||
tabifyAggResponse,
|
||||
|
@ -63,7 +63,7 @@ const {
|
|||
chrome,
|
||||
docTitle,
|
||||
FilterBarQueryFilterProvider,
|
||||
ShareContextMenuExtensionsRegistryProvider,
|
||||
share,
|
||||
StateProvider,
|
||||
timefilter,
|
||||
toastNotifications,
|
||||
|
@ -190,7 +190,6 @@ function discoverController(
|
|||
) {
|
||||
const responseHandler = vislibSeriesResponseHandlerProvider().handler;
|
||||
const getUnhashableStates = Private(getUnhashableStatesProvider);
|
||||
const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider);
|
||||
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
|
||||
|
@ -323,14 +322,13 @@ function discoverController(
|
|||
testId: 'shareTopNavButton',
|
||||
run: async (anchorElement) => {
|
||||
const sharingData = await this.getSharingData();
|
||||
showShareContextMenu({
|
||||
share.toggleShareContextMenu({
|
||||
anchorElement,
|
||||
allowEmbed: false,
|
||||
allowShortUrl: uiCapabilities.discover.createShortUrl,
|
||||
getUnhashableStates,
|
||||
shareableUrl: unhashUrl(window.location.href, getUnhashableStates()),
|
||||
objectId: savedSearch.id,
|
||||
objectType: 'search',
|
||||
shareContextMenuExtensions,
|
||||
sharingData: {
|
||||
...sharingData,
|
||||
title: savedSearch.title,
|
||||
|
|
|
@ -36,7 +36,6 @@ import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
|||
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share';
|
||||
// @ts-ignore
|
||||
import { IndexPattern, IndexPatterns } from 'ui/index_patterns';
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
|
@ -58,6 +57,7 @@ const services = {
|
|||
uiSettings: npStart.core.uiSettings,
|
||||
uiActions: npStart.plugins.uiActions,
|
||||
embeddable: npStart.plugins.embeddable,
|
||||
share: npStart.plugins.share,
|
||||
// legacy
|
||||
docTitle,
|
||||
docViewsRegistry,
|
||||
|
@ -68,7 +68,6 @@ const services = {
|
|||
SavedObjectRegistryProvider,
|
||||
SavedObjectProvider,
|
||||
SearchSource,
|
||||
ShareContextMenuExtensionsRegistryProvider,
|
||||
StateProvider,
|
||||
timefilter,
|
||||
uiModules,
|
||||
|
@ -99,7 +98,6 @@ export { RequestAdapter } from 'ui/inspector/adapters';
|
|||
export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
|
||||
export { FieldList } from 'ui/index_patterns';
|
||||
export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
export { showShareContextMenu } from 'ui/share';
|
||||
export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
|
||||
export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
|
||||
// @ts-ignore
|
||||
|
@ -110,6 +108,7 @@ export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing';
|
|||
export { tabifyAggResponse } from 'ui/agg_response/tabify';
|
||||
// @ts-ignore
|
||||
export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
|
||||
export { unhashUrl } from 'ui/state_management/state_hashing';
|
||||
|
||||
// EXPORT types
|
||||
export { Vis } from 'ui/vis';
|
||||
|
|
|
@ -42,10 +42,10 @@ import {
|
|||
KibanaParsedUrl,
|
||||
migrateLegacyQuery,
|
||||
SavedObjectSaveModal,
|
||||
showShareContextMenu,
|
||||
showSaveModal,
|
||||
stateMonitorFactory,
|
||||
subscribeWithScope,
|
||||
unhashUrl,
|
||||
} from '../kibana_services';
|
||||
|
||||
const {
|
||||
|
@ -56,12 +56,12 @@ const {
|
|||
docTitle,
|
||||
FilterBarQueryFilterProvider,
|
||||
getBasePath,
|
||||
ShareContextMenuExtensionsRegistryProvider,
|
||||
toastNotifications,
|
||||
timefilter,
|
||||
uiModules,
|
||||
uiRoutes,
|
||||
visualizations,
|
||||
share,
|
||||
} = getServices();
|
||||
|
||||
const { savedQueryService } = data.search.services;
|
||||
|
@ -159,7 +159,6 @@ function VisEditor(
|
|||
) {
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
const getUnhashableStates = Private(getUnhashableStatesProvider);
|
||||
const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider);
|
||||
|
||||
// Retrieve the resolved SavedVis instance.
|
||||
const savedVis = $route.current.locals.savedVis;
|
||||
|
@ -239,14 +238,13 @@ function VisEditor(
|
|||
run: (anchorElement) => {
|
||||
const hasUnappliedChanges = vis.dirty;
|
||||
const hasUnsavedChanges = $appStatus.dirty;
|
||||
showShareContextMenu({
|
||||
share.toggleShareContextMenu({
|
||||
anchorElement,
|
||||
allowEmbed: true,
|
||||
allowShortUrl: capabilities.visualize.createShortUrl,
|
||||
getUnhashableStates,
|
||||
shareableUrl: unhashUrl(window.location.href, getUnhashableStates()),
|
||||
objectId: savedVis.id,
|
||||
objectType: 'visualization',
|
||||
shareContextMenuExtensions,
|
||||
sharingData: {
|
||||
title: savedVis.title,
|
||||
},
|
||||
|
|
|
@ -36,7 +36,6 @@ import { wrapInI18nContext } from 'ui/i18n';
|
|||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue';
|
||||
import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
// Saved objects
|
||||
|
@ -62,6 +61,7 @@ const services = {
|
|||
toastNotifications: npStart.core.notifications.toasts,
|
||||
uiSettings: npStart.core.uiSettings,
|
||||
|
||||
share: npStart.plugins.share,
|
||||
data,
|
||||
embeddables,
|
||||
visualizations,
|
||||
|
@ -77,7 +77,6 @@ const services = {
|
|||
SavedObjectProvider,
|
||||
SavedObjectRegistryProvider,
|
||||
SavedObjectsClientProvider,
|
||||
ShareContextMenuExtensionsRegistryProvider,
|
||||
timefilter,
|
||||
uiModules,
|
||||
uiRoutes,
|
||||
|
@ -99,13 +98,13 @@ export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types';
|
|||
// @ts-ignore
|
||||
export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing';
|
||||
export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
export { showShareContextMenu } from 'ui/share';
|
||||
export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
|
||||
export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
|
||||
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 {
|
||||
Container,
|
||||
Embeddable,
|
||||
|
|
|
@ -62,6 +62,9 @@ export const npSetup = {
|
|||
}
|
||||
},
|
||||
},
|
||||
share: {
|
||||
register: () => {},
|
||||
},
|
||||
devTools: {
|
||||
register: () => {},
|
||||
},
|
||||
|
@ -162,6 +165,9 @@ export const npStart = {
|
|||
},
|
||||
},
|
||||
},
|
||||
share: {
|
||||
toggleShareContextMenu: () => {},
|
||||
},
|
||||
inspector: {
|
||||
isAvailable: () => false,
|
||||
open: () => ({
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
FeatureCatalogueSetup,
|
||||
FeatureCatalogueStart,
|
||||
} from '../../../../plugins/feature_catalogue/public';
|
||||
import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public';
|
||||
|
||||
export interface PluginsSetup {
|
||||
data: ReturnType<DataPlugin['setup']>;
|
||||
|
@ -41,6 +42,7 @@ export interface PluginsSetup {
|
|||
feature_catalogue: FeatureCatalogueSetup;
|
||||
inspector: InspectorSetup;
|
||||
uiActions: IUiActionsSetup;
|
||||
share: SharePluginSetup;
|
||||
devTools: DevToolsSetup;
|
||||
}
|
||||
|
||||
|
@ -52,6 +54,7 @@ export interface PluginsStart {
|
|||
feature_catalogue: FeatureCatalogueStart;
|
||||
inspector: InspectorStart;
|
||||
uiActions: IUiActionsStart;
|
||||
share: SharePluginStart;
|
||||
devTools: DevToolsStart;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
@import './components/index';
|
||||
@import './share_context_menu';
|
||||
|
|
3
src/legacy/ui/public/share/_share_context_menu.scss
Normal file
3
src/legacy/ui/public/share/_share_context_menu.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.kbnShareContextMenu__finalPanel {
|
||||
padding: $euiSize;
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`shareContextMenuExtensions should sort ascending on sort order first and then ascending on name 1`] = `
|
||||
<EuiContextMenu
|
||||
data-test-subj="shareContextMenu"
|
||||
initialPanelId={4}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"content": <InjectIntl(UrlPanelContentUI)
|
||||
getUnhashableStates={[Function]}
|
||||
objectType="dashboard"
|
||||
/>,
|
||||
"id": 1,
|
||||
"title": "Permalink",
|
||||
},
|
||||
Object {
|
||||
"content": <div>
|
||||
panel content
|
||||
</div>,
|
||||
"id": 2,
|
||||
"title": "AAA panel",
|
||||
},
|
||||
Object {
|
||||
"content": <div>
|
||||
panel content
|
||||
</div>,
|
||||
"id": 3,
|
||||
"title": "ZZZ panel",
|
||||
},
|
||||
Object {
|
||||
"id": 4,
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-Permalinks",
|
||||
"icon": "link",
|
||||
"name": "Permalinks",
|
||||
"panel": 1,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-ZZZpanel",
|
||||
"name": "ZZZ panel",
|
||||
"panel": 3,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-AAApanel",
|
||||
"name": "AAA panel",
|
||||
"panel": 2,
|
||||
},
|
||||
],
|
||||
"title": "Share this dashboard",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`should only render permalink panel when there are no other panels 1`] = `
|
||||
<EuiContextMenu
|
||||
data-test-subj="shareContextMenu"
|
||||
initialPanelId={1}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"content": <InjectIntl(UrlPanelContentUI)
|
||||
getUnhashableStates={[Function]}
|
||||
objectType="dashboard"
|
||||
/>,
|
||||
"id": 1,
|
||||
"title": "Permalink",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`should render context menu panel when there are more than one panel 1`] = `
|
||||
<EuiContextMenu
|
||||
data-test-subj="shareContextMenu"
|
||||
initialPanelId={3}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"content": <InjectIntl(UrlPanelContentUI)
|
||||
getUnhashableStates={[Function]}
|
||||
objectType="dashboard"
|
||||
/>,
|
||||
"id": 1,
|
||||
"title": "Permalink",
|
||||
},
|
||||
Object {
|
||||
"content": <InjectIntl(UrlPanelContentUI)
|
||||
getUnhashableStates={[Function]}
|
||||
isEmbedded={true}
|
||||
objectType="dashboard"
|
||||
/>,
|
||||
"id": 2,
|
||||
"title": "Embed Code",
|
||||
},
|
||||
Object {
|
||||
"id": 3,
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-Embedcode",
|
||||
"icon": "console",
|
||||
"name": "Embed code",
|
||||
"panel": 2,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-Permalinks",
|
||||
"icon": "link",
|
||||
"name": "Permalinks",
|
||||
"panel": 1,
|
||||
},
|
||||
],
|
||||
"title": "Share this dashboard",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
|
@ -1,431 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`render 1`] = `
|
||||
<EuiForm
|
||||
className="kbnShareContextMenu__finalPanel"
|
||||
data-test-subj="shareUrlForm"
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
defaultMessage="Can't share as saved object until the {objectType} has been saved."
|
||||
id="common.ui.share.urlPanel.canNotShareAsSavedObjectHelpText"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Generate the link as"
|
||||
id="common.ui.share.urlPanel.generateLinkAsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected="snapshot"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot"
|
||||
id="common.ui.share.urlPanel.snapshotLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself. Edits to the saved {objectType} won't be visible via this URL."
|
||||
id="common.ui.share.urlPanel.snapshotDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
"disabled": true,
|
||||
"id": "savedObject",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved object"
|
||||
id="common.ui.share.urlPanel.savedObjectLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
id="common.ui.share.urlPanel.savedObjectDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
data-test-subj="createShortUrl"
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="useShortUrl"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Short URL"
|
||||
id="common.ui.share.urlPanel.shortUrlLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="We recommend sharing shortened snapshot URLs for maximum compatibility. Internet Explorer has URL length restrictions, and some wiki and markup parsers don't do well with the full-length version of the snapshot URL, but the short URL should work great."
|
||||
id="common.ui.share.urlPanel.shortUrlHelpText"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="kbnShareContextMenu__copyAnchor"
|
||||
textToCopy="http://localhost/"
|
||||
>
|
||||
<Component />
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
`;
|
||||
|
||||
exports[`should enable saved object export option when objectId is provided 1`] = `
|
||||
<EuiForm
|
||||
className="kbnShareContextMenu__finalPanel"
|
||||
data-test-subj="shareUrlForm"
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Generate the link as"
|
||||
id="common.ui.share.urlPanel.generateLinkAsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected="snapshot"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot"
|
||||
id="common.ui.share.urlPanel.snapshotLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself. Edits to the saved {objectType} won't be visible via this URL."
|
||||
id="common.ui.share.urlPanel.snapshotDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
"disabled": false,
|
||||
"id": "savedObject",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved object"
|
||||
id="common.ui.share.urlPanel.savedObjectLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
id="common.ui.share.urlPanel.savedObjectDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
data-test-subj="createShortUrl"
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="useShortUrl"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Short URL"
|
||||
id="common.ui.share.urlPanel.shortUrlLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="We recommend sharing shortened snapshot URLs for maximum compatibility. Internet Explorer has URL length restrictions, and some wiki and markup parsers don't do well with the full-length version of the snapshot URL, but the short URL should work great."
|
||||
id="common.ui.share.urlPanel.shortUrlHelpText"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="kbnShareContextMenu__copyAnchor"
|
||||
textToCopy="http://localhost/"
|
||||
>
|
||||
<Component />
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
`;
|
||||
|
||||
exports[`should hide short url section when allowShortUrl is false 1`] = `
|
||||
<EuiForm
|
||||
className="kbnShareContextMenu__finalPanel"
|
||||
data-test-subj="shareUrlForm"
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Generate the link as"
|
||||
id="common.ui.share.urlPanel.generateLinkAsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected="snapshot"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot"
|
||||
id="common.ui.share.urlPanel.snapshotLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself. Edits to the saved {objectType} won't be visible via this URL."
|
||||
id="common.ui.share.urlPanel.snapshotDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
"disabled": false,
|
||||
"id": "savedObject",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved object"
|
||||
id="common.ui.share.urlPanel.savedObjectLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
id="common.ui.share.urlPanel.savedObjectDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="kbnShareContextMenu__copyAnchor"
|
||||
textToCopy="http://localhost/"
|
||||
>
|
||||
<Component />
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
`;
|
|
@ -1 +0,0 @@
|
|||
@import './share_context_menu';
|
|
@ -1,8 +0,0 @@
|
|||
.kbnShareContextMenu__finalPanel {
|
||||
padding: $euiSize;
|
||||
}
|
||||
|
||||
.kbnShareContextMenu__copyAnchor,
|
||||
.kbnShareContextMenu__copyButton {
|
||||
width: 100%;
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
jest.mock('../lib/url_shortener', () => ({}));
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
|
||||
import {
|
||||
ShareContextMenu,
|
||||
} from './share_context_menu';
|
||||
|
||||
test('should render context menu panel when there are more than one panel', () => {
|
||||
const component = shallowWithIntl(<ShareContextMenu.WrappedComponent
|
||||
allowEmbed
|
||||
objectType="dashboard"
|
||||
getUnhashableStates={() => {}}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should only render permalink panel when there are no other panels', () => {
|
||||
const component = shallowWithIntl(<ShareContextMenu.WrappedComponent
|
||||
allowEmbed={false}
|
||||
objectType="dashboard"
|
||||
getUnhashableStates={() => {}}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('shareContextMenuExtensions', () => {
|
||||
const shareContextMenuExtensions = [
|
||||
{
|
||||
getShareActions: () => {
|
||||
return [
|
||||
{
|
||||
panel: {
|
||||
title: 'AAA panel',
|
||||
content: (<div>panel content</div>),
|
||||
},
|
||||
shareMenuItem: {
|
||||
name: 'AAA panel',
|
||||
sortOrder: 5,
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
getShareActions: () => {
|
||||
return [
|
||||
{
|
||||
panel: {
|
||||
title: 'ZZZ panel',
|
||||
content: (<div>panel content</div>),
|
||||
},
|
||||
shareMenuItem: {
|
||||
name: 'ZZZ panel',
|
||||
sortOrder: 0,
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
test('should sort ascending on sort order first and then ascending on name', () => {
|
||||
const component = shallowWithIntl(<ShareContextMenu.WrappedComponent
|
||||
allowEmbed={false}
|
||||
objectType="dashboard"
|
||||
getUnhashableStates={() => {}}
|
||||
shareContextMenuExtensions={shareContextMenuExtensions}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
jest.mock('ui/kfetch', () => ({}));
|
||||
|
||||
jest.mock('../../chrome', () => ({}));
|
||||
|
||||
import sinon from 'sinon';
|
||||
import expect from '@kbn/expect';
|
||||
import { shortenUrl } from './url_shortener';
|
||||
|
||||
describe('Url shortener', () => {
|
||||
const shareId = 'id123';
|
||||
|
||||
let kfetchStub;
|
||||
beforeEach(() => {
|
||||
kfetchStub = sinon.stub();
|
||||
require('ui/kfetch').kfetch = async (...args) => {
|
||||
return kfetchStub(...args);
|
||||
};
|
||||
});
|
||||
|
||||
describe('Shorten without base path', () => {
|
||||
beforeAll(() => {
|
||||
require('../../chrome').getBasePath = () => {
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
it('should shorten urls with a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl('http://localhost:5601/app/kibana#123');
|
||||
expect(shortUrl).to.be(`http://localhost:5601/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl('http://localhost/app/kibana#123');
|
||||
expect(shortUrl).to.be(`http://localhost/goto/${shareId}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shorten with base path', () => {
|
||||
const basePath = '/foo';
|
||||
|
||||
beforeAll(() => {
|
||||
require('../../chrome').getBasePath = () => {
|
||||
return basePath;
|
||||
};
|
||||
});
|
||||
|
||||
it('should shorten urls with a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost:5601${basePath}/app/kibana#123`);
|
||||
expect(shortUrl).to.be(`http://localhost:5601${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana#123`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana?foo#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana?foo#123`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls without a hash', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string in the hash', async () => {
|
||||
const relativeUrl = "/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"; //eslint-disable-line max-len, quotes
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"}' //eslint-disable-line max-len, quotes
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}${relativeUrl}`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 mayexport
|
||||
* 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 { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
|
||||
|
||||
export interface ShareActionProps {
|
||||
objectType: string;
|
||||
objectId?: string;
|
||||
getUnhashableStates: () => object[];
|
||||
sharingData: any;
|
||||
isDirty: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export interface ShareContextMenuPanelItem extends EuiContextMenuPanelItemDescriptor {
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface ShareAction {
|
||||
shareMenuItem: ShareContextMenuPanelItem;
|
||||
panel: EuiContextMenuPanelDescriptor;
|
||||
}
|
||||
|
||||
export interface ShareActionProvider {
|
||||
readonly id: string;
|
||||
|
||||
getShareActions: (actionProps: ShareActionProps) => ShareAction[];
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { EuiWrappingPopover } from '@elastic/eui';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { ShareContextMenu } from './components/share_context_menu';
|
||||
import { ShareActionProvider } from './share_action';
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
const onClose = () => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
isOpen = false;
|
||||
};
|
||||
|
||||
interface ShowProps {
|
||||
anchorElement: any;
|
||||
allowEmbed: boolean;
|
||||
allowShortUrl: boolean;
|
||||
getUnhashableStates: () => object[];
|
||||
objectId?: string;
|
||||
objectType: string;
|
||||
shareContextMenuExtensions?: ShareActionProvider[];
|
||||
sharingData: any;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
export function showShareContextMenu({
|
||||
anchorElement,
|
||||
allowEmbed,
|
||||
allowShortUrl,
|
||||
getUnhashableStates,
|
||||
objectId,
|
||||
objectType,
|
||||
shareContextMenuExtensions,
|
||||
sharingData,
|
||||
isDirty,
|
||||
}: ShowProps) {
|
||||
if (isOpen) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
isOpen = true;
|
||||
|
||||
document.body.appendChild(container);
|
||||
const element = (
|
||||
<I18nContext>
|
||||
<EuiWrappingPopover
|
||||
id="sharePopover"
|
||||
button={anchorElement}
|
||||
isOpen={true}
|
||||
closePopover={onClose}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<ShareContextMenu
|
||||
allowEmbed={allowEmbed}
|
||||
allowShortUrl={allowShortUrl}
|
||||
getUnhashableStates={getUnhashableStates}
|
||||
objectId={objectId}
|
||||
objectType={objectType}
|
||||
shareContextMenuExtensions={shareContextMenuExtensions}
|
||||
sharingData={sharingData}
|
||||
isDirty={isDirty}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</EuiWrappingPopover>
|
||||
</I18nContext>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
}
|
24
src/plugins/share/README.md
Normal file
24
src/plugins/share/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Share plugin
|
||||
|
||||
Replaces the legacy `ui/share` module for registering share context menus.
|
||||
|
||||
## Example registration
|
||||
|
||||
```ts
|
||||
// For legacy plugins
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
npSetup.plugins.share.register(/* same details here */);
|
||||
|
||||
// For new plugins: first add 'share' to the list of `optionalPlugins`
|
||||
// in your kibana.json file. Then access the plugin directly in `setup`:
|
||||
|
||||
class MyPlugin {
|
||||
setup(core, plugins) {
|
||||
if (plugins.share) {
|
||||
plugins.share.register(/* same details here. */);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0.
|
6
src/plugins/share/kibana.json
Normal file
6
src/plugins/share/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "share",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true
|
||||
}
|
135
src/plugins/share/public/components/__snapshots__/share_context_menu.test.tsx.snap
generated
Normal file
135
src/plugins/share/public/components/__snapshots__/share_context_menu.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`shareContextMenuExtensions should sort ascending on sort order first and then ascending on name 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiContextMenu
|
||||
data-test-subj="shareContextMenu"
|
||||
initialPanelId={4}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"content": <UrlPanelContent
|
||||
allowShortUrl={false}
|
||||
basePath=""
|
||||
objectType="dashboard"
|
||||
post={[Function]}
|
||||
/>,
|
||||
"id": 1,
|
||||
"title": "Permalink",
|
||||
},
|
||||
Object {
|
||||
"content": <div>
|
||||
panel content
|
||||
</div>,
|
||||
"id": 2,
|
||||
"title": "AAA panel",
|
||||
},
|
||||
Object {
|
||||
"content": <div>
|
||||
panel content
|
||||
</div>,
|
||||
"id": 3,
|
||||
"title": "ZZZ panel",
|
||||
},
|
||||
Object {
|
||||
"id": 4,
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-Permalinks",
|
||||
"icon": "link",
|
||||
"name": "Permalinks",
|
||||
"panel": 1,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-ZZZpanel",
|
||||
"name": "ZZZ panel",
|
||||
"panel": 3,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-AAApanel",
|
||||
"name": "AAA panel",
|
||||
"panel": 2,
|
||||
},
|
||||
],
|
||||
"title": "Share this dashboard",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</I18nProvider>
|
||||
`;
|
||||
|
||||
exports[`should only render permalink panel when there are no other panels 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiContextMenu
|
||||
data-test-subj="shareContextMenu"
|
||||
initialPanelId={1}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"content": <UrlPanelContent
|
||||
allowShortUrl={false}
|
||||
basePath=""
|
||||
objectType="dashboard"
|
||||
post={[Function]}
|
||||
/>,
|
||||
"id": 1,
|
||||
"title": "Permalink",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</I18nProvider>
|
||||
`;
|
||||
|
||||
exports[`should render context menu panel when there are more than one panel 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiContextMenu
|
||||
data-test-subj="shareContextMenu"
|
||||
initialPanelId={3}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"content": <UrlPanelContent
|
||||
allowShortUrl={false}
|
||||
basePath=""
|
||||
objectType="dashboard"
|
||||
post={[Function]}
|
||||
/>,
|
||||
"id": 1,
|
||||
"title": "Permalink",
|
||||
},
|
||||
Object {
|
||||
"content": <UrlPanelContent
|
||||
allowShortUrl={false}
|
||||
basePath=""
|
||||
isEmbedded={true}
|
||||
objectType="dashboard"
|
||||
post={[Function]}
|
||||
/>,
|
||||
"id": 2,
|
||||
"title": "Embed Code",
|
||||
},
|
||||
Object {
|
||||
"id": 3,
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-Embedcode",
|
||||
"icon": "console",
|
||||
"name": "Embed code",
|
||||
"panel": 2,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "sharePanel-Permalinks",
|
||||
"icon": "link",
|
||||
"name": "Permalinks",
|
||||
"panel": 1,
|
||||
},
|
||||
],
|
||||
"title": "Share this dashboard",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</I18nProvider>
|
||||
`;
|
437
src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap
generated
Normal file
437
src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,437 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`render 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiForm
|
||||
className="kbnShareContextMenu__finalPanel"
|
||||
data-test-subj="shareUrlForm"
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
defaultMessage="Can't share as saved object until the {objectType} has been saved."
|
||||
id="share.urlPanel.canNotShareAsSavedObjectHelpText"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Generate the link as"
|
||||
id="share.urlPanel.generateLinkAsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected="snapshot"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot"
|
||||
id="share.urlPanel.snapshotLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself. Edits to the saved {objectType} won't be visible via this URL."
|
||||
id="share.urlPanel.snapshotDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
"disabled": true,
|
||||
"id": "savedObject",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved object"
|
||||
id="share.urlPanel.savedObjectLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
id="share.urlPanel.savedObjectDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
data-test-subj="createShortUrl"
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="useShortUrl"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Short URL"
|
||||
id="share.urlPanel.shortUrlLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="We recommend sharing shortened snapshot URLs for maximum compatibility. Internet Explorer has URL length restrictions, and some wiki and markup parsers don't do well with the full-length version of the snapshot URL, but the short URL should work great."
|
||||
id="share.urlPanel.shortUrlHelpText"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="eui-displayBlock"
|
||||
textToCopy="http://localhost/"
|
||||
>
|
||||
<Component />
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
</I18nProvider>
|
||||
`;
|
||||
|
||||
exports[`should enable saved object export option when objectId is provided 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiForm
|
||||
className="kbnShareContextMenu__finalPanel"
|
||||
data-test-subj="shareUrlForm"
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Generate the link as"
|
||||
id="share.urlPanel.generateLinkAsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected="snapshot"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot"
|
||||
id="share.urlPanel.snapshotLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself. Edits to the saved {objectType} won't be visible via this URL."
|
||||
id="share.urlPanel.snapshotDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
"disabled": false,
|
||||
"id": "savedObject",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved object"
|
||||
id="share.urlPanel.savedObjectLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
id="share.urlPanel.savedObjectDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
data-test-subj="createShortUrl"
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="useShortUrl"
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Short URL"
|
||||
id="share.urlPanel.shortUrlLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="We recommend sharing shortened snapshot URLs for maximum compatibility. Internet Explorer has URL length restrictions, and some wiki and markup parsers don't do well with the full-length version of the snapshot URL, but the short URL should work great."
|
||||
id="share.urlPanel.shortUrlHelpText"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="eui-displayBlock"
|
||||
textToCopy="http://localhost/"
|
||||
>
|
||||
<Component />
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
</I18nProvider>
|
||||
`;
|
||||
|
||||
exports[`should hide short url section when allowShortUrl is false 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiForm
|
||||
className="kbnShareContextMenu__finalPanel"
|
||||
data-test-subj="shareUrlForm"
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Generate the link as"
|
||||
id="share.urlPanel.generateLinkAsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected="snapshot"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot"
|
||||
id="share.urlPanel.snapshotLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself. Edits to the saved {objectType} won't be visible via this URL."
|
||||
id="share.urlPanel.snapshotDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
"disabled": false,
|
||||
"id": "savedObject",
|
||||
"label": <ForwardRef
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
defaultMessage="Saved object"
|
||||
id="share.urlPanel.savedObjectLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
id="share.urlPanel.savedObjectDescription"
|
||||
values={
|
||||
Object {
|
||||
"objectType": "dashboard",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiCopy
|
||||
afterMessage="Copied"
|
||||
anchorClassName="eui-displayBlock"
|
||||
textToCopy="http://localhost/"
|
||||
>
|
||||
<Component />
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
</I18nProvider>
|
||||
`;
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 { ShareMenuItem } from '../types';
|
||||
|
||||
jest.mock('../lib/url_shortener', () => ({}));
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { ShareContextMenu } from './share_context_menu';
|
||||
|
||||
const defaultProps = {
|
||||
allowEmbed: true,
|
||||
allowShortUrl: false,
|
||||
shareMenuItems: [],
|
||||
sharingData: null,
|
||||
isDirty: false,
|
||||
onClose: () => {},
|
||||
basePath: '',
|
||||
post: () => Promise.resolve(),
|
||||
objectType: 'dashboard',
|
||||
};
|
||||
|
||||
test('should render context menu panel when there are more than one panel', () => {
|
||||
const component = shallow(<ShareContextMenu {...defaultProps} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should only render permalink panel when there are no other panels', () => {
|
||||
const component = shallow(<ShareContextMenu {...defaultProps} allowEmbed={false} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('shareContextMenuExtensions', () => {
|
||||
const shareContextMenuItems: ShareMenuItem[] = [
|
||||
{
|
||||
panel: {
|
||||
id: '1',
|
||||
title: 'AAA panel',
|
||||
content: <div>panel content</div>,
|
||||
},
|
||||
shareMenuItem: {
|
||||
name: 'AAA panel',
|
||||
sortOrder: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
panel: {
|
||||
id: '2',
|
||||
title: 'ZZZ panel',
|
||||
content: <div>panel content</div>,
|
||||
},
|
||||
shareMenuItem: {
|
||||
name: 'ZZZ panel',
|
||||
sortOrder: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test('should sort ascending on sort order first and then ascending on name', () => {
|
||||
const component = shallow(
|
||||
<ShareContextMenu
|
||||
{...defaultProps}
|
||||
allowEmbed={false}
|
||||
shareMenuItems={shareContextMenuItems}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -19,47 +19,50 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiContextMenuPanelDescriptor } from '@elastic/eui';
|
||||
import { EuiContextMenu } from '@elastic/eui';
|
||||
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { ShareAction, ShareActionProvider, ShareContextMenuPanelItem } from 'ui/share/share_action';
|
||||
import { HttpStart } from 'kibana/public';
|
||||
|
||||
import { UrlPanelContent } from './url_panel_content';
|
||||
import { ShareMenuItem, ShareContextMenuPanelItem } from '../types';
|
||||
|
||||
interface Props {
|
||||
allowEmbed: boolean;
|
||||
allowShortUrl: boolean;
|
||||
objectId?: string;
|
||||
objectType: string;
|
||||
getUnhashableStates: () => object[];
|
||||
shareContextMenuExtensions?: ShareActionProvider[];
|
||||
shareableUrl?: string;
|
||||
shareMenuItems: ShareMenuItem[];
|
||||
sharingData: any;
|
||||
isDirty: boolean;
|
||||
onClose: () => void;
|
||||
intl: InjectedIntl;
|
||||
basePath: string;
|
||||
post: HttpStart['post'];
|
||||
}
|
||||
|
||||
class ShareContextMenuUI extends Component<Props> {
|
||||
export class ShareContextMenu extends Component<Props> {
|
||||
public render() {
|
||||
const { panels, initialPanelId } = this.getPanels();
|
||||
return (
|
||||
<EuiContextMenu
|
||||
initialPanelId={initialPanelId}
|
||||
panels={panels}
|
||||
data-test-subj="shareContextMenu"
|
||||
/>
|
||||
<I18nProvider>
|
||||
<EuiContextMenu
|
||||
initialPanelId={initialPanelId}
|
||||
panels={panels}
|
||||
data-test-subj="shareContextMenu"
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
||||
private getPanels = () => {
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [];
|
||||
const menuItems: ShareContextMenuPanelItem[] = [];
|
||||
const { intl } = this.props;
|
||||
|
||||
const permalinkPanel = {
|
||||
id: panels.length + 1,
|
||||
title: intl.formatMessage({
|
||||
id: 'common.ui.share.contextMenu.permalinkPanelTitle',
|
||||
title: i18n.translate('share.contextMenu.permalinkPanelTitle', {
|
||||
defaultMessage: 'Permalink',
|
||||
}),
|
||||
content: (
|
||||
|
@ -67,13 +70,14 @@ class ShareContextMenuUI extends Component<Props> {
|
|||
allowShortUrl={this.props.allowShortUrl}
|
||||
objectId={this.props.objectId}
|
||||
objectType={this.props.objectType}
|
||||
getUnhashableStates={this.props.getUnhashableStates}
|
||||
basePath={this.props.basePath}
|
||||
post={this.props.post}
|
||||
shareableUrl={this.props.shareableUrl}
|
||||
/>
|
||||
),
|
||||
};
|
||||
menuItems.push({
|
||||
name: intl.formatMessage({
|
||||
id: 'common.ui.share.contextMenu.permalinksLabel',
|
||||
name: i18n.translate('share.contextMenu.permalinksLabel', {
|
||||
defaultMessage: 'Permalinks',
|
||||
}),
|
||||
icon: 'link',
|
||||
|
@ -85,8 +89,7 @@ class ShareContextMenuUI extends Component<Props> {
|
|||
if (this.props.allowEmbed) {
|
||||
const embedPanel = {
|
||||
id: panels.length + 1,
|
||||
title: intl.formatMessage({
|
||||
id: 'common.ui.share.contextMenu.embedCodePanelTitle',
|
||||
title: i18n.translate('share.contextMenu.embedCodePanelTitle', {
|
||||
defaultMessage: 'Embed Code',
|
||||
}),
|
||||
content: (
|
||||
|
@ -95,14 +98,15 @@ class ShareContextMenuUI extends Component<Props> {
|
|||
isEmbedded
|
||||
objectId={this.props.objectId}
|
||||
objectType={this.props.objectType}
|
||||
getUnhashableStates={this.props.getUnhashableStates}
|
||||
basePath={this.props.basePath}
|
||||
post={this.props.post}
|
||||
shareableUrl={this.props.shareableUrl}
|
||||
/>
|
||||
),
|
||||
};
|
||||
panels.push(embedPanel);
|
||||
menuItems.push({
|
||||
name: intl.formatMessage({
|
||||
id: 'common.ui.share.contextMenu.embedCodeLabel',
|
||||
name: i18n.translate('share.contextMenu.embedCodeLabel', {
|
||||
defaultMessage: 'Embed code',
|
||||
}),
|
||||
icon: 'console',
|
||||
|
@ -111,51 +115,27 @@ class ShareContextMenuUI extends Component<Props> {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.props.shareContextMenuExtensions) {
|
||||
const {
|
||||
objectType,
|
||||
objectId,
|
||||
getUnhashableStates,
|
||||
sharingData,
|
||||
isDirty,
|
||||
onClose,
|
||||
} = this.props;
|
||||
this.props.shareContextMenuExtensions.forEach((provider: ShareActionProvider) => {
|
||||
provider
|
||||
.getShareActions({
|
||||
objectType,
|
||||
objectId,
|
||||
getUnhashableStates,
|
||||
sharingData,
|
||||
isDirty,
|
||||
onClose,
|
||||
})
|
||||
.forEach(({ shareMenuItem, panel }: ShareAction) => {
|
||||
const panelId = panels.length + 1;
|
||||
panels.push({
|
||||
...panel,
|
||||
id: panelId,
|
||||
});
|
||||
menuItems.push({
|
||||
...shareMenuItem,
|
||||
panel: panelId,
|
||||
});
|
||||
});
|
||||
this.props.shareMenuItems.forEach(({ shareMenuItem, panel }) => {
|
||||
const panelId = panels.length + 1;
|
||||
panels.push({
|
||||
...panel,
|
||||
id: panelId,
|
||||
});
|
||||
}
|
||||
menuItems.push({
|
||||
...shareMenuItem,
|
||||
panel: panelId,
|
||||
});
|
||||
});
|
||||
|
||||
if (menuItems.length > 1) {
|
||||
const topLevelMenuPanel = {
|
||||
id: panels.length + 1,
|
||||
title: intl.formatMessage(
|
||||
{
|
||||
id: 'common.ui.share.contextMenuTitle',
|
||||
defaultMessage: 'Share this {objectType}',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('share.contextMenuTitle', {
|
||||
defaultMessage: 'Share this {objectType}',
|
||||
values: {
|
||||
objectType: this.props.objectType,
|
||||
}
|
||||
),
|
||||
},
|
||||
}),
|
||||
items: menuItems
|
||||
// Sorts ascending on sort order first and then ascending on name
|
||||
.sort((a, b) => {
|
||||
|
@ -186,5 +166,3 @@ class ShareContextMenuUI extends Component<Props> {
|
|||
return { panels, initialPanelId };
|
||||
};
|
||||
}
|
||||
|
||||
export const ShareContextMenu = injectI18n(ShareContextMenuUI);
|
|
@ -20,37 +20,30 @@
|
|||
jest.mock('../lib/url_shortener', () => ({}));
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
UrlPanelContent,
|
||||
} from './url_panel_content';
|
||||
import { UrlPanelContent } from './url_panel_content';
|
||||
|
||||
const defaultProps = {
|
||||
allowShortUrl: true,
|
||||
objectType: 'dashboard',
|
||||
basePath: '',
|
||||
post: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
test('render', () => {
|
||||
const component = shallowWithIntl(<UrlPanelContent.WrappedComponent
|
||||
allowShortUrl={true}
|
||||
objectType="dashboard"
|
||||
getUnhashableStates={() => {}}
|
||||
/>);
|
||||
const component = shallow(<UrlPanelContent {...defaultProps} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should enable saved object export option when objectId is provided', () => {
|
||||
const component = shallowWithIntl(<UrlPanelContent.WrappedComponent
|
||||
allowShortUrl={true}
|
||||
objectId="id1"
|
||||
objectType="dashboard"
|
||||
getUnhashableStates={() => {}}
|
||||
/>);
|
||||
const component = shallow(<UrlPanelContent {...defaultProps} objectId="id1" />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should hide short url section when allowShortUrl is false', () => {
|
||||
const component = shallowWithIntl(<UrlPanelContent.WrappedComponent
|
||||
allowShortUrl={false}
|
||||
objectId="id1"
|
||||
objectType="dashboard"
|
||||
getUnhashableStates={() => {}}
|
||||
/>);
|
||||
const component = shallow(
|
||||
<UrlPanelContent {...defaultProps} allowShortUrl={false} objectId="id1" />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
|
@ -31,24 +31,25 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
EuiRadioGroup,
|
||||
EuiSwitch,
|
||||
EuiSwitchEvent,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { format as formatUrl, parse as parseUrl } from 'url';
|
||||
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { unhashUrl } from '../../state_management/state_hashing';
|
||||
import { shortenUrl } from '../lib/url_shortener';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { HttpStart } from 'kibana/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
// TODO: Remove once EuiIconTip supports "content" prop
|
||||
const FixedEuiIconTip = EuiIconTip as React.SFC<any>;
|
||||
import { shortenUrl } from '../lib/url_shortener';
|
||||
|
||||
interface Props {
|
||||
allowShortUrl: boolean;
|
||||
isEmbedded?: boolean;
|
||||
objectId?: string;
|
||||
objectType: string;
|
||||
getUnhashableStates: () => object[];
|
||||
intl: InjectedIntl;
|
||||
shareableUrl?: string;
|
||||
basePath: string;
|
||||
post: HttpStart['post'];
|
||||
}
|
||||
|
||||
enum ExportUrlAsType {
|
||||
|
@ -64,7 +65,7 @@ interface State {
|
|||
shortUrlErrorMsg?: string;
|
||||
}
|
||||
|
||||
class UrlPanelContentUI extends Component<Props, State> {
|
||||
export class UrlPanelContent extends Component<Props, State> {
|
||||
private mounted?: boolean;
|
||||
private shortUrlCache?: string;
|
||||
|
||||
|
@ -96,41 +97,41 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
|
||||
public render() {
|
||||
return (
|
||||
<EuiForm className="kbnShareContextMenu__finalPanel" data-test-subj="shareUrlForm">
|
||||
{this.renderExportAsRadioGroup()}
|
||||
<I18nProvider>
|
||||
<EuiForm className="kbnShareContextMenu__finalPanel" data-test-subj="shareUrlForm">
|
||||
{this.renderExportAsRadioGroup()}
|
||||
|
||||
{this.renderShortUrlSwitch()}
|
||||
{this.renderShortUrlSwitch()}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCopy
|
||||
textToCopy={this.state.url || ''}
|
||||
anchorClassName="kbnShareContextMenu__copyAnchor"
|
||||
>
|
||||
{(copy: () => void) => (
|
||||
<EuiButton
|
||||
fill
|
||||
onClick={copy}
|
||||
disabled={this.state.isCreatingShortUrl || this.state.url === ''}
|
||||
data-share-url={this.state.url}
|
||||
data-test-subj="copyShareUrlButton"
|
||||
size="s"
|
||||
>
|
||||
{this.props.isEmbedded ? (
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.copyIframeCodeButtonLabel"
|
||||
defaultMessage="Copy iFrame code"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.copyLinkButtonLabel"
|
||||
defaultMessage="Copy link"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
<EuiCopy textToCopy={this.state.url || ''} anchorClassName="eui-displayBlock">
|
||||
{(copy: () => void) => (
|
||||
<EuiButton
|
||||
fill
|
||||
fullWidth
|
||||
onClick={copy}
|
||||
disabled={this.state.isCreatingShortUrl || this.state.url === ''}
|
||||
data-share-url={this.state.url}
|
||||
data-test-subj="copyShareUrlButton"
|
||||
size="s"
|
||||
>
|
||||
{this.props.isEmbedded ? (
|
||||
<FormattedMessage
|
||||
id="share.urlPanel.copyIframeCodeButtonLabel"
|
||||
defaultMessage="Copy iFrame code"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="share.urlPanel.copyLinkButtonLabel"
|
||||
defaultMessage="Copy link"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -155,11 +156,9 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
const url = window.location.href;
|
||||
// Replace hashes with original RISON values.
|
||||
const unhashedUrl = unhashUrl(url, this.props.getUnhashableStates());
|
||||
const url = this.getSnapshotUrl();
|
||||
|
||||
const parsedUrl = parseUrl(unhashedUrl);
|
||||
const parsedUrl = parseUrl(url);
|
||||
if (!parsedUrl || !parsedUrl.hash) {
|
||||
return;
|
||||
}
|
||||
|
@ -184,9 +183,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
};
|
||||
|
||||
private getSnapshotUrl = () => {
|
||||
const url = window.location.href;
|
||||
// Replace hashes with original RISON values.
|
||||
return unhashUrl(url, this.props.getUnhashableStates());
|
||||
return this.props.shareableUrl || window.location.href;
|
||||
};
|
||||
|
||||
private makeUrlEmbeddable = (url: string) => {
|
||||
|
@ -233,8 +230,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
);
|
||||
};
|
||||
|
||||
// TODO: switch evt type to ChangeEvent<HTMLInputElement> once https://github.com/elastic/eui/issues/1134 is resolved
|
||||
private handleShortUrlChange = async (evt: any) => {
|
||||
private handleShortUrlChange = async (evt: EuiSwitchEvent) => {
|
||||
const isChecked = evt.target.checked;
|
||||
|
||||
if (!isChecked || this.shortUrlCache !== undefined) {
|
||||
|
@ -249,7 +245,10 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
});
|
||||
|
||||
try {
|
||||
const shortUrl = await shortenUrl(this.getSnapshotUrl());
|
||||
const shortUrl = await shortenUrl(this.getSnapshotUrl(), {
|
||||
basePath: this.props.basePath,
|
||||
post: this.props.post,
|
||||
});
|
||||
if (this.mounted) {
|
||||
this.shortUrlCache = shortUrl;
|
||||
this.setState(
|
||||
|
@ -267,15 +266,12 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
{
|
||||
useShortUrl: false,
|
||||
isCreatingShortUrl: false,
|
||||
shortUrlErrorMsg: this.props.intl.formatMessage(
|
||||
{
|
||||
id: 'common.ui.share.urlPanel.unableCreateShortUrlErrorMessage',
|
||||
defaultMessage: 'Unable to create short URL. Error: {errorMessage}',
|
||||
},
|
||||
{
|
||||
shortUrlErrorMsg: i18n.translate('share.urlPanel.unableCreateShortUrlErrorMessage', {
|
||||
defaultMessage: 'Unable to create short URL. Error: {errorMessage}',
|
||||
values: {
|
||||
errorMessage: fetchError.message,
|
||||
}
|
||||
),
|
||||
},
|
||||
}),
|
||||
},
|
||||
this.setUrl
|
||||
);
|
||||
|
@ -288,12 +284,9 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
{
|
||||
id: ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT,
|
||||
label: this.renderWithIconTip(
|
||||
<FormattedMessage id="share.urlPanel.snapshotLabel" defaultMessage="Snapshot" />,
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.snapshotLabel"
|
||||
defaultMessage="Snapshot"
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.snapshotDescription"
|
||||
id="share.urlPanel.snapshotDescription"
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself.
|
||||
Edits to the saved {objectType} won't be visible via this URL."
|
||||
values={{ objectType: this.props.objectType }}
|
||||
|
@ -305,12 +298,9 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
id: ExportUrlAsType.EXPORT_URL_AS_SAVED_OBJECT,
|
||||
disabled: this.isNotSaved(),
|
||||
label: this.renderWithIconTip(
|
||||
<FormattedMessage id="share.urlPanel.savedObjectLabel" defaultMessage="Saved object" />,
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.savedObjectLabel"
|
||||
defaultMessage="Saved object"
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.savedObjectDescription"
|
||||
id="share.urlPanel.savedObjectDescription"
|
||||
defaultMessage="You can share this URL with people to let them load the most recent saved version of this {objectType}."
|
||||
values={{ objectType: this.props.objectType }}
|
||||
/>
|
||||
|
@ -325,7 +315,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
<EuiFlexGroup gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem>{child}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FixedEuiIconTip content={tipContent} position="bottom" />
|
||||
<EuiIconTip content={tipContent} position="bottom" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
@ -334,7 +324,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
private renderExportAsRadioGroup = () => {
|
||||
const generateLinkAsHelp = this.isNotSaved() ? (
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.canNotShareAsSavedObjectHelpText"
|
||||
id="share.urlPanel.canNotShareAsSavedObjectHelpText"
|
||||
defaultMessage="Can't share as saved object until the {objectType} has been saved."
|
||||
values={{ objectType: this.props.objectType }}
|
||||
/>
|
||||
|
@ -345,7 +335,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.generateLinkAsLabel"
|
||||
id="share.urlPanel.generateLinkAsLabel"
|
||||
defaultMessage="Generate the link as"
|
||||
/>
|
||||
}
|
||||
|
@ -368,7 +358,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
const shortUrlLabel = (
|
||||
<FormattedMessage id="common.ui.share.urlPanel.shortUrlLabel" defaultMessage="Short URL" />
|
||||
<FormattedMessage id="share.urlPanel.shortUrlLabel" defaultMessage="Short URL" />
|
||||
);
|
||||
const switchLabel = this.state.isCreatingShortUrl ? (
|
||||
<span>
|
||||
|
@ -387,7 +377,7 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
);
|
||||
const tipContent = (
|
||||
<FormattedMessage
|
||||
id="common.ui.share.urlPanel.shortUrlHelpText"
|
||||
id="share.urlPanel.shortUrlHelpText"
|
||||
defaultMessage="We recommend sharing shortened snapshot URLs for maximum compatibility.
|
||||
Internet Explorer has URL length restrictions,
|
||||
and some wiki and markup parsers don't do well with the full-length version of the snapshot URL,
|
||||
|
@ -402,5 +392,3 @@ class UrlPanelContentUI extends Component<Props, State> {
|
|||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const UrlPanelContent = injectI18n(UrlPanelContentUI);
|
|
@ -17,11 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore: implicit any for JS file
|
||||
import { uiRegistry } from 'ui/registry/_registry';
|
||||
import { ShareActionProvider } from './share_action';
|
||||
export { SharePluginSetup, SharePluginStart } from './plugin';
|
||||
export {
|
||||
ShareContext,
|
||||
ShareMenuProvider,
|
||||
ShareMenuItem,
|
||||
ShowShareMenuOptions,
|
||||
ShareContextMenuPanelItem,
|
||||
} from './types';
|
||||
import { SharePlugin } from './plugin';
|
||||
|
||||
export const ShareContextMenuExtensionsRegistryProvider = uiRegistry<ShareActionProvider>({
|
||||
name: 'shareContextMenuExtensions',
|
||||
index: ['id'],
|
||||
});
|
||||
export const plugin = () => new SharePlugin();
|
115
src/plugins/share/public/lib/url_shortener.test.ts
Normal file
115
src/plugins/share/public/lib/url_shortener.test.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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 { shortenUrl } from './url_shortener';
|
||||
|
||||
describe('Url shortener', () => {
|
||||
const shareId = 'id123';
|
||||
|
||||
let postStub: jest.Mock;
|
||||
beforeEach(() => {
|
||||
postStub = jest.fn(() => Promise.resolve({ urlId: shareId }));
|
||||
});
|
||||
|
||||
describe('Shorten without base path', () => {
|
||||
it('should shorten urls with a port', async () => {
|
||||
const shortUrl = await shortenUrl('http://localhost:5601/app/kibana#123', {
|
||||
basePath: '',
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost:5601/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body: '{"url":"/app/kibana#123"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', async () => {
|
||||
const shortUrl = await shortenUrl('http://localhost/app/kibana#123', {
|
||||
basePath: '',
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body: '{"url":"/app/kibana#123"}',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shorten with base path', () => {
|
||||
const basePath = '/foo';
|
||||
|
||||
it('should shorten urls with a port', async () => {
|
||||
const shortUrl = await shortenUrl(`http://localhost:5601${basePath}/app/kibana#123`, {
|
||||
basePath,
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost:5601${basePath}/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body: '{"url":"/app/kibana#123"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', async () => {
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana#123`, {
|
||||
basePath,
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body: '{"url":"/app/kibana#123"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string', async () => {
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana?foo#123`, {
|
||||
basePath,
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body: '{"url":"/app/kibana?foo#123"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should shorten urls without a hash', async () => {
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana`, {
|
||||
basePath,
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body: '{"url":"/app/kibana"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string in the hash', async () => {
|
||||
const relativeUrl =
|
||||
'/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))';
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}${relativeUrl}`, {
|
||||
basePath,
|
||||
post: postStub,
|
||||
});
|
||||
expect(shortUrl).toBe(`http://localhost${basePath}/goto/${shareId}`);
|
||||
expect(postStub).toHaveBeenCalledWith(`/api/shorten_url`, {
|
||||
body:
|
||||
'{"url":"/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"}',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,13 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import url from 'url';
|
||||
import chrome from '../../chrome';
|
||||
|
||||
export async function shortenUrl(absoluteUrl: string) {
|
||||
const basePath = chrome.getBasePath();
|
||||
import { HttpStart } from 'kibana/public';
|
||||
|
||||
export async function shortenUrl(
|
||||
absoluteUrl: string,
|
||||
{ basePath, post }: { basePath: string; post: HttpStart['post'] }
|
||||
) {
|
||||
const parsedUrl = url.parse(absoluteUrl);
|
||||
if (!parsedUrl || !parsedUrl.path) {
|
||||
return;
|
||||
|
@ -34,7 +34,7 @@ export async function shortenUrl(absoluteUrl: string) {
|
|||
|
||||
const body = JSON.stringify({ url: relativeUrl });
|
||||
|
||||
const resp = await kfetch({ method: 'POST', pathname: '/api/shorten_url', body });
|
||||
const resp = await post('/api/shorten_url', { body });
|
||||
return url.format({
|
||||
protocol: parsedUrl.protocol,
|
||||
host: parsedUrl.host,
|
28
src/plugins/share/public/plugin.test.mocks.ts
Normal file
28
src/plugins/share/public/plugin.test.mocks.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { shareMenuRegistryMock } from './services/share_menu_registry.mock';
|
||||
import { shareMenuManagerMock } from './services/share_menu_manager.mock';
|
||||
|
||||
export const registryMock = shareMenuRegistryMock.create();
|
||||
export const managerMock = shareMenuManagerMock.create();
|
||||
jest.doMock('./services', () => ({
|
||||
ShareMenuRegistry: jest.fn(() => registryMock),
|
||||
ShareMenuManager: jest.fn(() => managerMock),
|
||||
}));
|
52
src/plugins/share/public/plugin.test.ts
Normal file
52
src/plugins/share/public/plugin.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { registryMock, managerMock } from './plugin.test.mocks';
|
||||
import { SharePlugin } from './plugin';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
|
||||
describe('SharePlugin', () => {
|
||||
beforeEach(() => {
|
||||
managerMock.start.mockClear();
|
||||
registryMock.setup.mockClear();
|
||||
registryMock.start.mockClear();
|
||||
});
|
||||
|
||||
describe('setup', () => {
|
||||
test('wires up and returns registry', async () => {
|
||||
const setup = await new SharePlugin().setup();
|
||||
expect(registryMock.setup).toHaveBeenCalledWith();
|
||||
expect(setup.register).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
test('wires up and returns show function, but not registry', async () => {
|
||||
const service = new SharePlugin();
|
||||
await service.setup();
|
||||
const start = await service.start({} as CoreStart);
|
||||
expect(registryMock.start).toHaveBeenCalled();
|
||||
expect(managerMock.start).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ getShareMenuItems: expect.any(Function) })
|
||||
);
|
||||
expect(start.toggleShareContextMenu).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
45
src/plugins/share/public/plugin.ts
Normal file
45
src/plugins/share/public/plugin.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { CoreStart, Plugin } from 'src/core/public';
|
||||
import { ShareMenuManager, ShareMenuManagerStart } from './services';
|
||||
import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services';
|
||||
|
||||
export class SharePlugin implements Plugin<SharePluginSetup, SharePluginStart> {
|
||||
private readonly shareMenuRegistry = new ShareMenuRegistry();
|
||||
private readonly shareContextMenu = new ShareMenuManager();
|
||||
|
||||
public async setup() {
|
||||
return {
|
||||
...this.shareMenuRegistry.setup(),
|
||||
};
|
||||
}
|
||||
|
||||
public async start(core: CoreStart) {
|
||||
return {
|
||||
...this.shareContextMenu.start(core, this.shareMenuRegistry.start()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type SharePluginSetup = ShareMenuRegistrySetup;
|
||||
|
||||
/** @public */
|
||||
export type SharePluginStart = ShareMenuManagerStart;
|
|
@ -17,5 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { showShareContextMenu } from './show_share_context_menu';
|
||||
export { ShareContextMenuExtensionsRegistryProvider } from './share_action_registry';
|
||||
export * from './share_menu_registry';
|
||||
export * from './share_menu_manager';
|
40
src/plugins/share/public/services/share_menu_manager.mock.ts
Normal file
40
src/plugins/share/public/services/share_menu_manager.mock.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { ShareMenuManager, ShareMenuManagerStart } from './share_menu_manager';
|
||||
|
||||
const createStartMock = (): jest.Mocked<ShareMenuManagerStart> => {
|
||||
const start = {
|
||||
toggleShareContextMenu: jest.fn(),
|
||||
};
|
||||
return start;
|
||||
};
|
||||
|
||||
const createMock = (): jest.Mocked<PublicMethodsOf<ShareMenuManager>> => {
|
||||
const service = {
|
||||
start: jest.fn(),
|
||||
};
|
||||
service.start.mockImplementation(createStartMock);
|
||||
return service;
|
||||
};
|
||||
|
||||
export const shareMenuManagerMock = {
|
||||
createStart: createStartMock,
|
||||
create: createMock,
|
||||
};
|
112
src/plugins/share/public/services/share_menu_manager.tsx
Normal file
112
src/plugins/share/public/services/share_menu_manager.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiWrappingPopover } from '@elastic/eui';
|
||||
|
||||
import { CoreStart, HttpStart } from 'kibana/public';
|
||||
import { ShareContextMenu } from '../components/share_context_menu';
|
||||
import { ShareMenuItem, ShowShareMenuOptions } from '../types';
|
||||
import { ShareMenuRegistryStart } from './share_menu_registry';
|
||||
|
||||
export class ShareMenuManager {
|
||||
private isOpen = false;
|
||||
|
||||
private container = document.createElement('div');
|
||||
|
||||
start(core: CoreStart, shareRegistry: ShareMenuRegistryStart) {
|
||||
return {
|
||||
/**
|
||||
* Collects share menu items from registered providers and mounts the share context menu under
|
||||
* the given `anchorElement`. If the context menu is already opened, a call to this method closes it.
|
||||
* @param options
|
||||
*/
|
||||
toggleShareContextMenu: (options: ShowShareMenuOptions) => {
|
||||
const menuItems = shareRegistry.getShareMenuItems({ ...options, onClose: this.onClose });
|
||||
this.toggleShareContextMenu({
|
||||
...options,
|
||||
menuItems,
|
||||
post: core.http.post,
|
||||
basePath: core.http.basePath.get(),
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private onClose = () => {
|
||||
ReactDOM.unmountComponentAtNode(this.container);
|
||||
this.isOpen = false;
|
||||
};
|
||||
|
||||
private toggleShareContextMenu({
|
||||
anchorElement,
|
||||
allowEmbed,
|
||||
allowShortUrl,
|
||||
objectId,
|
||||
objectType,
|
||||
sharingData,
|
||||
menuItems,
|
||||
shareableUrl,
|
||||
post,
|
||||
basePath,
|
||||
}: ShowShareMenuOptions & {
|
||||
menuItems: ShareMenuItem[];
|
||||
post: HttpStart['post'];
|
||||
basePath: string;
|
||||
}) {
|
||||
if (this.isOpen) {
|
||||
this.onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isOpen = true;
|
||||
|
||||
document.body.appendChild(this.container);
|
||||
const element = (
|
||||
<I18nProvider>
|
||||
<EuiWrappingPopover
|
||||
id="sharePopover"
|
||||
button={anchorElement}
|
||||
isOpen={true}
|
||||
closePopover={this.onClose}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<ShareContextMenu
|
||||
allowEmbed={allowEmbed}
|
||||
allowShortUrl={allowShortUrl}
|
||||
objectId={objectId}
|
||||
objectType={objectType}
|
||||
shareMenuItems={menuItems}
|
||||
sharingData={sharingData}
|
||||
shareableUrl={shareableUrl}
|
||||
onClose={this.onClose}
|
||||
post={post}
|
||||
basePath={basePath}
|
||||
/>
|
||||
</EuiWrappingPopover>
|
||||
</I18nProvider>
|
||||
);
|
||||
ReactDOM.render(element, this.container);
|
||||
}
|
||||
}
|
||||
export type ShareMenuManagerStart = ReturnType<ShareMenuManager['start']>;
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 {
|
||||
ShareMenuRegistry,
|
||||
ShareMenuRegistrySetup,
|
||||
ShareMenuRegistryStart,
|
||||
} from './share_menu_registry';
|
||||
import { ShareMenuItem, ShareContext } from '../types';
|
||||
|
||||
const createSetupMock = (): jest.Mocked<ShareMenuRegistrySetup> => {
|
||||
const setup = {
|
||||
register: jest.fn(),
|
||||
};
|
||||
return setup;
|
||||
};
|
||||
|
||||
const createStartMock = (): jest.Mocked<ShareMenuRegistryStart> => {
|
||||
const start = {
|
||||
getShareMenuItems: jest.fn((props: ShareContext) => [] as ShareMenuItem[]),
|
||||
};
|
||||
return start;
|
||||
};
|
||||
|
||||
const createMock = (): jest.Mocked<PublicMethodsOf<ShareMenuRegistry>> => {
|
||||
const service = {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
};
|
||||
service.setup.mockImplementation(createSetupMock);
|
||||
service.start.mockImplementation(createStartMock);
|
||||
return service;
|
||||
};
|
||||
|
||||
export const shareMenuRegistryMock = {
|
||||
createSetup: createSetupMock,
|
||||
createStart: createStartMock,
|
||||
create: createMock,
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { ShareMenuRegistry } from './share_menu_registry';
|
||||
import { ShareMenuItem, ShareContext } from '../types';
|
||||
|
||||
describe('ShareActionsRegistry', () => {
|
||||
describe('setup', () => {
|
||||
test('throws when registering duplicate id', () => {
|
||||
const setup = new ShareMenuRegistry().setup();
|
||||
setup.register({
|
||||
id: 'myTest',
|
||||
getShareMenuItems: () => [],
|
||||
});
|
||||
expect(() =>
|
||||
setup.register({
|
||||
id: 'myTest',
|
||||
getShareMenuItems: () => [],
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Share menu provider with id [myTest] has already been registered. Use a unique id."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
describe('getActions', () => {
|
||||
test('returns a flat list of actions returned by all providers', () => {
|
||||
const service = new ShareMenuRegistry();
|
||||
const registerFunction = service.setup().register;
|
||||
const shareAction1 = {} as ShareMenuItem;
|
||||
const shareAction2 = {} as ShareMenuItem;
|
||||
const shareAction3 = {} as ShareMenuItem;
|
||||
const provider1Callback = jest.fn(() => [shareAction1]);
|
||||
const provider2Callback = jest.fn(() => [shareAction2, shareAction3]);
|
||||
registerFunction({
|
||||
id: 'myTest',
|
||||
getShareMenuItems: provider1Callback,
|
||||
});
|
||||
registerFunction({
|
||||
id: 'myTest2',
|
||||
getShareMenuItems: provider2Callback,
|
||||
});
|
||||
const context = {} as ShareContext;
|
||||
expect(service.start().getShareMenuItems(context)).toEqual([
|
||||
shareAction1,
|
||||
shareAction2,
|
||||
shareAction3,
|
||||
]);
|
||||
expect(provider1Callback).toHaveBeenCalledWith(context);
|
||||
expect(provider2Callback).toHaveBeenCalledWith(context);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
57
src/plugins/share/public/services/share_menu_registry.ts
Normal file
57
src/plugins/share/public/services/share_menu_registry.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { ShareContext, ShareMenuProvider } from '../types';
|
||||
|
||||
export class ShareMenuRegistry {
|
||||
private readonly shareMenuProviders = new Map<string, ShareMenuProvider>();
|
||||
|
||||
public setup() {
|
||||
return {
|
||||
/**
|
||||
* Register an additional source of items for share context menu items. All registered providers
|
||||
* will be called if a consumer displays the context menu. Returned `ShareMenuItem`s will be shown
|
||||
* in the context menu together with the default built-in share options.
|
||||
* Each share provider needs a globally unique id.
|
||||
* @param shareMenuProvider
|
||||
*/
|
||||
register: (shareMenuProvider: ShareMenuProvider) => {
|
||||
if (this.shareMenuProviders.has(shareMenuProvider.id)) {
|
||||
throw new Error(
|
||||
`Share menu provider with id [${shareMenuProvider.id}] has already been registered. Use a unique id.`
|
||||
);
|
||||
}
|
||||
|
||||
this.shareMenuProviders.set(shareMenuProvider.id, shareMenuProvider);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start() {
|
||||
return {
|
||||
getShareMenuItems: (context: ShareContext) =>
|
||||
Array.from(this.shareMenuProviders.values()).flatMap(shareActionProvider =>
|
||||
shareActionProvider.getShareMenuItems(context)
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type ShareMenuRegistrySetup = ReturnType<ShareMenuRegistry['setup']>;
|
||||
export type ShareMenuRegistryStart = ReturnType<ShareMenuRegistry['start']>;
|
90
src/plugins/share/public/types.ts
Normal file
90
src/plugins/share/public/types.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
} from '@elastic/eui/src/components/context_menu/context_menu';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Properties of the current object to share. Registered share
|
||||
* menu providers will provide suitable items which have to
|
||||
* be rendered in an appropriate place by the caller.
|
||||
*
|
||||
* It is possible to use the static function `toggleShareContextMenu`
|
||||
* to render the menu as a popover.
|
||||
* */
|
||||
export interface ShareContext {
|
||||
objectType: string;
|
||||
objectId?: string;
|
||||
/**
|
||||
* Current url for sharing. This can be set in cases where `window.location.href`
|
||||
* does not contain a shareable URL (e.g. if using session storage to store the current
|
||||
* app state is enabled). In these cases the property should contain the URL in a
|
||||
* format which makes it possible to use it without having access to any other state
|
||||
* like the current session.
|
||||
*
|
||||
* If not set it will default to `window.location.href`
|
||||
*/
|
||||
shareableUrl: string;
|
||||
sharingData: { [key: string]: unknown };
|
||||
isDirty: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Eui context menu entry shown directly in the context menu. `sortOrder` is
|
||||
* used to order the individual items in a flat list returned by all registered
|
||||
* menu providers.
|
||||
* */
|
||||
export interface ShareContextMenuPanelItem extends EuiContextMenuPanelItemDescriptor {
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Definition of a menu item rendered in the share menu. `shareMenuItem` is shown
|
||||
* directly in the context menu. If the item is clicked, the `panel` is shown.
|
||||
* */
|
||||
export interface ShareMenuItem {
|
||||
shareMenuItem: ShareContextMenuPanelItem;
|
||||
panel: EuiContextMenuPanelDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* A source for additional menu items shown in the share context menu. Any provider
|
||||
* registered via `share.register()` will be called if a consumer displays the context
|
||||
* menu. Returned `ShareMenuItem`s will be shown in the context menu together with the
|
||||
* default built-in share options. Each share provider needs a globally unique id.
|
||||
* */
|
||||
export interface ShareMenuProvider {
|
||||
readonly id: string;
|
||||
|
||||
getShareMenuItems: (context: ShareContext) => ShareMenuItem[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ShowShareMenuOptions extends Omit<ShareContext, 'onClose'> {
|
||||
anchorElement: HTMLElement;
|
||||
allowEmbed: boolean;
|
||||
allowShortUrl: boolean;
|
||||
}
|
|
@ -127,12 +127,9 @@ class ReportingPanelContentUi extends Component<Props, State> {
|
|||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiCopy
|
||||
textToCopy={this.state.absoluteUrl}
|
||||
anchorClassName="kbnShareContextMenu__copyAnchor"
|
||||
>
|
||||
<EuiCopy textToCopy={this.state.absoluteUrl} anchorClassName="eui-displayBlock">
|
||||
{copy => (
|
||||
<EuiButton className="kbnShareContextMenu__copyButton" onClick={copy} size="s">
|
||||
<EuiButton fullWidth onClick={copy} size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.reporting.panelContent.copyUrlButtonLabel"
|
||||
defaultMessage="Copy POST URL"
|
||||
|
@ -147,8 +144,8 @@ class ReportingPanelContentUi extends Component<Props, State> {
|
|||
private renderGenerateReportButton = (isDisabled: boolean) => {
|
||||
return (
|
||||
<EuiButton
|
||||
className="kbnShareContextMenu__copyButton"
|
||||
disabled={isDisabled}
|
||||
fullWidth
|
||||
fill
|
||||
onClick={this.createReportingJob}
|
||||
data-test-subj="generateReportButton"
|
||||
|
|
|
@ -8,18 +8,18 @@ import { i18n } from '@kbn/i18n';
|
|||
// @ts-ignore: implicit any for JS file
|
||||
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
|
||||
import React from 'react';
|
||||
import { ShareActionProps } from 'ui/share/share_action';
|
||||
import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share/share_action_registry';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import { ReportingPanelContent } from '../components/reporting_panel_content';
|
||||
import { ShareContext } from '../../../../../../src/plugins/share/public';
|
||||
|
||||
function reportingProvider() {
|
||||
const getShareActions = ({
|
||||
const getShareMenuItems = ({
|
||||
objectType,
|
||||
objectId,
|
||||
sharingData,
|
||||
isDirty,
|
||||
onClose,
|
||||
}: ShareActionProps) => {
|
||||
}: ShareContext) => {
|
||||
if ('search' !== objectType) {
|
||||
return [];
|
||||
}
|
||||
|
@ -44,8 +44,10 @@ function reportingProvider() {
|
|||
toolTipContent: xpackInfo.get('features.reporting.csv.message'),
|
||||
disabled: !xpackInfo.get('features.reporting.csv.enableLinks', false) ? true : false,
|
||||
['data-test-subj']: 'csvReportMenuItem',
|
||||
sortOrder: 1,
|
||||
},
|
||||
panel: {
|
||||
id: 'csvReportingPanel',
|
||||
title: panelTitle,
|
||||
content: (
|
||||
<ReportingPanelContent
|
||||
|
@ -67,8 +69,8 @@ function reportingProvider() {
|
|||
|
||||
return {
|
||||
id: 'csvReports',
|
||||
getShareActions,
|
||||
getShareMenuItems,
|
||||
};
|
||||
}
|
||||
|
||||
ShareContextMenuExtensionsRegistryProvider.register(reportingProvider);
|
||||
npSetup.plugins.share.register(reportingProvider());
|
||||
|
|
|
@ -8,35 +8,34 @@ import { i18n } from '@kbn/i18n';
|
|||
import moment from 'moment-timezone';
|
||||
// @ts-ignore: implicit any for JS file
|
||||
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import React from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
import { ShareActionProps } from 'ui/share/share_action';
|
||||
import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share/share_action_registry';
|
||||
import { unhashUrl } from 'ui/state_management/state_hashing';
|
||||
import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content';
|
||||
import { ShareContext } from '../../../../../../src/plugins/share/public';
|
||||
|
||||
function reportingProvider(dashboardConfig: any) {
|
||||
const getShareActions = ({
|
||||
async function reportingProvider() {
|
||||
const injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const getShareMenuItems = ({
|
||||
objectType,
|
||||
objectId,
|
||||
getUnhashableStates,
|
||||
sharingData,
|
||||
isDirty,
|
||||
onClose,
|
||||
}: ShareActionProps) => {
|
||||
shareableUrl,
|
||||
}: ShareContext) => {
|
||||
if (!['dashboard', 'visualization'].includes(objectType)) {
|
||||
return [];
|
||||
}
|
||||
// Dashboard only mode does not currently support reporting
|
||||
// https://github.com/elastic/kibana/issues/18286
|
||||
if (objectType === 'dashboard' && dashboardConfig.getHideWriteControls()) {
|
||||
if (objectType === 'dashboard' && injector.get<any>('dashboardConfig').getHideWriteControls()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const getReportingJobParams = () => {
|
||||
// Replace hashes with original RISON values.
|
||||
const unhashedUrl = unhashUrl(window.location.href, getUnhashableStates());
|
||||
const relativeUrl = unhashedUrl.replace(window.location.origin + chrome.getBasePath(), '');
|
||||
const relativeUrl = shareableUrl.replace(window.location.origin + chrome.getBasePath(), '');
|
||||
|
||||
const browserTimezone =
|
||||
chrome.getUiSettingsClient().get('dateFormat:tz') === 'Browser'
|
||||
|
@ -53,8 +52,7 @@ function reportingProvider(dashboardConfig: any) {
|
|||
|
||||
const getPngJobParams = () => {
|
||||
// Replace hashes with original RISON values.
|
||||
const unhashedUrl = unhashUrl(window.location.href, getUnhashableStates());
|
||||
const relativeUrl = unhashedUrl.replace(window.location.origin + chrome.getBasePath(), '');
|
||||
const relativeUrl = shareableUrl.replace(window.location.origin + chrome.getBasePath(), '');
|
||||
|
||||
const browserTimezone =
|
||||
chrome.getUiSettingsClient().get('dateFormat:tz') === 'Browser'
|
||||
|
@ -87,6 +85,7 @@ function reportingProvider(dashboardConfig: any) {
|
|||
sortOrder: 10,
|
||||
},
|
||||
panel: {
|
||||
id: 'reportingPdfPanel',
|
||||
title: panelTitle,
|
||||
content: (
|
||||
<ScreenCapturePanelContent
|
||||
|
@ -115,6 +114,7 @@ function reportingProvider(dashboardConfig: any) {
|
|||
sortOrder: 10,
|
||||
},
|
||||
panel: {
|
||||
id: 'reportingPngPanel',
|
||||
title: panelTitle,
|
||||
content: (
|
||||
<ScreenCapturePanelContent
|
||||
|
@ -135,8 +135,10 @@ function reportingProvider(dashboardConfig: any) {
|
|||
|
||||
return {
|
||||
id: 'screenCaptureReports',
|
||||
getShareActions,
|
||||
getShareMenuItems,
|
||||
};
|
||||
}
|
||||
|
||||
ShareContextMenuExtensionsRegistryProvider.register(reportingProvider);
|
||||
(async () => {
|
||||
npSetup.plugins.share.register(await reportingProvider());
|
||||
})();
|
||||
|
|
|
@ -493,22 +493,6 @@
|
|||
"common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存",
|
||||
"common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました",
|
||||
"common.ui.scriptingLanguages.errorFetchingToastDescription": "Elasticsearch から利用可能なスクリプト言語の取得中にエラーが発生しました",
|
||||
"common.ui.share.contextMenu.embedCodeLabel": "埋め込みコード",
|
||||
"common.ui.share.contextMenu.embedCodePanelTitle": "埋め込みコード",
|
||||
"common.ui.share.contextMenu.permalinkPanelTitle": "パーマリンク",
|
||||
"common.ui.share.contextMenu.permalinksLabel": "パーマリンク",
|
||||
"common.ui.share.contextMenuTitle": "この {objectType} を共有",
|
||||
"common.ui.share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。",
|
||||
"common.ui.share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー",
|
||||
"common.ui.share.urlPanel.copyLinkButtonLabel": "リンクをコピー",
|
||||
"common.ui.share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成",
|
||||
"common.ui.share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。",
|
||||
"common.ui.share.urlPanel.savedObjectLabel": "保存されたオブジェクト",
|
||||
"common.ui.share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。",
|
||||
"common.ui.share.urlPanel.shortUrlLabel": "短い URL",
|
||||
"common.ui.share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.",
|
||||
"common.ui.share.urlPanel.snapshotLabel": "スナップショット",
|
||||
"common.ui.share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}",
|
||||
"common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません",
|
||||
"common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。",
|
||||
"common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。",
|
||||
|
@ -2836,6 +2820,22 @@
|
|||
"server.status.redTitle": "赤",
|
||||
"server.status.uninitializedTitle": "アンインストールしました",
|
||||
"server.status.yellowTitle": "黄色",
|
||||
"share.contextMenu.embedCodeLabel": "埋め込みコード",
|
||||
"share.contextMenu.embedCodePanelTitle": "埋め込みコード",
|
||||
"share.contextMenu.permalinkPanelTitle": "パーマリンク",
|
||||
"share.contextMenu.permalinksLabel": "パーマリンク",
|
||||
"share.contextMenuTitle": "この {objectType} を共有",
|
||||
"share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。",
|
||||
"share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー",
|
||||
"share.urlPanel.copyLinkButtonLabel": "リンクをコピー",
|
||||
"share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成",
|
||||
"share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。",
|
||||
"share.urlPanel.savedObjectLabel": "保存されたオブジェクト",
|
||||
"share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。",
|
||||
"share.urlPanel.shortUrlLabel": "短い URL",
|
||||
"share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.",
|
||||
"share.urlPanel.snapshotLabel": "スナップショット",
|
||||
"share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}",
|
||||
"statusPage.loadStatus.serverIsDownErrorMessage": "サーバーステータスのリクエストに失敗しました。サーバーがダウンしている可能性があります。",
|
||||
"statusPage.loadStatus.serverStatusCodeErrorMessage": "サーバーステータスのリクエストに失敗しました。ステータスコード: {responseStatus}",
|
||||
"statusPage.metricsTiles.columns.heapTotalHeader": "ヒープ合計",
|
||||
|
@ -12774,4 +12774,4 @@
|
|||
"xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。",
|
||||
"xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -493,22 +493,6 @@
|
|||
"common.ui.savedObjects.saveAsNewLabel": "另存为新的 {savedObjectName}",
|
||||
"common.ui.savedObjects.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认",
|
||||
"common.ui.scriptingLanguages.errorFetchingToastDescription": "从 Elasticsearch 获取可用的脚本语言时出错",
|
||||
"common.ui.share.contextMenu.embedCodeLabel": "嵌入代码",
|
||||
"common.ui.share.contextMenu.embedCodePanelTitle": "嵌入代码",
|
||||
"common.ui.share.contextMenu.permalinkPanelTitle": "固定链接",
|
||||
"common.ui.share.contextMenu.permalinksLabel": "固定链接",
|
||||
"common.ui.share.contextMenuTitle": "共享此 {objectType}",
|
||||
"common.ui.share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。",
|
||||
"common.ui.share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码",
|
||||
"common.ui.share.urlPanel.copyLinkButtonLabel": "复制链接",
|
||||
"common.ui.share.urlPanel.generateLinkAsLabel": "将链接生成为",
|
||||
"common.ui.share.urlPanel.savedObjectDescription": "您可以将此 URL 共享给相关人员,以便他们可以加载此 {objectType} 最新的已保存版本。",
|
||||
"common.ui.share.urlPanel.savedObjectLabel": "已保存对象",
|
||||
"common.ui.share.urlPanel.shortUrlHelpText": "建议共享缩短的快照 URL,以实现最大的兼容性。Internet Explorer 有 URL 长度限制,某些 wiki 和标记分析器无法很好地处理全长版本的快照 URL,但应能很好地处理短 URL。",
|
||||
"common.ui.share.urlPanel.shortUrlLabel": "短 URL",
|
||||
"common.ui.share.urlPanel.snapshotDescription": "快照 URL 将{objectType}的当前状态编入 URL 自身之中。通过此 URL,将无法看到对已保存的{objectType}的编辑。",
|
||||
"common.ui.share.urlPanel.snapshotLabel": "快照",
|
||||
"common.ui.share.urlPanel.unableCreateShortUrlErrorMessage": "无法创建短 URL。错误:{errorMessage}",
|
||||
"common.ui.stateManagement.unableToParseUrlErrorMessage": "无法解析 URL",
|
||||
"common.ui.stateManagement.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。",
|
||||
"common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。",
|
||||
|
@ -2837,6 +2821,22 @@
|
|||
"server.status.redTitle": "红",
|
||||
"server.status.uninitializedTitle": "未初始化",
|
||||
"server.status.yellowTitle": "黄",
|
||||
"share.contextMenu.embedCodeLabel": "嵌入代码",
|
||||
"share.contextMenu.embedCodePanelTitle": "嵌入代码",
|
||||
"share.contextMenu.permalinkPanelTitle": "固定链接",
|
||||
"share.contextMenu.permalinksLabel": "固定链接",
|
||||
"share.contextMenuTitle": "共享此 {objectType}",
|
||||
"share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。",
|
||||
"share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码",
|
||||
"share.urlPanel.copyLinkButtonLabel": "复制链接",
|
||||
"share.urlPanel.generateLinkAsLabel": "将链接生成为",
|
||||
"share.urlPanel.savedObjectDescription": "您可以将此 URL 共享给相关人员,以便他们可以加载此 {objectType} 最新的已保存版本。",
|
||||
"share.urlPanel.savedObjectLabel": "已保存对象",
|
||||
"share.urlPanel.shortUrlHelpText": "建议共享缩短的快照 URL,以实现最大的兼容性。Internet Explorer 有 URL 长度限制,某些 wiki 和标记分析器无法很好地处理全长版本的快照 URL,但应能很好地处理短 URL。",
|
||||
"share.urlPanel.shortUrlLabel": "短 URL",
|
||||
"share.urlPanel.snapshotDescription": "快照 URL 将{objectType}的当前状态编入 URL 自身之中。通过此 URL,将无法看到对已保存的{objectType}的编辑。",
|
||||
"share.urlPanel.snapshotLabel": "快照",
|
||||
"share.urlPanel.unableCreateShortUrlErrorMessage": "无法创建短 URL。错误:{errorMessage}",
|
||||
"statusPage.loadStatus.serverIsDownErrorMessage": "无法请求服务器状态。也许您的服务器已关闭?",
|
||||
"statusPage.loadStatus.serverStatusCodeErrorMessage": "无法使用状态代码 {responseStatus} 请求服务器状态",
|
||||
"statusPage.metricsTiles.columns.heapTotalHeader": "堆总计",
|
||||
|
@ -12864,4 +12864,4 @@
|
|||
"xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。",
|
||||
"xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue