Skip to content

Commit

Permalink
Change "Share to space" flyout to support sharing to all spaces
Browse files Browse the repository at this point in the history
* Added new checkable card options
* Added privilege checks which conditionally disable options
* Added descriptive text when unknown spaces are selected
  • Loading branch information
jportner committed Sep 30, 2020
1 parent 5e00640 commit 8ebcb3f
Show file tree
Hide file tree
Showing 17 changed files with 632 additions and 131 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/spaces/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart
spacesManager: this.spacesManager,
notificationsSetup: core.notifications,
savedObjectsManagementSetup: plugins.savedObjectsManagement,
getStartServices: core.getStartServices as CoreSetup<PluginsStart>['getStartServices'],
});
const copySavedObjectsToSpaceService = new CopySavedObjectsToSpaceService();
copySavedObjectsToSpaceService.setup({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,40 @@
*/

import React from 'react';
import { EuiEmptyPrompt } from '@elastic/eui';
import { EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ApplicationStart } from 'src/core/public';

interface Props {
application: ApplicationStart;
}

export const NoSpacesAvailable = (props: Props) => {
const { capabilities, getUrlForApp } = props.application;
const canCreateNewSpaces = capabilities.spaces?.manage;
if (!canCreateNewSpaces) {
return null;
}

export const NoSpacesAvailable = () => {
return (
<EuiEmptyPrompt
body={
<p>
<FormattedMessage
id="xpack.spaces.management.shareToSpace.noSpacesBody"
defaultMessage="There are no eligible spaces to share into."
/>
</p>
}
title={
<h3>
<FormattedMessage
id="xpack.spaces.management.shareToSpace.noSpacesTitle"
defaultMessage="No spaces available"
/>
</h3>
}
/>
<EuiText size="s">
<FormattedMessage
id="xpack.spaces.management.shareToSpace.noAvailableSpaces.canCreateNewSpace.text"
defaultMessage="You can {createANewSpaceLink} for sharing your objects."
values={{
createANewSpaceLink: (
<EuiLink
data-test-subj="sts-new-space-link"
href={getUrlForApp('management', { path: 'kibana/spaces/create' })}
>
<FormattedMessage
id="xpack.spaces.management.shareToSpace.noAvailableSpaces.canCreateNewSpace.linkText"
defaultMessage="create a new space"
/>
</EuiLink>
),
}}
/>
</EuiText>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,162 @@
*/

import './selectable_spaces_control.scss';
import React, { Fragment } from 'react';
import { EuiBadge, EuiSelectable, EuiSelectableOption, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react';
import {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiLink,
EuiSelectable,
EuiSelectableOption,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { CoreStart } from 'src/core/public';
import { NoSpacesAvailable } from './no_spaces_available';
import { SpaceAvatar } from '../../space_avatar';
import { SpaceTarget } from '../types';

interface Props {
coreStart: CoreStart;
spaces: SpaceTarget[];
selectedSpaceIds: string[];
onChange: (selectedSpaceIds: string[]) => void;
disabled?: boolean;
}

type SpaceOption = EuiSelectableOption & { ['data-space-id']: string };

const ALL_SPACES_ID = '*';
const UNKNOWN_SPACE = '?';
const ROW_HEIGHT = 40;
const activeSpaceProps = {
append: <EuiBadge color="hollow">Current</EuiBadge>,
disabled: true,
checked: 'on' as 'on',
};

export const SelectableSpacesControl = (props: Props) => {
if (props.spaces.length === 0) {
return <EuiLoadingSpinner />;
}
const { coreStart, spaces, selectedSpaceIds, onChange } = props;
const { application, docLinks } = coreStart;

const options = props.spaces
const isGlobalControlChecked = selectedSpaceIds.includes(ALL_SPACES_ID);
const options = spaces
.sort((a, b) => (a.isActiveSpace ? -1 : b.isActiveSpace ? 1 : 0))
.map<SpaceOption>((space) => ({
label: space.name,
prepend: <SpaceAvatar space={space} size={'s'} />,
checked: props.selectedSpaceIds.includes(space.id) ? 'on' : undefined,
checked: selectedSpaceIds.includes(space.id) ? 'on' : undefined,
['data-space-id']: space.id,
['data-test-subj']: `sts-space-selector-row-${space.id}`,
...(space.isActiveSpace ? activeSpaceProps : {}),
...(isGlobalControlChecked && { disabled: true }),
}));

function updateSelectedSpaces(selectedOptions: SpaceOption[]) {
if (props.disabled) return;

const selectedSpaceIds = selectedOptions
.filter((opt) => opt.checked && !opt.disabled)
.map((opt) => opt['data-space-id']);
function updateSelectedSpaces(spaceOptions: SpaceOption[]) {
const selectedOptions = spaceOptions
.filter(({ checked, disabled }) => checked && !disabled)
.map((x) => x['data-space-id']);
const updatedSpaceIds = [
...selectedOptions,
...selectedSpaceIds.filter((x) => x === UNKNOWN_SPACE), // preserve any unknown spaces (to keep the "selected spaces" count accurate)
];

props.onChange(selectedSpaceIds);
onChange(updatedSpaceIds);
}

const getUnknownSpacesLabel = () => {
const count = selectedSpaceIds.filter((id) => id === UNKNOWN_SPACE).length;
if (!count) {
return null;
}

const kibanaPrivilegesUrl = `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-privileges.html`;
return (
<>
<EuiSpacer size="xs" />
<EuiText size="s" color="subdued">
<FormattedMessage
id="xpack.spaces.management.shareToSpace.unknownSpacesLabel.text"
defaultMessage="To view all spaces, you need {additionalPrivilegesLink}."
values={{
additionalPrivilegesLink: (
<EuiLink href={kibanaPrivilegesUrl}>
<FormattedMessage
id="xpack.spaces.management.shareToSpace.unknownSpacesLabel.additionalPrivilegesLink"
defaultMessage="additional privileges"
/>
</EuiLink>
),
}}
/>
</EuiText>
</>
);
};
const getNoSpacesAvailable = () => {
if (spaces.length < 2) {
return <NoSpacesAvailable application={application} />;
}
return null;
};

const selectedCount =
selectedSpaceIds.filter((id) => id !== ALL_SPACES_ID && id !== UNKNOWN_SPACE).length + 1;
const hiddenCount = selectedSpaceIds.filter((id) => id === UNKNOWN_SPACE).length;
const selectSpacesLabel = i18n.translate(
'xpack.spaces.management.shareToSpace.shareModeControl.selectSpacesLabel',
{ defaultMessage: 'Select spaces' }
);
const selectedSpacesLabel = i18n.translate(
'xpack.spaces.management.shareToSpace.shareModeControl.selectedCountLabel',
{ defaultMessage: '{selectedCount} selected', values: { selectedCount } }
);
const hiddenSpacesLabel = i18n.translate(
'xpack.spaces.management.shareToSpace.shareModeControl.hiddenCountLabel',
{ defaultMessage: '({hiddenCount} hidden)', values: { hiddenCount } }
);
const hiddenSpaces = hiddenCount ? <EuiText size="xs">{hiddenSpacesLabel}</EuiText> : null;
return (
<EuiSelectable
options={options}
onChange={(newOptions) => updateSelectedSpaces(newOptions as SpaceOption[])}
listProps={{
bordered: true,
rowHeight: 40,
className: 'spcShareToSpace__spacesList',
'data-test-subj': 'sts-form-space-selector',
}}
searchable
<EuiFormRow
label={selectSpacesLabel}
labelAppend={
<EuiFlexGroup direction="column" gutterSize="none" alignItems="flexEnd">
<EuiFlexItem grow={false}>
<EuiText size="xs">{selectedSpacesLabel}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>{hiddenSpaces}</EuiFlexItem>
</EuiFlexGroup>
}
fullWidth
>
{(list, search) => {
return (
<Fragment>
{search}
{list}
</Fragment>
);
}}
</EuiSelectable>
<>
<EuiSelectable
options={options}
onChange={(newOptions) => updateSelectedSpaces(newOptions as SpaceOption[])}
listProps={{
bordered: true,
rowHeight: ROW_HEIGHT,
className: 'spcShareToSpace__spacesList',
'data-test-subj': 'sts-form-space-selector',
}}
height={ROW_HEIGHT * 3.5}
searchable
>
{(list, search) => {
return (
<>
{search}
{list}
</>
);
}}
</EuiSelectable>
{getUnknownSpacesLabel()}
{getNoSpacesAvailable()}
</>
</EuiFormRow>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.euiCheckableCard__children {
width: 100%; // required to expand the contents of EuiCheckableCard to the full width
}
Loading

0 comments on commit 8ebcb3f

Please sign in to comment.