Improve home screen for limited-access users (#77665)
This commit is contained in:
parent
5a31dce92d
commit
613509d81a
|
@ -164,6 +164,7 @@ exports[`home directories should not render directory entry when showOnHomePage
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
className="homHeader__actionItem"
|
||||
data-test-subj="homManagementActionItem"
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
iconType="gear"
|
||||
|
@ -906,6 +907,7 @@ exports[`home header should show "Manage" link if stack management is available
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
className="homHeader__actionItem"
|
||||
data-test-subj="homManagementActionItem"
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
iconType="gear"
|
||||
|
|
|
@ -163,7 +163,10 @@ export class Home extends Component {
|
|||
</EuiFlexItem>
|
||||
|
||||
{stackManagement ? (
|
||||
<EuiFlexItem className="homHeader__actionItem">
|
||||
<EuiFlexItem
|
||||
className="homHeader__actionItem"
|
||||
data-test-subj="homManagementActionItem"
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
onClick={createAppNavigationHandler(stackManagement.path)}
|
||||
iconType="gear"
|
||||
|
|
|
@ -9,6 +9,7 @@ exports[`ManageData render 1`] = `
|
|||
<section
|
||||
aria-labelledby="homDataManage__title"
|
||||
className="homDataManage"
|
||||
data-test-subj="homDataManage"
|
||||
>
|
||||
<EuiTitle
|
||||
size="s"
|
||||
|
@ -89,3 +90,5 @@ exports[`ManageData render 1`] = `
|
|||
</section>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`ManageData render empty without any features 1`] = `<Fragment />`;
|
||||
|
|
|
@ -88,4 +88,9 @@ describe('ManageData', () => {
|
|||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render empty without any features', () => {
|
||||
const component = shallowWithIntl(<ManageData addBasePath={addBasePathMock} features={[]} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -36,31 +36,37 @@ export const ManageData: FC<Props> = ({ addBasePath, features }) => (
|
|||
<>
|
||||
{features.length > 1 && <EuiHorizontalRule margin="xl" aria-hidden="true" />}
|
||||
|
||||
<section className="homDataManage" aria-labelledby="homDataManage__title">
|
||||
<EuiTitle size="s">
|
||||
<h2 id="homDataManage__title">
|
||||
<FormattedMessage id="home.manageData.sectionTitle" defaultMessage="Manage your data" />
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
{features.length > 0 && (
|
||||
<section
|
||||
className="homDataManage"
|
||||
aria-labelledby="homDataManage__title"
|
||||
data-test-subj="homDataManage"
|
||||
>
|
||||
<EuiTitle size="s">
|
||||
<h2 id="homDataManage__title">
|
||||
<FormattedMessage id="home.manageData.sectionTitle" defaultMessage="Manage your data" />
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup className="homDataManage__content">
|
||||
{features.map((feature) => (
|
||||
<EuiFlexItem key={feature.id}>
|
||||
<Synopsis
|
||||
id={feature.id}
|
||||
onClick={createAppNavigationHandler(feature.path)}
|
||||
description={feature.description}
|
||||
iconType={feature.icon}
|
||||
title={feature.title}
|
||||
url={addBasePath(feature.path)}
|
||||
wrapInPanel
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</section>
|
||||
<EuiFlexGroup className="homDataManage__content">
|
||||
{features.map((feature) => (
|
||||
<EuiFlexItem key={feature.id}>
|
||||
<Synopsis
|
||||
id={feature.id}
|
||||
onClick={createAppNavigationHandler(feature.path)}
|
||||
description={feature.description}
|
||||
iconType={feature.icon}
|
||||
title={feature.title}
|
||||
url={addBasePath(feature.path)}
|
||||
wrapInPanel
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
exports[`SolutionPanel renders the solution panel for the given solution 1`] = `
|
||||
<EuiFlexItem
|
||||
className="homSolutions__group homSolutions__group--single homSolutions__item"
|
||||
data-test-subj="homSolutionPanel homSolutionPanel_kibana"
|
||||
grow={1}
|
||||
key="kibana"
|
||||
>
|
||||
|
|
|
@ -53,6 +53,7 @@ interface Props {
|
|||
export const SolutionPanel: FC<Props> = ({ addBasePath, solution }) => (
|
||||
<EuiFlexItem
|
||||
key={solution.id}
|
||||
data-test-subj={`homSolutionPanel homSolutionPanel_${solution.id}`}
|
||||
className={`${
|
||||
solution.id === 'kibana' ? 'homSolutions__group homSolutions__group--single' : ''
|
||||
} homSolutions__item`}
|
||||
|
|
|
@ -88,6 +88,40 @@ describe('FeatureCatalogueRegistry', () => {
|
|||
expect(service.get()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visibility filtering', () => {
|
||||
test('retains items with no "visible" callback', () => {
|
||||
const service = new FeatureCatalogueRegistry();
|
||||
service.setup().register(DASHBOARD_FEATURE);
|
||||
const capabilities = { catalogue: {} } as any;
|
||||
service.start({ capabilities });
|
||||
expect(service.get()).toEqual([DASHBOARD_FEATURE]);
|
||||
});
|
||||
|
||||
test('retains items with a "visible" callback which returns "true"', () => {
|
||||
const service = new FeatureCatalogueRegistry();
|
||||
const feature = {
|
||||
...DASHBOARD_FEATURE,
|
||||
visible: () => true,
|
||||
};
|
||||
service.setup().register(feature);
|
||||
const capabilities = { catalogue: {} } as any;
|
||||
service.start({ capabilities });
|
||||
expect(service.get()).toEqual([feature]);
|
||||
});
|
||||
|
||||
test('removes items with a "visible" callback which returns "false"', () => {
|
||||
const service = new FeatureCatalogueRegistry();
|
||||
const feature = {
|
||||
...DASHBOARD_FEATURE,
|
||||
visible: () => false,
|
||||
};
|
||||
service.setup().register(feature);
|
||||
const capabilities = { catalogue: {} } as any;
|
||||
service.start({ capabilities });
|
||||
expect(service.get()).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('title sorting', () => {
|
||||
|
|
|
@ -45,6 +45,8 @@ export interface FeatureCatalogueEntry {
|
|||
readonly showOnHomePage: boolean;
|
||||
/** An ordinal used to sort features relative to one another for display on the home page */
|
||||
readonly order?: number;
|
||||
/** Optional function to control visibility of this feature. */
|
||||
readonly visible?: () => boolean;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -103,7 +105,10 @@ export class FeatureCatalogueRegistry {
|
|||
}
|
||||
const capabilities = this.capabilities;
|
||||
return [...this.features.values()]
|
||||
.filter((entry) => capabilities.catalogue[entry.id] !== false)
|
||||
.filter(
|
||||
(entry) =>
|
||||
capabilities.catalogue[entry.id] !== false && (entry.visible ? entry.visible() : true)
|
||||
)
|
||||
.sort(compareByKey('title'));
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart
|
|||
|
||||
private readonly appUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
|
||||
|
||||
private hasAnyEnabledApps = true;
|
||||
|
||||
constructor(private initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup, { home }: ManagementSetupDependencies) {
|
||||
|
@ -65,6 +67,7 @@ export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart
|
|||
path: '/app/management',
|
||||
showOnHomePage: false,
|
||||
category: FeatureCatalogueCategory.ADMIN,
|
||||
visible: () => this.hasAnyEnabledApps,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -96,11 +99,11 @@ export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart
|
|||
|
||||
public start(core: CoreStart) {
|
||||
this.managementSections.start({ capabilities: core.application.capabilities });
|
||||
const hasAnyEnabledApps = getSectionsServiceStartPrivate()
|
||||
this.hasAnyEnabledApps = getSectionsServiceStartPrivate()
|
||||
.getSectionsEnabled()
|
||||
.some((section) => section.getAppsEnabled().length > 0);
|
||||
|
||||
if (!hasAnyEnabledApps) {
|
||||
if (!this.hasAnyEnabledApps) {
|
||||
this.appUpdater.next(() => {
|
||||
return {
|
||||
status: AppStatus.inaccessible,
|
||||
|
|
|
@ -43,6 +43,14 @@ export function HomePageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
return !(await testSubjects.exists(`addSampleDataSet${id}`));
|
||||
}
|
||||
|
||||
async getVisibileSolutions() {
|
||||
const solutionPanels = await testSubjects.findAll('~homSolutionPanel', 2000);
|
||||
const panelAttributes = await Promise.all(
|
||||
solutionPanels.map((panel) => panel.getAttribute('data-test-subj'))
|
||||
);
|
||||
return panelAttributes.map((attributeValue) => attributeValue.split('homSolutionPanel_')[1]);
|
||||
}
|
||||
|
||||
async addSampleDataSet(id: string) {
|
||||
const isInstalled = await this.isSampleDataSetInstalled(id);
|
||||
if (!isInstalled) {
|
||||
|
|
|
@ -82,10 +82,11 @@ export class IndexLifecycleManagementServerPlugin implements Plugin<void, void,
|
|||
);
|
||||
|
||||
features.registerElasticsearchFeature({
|
||||
id: 'index_lifecycle_management',
|
||||
id: PLUGIN.ID,
|
||||
management: {
|
||||
data: ['index_lifecycle_management'],
|
||||
data: [PLUGIN.ID],
|
||||
},
|
||||
catalogue: [PLUGIN.ID],
|
||||
privileges: [
|
||||
{
|
||||
requiredClusterPrivileges: ['manage_ilm'],
|
||||
|
|
|
@ -86,6 +86,7 @@ export class SnapshotRestoreServerPlugin implements Plugin<void, void, any, any>
|
|||
management: {
|
||||
data: [PLUGIN.id],
|
||||
},
|
||||
catalogue: [PLUGIN.id],
|
||||
privileges: [
|
||||
{
|
||||
requiredClusterPrivileges: [...APP_REQUIRED_CLUSTER_PRIVILEGES],
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const security = getService('security');
|
||||
const PageObjects = getPageObjects(['security', 'home']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('security', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('dashboard/feature_controls/security');
|
||||
await esArchiver.loadIfNeeded('logstash_functional');
|
||||
|
||||
// ensure we're logged out so we can login as the appropriate users
|
||||
await PageObjects.security.forceLogout();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('dashboard/feature_controls/security');
|
||||
|
||||
// logout, so the other tests don't accidentally run as the custom users we're testing below
|
||||
await PageObjects.security.forceLogout();
|
||||
});
|
||||
|
||||
describe('global all privileges', () => {
|
||||
before(async () => {
|
||||
await security.role.create('global_all_role', {
|
||||
elasticsearch: {},
|
||||
kibana: [
|
||||
{
|
||||
base: ['all'],
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await security.user.create('global_all_user', {
|
||||
password: 'global_all_user-password',
|
||||
roles: ['global_all_role'],
|
||||
full_name: 'test user',
|
||||
});
|
||||
|
||||
await PageObjects.security.login('global_all_user', 'global_all_user-password', {
|
||||
expectSpaceSelector: false,
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await security.role.delete('global_all_role');
|
||||
await security.user.delete('global_all_user');
|
||||
});
|
||||
|
||||
it('shows all available solutions', async () => {
|
||||
const solutions = await PageObjects.home.getVisibileSolutions();
|
||||
expect(solutions).to.eql([
|
||||
'enterpriseSearch',
|
||||
'observability',
|
||||
'securitySolution',
|
||||
'kibana',
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows the management section', async () => {
|
||||
await testSubjects.existOrFail('homDataManage', { timeout: 2000 });
|
||||
});
|
||||
|
||||
it('shows the "Manage" action item', async () => {
|
||||
await testSubjects.existOrFail('homManagementActionItem', {
|
||||
timeout: 2000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('global dashboard all privileges', () => {
|
||||
before(async () => {
|
||||
await security.role.create('global_dashboard_all_role', {
|
||||
elasticsearch: {},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await security.user.create('global_dashboard_all_user', {
|
||||
password: 'global_dashboard_all_user-password',
|
||||
roles: ['global_dashboard_all_role'],
|
||||
full_name: 'test user',
|
||||
});
|
||||
|
||||
await PageObjects.security.login(
|
||||
'global_dashboard_all_user',
|
||||
'global_dashboard_all_user-password',
|
||||
{
|
||||
expectSpaceSelector: false,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await security.role.delete('global_dashboard_all_role');
|
||||
await security.user.delete('global_dashboard_all_user');
|
||||
});
|
||||
|
||||
it('shows only the kibana solution', async () => {
|
||||
const solutions = await PageObjects.home.getVisibileSolutions();
|
||||
expect(solutions).to.eql(['kibana']);
|
||||
});
|
||||
|
||||
it('does not show the management section', async () => {
|
||||
await testSubjects.missingOrFail('homDataManage', { timeout: 2000 });
|
||||
});
|
||||
|
||||
it('does not show the "Manage" action item', async () => {
|
||||
await testSubjects.missingOrFail('homManagementActionItem', {
|
||||
timeout: 2000,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
14
x-pack/test/functional/apps/home/feature_controls/index.ts
Normal file
14
x-pack/test/functional/apps/home/feature_controls/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ loadTestFile }: FtrProviderContext) => {
|
||||
describe('feature controls', function () {
|
||||
this.tags('skipFirefox');
|
||||
loadTestFile(require.resolve('./home_security'));
|
||||
});
|
||||
};
|
14
x-pack/test/functional/apps/home/index.ts
Normal file
14
x-pack/test/functional/apps/home/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default ({ loadTestFile }: FtrProviderContext) => {
|
||||
describe('Home page', function () {
|
||||
this.tags('ciGroup7');
|
||||
loadTestFile(require.resolve('./feature_controls'));
|
||||
});
|
||||
};
|
|
@ -13,7 +13,15 @@ import { UserAtSpaceScenarios } from '../scenarios';
|
|||
export default function catalogueTests({ getService }: FtrProviderContext) {
|
||||
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
|
||||
|
||||
const esFeatureExceptions = ['security', 'rollup_jobs', 'reporting', 'transform', 'watcher'];
|
||||
const esFeatureExceptions = [
|
||||
'security',
|
||||
'index_lifecycle_management',
|
||||
'snapshot_restore',
|
||||
'rollup_jobs',
|
||||
'reporting',
|
||||
'transform',
|
||||
'watcher',
|
||||
];
|
||||
|
||||
describe('catalogue', () => {
|
||||
UserAtSpaceScenarios.forEach((scenario) => {
|
||||
|
|
|
@ -13,7 +13,15 @@ import { UserScenarios } from '../scenarios';
|
|||
export default function catalogueTests({ getService }: FtrProviderContext) {
|
||||
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
|
||||
|
||||
const esFeatureExceptions = ['security', 'rollup_jobs', 'reporting', 'transform', 'watcher'];
|
||||
const esFeatureExceptions = [
|
||||
'security',
|
||||
'index_lifecycle_management',
|
||||
'snapshot_restore',
|
||||
'rollup_jobs',
|
||||
'reporting',
|
||||
'transform',
|
||||
'watcher',
|
||||
];
|
||||
|
||||
describe('catalogue', () => {
|
||||
UserScenarios.forEach((scenario) => {
|
||||
|
|
|
@ -13,7 +13,15 @@ import { SpaceScenarios } from '../scenarios';
|
|||
export default function catalogueTests({ getService }: FtrProviderContext) {
|
||||
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
|
||||
|
||||
const esFeatureExceptions = ['security', 'rollup_jobs', 'reporting', 'transform', 'watcher'];
|
||||
const esFeatureExceptions = [
|
||||
'security',
|
||||
'index_lifecycle_management',
|
||||
'snapshot_restore',
|
||||
'rollup_jobs',
|
||||
'reporting',
|
||||
'transform',
|
||||
'watcher',
|
||||
];
|
||||
|
||||
describe('catalogue', () => {
|
||||
SpaceScenarios.forEach((scenario) => {
|
||||
|
|
Loading…
Reference in a new issue