mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Canvas][Labs] Integrate Labs Service into Canvas (#96920)
This commit is contained in:
parent
23b84d6a52
commit
e2933b5141
34 changed files with 345 additions and 99 deletions
|
@ -432,7 +432,11 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
type: 'text',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'labs:presentation:unifiedToolbar': {
|
||||
'labs:presentation:timeToPresent': {
|
||||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'labs:canvas:enable_ui': {
|
||||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
|
|
|
@ -118,5 +118,6 @@ export interface UsageStats {
|
|||
'banners:placement': string;
|
||||
'banners:textColor': string;
|
||||
'banners:backgroundColor': string;
|
||||
'labs:presentation:unifiedToolbar': boolean;
|
||||
'labs:canvas:enable_ui': boolean;
|
||||
'labs:presentation:timeToPresent': boolean;
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const UNIFIED_TOOLBAR = 'labs:presentation:unifiedToolbar';
|
||||
export const TIME_TO_PRESENT = 'labs:presentation:timeToPresent';
|
||||
|
||||
export const projectIDs = [UNIFIED_TOOLBAR] as const;
|
||||
export const projectIDs = [TIME_TO_PRESENT] as const;
|
||||
export const environmentNames = ['kibana', 'browser', 'session'] as const;
|
||||
export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const;
|
||||
|
||||
|
@ -19,17 +19,18 @@ export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const;
|
|||
* provided to users of our solutions in Kibana.
|
||||
*/
|
||||
export const projects: { [ID in ProjectID]: ProjectConfig & { id: ID } } = {
|
||||
[UNIFIED_TOOLBAR]: {
|
||||
id: UNIFIED_TOOLBAR,
|
||||
[TIME_TO_PRESENT]: {
|
||||
id: TIME_TO_PRESENT,
|
||||
isActive: false,
|
||||
isDisplayed: false,
|
||||
environments: ['kibana', 'browser', 'session'],
|
||||
name: i18n.translate('presentationUtil.labs.enableUnifiedToolbarProjectName', {
|
||||
defaultMessage: 'Unified Toolbar',
|
||||
name: i18n.translate('presentationUtil.labs.enableTimeToPresentProjectName', {
|
||||
defaultMessage: 'Canvas Presentation UI',
|
||||
}),
|
||||
description: i18n.translate('presentationUtil.labs.enableUnifiedToolbarProjectDescription', {
|
||||
defaultMessage: 'Enable the new unified toolbar design for Presentation solutions',
|
||||
defaultMessage: 'Enable the new presentation-oriented UI for Canvas.',
|
||||
}),
|
||||
solutions: ['dashboard', 'canvas'],
|
||||
solutions: ['canvas'],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -51,6 +52,7 @@ export interface ProjectConfig {
|
|||
id: ProjectID;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
isDisplayed: boolean;
|
||||
environments: EnvironmentName[];
|
||||
description: string;
|
||||
solutions: SolutionName[];
|
||||
|
|
|
@ -25,11 +25,9 @@ export const withSuspense = <P extends {}>(
|
|||
</EuiErrorBoundary>
|
||||
);
|
||||
|
||||
export const LazyLabsBeakerButton = withSuspense(
|
||||
React.lazy(() => import('./labs/labs_beaker_button'))
|
||||
);
|
||||
export const LazyLabsBeakerButton = React.lazy(() => import('./labs/labs_beaker_button'));
|
||||
|
||||
export const LazyLabsFlyout = withSuspense(React.lazy(() => import('./labs/labs_flyout')));
|
||||
export const LazyLabsFlyout = React.lazy(() => import('./labs/labs_flyout'));
|
||||
|
||||
export const LazyDashboardPicker = React.lazy(() => import('./dashboard_picker'));
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
EuiScreenReaderOnly,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { pluginServices } from '../../services';
|
||||
import { EnvironmentName } from '../../../common/labs';
|
||||
import { LabsStrings } from '../../i18n';
|
||||
|
||||
|
@ -34,29 +35,36 @@ export interface Props {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export const EnvironmentSwitch = ({ env, isChecked, onChange, name }: Props) => (
|
||||
<EuiFlexItem grow={false} style={{ marginBottom: '.25rem' }}>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
checked={isChecked}
|
||||
style={{ marginTop: 1 }}
|
||||
label={
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiScreenReaderOnly>
|
||||
<span>{name} - </span>
|
||||
</EuiScreenReaderOnly>
|
||||
{switchText[env].name}
|
||||
</EuiFlexItem>
|
||||
}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ textAlign: 'right' }}>
|
||||
<EuiIconTip content={switchText[env].help} position="left" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
export const EnvironmentSwitch = ({ env, isChecked, onChange, name }: Props) => {
|
||||
const { capabilities } = pluginServices.getHooks();
|
||||
|
||||
const canSet = env === 'kibana' ? capabilities.useService().canSetAdvancedSettings() : true;
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false} style={{ marginBottom: '.25rem' }}>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
disabled={!canSet}
|
||||
checked={isChecked}
|
||||
style={{ marginTop: 1 }}
|
||||
label={
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiScreenReaderOnly>
|
||||
<span>{name} - </span>
|
||||
</EuiScreenReaderOnly>
|
||||
{switchText[env].name}
|
||||
</EuiFlexItem>
|
||||
}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ textAlign: 'right' }}>
|
||||
<EuiIconTip content={switchText[env].help} position="left" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,7 +16,12 @@ export default {
|
|||
title: 'Labs/Flyout',
|
||||
description:
|
||||
'A set of components used for providing Labs controls and projects in another solution.',
|
||||
argTypes: {},
|
||||
argTypes: {
|
||||
canSetAdvancedSettings: {
|
||||
control: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function BeakerButton() {
|
||||
|
|
|
@ -10,6 +10,8 @@ import React, { ReactNode, useRef, useState, useEffect } from 'react';
|
|||
import {
|
||||
EuiFlyout,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
|
@ -18,6 +20,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiIcon,
|
||||
EuiOverlayMask,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SolutionName, ProjectStatus, ProjectID, Project, EnvironmentName } from '../../../common';
|
||||
|
@ -104,32 +107,47 @@ export const LabsFlyout = (props: Props) => {
|
|||
|
||||
footer = (
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{resetButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{refreshButton}</EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={() => onClose()} flush="left">
|
||||
{strings.getCloseButtonLabel()}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{resetButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{refreshButton}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose}>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="beaker" size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{strings.getTitleLabel()}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<ProjectList {...{ projects, solutions, onStatusChange }} />
|
||||
</EuiFlyoutBody>
|
||||
{footer}
|
||||
</EuiFlyout>
|
||||
<EuiOverlayMask onClick={() => onClose()} headerZindexLocation="below">
|
||||
<EuiFlyout onClose={onClose} hideCloseButton={true}>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="beaker" size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{strings.getTitleLabel()}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>{strings.getDescriptionMessage()}</p>
|
||||
</EuiText>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<ProjectList {...{ projects, solutions, onStatusChange }} />
|
||||
</EuiFlyoutBody>
|
||||
{footer}
|
||||
</EuiFlyout>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ export const ProjectList = (props: Props) => {
|
|||
|
||||
const items = Object.values(projects)
|
||||
.map((project) => {
|
||||
if (!project.isDisplayed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter out any panels that don't match the solutions filter, (if provided).
|
||||
if (solutions && !solutions.some((solution) => project.solutions.includes(solution))) {
|
||||
return null;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
left: 4px;
|
||||
bottom: $euiSizeL;
|
||||
width: 4px;
|
||||
background: $euiColorPrimary;
|
||||
background: $euiColorSecondary;
|
||||
content: '';
|
||||
}
|
||||
|
||||
|
@ -37,10 +37,20 @@
|
|||
}
|
||||
|
||||
&--isOverridden:before {
|
||||
left: -12px;
|
||||
left: -$euiSizeS;
|
||||
}
|
||||
|
||||
&--isOverridden:first-child:before {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.projectListItem__titlePendingChangesIndicator {
|
||||
margin-left: $euiSizeS;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.projectListItem__solutions {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export function EmptyList() {
|
|||
export const ListItem = (
|
||||
props: Pick<
|
||||
Props['project'],
|
||||
'description' | 'isActive' | 'name' | 'solutions' | 'environments'
|
||||
'description' | 'isActive' | 'name' | 'solutions' | 'environments' | 'isDisplayed'
|
||||
> &
|
||||
Omit<ProjectStatus, 'defaultValue'>
|
||||
) => {
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
EuiText,
|
||||
EuiFormFieldset,
|
||||
EuiScreenReaderOnly,
|
||||
EuiSpacer,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
@ -47,8 +49,20 @@ export const ProjectListItem = ({ project, onStatusChange }: Props) => {
|
|||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle className="projectListItem__title" size="s">
|
||||
<h2>{name}</h2>
|
||||
<EuiTitle className="projectListItem__title" size="xs">
|
||||
<h2>
|
||||
{name}
|
||||
{isOverride ? (
|
||||
<span className="projectListItem__titlePendingChangesIndicator">
|
||||
<EuiIconTip
|
||||
content={strings.getOverriddenIconTipLabel()}
|
||||
position="top"
|
||||
type="dot"
|
||||
color="secondary"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -59,10 +73,14 @@ export const ProjectListItem = ({ project, onStatusChange }: Props) => {
|
|||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">{description}</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
{description}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s" color="subdued">
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
{isActive ? strings.getEnabledStatusMessage() : strings.getDisabledStatusMessage()}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCode } from '@elastic/eui';
|
||||
|
||||
export const LabsStrings = {
|
||||
Components: {
|
||||
|
@ -18,7 +19,8 @@ export const LabsStrings = {
|
|||
defaultMessage: 'Kibana',
|
||||
}),
|
||||
help: i18n.translate('presentationUtil.labs.components.kibanaSwitchHelp', {
|
||||
defaultMessage: 'Sets the corresponding Advanced Setting for this lab project in Kibana',
|
||||
defaultMessage:
|
||||
'Sets the corresponding Advanced Setting for this lab project; affects all Kibana users',
|
||||
}),
|
||||
}),
|
||||
getBrowserSwitchText: () => ({
|
||||
|
@ -51,24 +53,28 @@ export const LabsStrings = {
|
|||
i18n.translate('presentationUtil.labs.components.overrideFlagsLabel', {
|
||||
defaultMessage: 'Override flags',
|
||||
}),
|
||||
getOverriddenIconTipLabel: () =>
|
||||
i18n.translate('presentationUtil.labs.components.overridenIconTipLabel', {
|
||||
defaultMessage: 'Default overridden',
|
||||
}),
|
||||
getEnabledStatusMessage: () => (
|
||||
<FormattedMessage
|
||||
id="presentationUtil.labs.components.defaultStatusMessage"
|
||||
defaultMessage="{status} by default"
|
||||
id="presentationUtil.labs.components.enabledStatusMessage"
|
||||
defaultMessage="Default: {status}"
|
||||
values={{
|
||||
status: <strong>Enabled</strong>,
|
||||
status: <EuiCode>Enabled</EuiCode>,
|
||||
}}
|
||||
description="Displays the current status of a lab project"
|
||||
description="Displays the enabled status of a lab project"
|
||||
/>
|
||||
),
|
||||
getDisabledStatusMessage: () => (
|
||||
<FormattedMessage
|
||||
id="presentationUtil.labs.components.defaultStatusMessage"
|
||||
defaultMessage="{status} by default"
|
||||
id="presentationUtil.labs.components.disabledStatusMessage"
|
||||
defaultMessage="Default: {status}"
|
||||
values={{
|
||||
status: <strong>Disabled</strong>,
|
||||
status: <EuiCode>Disabled</EuiCode>,
|
||||
}}
|
||||
description="Displays the current status of a lab project"
|
||||
description="Displays the disabled status of a lab project"
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -77,6 +83,11 @@ export const LabsStrings = {
|
|||
i18n.translate('presentationUtil.labs.components.titleLabel', {
|
||||
defaultMessage: 'Lab projects',
|
||||
}),
|
||||
getDescriptionMessage: () =>
|
||||
i18n.translate('presentationUtil.labs.components.descriptionMessage', {
|
||||
defaultMessage:
|
||||
'Lab projects are features and functionality that are in-progress or experimental in nature. They can be enabled and disabled locally for your browser or tab, or in Kibana.',
|
||||
}),
|
||||
getResetToDefaultLabel: () =>
|
||||
i18n.translate('presentationUtil.labs.components.resetToDefaultLabel', {
|
||||
defaultMessage: 'Reset to defaults',
|
||||
|
@ -89,6 +100,10 @@ export const LabsStrings = {
|
|||
i18n.translate('presentationUtil.labs.components.calloutHelp', {
|
||||
defaultMessage: 'Refresh to apply changes',
|
||||
}),
|
||||
getCloseButtonLabel: () =>
|
||||
i18n.translate('presentationUtil.labs.components.closeButtonLabel', {
|
||||
defaultMessage: 'Close',
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
import { PresentationUtilPlugin } from './plugin';
|
||||
|
||||
export {
|
||||
PresentationCapabilitiesService,
|
||||
PresentationDashboardsService,
|
||||
PresentationLabsService,
|
||||
} from './services';
|
||||
|
||||
export { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types';
|
||||
export { SaveModalDashboardProps } from './components/types';
|
||||
export { projectIDs, ProjectID, Project } from '../common/labs';
|
||||
|
|
|
@ -10,4 +10,5 @@ export interface PresentationCapabilitiesService {
|
|||
canAccessDashboards: () => boolean;
|
||||
canCreateNewDashboards: () => boolean;
|
||||
canSaveVisualizations: () => boolean;
|
||||
canSetAdvancedSettings: () => boolean;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ import { PluginServices } from './create';
|
|||
import { PresentationCapabilitiesService } from './capabilities';
|
||||
import { PresentationDashboardsService } from './dashboards';
|
||||
import { PresentationLabsService } from './labs';
|
||||
|
||||
export { PresentationCapabilitiesService } from './capabilities';
|
||||
export { PresentationDashboardsService } from './dashboards';
|
||||
export { PresentationLabsService } from './labs';
|
||||
export interface PresentationUtilServices {
|
||||
dashboards: PresentationDashboardsService;
|
||||
capabilities: PresentationCapabilitiesService;
|
||||
|
|
|
@ -16,11 +16,12 @@ export type CapabilitiesServiceFactory = KibanaPluginServiceFactory<
|
|||
>;
|
||||
|
||||
export const capabilitiesServiceFactory: CapabilitiesServiceFactory = ({ coreStart }) => {
|
||||
const { dashboard, visualize } = coreStart.application.capabilities;
|
||||
const { dashboard, visualize, advancedSettings } = coreStart.application.capabilities;
|
||||
|
||||
return {
|
||||
canAccessDashboards: () => Boolean(dashboard.show),
|
||||
canCreateNewDashboards: () => Boolean(dashboard.createNew),
|
||||
canSaveVisualizations: () => Boolean(visualize.save),
|
||||
canSetAdvancedSettings: () => Boolean(advancedSettings.save),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
ProjectID,
|
||||
Project,
|
||||
getProjectIDs,
|
||||
SolutionName,
|
||||
} from '../../../common';
|
||||
import { PresentationUtilPluginStartDeps } from '../../types';
|
||||
import { KibanaPluginServiceFactory } from '../create';
|
||||
|
@ -35,9 +36,15 @@ export const labsServiceFactory: LabsServiceFactory = ({ coreStart }) => {
|
|||
const localStorage = window.localStorage;
|
||||
const sessionStorage = window.sessionStorage;
|
||||
|
||||
const getProjects = () =>
|
||||
const getProjects = (solutions: SolutionName[] = []) =>
|
||||
projectIDs.reduce((acc, id) => {
|
||||
acc[id] = getProject(id);
|
||||
const project = getProject(id);
|
||||
if (
|
||||
solutions.length === 0 ||
|
||||
solutions.some((solution) => project.solutions.includes(solution))
|
||||
) {
|
||||
acc[id] = project;
|
||||
}
|
||||
return acc;
|
||||
}, {} as { [id in ProjectID]: Project });
|
||||
|
||||
|
|
|
@ -16,12 +16,13 @@ import {
|
|||
EnvironmentStatus,
|
||||
environmentNames,
|
||||
isProjectEnabledByStatus,
|
||||
SolutionName,
|
||||
} from '../../common';
|
||||
|
||||
export interface PresentationLabsService {
|
||||
getProjectIDs: () => typeof projectIDs;
|
||||
getProject: (id: ProjectID) => Project;
|
||||
getProjects: () => Record<ProjectID, Project>;
|
||||
getProjects: (solutions?: SolutionName[]) => Record<ProjectID, Project>;
|
||||
setProjectStatus: (id: ProjectID, env: EnvironmentName, status: boolean) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
|
|
@ -18,14 +18,14 @@ type CapabilitiesServiceFactory = PluginServiceFactory<
|
|||
export const capabilitiesServiceFactory: CapabilitiesServiceFactory = ({
|
||||
canAccessDashboards,
|
||||
canCreateNewDashboards,
|
||||
canEditDashboards,
|
||||
canSaveVisualizations,
|
||||
canSetAdvancedSettings,
|
||||
}) => {
|
||||
const check = (value: boolean = true) => value;
|
||||
return {
|
||||
canAccessDashboards: () => check(canAccessDashboards),
|
||||
canCreateNewDashboards: () => check(canCreateNewDashboards),
|
||||
canEditDashboards: () => check(canEditDashboards),
|
||||
canSaveVisualizations: () => check(canSaveVisualizations),
|
||||
canSetAdvancedSettings: () => check(canSetAdvancedSettings),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -18,8 +18,8 @@ export { PresentationUtilServices } from '..';
|
|||
export interface StorybookParams {
|
||||
canAccessDashboards?: boolean;
|
||||
canCreateNewDashboards?: boolean;
|
||||
canEditDashboards?: boolean;
|
||||
canSaveVisualizations?: boolean;
|
||||
canSetAdvancedSettings?: boolean;
|
||||
}
|
||||
|
||||
export const providers: PluginServiceProviders<PresentationUtilServices, StorybookParams> = {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { EnvironmentName, projectIDs, Project } from '../../../common';
|
||||
import { PluginServiceFactory } from '../create';
|
||||
import { projects, ProjectID, getProjectIDs } from '../../../common';
|
||||
import { projects, ProjectID, getProjectIDs, SolutionName } from '../../../common';
|
||||
import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } from '../labs';
|
||||
|
||||
export type LabsServiceFactory = PluginServiceFactory<PresentationLabsService>;
|
||||
|
@ -16,9 +16,15 @@ export type LabsServiceFactory = PluginServiceFactory<PresentationLabsService>;
|
|||
export const labsServiceFactory: LabsServiceFactory = () => {
|
||||
const storage = window.sessionStorage;
|
||||
|
||||
const getProjects = () =>
|
||||
const getProjects = (solutions: SolutionName[] = []) =>
|
||||
projectIDs.reduce((acc, id) => {
|
||||
acc[id] = getProject(id);
|
||||
const project = getProject(id);
|
||||
if (
|
||||
solutions.length === 0 ||
|
||||
solutions.some((solution) => project.solutions.includes(solution))
|
||||
) {
|
||||
acc[id] = project;
|
||||
}
|
||||
return acc;
|
||||
}, {} as { [id in ProjectID]: Project });
|
||||
|
||||
|
|
|
@ -14,6 +14,6 @@ type CapabilitiesServiceFactory = PluginServiceFactory<PresentationCapabilitiesS
|
|||
export const capabilitiesServiceFactory: CapabilitiesServiceFactory = () => ({
|
||||
canAccessDashboards: () => true,
|
||||
canCreateNewDashboards: () => true,
|
||||
canEditDashboards: () => true,
|
||||
canSaveVisualizations: () => true,
|
||||
canSetAdvancedSettings: () => true,
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
EnvironmentName,
|
||||
getProjectIDs,
|
||||
Project,
|
||||
SolutionName,
|
||||
} from '../../../common';
|
||||
import { PluginServiceFactory } from '../create';
|
||||
import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } from '../labs';
|
||||
|
@ -36,9 +37,15 @@ export const labsServiceFactory: LabsServiceFactory = () => {
|
|||
|
||||
let statuses = reset();
|
||||
|
||||
const getProjects = () =>
|
||||
const getProjects = (solutions: SolutionName[] = []) =>
|
||||
projectIDs.reduce((acc, id) => {
|
||||
acc[id] = getProject(id);
|
||||
const project = getProject(id);
|
||||
if (
|
||||
solutions.length === 0 ||
|
||||
solutions.some((solution) => project.solutions.includes(solution))
|
||||
) {
|
||||
acc[id] = project;
|
||||
}
|
||||
return acc;
|
||||
}, {} as { [id in ProjectID]: Project });
|
||||
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
|
||||
import { PresentationUtilPlugin } from './plugin';
|
||||
|
||||
export { SETTING_CATEGORY } from './ui_settings';
|
||||
export const plugin = () => new PresentationUtilPlugin();
|
||||
|
|
|
@ -8331,7 +8331,13 @@
|
|||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"labs:presentation:unifiedToolbar": {
|
||||
"labs:presentation:timeToPresent": {
|
||||
"type": "boolean",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"labs:canvas:enable_ui": {
|
||||
"type": "boolean",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
|
|
10
x-pack/plugins/canvas/common/index.ts
Normal file
10
x-pack/plugins/canvas/common/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 = {
|
||||
ENABLE_LABS_UI: 'labs:canvas:enable_ui',
|
||||
};
|
|
@ -514,6 +514,20 @@ export const ComponentStrings = {
|
|||
defaultMessage: 'Keyboard shortcuts',
|
||||
}),
|
||||
},
|
||||
LabsControl: {
|
||||
getLabsButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsButtonLabel', {
|
||||
defaultMessage: 'Labs',
|
||||
}),
|
||||
getAriaLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsAriaLabel', {
|
||||
defaultMessage: 'View labs projects',
|
||||
}),
|
||||
getTooltip: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsTooltip', {
|
||||
defaultMessage: 'View labs projects',
|
||||
}),
|
||||
},
|
||||
Link: {
|
||||
getErrorMessage: (message: string) =>
|
||||
i18n.translate('xpack.canvas.link.errorMessage', {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { LabsControl } from './labs_control';
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { EuiButtonEmpty, EuiNotificationBadge } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
LazyLabsFlyout,
|
||||
withSuspense,
|
||||
} from '../../../../../../../src/plugins/presentation_util/public';
|
||||
|
||||
import { ComponentStrings } from '../../../../i18n';
|
||||
import { useLabsService } from '../../../services';
|
||||
const { LabsControl: strings } = ComponentStrings;
|
||||
|
||||
const Flyout = withSuspense(LazyLabsFlyout, null);
|
||||
|
||||
export const LabsControl = () => {
|
||||
const { isLabsEnabled, getProjects } = useLabsService();
|
||||
const [isShown, setIsShown] = useState(false);
|
||||
|
||||
if (!isLabsEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projects = getProjects(['canvas']);
|
||||
const overrideCount = Object.values(projects).filter((project) => project.status.isOverride)
|
||||
.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiButtonEmpty onClick={() => setIsShown(!isShown)} size="xs">
|
||||
{strings.getLabsButtonLabel()}
|
||||
{overrideCount > 0 ? (
|
||||
<EuiNotificationBadge color="subdued" style={{ marginLeft: 4 }}>
|
||||
{overrideCount}
|
||||
</EuiNotificationBadge>
|
||||
) : null}
|
||||
</EuiButtonEmpty>
|
||||
{isShown ? <Flyout solutions={['canvas']} onClose={() => setIsShown(false)} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -19,6 +19,7 @@ import { EditMenu } from './edit_menu';
|
|||
import { ElementMenu } from './element_menu';
|
||||
import { ShareMenu } from './share_menu';
|
||||
import { ViewMenu } from './view_menu';
|
||||
import { LabsControl } from './labs_control';
|
||||
import { CommitFn } from '../../../types';
|
||||
|
||||
const { WorkpadHeader: strings } = ComponentStrings;
|
||||
|
@ -111,6 +112,9 @@ export const WorkpadHeader: FunctionComponent<Props> = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<ShareMenu />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LabsControl />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -7,23 +7,23 @@
|
|||
|
||||
import {
|
||||
projectIDs,
|
||||
Project,
|
||||
ProjectID,
|
||||
PresentationLabsService,
|
||||
} from '../../../../../src/plugins/presentation_util/public';
|
||||
|
||||
import { CanvasServiceFactory } from '.';
|
||||
|
||||
export interface CanvasLabsService {
|
||||
getProject: (id: ProjectID) => Project;
|
||||
getProjects: () => Record<ProjectID, Project>;
|
||||
import { UI_SETTINGS } from '../../common';
|
||||
export interface CanvasLabsService extends PresentationLabsService {
|
||||
projectIDs: typeof projectIDs;
|
||||
isLabsEnabled: () => boolean;
|
||||
}
|
||||
|
||||
export const labsServiceFactory: CanvasServiceFactory<CanvasLabsService> = async (
|
||||
_coreSetup,
|
||||
_coreStart,
|
||||
coreStart,
|
||||
_setupPlugins,
|
||||
startPlugins
|
||||
) => ({
|
||||
projectIDs,
|
||||
isLabsEnabled: () => coreStart.uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI),
|
||||
...startPlugins.presentationUtil.labsService,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { projectIDs } from '../../../../../../src/plugins/presentation_util/public';
|
||||
import { CanvasLabsService } from '../labs';
|
||||
|
||||
const noop = (..._args: any[]): any => {};
|
||||
|
@ -12,4 +13,9 @@ const noop = (..._args: any[]): any => {};
|
|||
export const labsService: CanvasLabsService = {
|
||||
getProject: noop,
|
||||
getProjects: noop,
|
||||
getProjectIDs: () => projectIDs,
|
||||
isLabsEnabled: () => true,
|
||||
projectIDs,
|
||||
reset: noop,
|
||||
setProjectStatus: noop,
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ import { loadSampleData } from './sample_data';
|
|||
import { setupInterpreter } from './setup_interpreter';
|
||||
import { customElementType, workpadType, workpadTemplateType } from './saved_objects';
|
||||
import { initializeTemplates } from './templates';
|
||||
import { getUISettings } from './ui_settings';
|
||||
|
||||
interface PluginsSetup {
|
||||
expressions: ExpressionsServerSetup;
|
||||
|
@ -36,6 +37,7 @@ export class CanvasPlugin implements Plugin {
|
|||
}
|
||||
|
||||
public setup(coreSetup: CoreSetup, plugins: PluginsSetup) {
|
||||
coreSetup.uiSettings.register(getUISettings());
|
||||
coreSetup.savedObjects.registerType(customElementType);
|
||||
coreSetup.savedObjects.registerType(workpadType);
|
||||
coreSetup.savedObjects.registerType(workpadTemplateType);
|
||||
|
|
32
x-pack/plugins/canvas/server/ui_settings.ts
Normal file
32
x-pack/plugins/canvas/server/ui_settings.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { SETTING_CATEGORY } from '../../../../src/plugins/presentation_util/server';
|
||||
import { UiSettingsParams } from '../../../../src/core/types';
|
||||
import { UI_SETTINGS } from '../common';
|
||||
|
||||
/**
|
||||
* uiSettings definitions for Presentation Util.
|
||||
*/
|
||||
export const getUISettings = (): Record<string, UiSettingsParams<boolean>> => ({
|
||||
[UI_SETTINGS.ENABLE_LABS_UI]: {
|
||||
name: i18n.translate('xpack.canvas.labs.enableUI', {
|
||||
defaultMessage: 'Enable labs button in Canvas',
|
||||
}),
|
||||
description: i18n.translate('xpack.canvas.labs.enableUnifiedToolbarProjectDescription', {
|
||||
defaultMessage:
|
||||
'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Canvas.',
|
||||
}),
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
schema: schema.boolean(),
|
||||
category: [SETTING_CATEGORY],
|
||||
requiresPageReload: true,
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue