Skip to content

Commit

Permalink
[Enterprise Search] fix: show/hide ent-search cards based on access (#…
Browse files Browse the repository at this point in the history
…168890)

## Summary

When refactoring the Enterprise Search overview page we inadvertently
removed the access and admin checks for app search and workplace search
product cards. This fix re-adds those checks.
  • Loading branch information
TattdCodeMonkey authored Oct 16, 2023
1 parent d0a3aa7 commit f6a1858
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { setMockValues } from '../../../__mocks__/kea_logic';

import React from 'react';

import { mount } from 'enzyme';

import { AppSearchProductCard } from './app_search_product_card';
import { EnterpriseSearchProductCard } from './enterprise_search_product_card';
import { WorkplaceSearchProductCard } from './workplace_search_product_card';

describe('EnterpriseSearchProductCard', () => {
beforeEach(() => {
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
});

it('renders both services with access', () => {
const wrapper = mount(
<EnterpriseSearchProductCard
hasAppSearchAccess
hasWorkplaceSearchAccess
isWorkplaceSearchAdmin
/>
);

expect(wrapper.find(AppSearchProductCard)).toHaveLength(1);
expect(wrapper.find(WorkplaceSearchProductCard)).toHaveLength(1);
});
it('can render just app search', () => {
const wrapper = mount(
<EnterpriseSearchProductCard
hasAppSearchAccess
hasWorkplaceSearchAccess={false}
isWorkplaceSearchAdmin={false}
/>
);

expect(wrapper.find(AppSearchProductCard)).toHaveLength(1);
expect(wrapper.find(WorkplaceSearchProductCard)).toHaveLength(0);
});
it('can render just workplace search', () => {
const wrapper = mount(
<EnterpriseSearchProductCard
hasAppSearchAccess={false}
hasWorkplaceSearchAccess
isWorkplaceSearchAdmin
/>
);

expect(wrapper.find(AppSearchProductCard)).toHaveLength(0);
expect(wrapper.find(WorkplaceSearchProductCard)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,45 @@ import { ProductCard } from '../product_card';
import { AppSearchProductCard } from './app_search_product_card';
import { WorkplaceSearchProductCard } from './workplace_search_product_card';

export const EnterpriseSearchProductCard = () => (
<ProductCard
description={i18n.translate('xpack.enterpriseSearch.entSearch.productCardDescription', {
defaultMessage:
'Standalone applications tailored to simpler, user-friendly and business-focused search experiences.',
})}
emptyCta
cta={i18n.translate('xpack.enterpriseSearch.enterpriseSearchCard.cta', {
defaultMessage: 'Learn more',
})}
url={docLinks.start}
icon="logoEnterpriseSearch"
name={ENTERPRISE_SEARCH_PRODUCT_NAME}
productId={ENTERPRISE_SEARCH_CONTENT_PLUGIN.ID}
rightPanelItems={[
<AppSearchProductCard hasBorder={false} hasShadow={false} />,
<WorkplaceSearchProductCard hasBorder={false} hasShadow={false} />,
]}
/>
);
export interface EnterpriseSearchProductCardProps {
hasAppSearchAccess: boolean;
hasWorkplaceSearchAccess: boolean;
isWorkplaceSearchAdmin: boolean;
}

export const EnterpriseSearchProductCard = ({
hasAppSearchAccess,
hasWorkplaceSearchAccess,
isWorkplaceSearchAdmin,
}: EnterpriseSearchProductCardProps) => {
const rightPanelItems: React.ReactNode[] = [];
if (hasAppSearchAccess) {
rightPanelItems.push(<AppSearchProductCard hasBorder={false} hasShadow={false} />);
}
if (hasWorkplaceSearchAccess) {
rightPanelItems.push(
<WorkplaceSearchProductCard
isWorkplaceSearchAdmin={isWorkplaceSearchAdmin}
hasBorder={false}
hasShadow={false}
/>
);
}
return (
<ProductCard
description={i18n.translate('xpack.enterpriseSearch.entSearch.productCardDescription', {
defaultMessage:
'Standalone applications tailored to simpler, user-friendly and business-focused search experiences.',
})}
emptyCta
cta={i18n.translate('xpack.enterpriseSearch.enterpriseSearchCard.cta', {
defaultMessage: 'Learn more',
})}
url={docLinks.start}
icon="logoEnterpriseSearch"
name={ENTERPRISE_SEARCH_PRODUCT_NAME}
productId={ENTERPRISE_SEARCH_CONTENT_PLUGIN.ID}
rightPanelItems={rightPanelItems}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ import { EnterpriseSearchProductCard } from './enterprise_search_product_card';

import { ProductSelector } from '.';

const props = {
access: { hasAppSearchAccess: true, hasWorkplaceSearchAccess: true },
isWorkplaceSearchAdmin: true,
};

describe('ProductSelector', () => {
it('renders the overview page, product cards, & setup guide CTAs with no host set', () => {
setMockValues({ config: { canDeployEntSearch: true, host: '' } });
const wrapper = shallow(<ProductSelector />);
const wrapper = shallow(<ProductSelector {...props} />);

expect(wrapper.find(ElasticsearchProductCard)).toHaveLength(1);
expect(wrapper.find(EnterpriseSearchProductCard)).toHaveLength(1);
Expand All @@ -33,14 +38,14 @@ describe('ProductSelector', () => {

it('renders the trial callout', () => {
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
const wrapper = shallow(<ProductSelector />);
const wrapper = shallow(<ProductSelector {...props} />);

expect(wrapper.find(TrialCallout)).toHaveLength(1);
});

it('does not render connection error callout without an error', () => {
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
const wrapper = shallow(<ProductSelector />);
const wrapper = shallow(<ProductSelector {...props} />);

expect(wrapper.find(ErrorStateCallout)).toHaveLength(0);
});
Expand All @@ -50,7 +55,7 @@ describe('ProductSelector', () => {
config: { canDeployEntSearch: true, host: 'localhost' },
errorConnectingMessage: '502 Bad Gateway',
});
const wrapper = shallow(<ProductSelector />);
const wrapper = shallow(<ProductSelector {...props} />);

expect(wrapper.find(ErrorStateCallout)).toHaveLength(1);
});
Expand All @@ -61,11 +66,37 @@ describe('ProductSelector', () => {
});

it('does not render the Setup CTA when there is a host', () => {
const wrapper = shallow(<ProductSelector />);
const wrapper = shallow(<ProductSelector {...props} />);

expect(wrapper.find(ElasticsearchProductCard)).toHaveLength(1);
expect(wrapper.find(EnterpriseSearchProductCard)).toHaveLength(1);
expect(wrapper.find(SetupGuideCta)).toHaveLength(0);
});

it('does not render EnterpriseSearch card without access', () => {
const wrapper = shallow(<ProductSelector access={{}} isWorkplaceSearchAdmin={false} />);

expect(wrapper.find(ElasticsearchProductCard)).toHaveLength(1);
expect(wrapper.find(EnterpriseSearchProductCard)).toHaveLength(0);
expect(wrapper.find(SetupGuideCta)).toHaveLength(0);
});

it('does render EnterpriseSearch card with access to either service', () => {
const appSearchWrapper = shallow(
<ProductSelector
access={{ hasAppSearchAccess: true, hasWorkplaceSearchAccess: false }}
isWorkplaceSearchAdmin={false}
/>
);
const workplaceSearchWrapper = shallow(
<ProductSelector
access={{ hasAppSearchAccess: false, hasWorkplaceSearchAccess: true }}
isWorkplaceSearchAdmin={false}
/>
);

expect(appSearchWrapper.find(EnterpriseSearchProductCard)).toHaveLength(1);
expect(workplaceSearchWrapper.find(EnterpriseSearchProductCard)).toHaveLength(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,19 @@ import { IngestionSelector } from './ingestion_selector';

import './product_selector.scss';

export const ProductSelector: React.FC = () => {
interface ProductSelectorProps {
access: {
hasAppSearchAccess?: boolean;
hasWorkplaceSearchAccess?: boolean;
};
isWorkplaceSearchAdmin: boolean;
}

export const ProductSelector: React.FC<ProductSelectorProps> = ({
access,
isWorkplaceSearchAdmin,
}) => {
const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access;
const { config } = useValues(KibanaLogic);
const { errorConnectingMessage } = useValues(HttpLogic);
const { security } = useValues(KibanaLogic);
Expand Down Expand Up @@ -138,9 +150,15 @@ export const ProductSelector: React.FC = () => {
<EuiFlexItem>
<ElasticsearchProductCard />
</EuiFlexItem>
<EuiFlexItem>
<EnterpriseSearchProductCard />
</EuiFlexItem>
{(hasAppSearchAccess || hasWorkplaceSearchAccess) && (
<EuiFlexItem>
<EnterpriseSearchProductCard
hasAppSearchAccess={hasAppSearchAccess ?? false}
hasWorkplaceSearchAccess={hasWorkplaceSearchAccess ?? false}
isWorkplaceSearchAdmin={isWorkplaceSearchAdmin}
/>
</EuiFlexItem>
)}
{!config.host && config.canDeployEntSearch && (
<EuiFlexItem>
<SetupGuideCta />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 from 'react';

import { shallow } from 'enzyme';

import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import { ProductCard } from '../product_card';

import { WorkplaceSearchProductCard } from './workplace_search_product_card';

describe('WorkplaceSearchProductCard', () => {
it('renders with url when admin', () => {
const wrapper = shallow(
<WorkplaceSearchProductCard hasBorder hasShadow isWorkplaceSearchAdmin />
);

expect(wrapper.find(ProductCard)).toHaveLength(1);
expect(wrapper.find(ProductCard).prop('url')).toEqual(WORKPLACE_SEARCH_PLUGIN.URL);
});
it('renders with non-admin url when not admin', () => {
const wrapper = shallow(
<WorkplaceSearchProductCard hasBorder hasShadow isWorkplaceSearchAdmin={false} />
);

expect(wrapper.find(ProductCard)).toHaveLength(1);
expect(wrapper.find(ProductCard).prop('url')).toEqual(WORKPLACE_SEARCH_PLUGIN.NON_ADMIN_URL);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import { ProductCard } from '../product_card';
export interface WorkplaceSearchProductCardProps {
hasBorder: boolean;
hasShadow: boolean;
isWorkplaceSearchAdmin: boolean;
}

export const WorkplaceSearchProductCard: React.FC<WorkplaceSearchProductCardProps> = ({
hasBorder = true,
hasShadow = true,
isWorkplaceSearchAdmin,
}) => (
<ProductCard
hasBorder={hasBorder}
Expand All @@ -35,6 +37,8 @@ export const WorkplaceSearchProductCard: React.FC<WorkplaceSearchProductCardProp
icon="logoWorkplaceSearch"
name={WORKPLACE_SEARCH_PLUGIN.NAME}
productId={WORKPLACE_SEARCH_PLUGIN.ID}
url={WORKPLACE_SEARCH_PLUGIN.URL}
url={
isWorkplaceSearchAdmin ? WORKPLACE_SEARCH_PLUGIN.URL : WORKPLACE_SEARCH_PLUGIN.NON_ADMIN_URL
}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { SetupGuide } from './components/setup_guide';
import { ROOT_PATH, SETUP_GUIDE_PATH } from './routes';

export const EnterpriseSearchOverview: React.FC<InitialAppData> = ({
access = {},
workplaceSearch,
enterpriseSearchVersion,
kibanaVersion,
}) => {
Expand All @@ -29,6 +31,8 @@ export const EnterpriseSearchOverview: React.FC<InitialAppData> = ({
const incompatibleVersions = !!(
config.host && isVersionMismatch(enterpriseSearchVersion, kibanaVersion)
);
const isWorkplaceSearchAdmin = !!workplaceSearch?.account?.isAdmin;

const showView = () => {
if (incompatibleVersions) {
return (
Expand All @@ -39,7 +43,7 @@ export const EnterpriseSearchOverview: React.FC<InitialAppData> = ({
);
}

return <ProductSelector />;
return <ProductSelector isWorkplaceSearchAdmin={isWorkplaceSearchAdmin} access={access} />;
};

return (
Expand Down

0 comments on commit f6a1858

Please sign in to comment.