From a12bb044382ada8f557590851943a1568f24c58f Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 24 Nov 2020 10:16:33 -0600 Subject: [PATCH] [Workplace Search] Initial rendering of Org Sources (#84164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix broken routes Didn’t have a way to test these when created * Get context from global state No need to do this in 2 places now. There was a race condition where the default logic value for `isOrganization` was set to `false` We don’t need a useEffect call here because the value is synchronous and has no side effects. Calling the method directly fixes the race condition. * Add the ‘path’ to the logic files for easier debugging * Add SourceSubNav component * Flip routes to match new convention It was decided by Product that instead of keying off of `/org` to determine context, that we would now flip it where we key of provate with `/p`. This means that /sources is now organization where before it was personal * Convert routers to use children instead of props This aligns with App Search and allows for easier telemtry and breadcrumbs * Add breadcrumbs and basic telemetry * Add in and refactor subnavigation As a part of this commit, the approach for rendering subnavs was refactored to align with App Search. There was a bug where some components weren’t rendering properly because the SourceLogic and GroupsLogic files were never unmounting. The reason for this is the subnav components use their respective logic files to get the IDs needed for rendering the subnav links. That is, SourceSubNav would call SourceLogic to get the ID to render the links and would stay rendered for the duration of the user’s time in the app. The result is that users would leave the source details page and navigate to add a new source and the logic file would never reset to a loading state and the UI would break. The fix was to borrow from the pattern App Search uses and pass the subnavs as props. Because App Search only uses a single engines subnav, they only needed one prop. We use multiple props for each subnav. Also, the subnav should not be rendered on the root routes (/sources, /p/sources, and /groups) so conditionals were added to only render the subnavs when not on those root routes. * Add FlashMessages * Fix some failed tests Missed this in first commit * Update SourceIcon to use EuiIcon Before this change, the legacy styles were not ported over. This gives a uniform size for both wrapped and unwrapped icons. The icons are a bit smaller on the add source page but Eui has lowered it’s largest size ‘xxl’ and we would need to write manual overrides. IMO the change is not significant enough to override. * Fix broken icons * Replace legacy div with component The eui.css file in ent-search is no longer up to date with current EUI and this was broken. The best fix was to use the component that renders as expected * Add base styles for Sources More in a future PR but this makes the majority of things look correct. * Cleanup Fix some type errors and rename constants * Couple more failing tests We have multiple `Layouts` now with the new subnavs * Fix prepare routes Like the first commit, missed these when porting over routes with no UI. * Clean up the desgin of the source connect screen The columns were way off in Kibana * Remove ORG_PATH const No longer needed since ‘/org’ is gone --- .../components/layout/nav.tsx | 13 +- .../shared/assets/source_icons/index.ts | 5 + .../shared/source_icon/source_icon.scss | 21 +++ .../shared/source_icon/source_icon.test.tsx | 6 +- .../shared/source_icon/source_icon.tsx | 13 +- .../workplace_search/constants.ts | 12 ++ .../workplace_search/index.test.tsx | 4 +- .../applications/workplace_search/index.tsx | 44 ++++-- .../workplace_search/routes.test.tsx | 6 +- .../applications/workplace_search/routes.ts | 24 ++-- .../add_source/configured_sources_list.tsx | 5 +- .../add_source/connect_instance.tsx | 4 +- .../components/source_sub_nav.tsx | 59 ++++++++ .../views/content_sources/index.ts | 1 + .../views/content_sources/source_logic.ts | 3 +- .../views/content_sources/source_router.tsx | 62 ++++---- .../views/content_sources/sources.scss | 23 +++ .../views/content_sources/sources_logic.ts | 1 + .../views/content_sources/sources_router.tsx | 133 ++++++++++-------- .../groups/components/group_manager_modal.tsx | 4 +- .../views/overview/onboarding_steps.test.tsx | 4 +- .../views/overview/onboarding_steps.tsx | 4 +- .../views/overview/organization_stats.tsx | 4 +- .../routes/workplace_search/sources.test.ts | 16 +-- .../server/routes/workplace_search/sources.ts | 18 +-- 25 files changed, 332 insertions(+), 157 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx index 6fa6698e6b6ba..de6c75d60189e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx @@ -11,11 +11,9 @@ import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { SideNav, SideNavLink } from '../../../shared/layout'; -import { GroupSubNav } from '../../views/groups/components/group_sub_nav'; import { NAV } from '../../constants'; import { - ORG_SOURCES_PATH, SOURCES_PATH, SECURITY_PATH, ROLE_MAPPINGS_PATH, @@ -23,17 +21,22 @@ import { ORG_SETTINGS_PATH, } from '../../routes'; -export const WorkplaceSearchNav: React.FC = () => { +interface Props { + sourcesSubNav?: React.ReactNode; + groupsSubNav?: React.ReactNode; +} + +export const WorkplaceSearchNav: React.FC = ({ sourcesSubNav, groupsSubNav }) => { // TODO: icons return ( {NAV.OVERVIEW} - + {NAV.SOURCES} - }> + {NAV.GROUPS} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts index 5f93694da09b8..2ac3f518e4e11 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts @@ -30,22 +30,27 @@ import zendesk from './zendesk.svg'; export const images = { box, confluence, + confluenceCloud: confluence, + confluenceServer: confluence, crawler, custom, drive, dropbox, github, + githubEnterpriseServer: github, gmail, googleDrive, google, jira, jiraServer, + jiraCloud: jira, loadingSmall, office365, oneDrive, outlook, people, salesforce, + salesforceSandbox: salesforce, serviceNow, sharePoint, slack, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss new file mode 100644 index 0000000000000..b04d5b8bc218f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss @@ -0,0 +1,21 @@ +/* + * 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. + */ + +.wrapped-icon { + width: 30px; + height: 30px; + overflow: hidden; + margin-right: 4px; + position: relative; + display: flex; + justify-content: center; + align-items: center; + + img { + max-width: 100%; + max-height: 100%; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx index c17b89c93a28b..4007f7a69f77a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx @@ -7,19 +7,21 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { EuiIcon } from '@elastic/eui'; + import { SourceIcon } from './'; describe('SourceIcon', () => { it('renders unwrapped icon', () => { const wrapper = shallow(); - expect(wrapper.find('img')).toHaveLength(1); + expect(wrapper.find(EuiIcon)).toHaveLength(1); expect(wrapper.find('.user-group-source')).toHaveLength(0); }); it('renders wrapped icon', () => { const wrapper = shallow(); - expect(wrapper.find('.user-group-source')).toHaveLength(1); + expect(wrapper.find('.wrapped-icon')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx index dec9e25fe2440..1af5420a164be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -8,6 +8,10 @@ import React from 'react'; import { camelCase } from 'lodash'; +import { EuiIcon } from '@elastic/eui'; + +import './source_icon.scss'; + import { images } from '../assets/source_icons'; import { imagesFull } from '../assets/sources_full_bleed'; @@ -27,14 +31,15 @@ export const SourceIcon: React.FC = ({ fullBleed = false, }) => { const icon = ( - {name} ); return wrapped ? ( -
+
{icon}
) : ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 1846115d73900..327ee7b30582b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -25,15 +25,27 @@ export const NAV = { 'xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization', { defaultMessage: 'Source Prioritization' } ), + CONTENT: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.content', { + defaultMessage: 'Content', + }), ROLE_MAPPINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', { defaultMessage: 'Role Mappings', }), SECURITY: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', { defaultMessage: 'Security', }), + SCHEMA: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.schema', { + defaultMessage: 'Schema', + }), + DISPLAY_SETTINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.displaySettings', { + defaultMessage: 'Display Settings', + }), SETTINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.settings', { defaultMessage: 'Settings', }), + ADD_SOURCE: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.addSource', { + defaultMessage: 'Add Source', + }), PERSONAL_DASHBOARD: i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index 5f1e2dd18d3b6..20b15bcfc45ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -57,7 +57,7 @@ describe('WorkplaceSearchConfigured', () => { it('renders layout and header actions', () => { const wrapper = shallow(); - expect(wrapper.find(Layout).prop('readOnlyMode')).toBeFalsy(); + expect(wrapper.find(Layout).first().prop('readOnlyMode')).toBeFalsy(); expect(wrapper.find(Overview)).toHaveLength(1); expect(mockKibanaValues.renderHeaderActions).toHaveBeenCalledWith(WorkplaceSearchHeaderActions); }); @@ -90,6 +90,6 @@ describe('WorkplaceSearchConfigured', () => { const wrapper = shallow(); - expect(wrapper.find(Layout).prop('readOnlyMode')).toEqual(true); + expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 776cae24dfdfb..562a2ffb32888 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -16,13 +16,17 @@ import { AppLogic } from './app_logic'; import { Layout } from '../shared/layout'; import { WorkplaceSearchNav, WorkplaceSearchHeaderActions } from './components/layout'; -import { GROUPS_PATH, SETUP_GUIDE_PATH } from './routes'; +import { GROUPS_PATH, SETUP_GUIDE_PATH, SOURCES_PATH, PERSONAL_SOURCES_PATH } from './routes'; import { SetupGuide } from './views/setup_guide'; import { ErrorState } from './views/error_state'; import { NotFound } from '../shared/not_found'; import { Overview } from './views/overview'; import { GroupsRouter } from './views/groups'; +import { SourcesRouter } from './views/content_sources'; + +import { GroupSubNav } from './views/groups/components/group_sub_nav'; +import { SourceSubNav } from './views/content_sources/components/source_sub_nav'; export const WorkplaceSearch: React.FC = (props) => { const { config } = useValues(KibanaLogic); @@ -37,6 +41,10 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { const { pathname } = useLocation(); + // We don't want so show the subnavs on the container root pages. + const showSourcesSubnav = pathname !== SOURCES_PATH && pathname !== PERSONAL_SOURCES_PATH; + const showGroupsSubnav = pathname !== GROUPS_PATH; + /** * Personal dashboard urls begin with /p/ * EX: http://localhost:5601/app/enterprise_search/workplace_search/p/sources @@ -45,6 +53,7 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { // TODO: Once auth is figured out, we need to have a check for the equivilent of `isAdmin`. const isOrganization = !pathname.match(personalSourceUrlRegex); + setContext(isOrganization); useEffect(() => { if (!hasInitialized) { @@ -53,10 +62,6 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { } }, [hasInitialized]); - useEffect(() => { - setContext(isOrganization); - }, [isOrganization]); - return ( @@ -65,19 +70,32 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { {errorConnecting ? : } + + } />} + restrictWidth + readOnlyMode={readOnlyMode} + > + + + + + } />} + restrictWidth + readOnlyMode={readOnlyMode} + > + + + } restrictWidth readOnlyMode={readOnlyMode}> {errorConnecting ? ( ) : ( - - - - - - - - + + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx index d03c0abb441b9..3fddcf3b77fe4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx @@ -12,7 +12,7 @@ import { EuiLink } from '@elastic/eui'; import { getContentSourcePath, SOURCES_PATH, - ORG_SOURCES_PATH, + PERSONAL_SOURCES_PATH, SOURCE_DETAILS_PATH, } from './routes'; @@ -26,13 +26,13 @@ describe('getContentSourcePath', () => { const wrapper = shallow(); const path = wrapper.find(EuiLink).prop('href'); - expect(path).toEqual(`${ORG_SOURCES_PATH}/123`); + expect(path).toEqual(`${SOURCES_PATH}/123`); }); it('should format user route', () => { const wrapper = shallow(); const path = wrapper.find(EuiLink).prop('href'); - expect(path).toEqual(`${SOURCES_PATH}/123`); + expect(path).toEqual(`${PERSONAL_SOURCES_PATH}/123`); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index e41a043911dc9..3ec22ede888ab 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -44,21 +44,21 @@ export const CUSTOM_API_DOCS_URL = `${DOCS_PREFIX}/workplace-search-custom-sourc export const CUSTOM_API_DOCUMENT_PERMISSIONS_DOCS_URL = `${CUSTOM_SOURCE_DOCS_URL}#custom-api-source-document-level-access-control`; export const ENT_SEARCH_LICENSE_MANAGEMENT = `${ENT_SEARCH_DOCS_PREFIX}/license-management.html`; -export const ORG_PATH = '/org'; +export const PERSONAL_PATH = '/p'; -export const ROLE_MAPPINGS_PATH = `${ORG_PATH}/role-mappings`; +export const ROLE_MAPPINGS_PATH = '/role-mappings'; export const ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/:roleId`; export const ROLE_MAPPING_NEW_PATH = `${ROLE_MAPPINGS_PATH}/new`; -export const USERS_PATH = `${ORG_PATH}/users`; -export const SECURITY_PATH = `${ORG_PATH}/security`; +export const USERS_PATH = '/users'; +export const SECURITY_PATH = '/security'; export const GROUPS_PATH = '/groups'; export const GROUP_PATH = `${GROUPS_PATH}/:groupId`; export const GROUP_SOURCE_PRIORITIZATION_PATH = `${GROUPS_PATH}/:groupId/source_prioritization`; export const SOURCES_PATH = '/sources'; -export const ORG_SOURCES_PATH = `${ORG_PATH}${SOURCES_PATH}`; +export const PERSONAL_SOURCES_PATH = `${PERSONAL_PATH}${SOURCES_PATH}`; export const SOURCE_ADDED_PATH = `${SOURCES_PATH}/added`; export const ADD_SOURCE_PATH = `${SOURCES_PATH}/add`; @@ -81,7 +81,7 @@ export const ADD_SLACK_PATH = `${SOURCES_PATH}/add/slack`; export const ADD_ZENDESK_PATH = `${SOURCES_PATH}/add/zendesk`; export const ADD_CUSTOM_PATH = `${SOURCES_PATH}/add/custom`; -export const PERSONAL_SETTINGS_PATH = '/settings'; +export const PERSONAL_SETTINGS_PATH = `${PERSONAL_PATH}/settings`; export const SOURCE_DETAILS_PATH = `${SOURCES_PATH}/:sourceId`; export const SOURCE_CONTENT_PATH = `${SOURCES_PATH}/:sourceId/content`; @@ -93,7 +93,7 @@ export const REINDEX_JOB_PATH = `${SOURCES_PATH}/:sourceId/schema-errors/:active export const DISPLAY_SETTINGS_SEARCH_RESULT_PATH = `${SOURCE_DISPLAY_SETTINGS_PATH}/`; export const DISPLAY_SETTINGS_RESULT_DETAIL_PATH = `${SOURCE_DISPLAY_SETTINGS_PATH}/result-detail`; -export const ORG_SETTINGS_PATH = `${ORG_PATH}/settings`; +export const ORG_SETTINGS_PATH = '/settings'; export const ORG_SETTINGS_CUSTOMIZE_PATH = `${ORG_SETTINGS_PATH}/customize`; export const ORG_SETTINGS_CONNECTORS_PATH = `${ORG_SETTINGS_PATH}/connectors`; export const ORG_SETTINGS_OAUTH_APPLICATION_PATH = `${ORG_SETTINGS_PATH}/oauth`; @@ -120,9 +120,9 @@ export const getContentSourcePath = ( path: string, sourceId: string, isOrganization: boolean -): string => generatePath(isOrganization ? ORG_PATH + path : path, { sourceId }); -export const getGroupPath = (groupId: string) => generatePath(GROUP_PATH, { groupId }); -export const getGroupSourcePrioritizationPath = (groupId: string) => +): string => generatePath(isOrganization ? path : `${PERSONAL_PATH}${path}`, { sourceId }); +export const getGroupPath = (groupId: string): string => generatePath(GROUP_PATH, { groupId }); +export const getGroupSourcePrioritizationPath = (groupId: string): string => `${GROUPS_PATH}/${groupId}/source_prioritization`; -export const getSourcesPath = (path: string, isOrganization: boolean) => - isOrganization ? `${ORG_PATH}${path}` : path; +export const getSourcesPath = (path: string, isOrganization: boolean): string => + isOrganization ? path : `${PERSONAL_PATH}${path}`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx index a95d5ca75b0b6..fbd053f9b8374 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx @@ -13,6 +13,7 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, + EuiPanel, EuiSpacer, EuiText, EuiTitle, @@ -57,7 +58,7 @@ export const ConfiguredSourcesList: React.FC = ({ {sources.map(({ name, serviceType, addPath, connected, accountContextOnly }, i) => ( -
+ = ({ )} -
+
))} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index ad183181b4eca..f9123ab4e1cca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -240,13 +240,13 @@ export const ConnectInstance: React.FC = ({ gutterSize="xl" responsive={false} > - + {header} {featureBadgeGroup()} {descriptionBlock} {formFields} - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx new file mode 100644 index 0000000000000..cc68a62b9555d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues } from 'kea'; + +import { AppLogic } from '../../../app_logic'; +import { NAV, CUSTOM_SERVICE_TYPE } from '../../../constants'; + +import { SourceLogic } from '../source_logic'; + +import { SideNavLink } from '../../../../shared/layout'; + +import { + getContentSourcePath, + SOURCE_DETAILS_PATH, + SOURCE_CONTENT_PATH, + SOURCE_SCHEMAS_PATH, + SOURCE_DISPLAY_SETTINGS_PATH, + SOURCE_SETTINGS_PATH, +} from '../../../routes'; + +export const SourceSubNav: React.FC = () => { + const { isOrganization } = useValues(AppLogic); + const { + contentSource: { id, serviceType }, + } = useValues(SourceLogic); + + if (!id) return null; + + const isCustom = serviceType === CUSTOM_SERVICE_TYPE; + + return ( + <> + + {NAV.OVERVIEW} + + + {NAV.CONTENT} + + {isCustom && ( + <> + + {NAV.SCHEMA} + + + {NAV.DISPLAY_SETTINGS} + + + )} + + {NAV.SETTINGS} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/index.ts index 0ef2099968f10..f447751e96594 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/index.ts @@ -5,3 +5,4 @@ */ export { Overview } from './components/overview'; +export { SourcesRouter } from './sources_router'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 0a11da02dc789..51b5735f01045 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -146,6 +146,7 @@ interface PreContentSourceResponse { } export const SourceLogic = kea>({ + path: ['enterprise_search', 'workplace_search', 'source_logic'], actions: { onInitializeSource: (contentSource: ContentSourceFullData) => contentSource, onUpdateSourceName: (name: string) => name, @@ -601,7 +602,7 @@ export const SourceLogic = kea>({ try { const response = await HttpLogic.values.http.post(route, { - body: JSON.stringify({ params }), + body: JSON.stringify({ ...params }), }); actions.setCustomSourceData(response); successCallback(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index b8b8e6e1040a1..7161e613247cd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -13,6 +13,11 @@ import { Route, Switch, useHistory, useParams } from 'react-router-dom'; import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; + +import { NAV } from '../../constants'; + import { ENT_SEARCH_LICENSE_MANAGEMENT, REINDEX_JOB_PATH, @@ -99,39 +104,42 @@ export const SourceRouter: React.FC = () => { {/* TODO: Figure out with design how to make this look better */} {pageHeader} - - + + + + + + + + + + {isCustomSource && ( - + + + + + )} {isCustomSource && ( - + + + + + )} {isCustomSource && ( - + + + + + )} - + + + + + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss new file mode 100644 index 0000000000000..fb0cecc181487 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss @@ -0,0 +1,23 @@ +/* + * 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. + */ + +.source-grid-configured { + + .source-card-configured { + padding: 8px; + + &__icon { + width: 2em; + height: 2em; + } + + &__not-connected-tooltip { + position: relative; + top: 3px; + left: 4px; + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts index 600b5871fc499..1757f2a6414f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts @@ -78,6 +78,7 @@ interface ISourcesServerResponse { } export const SourcesLogic = kea>({ + path: ['enterprise_search', 'workplace_search', 'sources_logic'], actions: { setServerSourceStatuses: (statuses: ContentSourceStatus[]) => statuses, onInitializeSources: (serverResponse: ISourcesServerResponse) => serverResponse, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx index e4f15286145f9..9f96a13e272d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx @@ -10,18 +10,23 @@ import { Location } from 'history'; import { useActions, useValues } from 'kea'; import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; +import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; + import { LicensingLogic } from '../../../../applications/shared/licensing'; +import { NAV } from '../../constants'; import { ADD_SOURCE_PATH, SOURCE_ADDED_PATH, SOURCE_DETAILS_PATH, - ORG_PATH, - ORG_SOURCES_PATH, + PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath, } from '../../routes'; +import { FlashMessages } from '../../../shared/flash_messages'; + import { AppLogic } from '../../app_logic'; import { staticSourceData } from './source_data'; import { SourcesLogic } from './sources_logic'; @@ -32,12 +37,15 @@ import { OrganizationSources } from './organization_sources'; import { PrivateSources } from './private_sources'; import { SourceRouter } from './source_router'; +import './sources.scss'; + export const SourcesRouter: React.FC = () => { const { pathname } = useLocation() as Location; const { hasPlatinumLicense } = useValues(LicensingLogic); const { resetSourcesState } = useActions(SourcesLogic); const { account: { canCreatePersonalSources }, + isOrganization, } = useValues(AppLogic); /** @@ -48,61 +56,76 @@ export const SourcesRouter: React.FC = () => { resetSourcesState(); }, [pathname]); - const isOrgRoute = pathname.includes(ORG_PATH); - return ( - - - - {staticSourceData.map(({ addPath, accountContextOnly }, i) => ( - - !hasPlatinumLicense && accountContextOnly ? ( - + <> + + + + + + + + + + + + + {staticSourceData.map(({ addPath, accountContextOnly, name }, i) => ( + + + {!hasPlatinumLicense && accountContextOnly ? ( + ) : ( - ) - } - /> - ))} - {staticSourceData.map(({ addPath }, i) => ( - } - /> - ))} - {staticSourceData.map(({ addPath }, i) => ( - } - /> - ))} - {staticSourceData.map(({ addPath, configuration: { needsConfiguration } }, i) => { - if (needsConfiguration) - return ( - } - /> - ); - })} - {canCreatePersonalSources ? ( - - ) : ( - - )} - : - - - + )} + + ))} + {staticSourceData.map(({ addPath, name }, i) => ( + + + + + ))} + {staticSourceData.map(({ addPath, name }, i) => ( + + + + + ))} + {staticSourceData.map(({ addPath, name, configuration: { needsConfiguration } }, i) => { + if (needsConfiguration) + return ( + + + + + ); + })} + {canCreatePersonalSources ? ( + + + + + + ) : ( + + )} + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx index c0f8bf57989ca..cbfb22915c4eb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx @@ -29,7 +29,7 @@ import { import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { Group } from '../../../types'; -import { ORG_SOURCES_PATH } from '../../../routes'; +import { SOURCES_PATH } from '../../../routes'; import noSharedSourcesIcon from '../../../assets/share_circle.svg'; @@ -96,7 +96,7 @@ export const GroupManagerModal: React.FC = ({ const handleSelectAll = () => selectAll(allSelected ? [] : allItems); const sourcesButton = ( - + {ADD_SOURCE_BUTTON_TEXT} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx index 268e4f8da445a..64dc5149decd5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx @@ -11,7 +11,7 @@ import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; -import { ORG_SOURCES_PATH, USERS_PATH } from '../../routes'; +import { SOURCES_PATH, USERS_PATH } from '../../routes'; import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; import { OnboardingCard } from './onboarding_card'; @@ -32,7 +32,7 @@ describe('OnboardingSteps', () => { const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(1); - expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(ORG_SOURCES_PATH); + expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(SOURCES_PATH); expect(wrapper.find(OnboardingCard).prop('description')).toBe( 'Add shared sources for your organization to start searching.' ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index ed5136a6f7a4e..4957324aa6bd7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -24,7 +24,7 @@ import { import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; import { TelemetryLogic } from '../../../shared/telemetry'; import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; -import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; +import { SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; import { ContentSection } from '../../components/shared/content_section'; @@ -75,7 +75,7 @@ export const OnboardingSteps: React.FC = () => { const accountsPath = !isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined; - const sourcesPath = canCreateContentSources || isCurated ? ORG_SOURCES_PATH : undefined; + const sourcesPath = canCreateContentSources || isCurated ? SOURCES_PATH : undefined; const SOURCES_CARD_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description', diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx index 6614ac58b0744..06c620ad384e6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { ContentSection } from '../../components/shared/content_section'; -import { ORG_SOURCES_PATH, USERS_PATH } from '../../routes'; +import { SOURCES_PATH, USERS_PATH } from '../../routes'; import { AppLogic } from '../../app_logic'; import { OverviewLogic } from './overview_logic'; @@ -43,7 +43,7 @@ export const OrganizationStats: React.FC = () => { { defaultMessage: 'Shared sources' } )} count={sourcesCount} - actionPath={ORG_SOURCES_PATH} + actionPath={SOURCES_PATH} /> {!isFederatedAuth && ( <> diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index 9cf491b79fd24..22e2deaace1dc 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -328,10 +328,8 @@ describe('sources routes', () => { const mockRequest = { params: { id: '123' }, body: { - query: { - content_source: { - name: 'foo', - }, + content_source: { + name: 'foo', }, }, }; @@ -406,7 +404,7 @@ describe('sources routes', () => { mockRouter.callRoute(mockRequest); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/pre_content_sources/zendesk', + path: '/ws/sources/zendesk/prepare', }); }); }); @@ -732,10 +730,8 @@ describe('sources routes', () => { const mockRequest = { params: { id: '123' }, body: { - query: { - content_source: { - name: 'foo', - }, + content_source: { + name: 'foo', }, }, }; @@ -810,7 +806,7 @@ describe('sources routes', () => { mockRouter.callRoute(mockRequest); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/pre_content_sources/zendesk', + path: '/ws/org/sources/zendesk/prepare', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index bdd048438dae5..24473388c03b1 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -200,10 +200,8 @@ export function registerAccountSourceSettingsRoute({ path: '/api/workplace_search/account/sources/{id}/settings', validate: { body: schema.object({ - query: schema.object({ - content_source: schema.object({ - name: schema.string(), - }), + content_source: schema.object({ + name: schema.string(), }), }), params: schema.object({ @@ -256,7 +254,7 @@ export function registerAccountPrepareSourcesRoute({ }, async (context, request, response) => { return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/pre_content_sources/${request.params.service_type}`, + path: `/ws/sources/${request.params.service_type}/prepare`, })(context, request, response); } ); @@ -372,7 +370,7 @@ export function registerOrgCreateSourceRoute({ login: schema.maybe(schema.string()), password: schema.maybe(schema.string()), organizations: schema.maybe(schema.arrayOf(schema.string())), - indexPermissions: schema.boolean(), + indexPermissions: schema.maybe(schema.boolean()), }), }, }, @@ -462,10 +460,8 @@ export function registerOrgSourceSettingsRoute({ path: '/api/workplace_search/org/sources/{id}/settings', validate: { body: schema.object({ - query: schema.object({ - content_source: schema.object({ - name: schema.string(), - }), + content_source: schema.object({ + name: schema.string(), }), }), params: schema.object({ @@ -518,7 +514,7 @@ export function registerOrgPrepareSourcesRoute({ }, async (context, request, response) => { return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/pre_content_sources/${request.params.service_type}`, + path: `/ws/org/sources/${request.params.service_type}/prepare`, })(context, request, response); } );