mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Dashboard] Modify state shared in dashboard permalinks (#141985)
* Remove unnecessary global state from share URL * Clean up * Add functional tests * Fix functional tests * Undo removal of time range from global state in URL * Clean up code * Clean up functional tests * Add warning when snapshot sharing with unsaved panel changes * Modify how error is passed down * Fix flakiness of new functional test suite * Update snapshots + clean up imports * Change wording of warning + colour of text * Address first round of feedback * Switch error state to button
This commit is contained in:
parent
6e5f13740c
commit
1eed5311c7
9 changed files with 354 additions and 171 deletions
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { cloneDeep, omit } from 'lodash';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import type { KibanaExecutionContext } from '@kbn/core/public';
|
||||
import { mapAndFlattenFilters } from '@kbn/data-plugin/public';
|
||||
|
@ -26,7 +26,7 @@ interface StateToDashboardContainerInputProps {
|
|||
}
|
||||
|
||||
interface StateToRawDashboardStateProps {
|
||||
state: DashboardState;
|
||||
state: Partial<DashboardState>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,13 +102,15 @@ const filtersAreEqual = (first: Filter, second: Filter) =>
|
|||
*/
|
||||
export const stateToRawDashboardState = ({
|
||||
state,
|
||||
}: StateToRawDashboardStateProps): RawDashboardState => {
|
||||
}: StateToRawDashboardStateProps): Partial<RawDashboardState> => {
|
||||
const {
|
||||
initializerContext: { kibanaVersion },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const savedDashboardPanels = Object.values(state.panels).map((panel) =>
|
||||
convertPanelStateToSavedDashboardPanel(panel, kibanaVersion)
|
||||
);
|
||||
return { ...omit(state, 'panels'), panels: savedDashboardPanels };
|
||||
const savedDashboardPanels = state?.panels
|
||||
? Object.values(state.panels).map((panel) =>
|
||||
convertPanelStateToSavedDashboardPanel(panel, kibanaVersion)
|
||||
)
|
||||
: undefined;
|
||||
return { ...state, panels: savedDashboardPanels };
|
||||
};
|
||||
|
|
|
@ -8,11 +8,14 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCheckboxGroup } from '@elastic/eui';
|
||||
import { QueryState } from '@kbn/data-plugin/common';
|
||||
import type { Capabilities } from '@kbn/core/public';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { getStateFromKbnUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import { setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { SerializableControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
|
||||
|
@ -21,8 +24,8 @@ import { dashboardUrlParams } from '../dashboard_router';
|
|||
import { shareModalStrings } from '../../dashboard_strings';
|
||||
import { convertPanelMapToSavedPanels } from '../../../common';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { stateToRawDashboardState } from '../lib/convert_dashboard_state';
|
||||
import { DashboardAppLocatorParams, DASHBOARD_APP_LOCATOR } from '../../locator';
|
||||
import { stateToRawDashboardState } from '../lib/convert_dashboard_state';
|
||||
|
||||
const showFilterBarId = 'showFilterBar';
|
||||
|
||||
|
@ -55,8 +58,8 @@ export function ShowShareModal({
|
|||
},
|
||||
},
|
||||
},
|
||||
share: { toggleShareContextMenu },
|
||||
initializerContext: { kibanaVersion },
|
||||
share: { toggleShareContextMenu },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
if (!toggleShareContextMenu) return; // TODO: Make this logic cleaner once share is an optional service
|
||||
|
@ -151,19 +154,25 @@ export function ShowShareModal({
|
|||
...unsavedStateForLocator,
|
||||
};
|
||||
|
||||
let _g = getStateFromKbnUrl<QueryState>('_g', window.location.href);
|
||||
if (_g?.filters && _g.filters.length === 0) {
|
||||
_g = omit(_g, 'filters');
|
||||
}
|
||||
const baseUrl = setStateToKbnUrl('_g', _g);
|
||||
|
||||
const shareableUrl = setStateToKbnUrl(
|
||||
'_a',
|
||||
stateToRawDashboardState({ state: unsavedDashboardState ?? {} }),
|
||||
{ useHash: false, storeInHashQuery: true },
|
||||
unhashUrl(baseUrl)
|
||||
);
|
||||
|
||||
toggleShareContextMenu({
|
||||
isDirty,
|
||||
anchorElement,
|
||||
allowEmbed: true,
|
||||
allowShortUrl,
|
||||
shareableUrl: setStateToKbnUrl(
|
||||
'_a',
|
||||
stateToRawDashboardState({
|
||||
state: currentDashboardState,
|
||||
}),
|
||||
{ useHash: false, storeInHashQuery: true },
|
||||
unhashUrl(window.location.href)
|
||||
),
|
||||
shareableUrl,
|
||||
objectId: savedObjectId,
|
||||
objectType: 'dashboard',
|
||||
sharingData: {
|
||||
|
@ -185,5 +194,8 @@ export function ShowShareModal({
|
|||
},
|
||||
],
|
||||
showPublicUrlSwitch,
|
||||
snapshotShareWarning: Boolean(unsavedDashboardState?.panels)
|
||||
? shareModalStrings.getSnapshotShareWarning()
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -278,6 +278,11 @@ export const shareModalStrings = {
|
|||
i18n.translate('dashboard.embedUrlParamExtension.include', {
|
||||
defaultMessage: 'Include',
|
||||
}),
|
||||
getSnapshotShareWarning: () =>
|
||||
i18n.translate('dashboard.snapshotShare.longUrlWarning', {
|
||||
defaultMessage:
|
||||
'One or more panels on this dashboard have changed. Before you generate a snapshot, save the dashboard.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const leaveConfirmStrings = {
|
||||
|
|
|
@ -42,38 +42,40 @@ exports[`share url panel content render 1`] = `
|
|||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
"label": <React.Fragment>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<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",
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<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>
|
||||
</EuiFlexGroup>,
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</React.Fragment>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
|
@ -225,38 +227,40 @@ exports[`share url panel content should enable saved object export option when o
|
|||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
"label": <React.Fragment>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<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",
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<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>
|
||||
</EuiFlexGroup>,
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</React.Fragment>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
|
@ -408,38 +412,40 @@ exports[`share url panel content should hide short url section when allowShortUr
|
|||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
"label": <React.Fragment>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<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",
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<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>
|
||||
</EuiFlexGroup>,
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</React.Fragment>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
|
@ -527,38 +533,40 @@ exports[`should show url param extensions 1`] = `
|
|||
Object {
|
||||
"data-test-subj": "exportAsSnapshot",
|
||||
"id": "snapshot",
|
||||
"label": <EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
"label": <React.Fragment>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<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",
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<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>
|
||||
</EuiFlexGroup>,
|
||||
/>
|
||||
}
|
||||
position="bottom"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</React.Fragment>,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "exportAsSavedObject",
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface ShareContextMenuProps {
|
|||
anonymousAccess?: AnonymousAccessServiceContract;
|
||||
showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean;
|
||||
urlService: BrowserUrlService;
|
||||
snapshotShareWarning?: string;
|
||||
}
|
||||
|
||||
export class ShareContextMenu extends Component<ShareContextMenuProps> {
|
||||
|
@ -66,6 +67,7 @@ export class ShareContextMenu extends Component<ShareContextMenuProps> {
|
|||
anonymousAccess={this.props.anonymousAccess}
|
||||
showPublicUrlSwitch={this.props.showPublicUrlSwitch}
|
||||
urlService={this.props.urlService}
|
||||
snapshotShareWarning={this.props.snapshotShareWarning}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
@ -96,6 +98,7 @@ export class ShareContextMenu extends Component<ShareContextMenuProps> {
|
|||
anonymousAccess={this.props.anonymousAccess}
|
||||
showPublicUrlSwitch={this.props.showPublicUrlSwitch}
|
||||
urlService={this.props.urlService}
|
||||
snapshotShareWarning={this.props.snapshotShareWarning}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
EuiRadioGroup,
|
||||
EuiSwitch,
|
||||
EuiSwitchEvent,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { format as formatUrl, parse as parseUrl } from 'url';
|
||||
|
@ -45,6 +46,7 @@ export interface UrlPanelContentProps {
|
|||
anonymousAccess?: AnonymousAccessServiceContract;
|
||||
showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean;
|
||||
urlService: BrowserUrlService;
|
||||
snapshotShareWarning?: string;
|
||||
}
|
||||
|
||||
export enum ExportUrlAsType {
|
||||
|
@ -78,7 +80,6 @@ export class UrlPanelContent extends Component<UrlPanelContentProps, State> {
|
|||
super(props);
|
||||
|
||||
this.shortUrlCache = undefined;
|
||||
|
||||
this.state = {
|
||||
exportUrlAs: ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT,
|
||||
useShortUrl: false,
|
||||
|
@ -155,6 +156,33 @@ export class UrlPanelContent extends Component<UrlPanelContentProps, State> {
|
|||
</EuiFormRow>
|
||||
);
|
||||
|
||||
const showWarningButton =
|
||||
this.props.snapshotShareWarning &&
|
||||
this.state.exportUrlAs === ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT;
|
||||
|
||||
const copyButton = (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"
|
||||
iconType={showWarningButton ? 'alert' : undefined}
|
||||
color={showWarningButton ? 'warning' : 'primary'}
|
||||
>
|
||||
{this.props.isEmbedded ? (
|
||||
<FormattedMessage
|
||||
id="share.urlPanel.copyIframeCodeButtonLabel"
|
||||
defaultMessage="Copy iFrame code"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage id="share.urlPanel.copyLinkButtonLabel" defaultMessage="Copy link" />
|
||||
)}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiForm className="kbnShareContextMenu__finalPanel" data-test-subj="shareUrlForm">
|
||||
|
@ -166,27 +194,19 @@ export class UrlPanelContent extends Component<UrlPanelContentProps, State> {
|
|||
|
||||
<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"
|
||||
/>
|
||||
<>
|
||||
{showWarningButton ? (
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={this.props.snapshotShareWarning}
|
||||
display="block"
|
||||
>
|
||||
{copyButton(copy)}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="share.urlPanel.copyLinkButtonLabel"
|
||||
defaultMessage="Copy link"
|
||||
/>
|
||||
copyButton(copy)
|
||||
)}
|
||||
</EuiButton>
|
||||
</>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiForm>
|
||||
|
@ -246,13 +266,11 @@ export class UrlPanelContent extends Component<UrlPanelContentProps, State> {
|
|||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return this.updateUrlParams(formattedUrl);
|
||||
};
|
||||
|
||||
private getSnapshotUrl = () => {
|
||||
const url = this.props.shareableUrl || window.location.href;
|
||||
|
||||
return this.updateUrlParams(url);
|
||||
};
|
||||
|
||||
|
@ -404,17 +422,24 @@ export class UrlPanelContent extends Component<UrlPanelContentProps, State> {
|
|||
};
|
||||
|
||||
private renderExportUrlAsOptions = () => {
|
||||
const snapshotLabel = (
|
||||
<FormattedMessage id="share.urlPanel.snapshotLabel" defaultMessage="Snapshot" />
|
||||
);
|
||||
return [
|
||||
{
|
||||
id: ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT,
|
||||
label: this.renderWithIconTip(
|
||||
<FormattedMessage id="share.urlPanel.snapshotLabel" defaultMessage="Snapshot" />,
|
||||
<FormattedMessage
|
||||
id="share.urlPanel.snapshotDescription"
|
||||
defaultMessage="Snapshot URLs encode the current state of the {objectType} in the URL itself.
|
||||
label: (
|
||||
<>
|
||||
{this.renderWithIconTip(
|
||||
snapshotLabel,
|
||||
<FormattedMessage
|
||||
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 }}
|
||||
/>
|
||||
values={{ objectType: this.props.objectType }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
['data-test-subj']: 'exportAsSnapshot',
|
||||
},
|
||||
|
|
|
@ -74,6 +74,7 @@ export class ShareMenuManager {
|
|||
showPublicUrlSwitch,
|
||||
urlService,
|
||||
anonymousAccess,
|
||||
snapshotShareWarning,
|
||||
onClose,
|
||||
}: ShowShareMenuOptions & {
|
||||
menuItems: ShareMenuItem[];
|
||||
|
@ -114,6 +115,7 @@ export class ShareMenuManager {
|
|||
anonymousAccess={anonymousAccess}
|
||||
showPublicUrlSwitch={showPublicUrlSwitch}
|
||||
urlService={urlService}
|
||||
snapshotShareWarning={snapshotShareWarning}
|
||||
/>
|
||||
</EuiWrappingPopover>
|
||||
</KibanaThemeProvider>
|
||||
|
|
|
@ -97,5 +97,6 @@ export interface ShowShareMenuOptions extends Omit<ShareContext, 'onClose'> {
|
|||
allowEmbed: boolean;
|
||||
allowShortUrl: boolean;
|
||||
embedUrlParamExtensions?: UrlParamExtension[];
|
||||
snapshotShareWarning?: string;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,94 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
type TestingModes = 'snapshot' | 'savedObject';
|
||||
type AppState = string | undefined;
|
||||
interface UrlState {
|
||||
globalState: string;
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
const getStateFromUrl = (url: string): UrlState => {
|
||||
const globalStateStart = url.indexOf('_g');
|
||||
const appStateStart = url.indexOf('_a');
|
||||
|
||||
// global state is always part of the URL, but app state is *not* - so, need to
|
||||
// modify the logic depending on whether app state exists or not
|
||||
if (appStateStart === -1) {
|
||||
return {
|
||||
globalState: url.substring(globalStateStart + 3),
|
||||
appState: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
globalState: url.substring(globalStateStart + 3, appStateStart - 1),
|
||||
appState: url.substring(appStateStart + 3, url.length),
|
||||
};
|
||||
};
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const filterBar = getService('filterBar');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects(['dashboard', 'common', 'share']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
|
||||
const PageObjects = getPageObjects(['dashboard', 'common', 'share', 'timePicker']);
|
||||
|
||||
const getSharedUrl = async (mode: TestingModes): Promise<string> => {
|
||||
await retry.waitFor('share menu to open', async () => {
|
||||
await PageObjects.share.clickShareTopNavButton();
|
||||
return await PageObjects.share.isShareMenuOpen();
|
||||
});
|
||||
if (mode === 'savedObject') {
|
||||
await PageObjects.share.exportAsSavedObject();
|
||||
}
|
||||
const sharedUrl = await PageObjects.share.getSharedUrl();
|
||||
return sharedUrl;
|
||||
};
|
||||
|
||||
describe('share dashboard', () => {
|
||||
const testFilterState = async (mode: TestingModes) => {
|
||||
it('should not have "filters" state in either app or global state when no filters', async () => {
|
||||
expect(await getSharedUrl(mode)).to.not.contain('filters');
|
||||
});
|
||||
|
||||
it('unpinned filter should show up only in app state when dashboard is unsaved', async () => {
|
||||
await filterBar.addFilter('geo.src', 'is', 'AE');
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
|
||||
const sharedUrl = await getSharedUrl(mode);
|
||||
const { globalState, appState } = getStateFromUrl(sharedUrl);
|
||||
expect(globalState).to.not.contain('filters');
|
||||
if (mode === 'snapshot') {
|
||||
expect(appState).to.contain('filters');
|
||||
} else {
|
||||
expect(sharedUrl).to.not.contain('appState');
|
||||
}
|
||||
});
|
||||
|
||||
it('unpinned filters should be removed from app state when dashboard is saved', async () => {
|
||||
await PageObjects.dashboard.clickQuickSave();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
|
||||
const sharedUrl = await getSharedUrl(mode);
|
||||
expect(sharedUrl).to.not.contain('appState');
|
||||
});
|
||||
|
||||
it('pinned filter should show up only in global state', async () => {
|
||||
await filterBar.toggleFilterPinned('geo.src');
|
||||
await PageObjects.dashboard.clickQuickSave();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
|
||||
const sharedUrl = await getSharedUrl(mode);
|
||||
const { globalState, appState } = getStateFromUrl(sharedUrl);
|
||||
expect(globalState).to.contain('filters');
|
||||
if (mode === 'snapshot') {
|
||||
expect(appState).to.not.contain('filters');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.load(
|
||||
|
@ -25,16 +108,58 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
await PageObjects.dashboard.loadSavedDashboard('few panels');
|
||||
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
const from = 'Sep 19, 2017 @ 06:31:44.000';
|
||||
const to = 'Sep 23, 2018 @ 18:31:44.000';
|
||||
await PageObjects.timePicker.setAbsoluteRange(from, to);
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
it('has "panels" state when sharing a snapshot', async () => {
|
||||
await PageObjects.share.clickShareTopNavButton();
|
||||
const sharedUrl = await PageObjects.share.getSharedUrl();
|
||||
expect(sharedUrl).to.contain('panels');
|
||||
describe('snapshot share', async () => {
|
||||
describe('test local state', async () => {
|
||||
it('should not have "panels" state when not in unsaved changes state', async () => {
|
||||
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
|
||||
expect(await getSharedUrl('snapshot')).to.not.contain('panels');
|
||||
});
|
||||
|
||||
it('should have "panels" in app state when a panel has been modified', async () => {
|
||||
await dashboardPanelActions.setCustomPanelTitle('Test New Title');
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
|
||||
|
||||
const sharedUrl = await getSharedUrl('snapshot');
|
||||
const { appState } = getStateFromUrl(sharedUrl);
|
||||
expect(appState).to.contain('panels');
|
||||
});
|
||||
|
||||
it('should once again not have "panels" state when save is clicked', async () => {
|
||||
await PageObjects.dashboard.clickQuickSave();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
|
||||
expect(await getSharedUrl('snapshot')).to.not.contain('panels');
|
||||
});
|
||||
});
|
||||
|
||||
describe('test filter state', async () => {
|
||||
await testFilterState('snapshot');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await filterBar.removeAllFilters();
|
||||
await PageObjects.dashboard.clickQuickSave();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
});
|
||||
});
|
||||
|
||||
describe('saved object share', async () => {
|
||||
describe('test filter state', async () => {
|
||||
await testFilterState('savedObject');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue