[Dashboard] Remove Legacy Dashboard Only Mode (#108103)

Remove all dashboard only mode code and tests. Align dashboard permissions to use showWriteControls only
This commit is contained in:
Devon Thomson 2021-08-20 15:39:10 -04:00 committed by GitHub
parent 92ec225cfd
commit 95463f47f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 132 additions and 1528 deletions

1
.github/CODEOWNERS vendored
View file

@ -144,7 +144,6 @@
/x-pack/plugins/dashboard_enhanced/ @elastic/kibana-presentation
/x-pack/test/functional/apps/canvas/ @elastic/kibana-presentation
#CC# /src/plugins/kibana_react/public/code_editor/ @elastic/kibana-presentation
#CC# /x-pack/plugins/dashboard_mode @elastic/kibana-presentation
# Machine Learning
/x-pack/plugins/ml/ @elastic/ml-ui

View file

@ -1,238 +0,0 @@
{
"id": "dashboardMode",
"client": {
"classes": [],
"functions": [],
"interfaces": [],
"enums": [],
"misc": [],
"objects": []
},
"server": {
"classes": [
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin",
"type": "Class",
"tags": [],
"label": "DashboardModeServerPlugin",
"description": [],
"signature": [
{
"pluginId": "dashboardMode",
"scope": "server",
"docId": "kibDashboardModePluginApi",
"section": "def-server.DashboardModeServerPlugin",
"text": "DashboardModeServerPlugin"
},
" implements ",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCorePluginApi",
"section": "def-server.Plugin",
"text": "Plugin"
},
"<void, void, object, object>"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"children": [
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.Unnamed",
"type": "Function",
"tags": [],
"label": "Constructor",
"description": [],
"signature": [
"any"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"children": [
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.Unnamed.$1",
"type": "Object",
"tags": [],
"label": "initializerContext",
"description": [],
"signature": [
{
"pluginId": "core",
"scope": "server",
"docId": "kibCorePluginApi",
"section": "def-server.PluginInitializerContext",
"text": "PluginInitializerContext"
},
"<unknown>"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"isRequired": true
}
],
"returnComment": []
},
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.setup",
"type": "Function",
"tags": [],
"label": "setup",
"description": [],
"signature": [
"(core: ",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCorePluginApi",
"section": "def-server.CoreSetup",
"text": "CoreSetup"
},
"<object, unknown>, { security }: DashboardModeServerSetupDependencies) => void"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"children": [
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.setup.$1",
"type": "Object",
"tags": [],
"label": "core",
"description": [],
"signature": [
{
"pluginId": "core",
"scope": "server",
"docId": "kibCorePluginApi",
"section": "def-server.CoreSetup",
"text": "CoreSetup"
},
"<object, unknown>"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"isRequired": true
},
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.setup.$2",
"type": "Object",
"tags": [],
"label": "{ security }",
"description": [],
"signature": [
"DashboardModeServerSetupDependencies"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"isRequired": true
}
],
"returnComment": []
},
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.start",
"type": "Function",
"tags": [],
"label": "start",
"description": [],
"signature": [
"(core: ",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCorePluginApi",
"section": "def-server.CoreStart",
"text": "CoreStart"
},
") => void"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"children": [
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.start.$1",
"type": "Object",
"tags": [],
"label": "core",
"description": [],
"signature": [
{
"pluginId": "core",
"scope": "server",
"docId": "kibCorePluginApi",
"section": "def-server.CoreStart",
"text": "CoreStart"
}
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"isRequired": true
}
],
"returnComment": []
},
{
"parentPluginId": "dashboardMode",
"id": "def-server.DashboardModeServerPlugin.stop",
"type": "Function",
"tags": [],
"label": "stop",
"description": [],
"signature": [
"() => void"
],
"path": "x-pack/plugins/dashboard_mode/server/plugin.ts",
"deprecated": false,
"children": [],
"returnComment": []
}
],
"initialIsOpen": false
}
],
"functions": [],
"interfaces": [],
"enums": [],
"misc": [],
"objects": []
},
"common": {
"classes": [],
"functions": [],
"interfaces": [],
"enums": [],
"misc": [],
"objects": [
{
"parentPluginId": "dashboardMode",
"id": "def-common.UI_SETTINGS",
"type": "Object",
"tags": [],
"label": "UI_SETTINGS",
"description": [],
"path": "x-pack/plugins/dashboard_mode/common/constants.ts",
"deprecated": false,
"children": [
{
"parentPluginId": "dashboardMode",
"id": "def-common.UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES",
"type": "string",
"tags": [],
"label": "CONFIG_DASHBOARD_ONLY_MODE_ROLES",
"description": [],
"path": "x-pack/plugins/dashboard_mode/common/constants.ts",
"deprecated": false
}
],
"initialIsOpen": false
}
]
}
}

View file

@ -1,32 +0,0 @@
---
id: kibDashboardModePluginApi
slug: /kibana-dev-docs/dashboardModePluginApi
title: dashboardMode
image: https://source.unsplash.com/400x175/?github
summary: API docs for the dashboardMode plugin
date: 2020-11-16
tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardMode']
warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info.
---
import dashboardModeObj from './dashboard_mode.json';
**Code health stats**
| Public API count | Any count | Items lacking comments | Missing exports |
|-------------------|-----------|------------------------|-----------------|
| 11 | 0 | 11 | 0 |
## Server
### Classes
<DocDefinitionList data={dashboardModeObj.server.classes}/>
## Common
### Objects
<DocDefinitionList data={dashboardModeObj.common.objects}/>

View file

@ -378,10 +378,6 @@ The client-side plugin configures following values:
|Adds drilldown capabilities to dashboard. Owned by the Kibana App team.
|{kib-repo}blob/{branch}/x-pack/plugins/dashboard_mode/README.md[dashboardMode]
|The deprecated dashboard only mode.
|{kib-repo}blob/{branch}/x-pack/plugins/data_enhanced/README.md[dataEnhanced]
|The data_enhanced plugin is the x-pack counterpart to the src/plguins/data plugin.

View file

@ -22,5 +22,4 @@ export interface ChromeNavLinks
| [getForceAppSwitcherNavigation$()](./kibana-plugin-core-public.chromenavlinks.getforceappswitchernavigation_.md) | An observable of the forced app switcher state. |
| [getNavLinks$()](./kibana-plugin-core-public.chromenavlinks.getnavlinks_.md) | Get an observable for a sorted list of navlinks. |
| [has(id)](./kibana-plugin-core-public.chromenavlinks.has.md) | Check whether or not a navlink exists. |
| [showOnly(id)](./kibana-plugin-core-public.chromenavlinks.showonly.md) | Remove all navlinks except the one matching the given id. |

View file

@ -1,28 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [ChromeNavLinks](./kibana-plugin-core-public.chromenavlinks.md) &gt; [showOnly](./kibana-plugin-core-public.chromenavlinks.showonly.md)
## ChromeNavLinks.showOnly() method
Remove all navlinks except the one matching the given id.
<b>Signature:</b>
```typescript
showOnly(id: string): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | |
<b>Returns:</b>
`void`
## Remarks
NOTE: this is not reversible.

View file

@ -12,7 +12,6 @@ pageLoadAssetSize:
crossClusterReplication: 65408
dashboard: 374194
dashboardEnhanced: 65646
dashboardMode: 22716
data: 824229
dataEnhanced: 50420
devTools: 38637

View file

@ -19,7 +19,6 @@ const createStartContractMock = () => {
has: jest.fn(),
get: jest.fn(),
getAll: jest.fn(),
showOnly: jest.fn(),
enableForcedAppSwitcherNavigation: jest.fn(),
getForceAppSwitcherNavigation$: jest.fn(),
},

View file

@ -89,10 +89,8 @@ describe('NavLinksService', () => {
const navLinkIds$ = start.getNavLinks$().pipe(map((links) => links.map((l) => l.id)));
const emittedLinks: string[][] = [];
navLinkIds$.subscribe((r) => emittedLinks.push(r));
start.showOnly('app1');
service.stop();
expect(emittedLinks).toEqual([['app2', 'app1', 'app2:deepApp2', 'app2:deepApp1'], ['app1']]);
expect(emittedLinks).toEqual([['app2', 'app1', 'app2:deepApp2', 'app2:deepApp1']]);
});
it('completes when service is stopped', async () => {
@ -133,74 +131,6 @@ describe('NavLinksService', () => {
});
});
describe('#showOnly()', () => {
it('does nothing if link does not exist', async () => {
start.showOnly('fake');
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
).toEqual(['app2', 'app1', 'app2:deepApp2', 'app2:deepApp1']);
});
it('does nothing on chromeless applications', async () => {
start.showOnly('chromelessApp');
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
).toEqual(['app2', 'app1', 'app2:deepApp2', 'app2:deepApp1']);
});
it('removes all other links', async () => {
start.showOnly('app2');
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
).toEqual(['app2']);
});
it('show only deep link', async () => {
start.showOnly('app2:deepApp1');
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
).toEqual(['app2:deepApp1']);
});
it('still removes all other links when availableApps are re-emitted', async () => {
start.showOnly('app2');
mockAppService.applications$.next(mockAppService.applications$.value);
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map((links) => links.map((l) => l.id))
)
.toPromise()
).toEqual(['app2']);
});
});
describe('#enableForcedAppSwitcherNavigation()', () => {
it('flips #getForceAppSwitcherNavigation$()', async () => {
await expect(start.getForceAppSwitcherNavigation$().pipe(take(1)).toPromise()).resolves.toBe(

View file

@ -7,7 +7,7 @@
*/
import { sortBy } from 'lodash';
import { BehaviorSubject, combineLatest, Observable, ReplaySubject } from 'rxjs';
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { InternalApplicationStart, PublicAppDeepLinkInfo, PublicAppInfo } from '../../application';
@ -48,16 +48,6 @@ export interface ChromeNavLinks {
*/
has(id: string): boolean;
/**
* Remove all navlinks except the one matching the given id.
*
* @remarks
* NOTE: this is not reversible.
*
* @param id
*/
showOnly(id: string): void;
/**
* Enable forced navigation mode, which will trigger a page refresh
* when a nav link is clicked and only the hash is updated.
@ -78,39 +68,25 @@ export interface ChromeNavLinks {
getForceAppSwitcherNavigation$(): Observable<boolean>;
}
type LinksUpdater = (navLinks: Map<string, NavLinkWrapper>) => Map<string, NavLinkWrapper>;
export class NavLinksService {
private readonly stop$ = new ReplaySubject(1);
public start({ application, http }: StartDeps): ChromeNavLinks {
const appLinks$ = application.applications$.pipe(
map((apps) => {
return new Map(
[...apps]
.filter(([, app]) => !app.chromeless)
.reduce((navLinks: Array<[string, NavLinkWrapper]>, [appId, app]) => {
navLinks.push(
[appId, toNavLink(app, http.basePath)],
...toNavDeepLinks(app, app.deepLinks, http.basePath)
);
return navLinks;
}, [])
);
})
);
// now that availableApps$ is an observable, we need to keep record of all
// manual link modifications to be able to re-apply then after every
// availableApps$ changes.
// Only in use by `showOnly` API, can be removed once dashboard_mode is removed in 8.0
const linkUpdaters$ = new BehaviorSubject<LinksUpdater[]>([]);
const navLinks$ = new BehaviorSubject<ReadonlyMap<string, NavLinkWrapper>>(new Map());
combineLatest([appLinks$, linkUpdaters$])
application.applications$
.pipe(
map(([appLinks, linkUpdaters]) => {
return linkUpdaters.reduce((links, updater) => updater(links), appLinks);
map((apps) => {
return new Map(
[...apps]
.filter(([, app]) => !app.chromeless)
.reduce((navLinks: Array<[string, NavLinkWrapper]>, [appId, app]) => {
navLinks.push(
[appId, toNavLink(app, http.basePath)],
...toNavDeepLinks(app, app.deepLinks, http.basePath)
);
return navLinks;
}, [])
);
})
)
.subscribe((navlinks) => {
@ -137,17 +113,6 @@ export class NavLinksService {
return navLinks$.value.has(id);
},
showOnly(id: string) {
if (!this.has(id)) {
return;
}
const updater: LinksUpdater = (navLinks) =>
new Map([...navLinks.entries()].filter(([linkId]) => linkId === id));
linkUpdaters$.next([...linkUpdaters$.value, updater]);
},
enableForcedAppSwitcherNavigation() {
forceAppSwitcherNavigation$.next(true);
},

View file

@ -324,7 +324,6 @@ export interface ChromeNavLinks {
getForceAppSwitcherNavigation$(): Observable<boolean>;
getNavLinks$(): Observable<Array<Readonly<ChromeNavLink>>>;
has(id: string): boolean;
showOnly(id: string): void;
}
// @public

View file

@ -80,7 +80,6 @@ export async function mountApp({
data: dataStart,
share: shareStart,
embeddable: embeddableStart,
kibanaLegacy: { dashboardConfig },
savedObjectsTaggingOss,
visualizations,
presentationUtil,
@ -117,12 +116,12 @@ export async function mountApp({
allowByValueEmbeddables: initializerContext.config.get<DashboardFeatureFlagConfig>()
.allowByValueEmbeddables,
dashboardCapabilities: {
hideWriteControls: dashboardConfig.getHideWriteControls(),
show: Boolean(coreStart.application.capabilities.dashboard.show),
saveQuery: Boolean(coreStart.application.capabilities.dashboard.saveQuery),
createNew: Boolean(coreStart.application.capabilities.dashboard.createNew),
mapsCapabilities: { save: Boolean(coreStart.application.capabilities.maps?.save) },
createShortUrl: Boolean(coreStart.application.capabilities.dashboard.createShortUrl),
showWriteControls: Boolean(coreStart.application.capabilities.dashboard.showWriteControls),
visualizeCapabilities: { save: Boolean(coreStart.application.capabilities.visualize?.save) },
storeSearchSession: Boolean(coreStart.application.capabilities.dashboard.storeSearchSession),
},
@ -251,7 +250,7 @@ export async function mountApp({
);
addHelpMenuToAppChrome(dashboardServices.chrome, coreStart.docLinks);
if (dashboardServices.dashboardCapabilities.hideWriteControls) {
if (!dashboardServices.dashboardCapabilities.showWriteControls) {
coreStart.chrome.setBadge({
text: dashboardReadonlyBadge.getText(),
tooltip: dashboardReadonlyBadge.getTooltip(),

View file

@ -79,7 +79,7 @@ const defaultCapabilities: DashboardAppCapabilities = {
createNew: false,
saveQuery: false,
createShortUrl: false,
hideWriteControls: true,
showWriteControls: false,
mapsCapabilities: { save: false },
visualizeCapabilities: { save: false },
storeSearchSession: true,

View file

@ -117,7 +117,7 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
<div className="dshDashboardEmptyScreen">
<DashboardEmptyScreen
isReadonlyMode={
this.props.container.getInput().dashboardCapabilities?.hideWriteControls
!this.props.container.getInput().dashboardCapabilities?.showWriteControls
}
isEditMode={isEditMode}
uiSettings={this.context.services.uiSettings}

View file

@ -259,7 +259,7 @@ export const useDashboardAppState = ({
const updateLastSavedState = () => {
setLastSavedState(
savedObjectToDashboardState({
hideWriteControls: dashboardBuildContext.dashboardCapabilities.hideWriteControls,
showWriteControls: dashboardBuildContext.dashboardCapabilities.showWriteControls,
version: dashboardBuildContext.kibanaVersion,
savedObjectsTagging,
usageCollection,
@ -341,7 +341,12 @@ export const useDashboardAppState = ({
if (from && to) timefilter.setTime({ from, to });
if (refreshInterval) timefilter.setRefreshInterval(refreshInterval);
}
dispatchDashboardStateChange(setDashboardState(lastSavedState));
dispatchDashboardStateChange(
setDashboardState({
...lastSavedState,
viewMode: ViewMode.VIEW,
})
);
}, [lastSavedState, dashboardAppState, data.query.timefilter, dispatchDashboardStateChange]);
/**

View file

@ -27,7 +27,7 @@ import {
interface SavedObjectToDashboardStateProps {
version: string;
hideWriteControls: boolean;
showWriteControls: boolean;
savedDashboard: DashboardSavedObject;
usageCollection: DashboardAppServices['usageCollection'];
savedObjectsTagging: DashboardAppServices['savedObjectsTagging'];
@ -55,9 +55,9 @@ interface StateToRawDashboardStateProps {
*/
export const savedObjectToDashboardState = ({
version,
hideWriteControls,
savedDashboard,
usageCollection,
showWriteControls,
savedObjectsTagging,
}: SavedObjectToDashboardStateProps): DashboardState => {
const rawState = migrateAppState(
@ -70,7 +70,7 @@ export const savedObjectToDashboardState = ({
description: savedDashboard.description || '',
tags: getTagsFromSavedDashboard(savedDashboard, savedObjectsTagging),
panels: savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : [],
viewMode: savedDashboard.id || hideWriteControls ? ViewMode.VIEW : ViewMode.EDIT,
viewMode: savedDashboard.id || showWriteControls ? ViewMode.EDIT : ViewMode.VIEW,
options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {},
},
version,

View file

@ -38,7 +38,7 @@ export const loadSavedDashboardState = async ({
}: DashboardBuildContext & { savedDashboardId?: string }): Promise<
LoadSavedDashboardStateReturn | undefined
> => {
const { hideWriteControls } = dashboardCapabilities;
const { showWriteControls } = dashboardCapabilities;
const { queryString } = query;
// BWC - remove for 8.0
@ -66,12 +66,12 @@ export const loadSavedDashboardState = async ({
const savedDashboardState = savedObjectToDashboardState({
savedDashboard,
usageCollection,
hideWriteControls,
showWriteControls,
savedObjectsTagging,
version: initializerContext.env.packageInfo.version,
});
const isViewMode = hideWriteControls || Boolean(savedDashboard.id);
const isViewMode = !showWriteControls || Boolean(savedDashboard.id);
savedDashboardState.viewMode = isViewMode ? ViewMode.VIEW : ViewMode.EDIT;
savedDashboardState.filters = cleanFiltersForSerialize(savedDashboardState.filters);
savedDashboardState.query = migrateLegacyQuery(

View file

@ -19,7 +19,7 @@ describe('createSessionRestorationDataProvider', () => {
getAppState: () =>
savedObjectToDashboardState({
version,
hideWriteControls: false,
showWriteControls: true,
usageCollection: undefined,
savedObjectsTagging: undefined,
savedDashboard: getSavedDashboardMock(),

View file

@ -112,87 +112,6 @@ exports[`after fetch When given a title that matches multiple dashboards, filter
</DashboardListing>
`;
exports[`after fetch hideWriteControls 1`] = `
<DashboardListing
kbnUrlStateStorage={
Object {
"cancel": [Function],
"change$": [Function],
"get": [Function],
"kbnUrlControls": Object {
"cancel": [Function],
"flush": [Function],
"getPendingUrl": [Function],
"listen": [Function],
"update": [Function],
"updateAsync": [Function],
},
"set": [Function],
}
}
redirectTo={[MockFunction]}
>
<TableListView
emptyPrompt={
<EuiEmptyPrompt
body={
<p>
There are no available dashboards. To change your permissions to view the dashboards in this space, contact your administrator.
</p>
}
iconType="glasses"
title={
<h1
id="dashboardListingHeading"
>
No dashboards to view
</h1>
}
/>
}
entityName="dashboard"
entityNamePlural="dashboards"
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter=""
initialPageSize={20}
listingLimit={100}
rowHeader="title"
searchFilters={Array []}
tableCaption="Dashboards"
tableColumns={
Array [
Object {
"field": "title",
"name": "Title",
"render": [Function],
"sortable": true,
},
Object {
"field": "description",
"name": "Description",
"render": [Function],
"sortable": true,
},
]
}
tableListTitle="Dashboards"
toastNotifications={
Object {
"add": [MockFunction],
"addDanger": [MockFunction],
"addError": [MockFunction],
"addInfo": [MockFunction],
"addSuccess": [MockFunction],
"addWarning": [MockFunction],
"get$": [MockFunction],
"remove": [MockFunction],
}
}
/>
</DashboardListing>
`;
exports[`after fetch initialFilter 1`] = `
<DashboardListing
initialFilter="testFilter"
@ -637,3 +556,84 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
/>
</DashboardListing>
`;
exports[`after fetch showWriteControls 1`] = `
<DashboardListing
kbnUrlStateStorage={
Object {
"cancel": [Function],
"change$": [Function],
"get": [Function],
"kbnUrlControls": Object {
"cancel": [Function],
"flush": [Function],
"getPendingUrl": [Function],
"listen": [Function],
"update": [Function],
"updateAsync": [Function],
},
"set": [Function],
}
}
redirectTo={[MockFunction]}
>
<TableListView
emptyPrompt={
<EuiEmptyPrompt
body={
<p>
There are no available dashboards. To change your permissions to view the dashboards in this space, contact your administrator.
</p>
}
iconType="glasses"
title={
<h1
id="dashboardListingHeading"
>
No dashboards to view
</h1>
}
/>
}
entityName="dashboard"
entityNamePlural="dashboards"
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter=""
initialPageSize={20}
listingLimit={100}
rowHeader="title"
searchFilters={Array []}
tableCaption="Dashboards"
tableColumns={
Array [
Object {
"field": "title",
"name": "Title",
"render": [Function],
"sortable": true,
},
Object {
"field": "description",
"name": "Description",
"render": [Function],
"sortable": true,
},
]
}
tableListTitle="Dashboards"
toastNotifications={
Object {
"add": [MockFunction],
"addDanger": [MockFunction],
"addError": [MockFunction],
"addInfo": [MockFunction],
"addSuccess": [MockFunction],
"addWarning": [MockFunction],
"get$": [MockFunction],
"remove": [MockFunction],
}
}
/>
</DashboardListing>
`;

View file

@ -133,9 +133,9 @@ describe('after fetch', () => {
});
});
test('hideWriteControls', async () => {
test('showWriteControls', async () => {
const services = makeDefaultServices();
services.dashboardCapabilities.hideWriteControls = true;
services.dashboardCapabilities.showWriteControls = false;
const { component } = mountWith({ services });
// Ensure all promises resolve
await new Promise((resolve) => process.nextTick(resolve));

View file

@ -87,7 +87,7 @@ export const DashboardListing = ({
};
}, [title, savedObjectsClient, redirectTo, data.query, kbnUrlStateStorage]);
const hideWriteControls = dashboardCapabilities.hideWriteControls;
const { showWriteControls } = dashboardCapabilities;
const listingLimit = savedObjects.settings.getListingLimit();
const defaultFilter = title ? `"${title}"` : '';
@ -118,8 +118,8 @@ export const DashboardListing = ({
}, [dashboardSessionStorage, redirectTo, core.overlays]);
const emptyPrompt = useMemo(
() => getNoItemsMessage(hideWriteControls, core.application, createItem),
[createItem, core.application, hideWriteControls]
() => getNoItemsMessage(showWriteControls, core.application, createItem),
[createItem, core.application, showWriteControls]
);
const fetchItems = useCallback(
@ -171,10 +171,10 @@ export const DashboardListing = ({
} = dashboardListingTable;
return (
<TableListView
createItem={hideWriteControls ? undefined : createItem}
deleteItems={hideWriteControls ? undefined : deleteItems}
createItem={!showWriteControls ? undefined : createItem}
deleteItems={!showWriteControls ? undefined : deleteItems}
initialPageSize={savedObjects.settings.getPerPage()}
editItem={hideWriteControls ? undefined : editItem}
editItem={!showWriteControls ? undefined : editItem}
initialFilter={initialFilter ?? defaultFilter}
toastNotifications={core.notifications.toasts}
headingId="dashboardListingHeading"
@ -239,11 +239,11 @@ const getTableColumns = (
};
const getNoItemsMessage = (
hideWriteControls: boolean,
showWriteControls: boolean,
application: ApplicationStart,
createItem: () => void
) => {
if (hideWriteControls) {
if (!showWriteControls) {
return (
<EuiEmptyPrompt
iconType="glasses"

View file

@ -62,7 +62,7 @@ export function makeDefaultServices(): DashboardAppServices {
createNew: true,
saveQuery: true,
createShortUrl: true,
hideWriteControls: false,
showWriteControls: true,
storeSearchSession: true,
mapsCapabilities: { save: true },
visualizeCapabilities: { save: true },

View file

@ -478,7 +478,7 @@ export function DashboardTopNav({
dashboardAppState.getLatestDashboardState().viewMode,
dashboardTopNavActions,
{
hideWriteControls: dashboardCapabilities.hideWriteControls,
showWriteControls: dashboardCapabilities.showWriteControls,
isDirty: Boolean(dashboardAppState.hasUnsavedChanges),
isSaveInProgress: state.isSaveInProgress,
isNewDashboard: !savedDashboard.id,

View file

@ -15,14 +15,14 @@ import { TopNavMenuData } from '../../../../navigation/public';
/**
* @param actions - A mapping of TopNavIds to an action function that should run when the
* corresponding top nav is clicked.
* @param hideWriteControls if true, does not include any controls that allow editing or creating objects.
* @param showWriteControls if false, does not include any controls that allow editing or creating objects.
* @return an array of objects for a top nav configuration, based on the mode.
*/
export function getTopNavConfig(
dashboardMode: ViewMode,
actions: { [key: string]: NavAction },
options: {
hideWriteControls: boolean;
showWriteControls: boolean;
isNewDashboard: boolean;
isDirty: boolean;
isSaveInProgress?: boolean;
@ -32,7 +32,7 @@ export function getTopNavConfig(
const labs = options.isLabsEnabled ? [getLabsConfig(actions[TopNavIds.LABS])] : [];
switch (dashboardMode) {
case ViewMode.VIEW:
return options.hideWriteControls
return !options.showWriteControls
? [
...labs,
getFullScreenConfig(actions[TopNavIds.FULL_SCREEN]),

View file

@ -188,7 +188,7 @@ export class DashboardPlugin
};
return {
SavedObjectFinder: getSavedObjectFinder(coreStart.savedObjects, coreStart.uiSettings),
hideWriteControls: deps.kibanaLegacy.dashboardConfig.getHideWriteControls(),
showWriteControls: Boolean(coreStart.application.capabilities.dashboard.showWriteControls),
notifications: coreStart.notifications,
application: coreStart.application,
uiSettings: coreStart.uiSettings,

View file

@ -168,7 +168,7 @@ export interface DashboardAppCapabilities {
createNew: boolean;
saveQuery: boolean;
createShortUrl: boolean;
hideWriteControls: boolean;
showWriteControls: boolean;
storeSearchSession: boolean;
mapsCapabilities: { save: boolean };
visualizeCapabilities: { save: boolean };

View file

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export interface DashboardConfig {
turnHideWriteControlsOn(): void;
getHideWriteControls(): boolean;
}
export function getDashboardConfig(hideWriteControls: boolean): DashboardConfig {
let _hideWriteControls = hideWriteControls;
return {
/**
* Part of the exposed plugin API - do not remove without careful consideration.
* @type {boolean}
*/
turnHideWriteControlsOn() {
_hideWriteControls = true;
},
getHideWriteControls() {
return _hideWriteControls;
},
};
}

View file

@ -17,10 +17,6 @@ const createStartContract = (): Start => ({
config: {
defaultAppId: 'home',
},
dashboardConfig: {
turnHideWriteControlsOn: jest.fn(),
getHideWriteControls: jest.fn(),
},
loadFontAwesome: jest.fn(),
loadAngularBootstrap: jest.fn(),
});

View file

@ -8,7 +8,6 @@
import { PluginInitializerContext, CoreStart, CoreSetup } from 'kibana/public';
import { ConfigSchema } from '../config';
import { getDashboardConfig } from './dashboard_config';
import { injectHeaderStyle } from './utils/inject_header_style';
export class KibanaLegacyPlugin {
@ -21,11 +20,6 @@ export class KibanaLegacyPlugin {
public start({ application, http: { basePath }, uiSettings }: CoreStart) {
injectHeaderStyle(uiSettings);
return {
/**
* Used to power dashboard mode. Should be removed when dashboard mode is removed eventually.
* @deprecated
*/
dashboardConfig: getDashboardConfig(!application.capabilities.dashboard.showWriteControls),
/**
* Loads the font-awesome icon font. Should be removed once the last consumer has migrated to EUI
* @deprecated

View file

@ -134,7 +134,6 @@ export const applicationUsageSchema = {
// X-Pack
apm: commonSchema,
canvas: commonSchema,
dashboard_mode: commonSchema, // It's a forward app so we'll likely never report it
enterpriseSearch: commonSchema,
appSearch: commonSchema,
workplaceSearch: commonSchema,

View file

@ -35,7 +35,6 @@ const uiMetricFromDataPluginSchema: MakeSchemaFrom<UIMetricUsage> = {
apm: commonSchema,
csm: commonSchema,
canvas: commonSchema,
dashboard_mode: commonSchema, // It's a forward app so we'll likely never report it
enterpriseSearch: commonSchema,
appSearch: commonSchema,
workplaceSearch: commonSchema,

View file

@ -2136,137 +2136,6 @@
}
}
},
"dashboard_mode": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "Always `main`"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 90 days"
}
},
"views": {
"type": "array",
"items": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "The application view being tracked"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application sub view since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application sub view is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
}
}
}
}
}
}
},
"enterpriseSearch": {
"properties": {
"appId": {
@ -8487,25 +8356,6 @@
}
}
},
"dashboard_mode": {
"type": "array",
"items": {
"properties": {
"key": {
"type": "keyword",
"_meta": {
"description": "The event that is tracked"
}
},
"value": {
"type": "long",
"_meta": {
"description": "The value of the event"
}
}
}
}
},
"enterpriseSearch": {
"type": "array",
"items": {

View file

@ -13,7 +13,6 @@
"xpack.dashboard": "plugins/dashboard_enhanced",
"xpack.discover": "plugins/discover_enhanced",
"xpack.crossClusterReplication": "plugins/cross_cluster_replication",
"xpack.dashboardMode": "plugins/dashboard_mode",
"xpack.data": "plugins/data_enhanced",
"xpack.embeddableEnhanced": "plugins/embeddable_enhanced",
"xpack.endpoint": "plugins/endpoint",

View file

@ -1 +0,0 @@
The deprecated dashboard only mode.

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const UI_SETTINGS = {
CONFIG_DASHBOARD_ONLY_MODE_ROLES: 'xpackDashboardMode:roles',
};

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { UI_SETTINGS } from './constants';

View file

@ -1,12 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/x-pack/plugins/dashboard_mode'],
};

View file

@ -1,14 +0,0 @@
{
"id": "dashboardMode",
"owner": {
"name": "Kibana Presentation",
"githubTeam": "kibana-presentation"
},
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "dashboard_mode"],
"optionalPlugins": ["security"],
"requiredPlugins": ["kibanaLegacy", "urlForwarding", "dashboard"],
"server": true,
"ui": true
}

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { plugin } from './plugin';

View file

@ -1,74 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { trimStart } from 'lodash';
import { CoreSetup } from 'kibana/public';
import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public';
import { UrlForwardingStart } from '../../../../src/plugins/url_forwarding/public';
import {
createDashboardEditUrl,
DashboardConstants,
} from '../../../../src/plugins/dashboard/public';
import { AppNavLinkStatus } from '../../../../src/core/public';
function defaultUrl(defaultAppId: string) {
const isDashboardId = defaultAppId.startsWith(dashboardAppIdPrefix());
return isDashboardId ? `/${defaultAppId}` : DashboardConstants.LANDING_PAGE_PATH;
}
function dashboardAppIdPrefix() {
return trimStart(createDashboardEditUrl(''), '/');
}
function migratePath(
currentHash: string,
kibanaLegacy: KibanaLegacyStart,
urlForwarding: UrlForwardingStart
) {
if (currentHash === '' || currentHash === '#' || currentHash === '#/') {
return `#${defaultUrl(kibanaLegacy.config.defaultAppId || '')}`;
}
if (!currentHash.startsWith('#/dashboard')) {
return currentHash;
}
const forwards = urlForwarding.getForwards();
if (currentHash.startsWith('#/dashboards')) {
const { rewritePath: migrateListingPath } = forwards.find(
({ legacyAppId }) => legacyAppId === 'dashboards'
)!;
return migrateListingPath(currentHash);
}
const { rewritePath: migrateDetailPath } = forwards.find(
({ legacyAppId }) => legacyAppId === 'dashboard'
)!;
return migrateDetailPath(currentHash);
}
export const plugin = () => ({
setup(core: CoreSetup<{ kibanaLegacy: KibanaLegacyStart; urlForwarding: UrlForwardingStart }>) {
core.application.register({
id: 'dashboard_mode',
title: 'Dashboard mode',
navLinkStatus: AppNavLinkStatus.hidden,
mount: async () => {
const [coreStart, { kibanaLegacy, urlForwarding }] = await core.getStartServices();
kibanaLegacy.dashboardConfig.turnHideWriteControlsOn();
coreStart.chrome.navLinks.showOnly('dashboards');
setTimeout(() => {
coreStart.application.navigateToApp('dashboards', {
path: migratePath(window.location.hash, kibanaLegacy, urlForwarding),
});
}, 0);
return () => {};
},
});
},
start() {},
});

View file

@ -1,23 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { DashboardModeServerPlugin } from './plugin';
export const config: PluginConfigDescriptor = {
schema: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
};
export function plugin(initializerContext: PluginInitializerContext) {
return new DashboardModeServerPlugin(initializerContext);
}
export { DashboardModeServerPlugin as Plugin };

View file

@ -1,112 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { parse as parseUrl } from 'url';
import {
OnPostAuthHandler,
OnPostAuthToolkit,
KibanaRequest,
LifecycleResponseFactory,
IUiSettingsClient,
} from 'kibana/server';
import { coreMock } from '../../../../../src/core/server/mocks';
import { AuthenticatedUser } from '../../../security/server';
import { securityMock } from '../../../security/server/mocks';
import { setupDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor';
const DASHBOARD_ONLY_MODE_ROLE = 'test_dashboard_only_mode_role';
describe('DashboardOnlyModeRequestInterceptor', () => {
const core = coreMock.createSetup();
const security = securityMock.createSetup();
let interceptor: OnPostAuthHandler;
let toolkit: OnPostAuthToolkit;
let uiSettingsMock: any;
beforeEach(() => {
toolkit = {
next: jest.fn(),
};
interceptor = setupDashboardModeRequestInterceptor({
http: core.http,
security,
getUiSettingsClient: () =>
(Promise.resolve({
get: () => Promise.resolve(uiSettingsMock),
}) as unknown) as Promise<IUiSettingsClient>,
});
});
test('should not redirects for not app/* requests', async () => {
const request = ({
url: {
pathname: 'api/test',
},
} as unknown) as KibanaRequest;
interceptor(request, {} as LifecycleResponseFactory, toolkit);
expect(toolkit.next).toHaveBeenCalled();
});
test('should not redirects not authenticated users', async () => {
const request = ({
url: {
pathname: '/app/home',
},
} as unknown) as KibanaRequest;
interceptor(request, {} as LifecycleResponseFactory, toolkit);
expect(toolkit.next).toHaveBeenCalled();
});
describe('request for dashboard-only user', () => {
function testRedirectToDashboardModeApp(url: string) {
describe(`requests to url:"${url}"`, () => {
test('redirects to the dashboard_mode app instead', async () => {
const { pathname, search, hash } = parseUrl(url);
const request = ({
url: { pathname, search, hash },
credentials: {
roles: [DASHBOARD_ONLY_MODE_ROLE],
},
} as unknown) as KibanaRequest;
const response = ({
redirected: jest.fn(),
} as unknown) as LifecycleResponseFactory;
security.authc.getCurrentUser = jest.fn(
(r: KibanaRequest) =>
(({
roles: [DASHBOARD_ONLY_MODE_ROLE],
} as unknown) as AuthenticatedUser)
);
uiSettingsMock = [DASHBOARD_ONLY_MODE_ROLE];
await interceptor(request, response, toolkit);
expect(response.redirected).toHaveBeenCalledWith({
headers: { location: `/mock-server-basepath/app/dashboard_mode` },
});
});
});
}
testRedirectToDashboardModeApp('/app/kibana');
testRedirectToDashboardModeApp('/app/kibana#/foo/bar');
testRedirectToDashboardModeApp('/app/kibana/foo/bar');
testRedirectToDashboardModeApp('/app/kibana?foo=bar');
testRedirectToDashboardModeApp('/app/dashboards?foo=bar');
testRedirectToDashboardModeApp('/app/home?foo=bar');
});
});

View file

@ -1,80 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { HttpServiceSetup, OnPostAuthHandler, IUiSettingsClient } from 'kibana/server';
import { SecurityPluginSetup } from '../../../security/server';
import { UI_SETTINGS } from '../../common';
const superuserRole = 'superuser';
interface DashboardModeRequestInterceptorDependencies {
http: HttpServiceSetup;
security: SecurityPluginSetup;
getUiSettingsClient: () => Promise<IUiSettingsClient>;
}
export const setupDashboardModeRequestInterceptor = ({
http,
security,
getUiSettingsClient,
}: DashboardModeRequestInterceptorDependencies) =>
(async (request, response, toolkit) => {
const path = request.url.pathname;
const isAppRequest = path.startsWith('/app/');
if (!isAppRequest) {
return toolkit.next();
}
const authenticatedUser = security.authc.getCurrentUser(request);
const roles = authenticatedUser?.roles || [];
if (!authenticatedUser || roles.length === 0) {
return toolkit.next();
}
const uiSettings = await getUiSettingsClient();
const dashboardOnlyModeRoles = await uiSettings.get(
UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES
);
if (!dashboardOnlyModeRoles) {
return toolkit.next();
}
const isDashboardOnlyModeUser = roles.find((role) => dashboardOnlyModeRoles.includes(role));
const isSuperUser = roles.find((role) => role === superuserRole);
const enforceDashboardOnlyMode = isDashboardOnlyModeUser && !isSuperUser;
if (enforceDashboardOnlyMode) {
if (
path.startsWith('/app/home') ||
path.startsWith('/app/kibana') ||
path.startsWith('/app/dashboards')
) {
const dashBoardModeUrl = `${http.basePath.get(request)}/app/dashboard_mode`;
// If the user is in "Dashboard only mode" they should only be allowed to see
// the dashboard app and none others.
return response.redirected({
headers: {
location: dashBoardModeUrl,
},
});
}
if (path.startsWith('/app/dashboard_mode')) {
// let through requests to the dashboard_mode app
return toolkit.next();
}
return response.notFound();
}
return toolkit.next();
}) as OnPostAuthHandler;

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { setupDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor';

View file

@ -1,63 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
SavedObjectsClient,
Logger,
} from '../../../../src/core/server';
import { SecurityPluginSetup } from '../../security/server';
import { setupDashboardModeRequestInterceptor } from './interceptors';
import { getUiSettings } from './ui_settings';
interface DashboardModeServerSetupDependencies {
security?: SecurityPluginSetup;
}
export class DashboardModeServerPlugin implements Plugin<void, void> {
private initializerContext: PluginInitializerContext;
private logger?: Logger;
constructor(initializerContext: PluginInitializerContext) {
this.initializerContext = initializerContext;
}
public setup(core: CoreSetup, { security }: DashboardModeServerSetupDependencies) {
this.logger = this.initializerContext.logger.get();
core.uiSettings.register(getUiSettings());
const getUiSettingsClient = async () => {
const [coreStart] = await core.getStartServices();
const { savedObjects, uiSettings } = coreStart;
const savedObjectsClient = new SavedObjectsClient(savedObjects.createInternalRepository());
return uiSettings.asScopedToClient(savedObjectsClient);
};
if (security) {
const dashboardModeRequestInterceptor = setupDashboardModeRequestInterceptor({
http: core.http,
security,
getUiSettingsClient,
});
core.http.registerOnPostAuth(dashboardModeRequestInterceptor);
this.logger.debug(`registered DashboardModeRequestInterceptor`);
}
}
public start(core: CoreStart) {}
public stop() {}
}

View file

@ -1,36 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { UiSettingsParams } from 'kibana/server';
import { UI_SETTINGS } from '../common';
const DASHBOARD_ONLY_USER_ROLE = 'kibana_dashboard_only_user';
export function getUiSettings(): Record<string, UiSettingsParams<unknown>> {
return {
[UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES]: {
name: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle', {
defaultMessage: 'Dashboards only roles',
}),
description: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription', {
defaultMessage: 'Roles that belong to View Dashboards Only mode',
}),
value: [DASHBOARD_ONLY_USER_ROLE],
category: ['dashboard'],
sensitive: true,
deprecation: {
message: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation', {
defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
}),
docLinksKey: 'dashboardSettings',
},
schema: schema.arrayOf(schema.string()),
},
};
}

View file

@ -1,22 +0,0 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true,
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
"../../../typings/**/*"
],
"references": [
{ "path": "../../../src/core/tsconfig.json" },
{ "path": "../../../src/plugins/dashboard/tsconfig.json" },
{ "path": "../../../src/plugins/kibana_legacy/tsconfig.json" },
{ "path": "../../../src/plugins/url_forwarding/tsconfig.json" },
{ "path": "../security/tsconfig.json" }
]
}

View file

@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
import { DiscoverStart } from '../../../../../../src/plugins/discover/public';
import { ViewMode, IEmbeddable } from '../../../../../../src/plugins/embeddable/public';
import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public';
import { KibanaLegacyStart } from '../../../../../../src/plugins/kibana_legacy/public';
import { CoreStart } from '../../../../../../src/core/public';
import { KibanaLocation } from '../../../../../../src/plugins/share/public';
import * as shared from './shared';
@ -18,11 +17,6 @@ export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA';
export interface PluginDeps {
discover: Pick<DiscoverStart, 'locator'>;
kibanaLegacy?: {
dashboardConfig: {
getHideWriteControls: KibanaLegacyStart['dashboardConfig']['getHideWriteControls'];
};
};
}
export interface CoreDeps {
@ -53,11 +47,6 @@ export abstract class AbstractExploreDataAction<Context extends { embeddable?: I
if (capabilities.discover && !capabilities.discover.show) return false;
if (!plugins.discover.locator) return false;
const isDashboardOnlyMode = !!this.params
.start()
.plugins.kibanaLegacy?.dashboardConfig.getHideWriteControls();
if (isDashboardOnlyMode) return false;
if (!shared.hasExactlyOneIndexPattern(embeddable)) return false;
if (embeddable.getInput().viewMode !== ViewMode.VIEW) return false;

View file

@ -35,12 +35,10 @@ const setup = (
useRangeEvent = false,
timeFieldName,
filters = [],
dashboardOnlyMode = false,
}: {
useRangeEvent?: boolean;
filters?: Filter[];
timeFieldName?: string;
dashboardOnlyMode?: boolean;
} = { filters: [] }
) => {
const core = coreMock.createStart();
@ -65,11 +63,6 @@ const setup = (
discover: {
locator,
},
kibanaLegacy: {
dashboardConfig: {
getHideWriteControls: () => dashboardOnlyMode,
},
},
};
const params: Params = {
@ -193,13 +186,6 @@ describe('"Explore underlying data" panel action', () => {
expect(isCompatible).toBe(false);
});
test('return false for dashboard_only mode', async () => {
const { action, context } = setup({ dashboardOnlyMode: true });
const isCompatible = await action.isCompatible(context);
expect(isCompatible).toBe(false);
});
test('returns false if Discover app is disabled', async () => {
const { action, context, core } = setup();

View file

@ -28,7 +28,7 @@ afterEach(() => {
i18nTranslateSpy.mockClear();
});
const setup = ({ dashboardOnlyMode = false }: { dashboardOnlyMode?: boolean } = {}) => {
const setup = () => {
const core = coreMock.createStart();
const locator: DiscoverAppLocator = {
getLocation: jest.fn(() =>
@ -51,11 +51,6 @@ const setup = ({ dashboardOnlyMode = false }: { dashboardOnlyMode?: boolean } =
discover: {
locator,
},
kibanaLegacy: {
dashboardConfig: {
getHideWriteControls: () => dashboardOnlyMode,
},
},
};
const params: Params = {
@ -177,13 +172,6 @@ describe('"Explore underlying data" panel action', () => {
expect(isCompatible).toBe(false);
});
test('return false for dashboard_only mode', async () => {
const { action, context } = setup({ dashboardOnlyMode: true });
const isCompatible = await action.isCompatible(context);
expect(isCompatible).toBe(false);
});
test('returns false if Discover app is disabled', async () => {
const { action, context, core } = setup();

View file

@ -7609,9 +7609,6 @@
"xpack.dashboard.drilldown.goToDashboard": "ダッシュボードに移動",
"xpack.dashboard.FlyoutCreateDrilldownAction.displayName": "ドリルダウンを作成",
"xpack.dashboard.panel.openFlyoutEditDrilldown.displayName": "ドリルダウンを管理",
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation": "この設定はサポートが終了し、Kibana 8.0 では削除されます。",
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription": "ダッシュボード表示専用モードのロールです",
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "ダッシュボード専用ロール",
"xpack.data.mgmt.searchSessions.actionDelete": "削除",
"xpack.data.mgmt.searchSessions.actionExtend": "延長",
"xpack.data.mgmt.searchSessions.actionRename": "名前を編集",

View file

@ -7851,9 +7851,6 @@
"xpack.dashboard.drilldown.goToDashboard": "前往仪表板",
"xpack.dashboard.FlyoutCreateDrilldownAction.displayName": "创建向下钻取",
"xpack.dashboard.panel.openFlyoutEditDrilldown.displayName": "管理向下钻取",
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation": "此设置已过时,将在 Kibana 8.0 中移除。",
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription": "属于“仅查看仪表板”模式的角色",
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "仅限仪表板的角色",
"xpack.data.mgmt.searchSessions.actionDelete": "删除",
"xpack.data.mgmt.searchSessions.actionExtend": "延长",
"xpack.data.mgmt.searchSessions.actionRename": "编辑名称",

View file

@ -1,92 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
export default function ({ getPageObjects, getService }) {
const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver');
const dashboardPanelActions = getService('dashboardPanelActions');
const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']);
// FLAKY: https://github.com/elastic/kibana/issues/102366
describe.skip('empty dashboard', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic');
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.preserveCrossAppState();
await PageObjects.dashboard.clickNewDashboard();
});
after(async () => {
await PageObjects.dashboard.gotoDashboardLandingPage();
});
it('adds Lens visualization to empty dashboard', async () => {
const title = 'Dashboard Test Lens';
await PageObjects.lens.createAndAddLensFromDashboard({ title, redirectToOrigin: true });
await PageObjects.dashboard.waitForRenderComplete();
await testSubjects.exists(`embeddablePanelHeading-${title}`);
});
it('redirects via save and return button after edit', async () => {
await dashboardPanelActions.openContextMenu();
await dashboardPanelActions.clickEdit();
await PageObjects.lens.saveAndReturn();
});
it('redirects via save as button after edit, renaming itself', async () => {
const newTitle = 'wowee, looks like I have a new title';
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await PageObjects.dashboard.waitForRenderComplete();
await dashboardPanelActions.openContextMenu();
await dashboardPanelActions.clickEdit();
await PageObjects.lens.save(newTitle, false, true);
await PageObjects.dashboard.waitForRenderComplete();
const newPanelCount = await PageObjects.dashboard.getPanelCount();
expect(newPanelCount).to.eql(originalPanelCount);
const titles = await PageObjects.dashboard.getPanelTitles();
expect(titles.indexOf(newTitle)).to.not.be(-1);
});
it('redirects via save as button after edit, adding a new panel', async () => {
const newTitle = 'wowee, my title just got cooler';
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await PageObjects.dashboard.waitForRenderComplete();
await dashboardPanelActions.openContextMenu();
await dashboardPanelActions.clickEdit();
await PageObjects.lens.save(newTitle, true, true);
await PageObjects.dashboard.waitForRenderComplete();
const newPanelCount = await PageObjects.dashboard.getPanelCount();
expect(newPanelCount).to.eql(originalPanelCount + 1);
const titles = await PageObjects.dashboard.getPanelTitles();
expect(titles.indexOf(newTitle)).to.not.be(-1);
});
it('loses originatingApp connection after save as when redirectToOrigin is false', async () => {
await PageObjects.dashboard.saveDashboard('empty dashboard test');
await PageObjects.dashboard.switchToEditMode();
const newTitle = 'wowee, my title just got cooler again';
await PageObjects.dashboard.waitForRenderComplete();
await dashboardPanelActions.openContextMenu();
await dashboardPanelActions.clickEdit();
await PageObjects.lens.save(newTitle, true, false);
await PageObjects.lens.notLinkedToOriginatingApp();
await PageObjects.common.navigateToApp('dashboard');
});
it('loses originatingApp connection after first save when redirectToOrigin is false', async () => {
const title = 'non-dashboard Test Lens';
await PageObjects.dashboard.loadSavedDashboard('empty dashboard test');
await PageObjects.dashboard.switchToEditMode();
await PageObjects.lens.createAndAddLensFromDashboard({ title });
await PageObjects.lens.notLinkedToOriginatingApp();
await PageObjects.common.navigateToApp('dashboard');
});
});
}

View file

@ -1,173 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const log = getService('log');
const pieChart = getService('pieChart');
const security = getService('security');
const testSubjects = getService('testSubjects');
const dashboardPanelActions = getService('dashboardPanelActions');
const appsMenu = getService('appsMenu');
const filterBar = getService('filterBar');
const PageObjects = getPageObjects([
'security',
'common',
'discover',
'dashboard',
'header',
'settings',
'timePicker',
'share',
]);
const dashboardName = 'Dashboard View Mode Test Dashboard';
describe('Dashboard View Mode', function () {
this.tags(['skipFirefox']);
before('initialize tests', async () => {
log.debug('Dashboard View Mode:initTests');
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/dashboard_view_mode'
);
await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
await browser.setWindowSize(1600, 1000);
await PageObjects.common.navigateToApp('dashboard');
});
after(async () => {
await kibanaServer.importExport.unload(
'x-pack/test/functional/fixtures/kbn_archiver/dashboard_view_mode'
);
const types = [
'search',
'dashboard',
'visualization',
'search-session',
'core-usage-stats',
'event_loop_delays_daily',
'search-telemetry',
'core-usage-stats',
];
await kibanaServer.savedObjects.clean({ types });
});
// FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/109351
describe.skip('Dashboard viewer', () => {
after(async () => {
await security.testUser.restoreDefaults();
});
it('shows only the dashboard app link', async () => {
await security.testUser.setRoles(['test_logstash_reader', 'kibana_dashboard_only_user']);
await PageObjects.header.waitUntilLoadingHasFinished();
const appLinks = await appsMenu.readLinks();
expect(appLinks).to.have.length(1);
expect(appLinks[0]).to.have.property('text', 'Dashboard');
});
it('shows the dashboard landing page by default', async () => {
const currentUrl = await browser.getCurrentUrl();
console.log('url: ', currentUrl);
expect(currentUrl).to.contain('dashboards');
});
it('does not show the create dashboard button', async () => {
const createNewButtonExists = await testSubjects.exists('newItemButton');
expect(createNewButtonExists).to.be(false);
});
it('opens a dashboard up', async () => {
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
const onDashboardLandingPage = await PageObjects.dashboard.onDashboardLandingPage();
expect(onDashboardLandingPage).to.be(false);
});
it('can filter on a visualization', async () => {
await PageObjects.timePicker.setHistoricalDataRange();
await pieChart.filterOnPieSlice();
const filterCount = await filterBar.getFilterCount();
expect(filterCount).to.equal(1);
});
it('shows the full screen menu item', async () => {
const fullScreenMenuItemExists = await testSubjects.exists('dashboardFullScreenMode');
expect(fullScreenMenuItemExists).to.be(true);
});
it('does not show the edit menu item', async () => {
const editMenuItemExists = await testSubjects.exists('dashboardEditMode');
expect(editMenuItemExists).to.be(false);
});
it('does not show the view menu item', async () => {
const viewMenuItemExists = await testSubjects.exists('dashboardViewOnlyMode');
expect(viewMenuItemExists).to.be(false);
});
it('does not show the reporting menu item', async () => {
const reportingMenuItemExists = await testSubjects.exists('topNavReportingLink');
expect(reportingMenuItemExists).to.be(false);
});
it('shows the sharing menu item', async () => {
const shareMenuItemExists = await testSubjects.exists('shareTopNavButton');
expect(shareMenuItemExists).to.be(true);
});
it(`Permalinks doesn't show create short-url button`, async () => {
await PageObjects.share.openShareMenuItem('Permalinks');
await PageObjects.share.createShortUrlMissingOrFail();
});
it('does not show the visualization edit icon', async () => {
await dashboardPanelActions.expectMissingEditPanelAction();
});
it('does not show the visualization delete icon', async () => {
await dashboardPanelActions.expectMissingRemovePanelAction();
});
it('shows the timepicker', async () => {
const timePickerExists = await PageObjects.timePicker.timePickerExists();
expect(timePickerExists).to.be(true);
});
it('can paginate on a saved search', async () => {
await PageObjects.dashboard.expectToolbarPaginationDisplayed({ displayed: true });
});
it('is loaded for a user who is assigned a non-dashboard mode role', async () => {
await security.testUser.setRoles([
'test_logstash_reader',
'kibana_dashboard_only_user',
'kibana_admin',
]);
await PageObjects.header.waitUntilLoadingHasFinished();
if (await appsMenu.linkExists('Stack Management')) {
throw new Error('Expected management nav link to not be shown');
}
});
it('is not loaded for a user who is assigned a superuser role', async () => {
await security.testUser.setRoles(['kibana_dashboard_only_user', 'superuser']);
await PageObjects.header.waitUntilLoadingHasFinished();
if (!(await appsMenu.linkExists('Stack Management'))) {
throw new Error('Expected management nav link to be shown');
}
});
});
});
}

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export default function ({ loadTestFile }) {
describe('dashboard mode', function () {
this.tags('ciGroup7');
loadTestFile(require.resolve('./dashboard_view_mode'));
loadTestFile(require.resolve('./dashboard_empty_screen'));
});
}

View file

@ -29,7 +29,6 @@ export default async function ({ readConfigFile }) {
resolve(__dirname, './apps/monitoring'),
resolve(__dirname, './apps/watcher'),
resolve(__dirname, './apps/dashboard'),
resolve(__dirname, './apps/dashboard_mode'),
resolve(__dirname, './apps/discover'),
resolve(__dirname, './apps/security'),
resolve(__dirname, './apps/spaces'),

View file

@ -54,7 +54,6 @@
{ "path": "../plugins/banners/tsconfig.json" },
{ "path": "../plugins/cases/tsconfig.json" },
{ "path": "../plugins/cloud/tsconfig.json" },
{ "path": "../plugins/dashboard_mode/tsconfig.json" },
{ "path": "../plugins/enterprise_search/tsconfig.json" },
{ "path": "../plugins/fleet/tsconfig.json" },
{ "path": "../plugins/global_search/tsconfig.json" },