Skip to content

Commit

Permalink
Merge pull request elastic#13 from eokoneyo/feat/assigning-roles-to-s…
Browse files Browse the repository at this point in the history
…pace

Assign Roles to Space
  • Loading branch information
tsullivan authored Jul 9, 2024
2 parents 19dd7ff + e5f24ac commit 825be30
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
*/

import {
EuiBadge,
type EuiBasicTableColumn,
EuiButton,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiLink,
EuiLoadingSpinner,
Expand Down Expand Up @@ -278,12 +281,25 @@ export class SpacesGridPage extends Component<Props, State> {
}),
sortable: true,
render: (value: string, rowRecord) => (
<EuiLink
{...reactRouterNavigate(this.props.history, this.getViewSpacePath(rowRecord))}
data-test-subj={`${rowRecord.id}-hyperlink`}
>
{value}
</EuiLink>
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiLink
{...reactRouterNavigate(this.props.history, this.getViewSpacePath(rowRecord))}
data-test-subj={`${rowRecord.id}-hyperlink`}
>
{value}
</EuiLink>
</EuiFlexItem>
{this.state.activeSpace?.name === rowRecord.name && (
<EuiFlexItem grow={false}>
<EuiBadge color="primary">
{i18n.translate('xpack.spaces.management.spacesGridPage.currentSpaceMarkerText', {
defaultMessage: 'current',
})}
</EuiBadge>
</EuiFlexItem>
)}
</EuiFlexGroup>
),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ interface CreateParams {

export const spacesManagementApp = Object.freeze({
id: 'spaces',
create({ getStartServices, spacesManager, config, solutionNavExperiment }: CreateParams) {
create({
getStartServices,
spacesManager,
config,
solutionNavExperiment,
getRolesAPIClient,
}: CreateParams) {
const title = i18n.translate('xpack.spaces.displayName', {
defaultMessage: 'Spaces',
});
Expand Down Expand Up @@ -160,6 +166,7 @@ export const spacesManagementApp = Object.freeze({
onLoadSpace={onLoadSpace}
spaceId={spaceId}
selectedTabId={selectedTabId}
getRolesAPIClient={getRolesAPIClient}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,30 @@
import { useMemo } from 'react';

import type { KibanaFeature } from '@kbn/features-plugin/public';
import type { Role } from '@kbn/security-plugin-types-common';

import type { Space } from '../../../../common';
import type { ViewSpaceTab } from '../view_space_tabs';
import { getTabs } from '../view_space_tabs';
import { getTabs, type GetTabsProps, type ViewSpaceTab } from '../view_space_tabs';

export const useTabs = (
space: Space | null,
features: KibanaFeature[] | null,
roles: Role[],
currentSelectedTabId: string
): [ViewSpaceTab[], JSX.Element | undefined] => {
type UseTabsProps = Omit<GetTabsProps, 'space' | 'features'> & {
space: Space | null;
features: KibanaFeature[] | null;
currentSelectedTabId: string;
};

export const useTabs = ({
space,
features,
currentSelectedTabId,
...getTabsArgs
}: UseTabsProps): [ViewSpaceTab[], JSX.Element | undefined] => {
const [tabs, selectedTabContent] = useMemo(() => {
if (space == null || features == null) {
if (space === null || features === null) {
return [[]];
}
const _tabs = space != null ? getTabs(space, features, roles) : [];

const _tabs = space != null ? getTabs({ space, features, ...getTabsArgs }) : [];
return [_tabs, _tabs.find((obj) => obj.id === currentSelectedTabId)?.content];
}, [space, currentSelectedTabId, features, roles]);
}, [space, features, getTabsArgs, currentSelectedTabId]);

return [tabs, selectedTabContent];
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import type { FC, PropsWithChildren } from 'react';
import React, { createContext, useContext } from 'react';

import type { ApplicationStart } from '@kbn/core-application-browser';
import type { RolesAPIClient } from '@kbn/security-plugin-types-public';

import type { SpacesManager } from '../../../spaces_manager';

interface ViewSpaceServices {
export interface ViewSpaceServices {
serverBasePath: string;
getUrlForApp: ApplicationStart['getUrlForApp'];
navigateToUrl: ApplicationStart['navigateToUrl'];
spacesManager: SpacesManager;
getRolesAPIClient: () => Promise<RolesAPIClient>;
}

const ViewSpaceContext = createContext<ViewSpaceServices | null>(null);
Expand Down
91 changes: 45 additions & 46 deletions x-pack/plugins/spaces/public/management/view_space/view_space.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,46 @@ import {
import React, { lazy, Suspense, useEffect, useState } from 'react';
import type { FC } from 'react';

import type { ApplicationStart, Capabilities, ScopedHistory } from '@kbn/core/public';
import type { Capabilities, ScopedHistory } from '@kbn/core/public';
import type { FeaturesPluginStart, KibanaFeature } from '@kbn/features-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public';
import type { Role } from '@kbn/security-plugin-types-common';

import { TAB_ID_CONTENT, TAB_ID_FEATURES, TAB_ID_ROLES } from './constants';
import { useTabs } from './hooks/use_tabs';
import { ViewSpaceContextProvider } from './hooks/view_space_context_provider';
import {
ViewSpaceContextProvider,
type ViewSpaceServices,
} from './hooks/view_space_context_provider';
import { addSpaceIdToPath, ENTER_SPACE_PATH, type Space } from '../../../common';
import { getSpaceAvatarComponent } from '../../space_avatar';
import { SpaceSolutionBadge } from '../../space_solution_badge';
import type { SpacesManager } from '../../spaces_manager';

// No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana.
const LazySpaceAvatar = lazy(() =>
getSpaceAvatarComponent().then((component) => ({ default: component }))
);

const getSelectedTabId = (selectedTabId?: string) => {
const getSelectedTabId = (canUserViewRoles: boolean, selectedTabId?: string) => {
// Validation of the selectedTabId routing parameter, default to the Content tab
return selectedTabId && [TAB_ID_FEATURES, TAB_ID_ROLES].includes(selectedTabId)
return selectedTabId &&
[TAB_ID_FEATURES, canUserViewRoles ? TAB_ID_ROLES : null]
.filter(Boolean)
.includes(selectedTabId)
? selectedTabId
: TAB_ID_CONTENT;
};

interface PageProps {
interface PageProps extends ViewSpaceServices {
spaceId?: string;
history: ScopedHistory;
selectedTabId?: string;
capabilities: Capabilities;
allowFeatureVisibility: boolean; // FIXME: handle this
solutionNavExperiment?: Promise<boolean>;
getFeatures: FeaturesPluginStart['getFeatures'];
getUrlForApp: ApplicationStart['getUrlForApp'];
navigateToUrl: ApplicationStart['navigateToUrl'];
serverBasePath: string;
spacesManager: SpacesManager;
history: ScopedHistory;
onLoadSpace: (space: Space) => void;
spaceId?: string;
selectedTabId?: string;
}

const handleApiError = (error: Error) => {
Expand All @@ -80,18 +81,25 @@ export const ViewSpacePage: FC<PageProps> = (props) => {
capabilities,
getUrlForApp,
navigateToUrl,
getRolesAPIClient,
} = props;

const selectedTabId = getSelectedTabId(_selectedTabId);
const [space, setSpace] = useState<Space | null>(null);
const [userActiveSpace, setUserActiveSpace] = useState<Space | null>(null);
const [features, setFeatures] = useState<KibanaFeature[] | null>(null);
const [roles, setRoles] = useState<Role[]>([]);
const [isLoadingSpace, setIsLoadingSpace] = useState(true);
const [isLoadingFeatures, setIsLoadingFeatures] = useState(true);
const [isLoadingRoles, setIsLoadingRoles] = useState(true);
const [tabs, selectedTabContent] = useTabs(space, features, roles, selectedTabId);
const [isSolutionNavEnabled, setIsSolutionNavEnabled] = useState(false);
const selectedTabId = getSelectedTabId(Boolean(capabilities?.roles?.view), _selectedTabId);
const [tabs, selectedTabContent] = useTabs({
space,
features,
roles,
capabilities,
currentSelectedTabId: selectedTabId,
});

useEffect(() => {
if (!spaceId) {
Expand Down Expand Up @@ -123,6 +131,7 @@ export const ViewSpacePage: FC<PageProps> = (props) => {
setIsLoadingRoles(false);
};

// maybe we do not make this call if user can't view roles? 🤔
getRoles().catch(handleApiError);
}, [spaceId, spacesManager]);

Expand Down Expand Up @@ -192,41 +201,16 @@ export const ViewSpacePage: FC<PageProps> = (props) => {
) : null;
};

const SwitchButton = () => {
if (userActiveSpace?.id === space.id) {
return null;
}

const { serverBasePath } = props;

// use href to force full page reload (needed in order to change spaces)
return (
<EuiButton
iconType="merge"
href={addSpaceIdToPath(
serverBasePath,
space.id,
`${ENTER_SPACE_PATH}?next=/app/management/kibana/spaces/view/${space.id}`
)}
data-test-subj="spaceSwitcherButton"
>
<FormattedMessage
id="xpack.spaces.management.spaceDetails.space.switchToSpaceButton.label"
defaultMessage="Switch to this space"
/>
</EuiButton>
);
};

return (
<ViewSpaceContextProvider
getRolesAPIClient={getRolesAPIClient}
spacesManager={spacesManager}
serverBasePath={props.serverBasePath}
navigateToUrl={navigateToUrl}
getUrlForApp={getUrlForApp}
>
<EuiText>
<EuiFlexGroup data-test-subj="spaceDetailsHeader">
<EuiFlexGroup data-test-subj="spaceDetailsHeader" alignItems="flexStart">
<EuiFlexItem grow={false}>
<HeaderAvatar />
</EuiFlexItem>
Expand Down Expand Up @@ -270,13 +254,28 @@ export const ViewSpacePage: FC<PageProps> = (props) => {
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center">
<EuiFlexGroup justifyContent="flexEnd" responsive={false}>
<EuiFlexItem>
<SettingsButton />
</EuiFlexItem>
<EuiFlexItem>
<SwitchButton />
</EuiFlexItem>
{userActiveSpace?.id !== space.id ? (
<EuiFlexItem>
<EuiButton
iconType="merge"
href={addSpaceIdToPath(
props.serverBasePath,
space.id,
`${ENTER_SPACE_PATH}?next=/app/management/kibana/spaces/view/${space.id}`
)}
data-test-subj="spaceSwitcherButton"
>
<FormattedMessage
id="xpack.spaces.management.spaceDetails.space.switchToSpaceButton.label"
defaultMessage="Switch to this space"
/>
</EuiButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Loading

0 comments on commit 825be30

Please sign in to comment.