Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/plugins/dashboard/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"requiredBundles": [
"home",
"kibanaReact",
"kibanaUtils"
"kibanaUtils",
"presentationUtil"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class DashboardContainerFactoryDefinition

public readonly getDisplayName = () => {
return i18n.translate('dashboard.factory.displayName', {
defaultMessage: 'dashboard',
defaultMessage: 'Dashboard',
});
};

Expand Down
110 changes: 108 additions & 2 deletions src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ import angular from 'angular';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import UseUnmount from 'react-use/lib/useUnmount';
import { EuiContextMenu } from '@elastic/eui';
import { VisualizeInput } from '../../../../visualizations/public';
import {
AddFromLibraryButton,
PrimaryActionButton,
QuickButtonGroup,
SolutionToolbarPopover,
} from '../../../../presentation_util/public';
import { useKibana } from '../../services/kibana_react';
import { IndexPattern, SavedQuery, TimefilterContract } from '../../services/data';
import {
EmbeddableFactoryDefinition,
EmbeddableFactoryNotFoundError,
EmbeddableInput,
isErrorEmbeddable,
Expand Down Expand Up @@ -43,11 +52,11 @@ import { showCloneModal } from './show_clone_modal';
import { showOptionsPopover } from './show_options_popover';
import { TopNavIds } from './top_nav_ids';
import { ShowShareModal } from './show_share_modal';
import { PanelToolbar } from './panel_toolbar';
import { confirmDiscardOrKeepUnsavedChanges } from '../listing/confirm_overlays';
import { OverlayRef } from '../../../../../core/public';
import { getNewDashboardTitle, unsavedChangesBadge } from '../../dashboard_strings';
import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage';
import { SolutionToolbar } from '../../../../presentation_util/public';
import { DashboardContainer } from '..';

export interface DashboardTopNavState {
Expand Down Expand Up @@ -103,6 +112,35 @@ export function DashboardTopNav({
const [state, setState] = useState<DashboardTopNavState>({ chromeIsVisible: false });
const [isSaveInProgress, setIsSaveInProgress] = useState(false);

const factories = embeddable
? Array.from(embeddable.getEmbeddableFactories()).filter(
({ isEditable, canCreateNew, isContainerType }) =>
isEditable() && !isContainerType && canCreateNew()
)
: [];

const editorMenuPanels = [
{
id: 0,
items: factories.map((factory: EmbeddableFactoryDefinition) => {
const onClick = async () => {
if (factory.getExplicitInput) {
const explicitInput = await factory.getExplicitInput();
await dashboardContainer.addNewEmbeddable(factory.type, explicitInput);
} else {
await factory.create({} as EmbeddableInput, dashboardContainer);
}
};

return {
name: factory.getDisplayName(),
icon: 'empty',
onClick,
};
}),
},
];

useEffect(() => {
const visibleSubscription = chrome.getIsVisible$().subscribe((chromeIsVisible) => {
setState((s) => ({ ...s, chromeIsVisible }));
Expand Down Expand Up @@ -147,12 +185,28 @@ export function DashboardTopNav({
const createNew = useCallback(async () => {
const type = 'visualization';
const factory = embeddable.getEmbeddableFactory(type);

if (!factory) {
throw new EmbeddableFactoryNotFoundError(type);
}

await factory.create({} as EmbeddableInput, dashboardContainer);
}, [dashboardContainer, embeddable]);

const createNewVisType = useCallback(
(newVisType: string) => async () => {
const type = 'visualization';
const factory = embeddable.getEmbeddableFactory(type);

if (!factory) {
throw new EmbeddableFactoryNotFoundError(type);
}

await factory.create({ newVisType, id: newVisType } as VisualizeInput, dashboardContainer);
},
[dashboardContainer, embeddable]
);

const clearAddPanel = useCallback(() => {
if (state.addPanelOverlay) {
state.addPanelOverlay.close();
Expand Down Expand Up @@ -540,11 +594,63 @@ export function DashboardTopNav({
};

const { TopNavMenu } = navigation.ui;

const quickButtons = [
{
iconType: 'visText',
createType: i18n.translate('dashboard.solutionToolbar.markdownQuickButtonLabel', {
defaultMessage: 'Markdown',
}),
onClick: createNewVisType('markdown'),
},
{
iconType: 'controlsHorizontal',
createType: i18n.translate('dashboard.solutionToolbar.inputControlsQuickButtonLabel', {
defaultMessage: 'Input control',
}),
onClick: createNewVisType('input_control_vis'),
},
];

return (
<>
<TopNavMenu {...getNavBarProps()} />
{viewMode !== ViewMode.VIEW ? (
<PanelToolbar onAddPanelClick={createNew} onLibraryClick={addFromLibrary} />
<SolutionToolbar>
{{
primaryActionButton: (
<PrimaryActionButton
label={i18n.translate('dashboard.solutionToolbar.addPanelButtonLabel', {
defaultMessage: 'Create panel',
})}
onClick={createNew}
iconType="plusInCircleFilled"
data-test-subj="dashboardAddNewPanelButton"
/>
),
quickButtonGroup: <QuickButtonGroup buttons={quickButtons} />,
addFromLibraryButton: (
<AddFromLibraryButton
onClick={addFromLibrary}
data-test-subj="dashboardAddPanelButton"
/>
),
extraButtons: factories.length
? [
<SolutionToolbarPopover
ownFocus
label={i18n.translate('dashboard.panelToolbar.editorMenuButtonLabel', {
defaultMessage: 'All editors',
})}
iconType="visualizeApp"
panelPaddingSize="none"
>
<EuiContextMenu initialPanelId={0} panels={editorMenuPanels} />
</SolutionToolbarPopover>,
]
: undefined,
}}
</SolutionToolbar>
) : null}
</>
);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import './panel_toolbar.scss';
import React, { FC } from 'react';
import React, { FC, ReactNode } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';

Expand All @@ -16,10 +16,17 @@ interface Props {
onAddPanelClick: () => void;
/** The click handler for the Library button for adding existing visualizations/embeddables */
onLibraryClick: () => void;
/** Additional content for toolbar */
children: ReactNode;
}

export const PanelToolbar: FC<Props> = ({ onAddPanelClick, onLibraryClick }) => (
<EuiFlexGroup className="panelToolbar" id="kbnDashboard__panelToolbar" gutterSize="s">
export const PanelToolbar: FC<Props> = ({ onAddPanelClick, onLibraryClick, children }) => (
<EuiFlexGroup
className="panelToolbar"
id="kbnDashboard__panelToolbar"
gutterSize="s"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
<EuiButton
fill
Expand All @@ -33,6 +40,7 @@ export const PanelToolbar: FC<Props> = ({ onAddPanelClick, onLibraryClick }) =>
})}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>{children}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
* Side Public License, v 1.
*/

export { PanelToolbar } from './panel_toolbar';
module.exports = require('@kbn/storybook').defaultConfig;
76 changes: 76 additions & 0 deletions src/plugins/dashboard/storybook/storyshots.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import fs from 'fs';
import { ReactChildren } from 'react';
import path from 'path';
import moment from 'moment';
import 'moment-timezone';
import ReactDOM from 'react-dom';

import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots';
// @ts-ignore
import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer';
import { addSerializer } from 'jest-specific-snapshot';

// Set our default timezone to UTC for tests so we can generate predictable snapshots
moment.tz.setDefault('UTC');

// Freeze time for the tests for predictable snapshots
const testTime = new Date(Date.UTC(2019, 5, 1)); // June 1 2019
Date.now = jest.fn(() => testTime.getTime());

// Mock React Portal for components that use modals, tooltips, etc
// @ts-expect-error Portal mocks are notoriously difficult to type
ReactDOM.createPortal = jest.fn((element) => element);

// Mock EUI generated ids to be consistently predictable for snapshots.
jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);

// Mock react-datepicker dep used by eui to avoid rendering the entire large component
jest.mock('@elastic/eui/packages/react-datepicker', () => {
return {
__esModule: true,
default: 'ReactDatePicker',
};
});

// Mock the EUI HTML ID Generator so elements have a predictable ID in snapshots
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
return {
htmlIdGenerator: () => () => `generated-id`,
};
});

// To be resolved by EUI team.
// https://github.com/elastic/eui/issues/3712
jest.mock('@elastic/eui/lib/components/overlay_mask/overlay_mask', () => {
return {
EuiOverlayMask: ({ children }: { children: ReactChildren }) => children,
};
});

// @ts-ignore
import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer';
jest.mock('@elastic/eui/test-env/components/observer/observer');
EuiObserver.mockImplementation(() => 'EuiObserver');

// Some of the code requires that this directory exists, but the tests don't actually require any css to be present
const cssDir = path.resolve(__dirname, '../../../../built_assets/css');
if (!fs.existsSync(cssDir)) {
fs.mkdirSync(cssDir, { recursive: true });
}

addSerializer(styleSheetSerializer);

// Initialize Storyshots and build the Jest Snapshots
// initStoryshots({
// configPath: path.resolve(__dirname, './../storybook'),
// framework: 'react',
// test: multiSnapshotWithOptions({}),
// });
3 changes: 3 additions & 0 deletions src/plugins/dashboard/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@
{ "path": "../saved_objects/tsconfig.json" },
{ "path": "../ui_actions/tsconfig.json" },
{ "path": "../spaces_oss/tsconfig.json" },
{ "path": "../charts/tsconfig.json" },
{ "path": "../discover/tsconfig.json" },
{ "path": "../visualizations/tsconfig.json" },
]
}
Loading