mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Workplace Search] Refactor Add Source Views to support base service types for external connectors (#131802)
This commit is contained in:
parent
4ef0f1e0b3
commit
626b4ae4ce
52 changed files with 1423 additions and 1455 deletions
|
@ -21,6 +21,8 @@ import {
|
|||
SOURCES_PATH,
|
||||
PRIVATE_SOURCES_PATH,
|
||||
SOURCE_DETAILS_PATH,
|
||||
getAddPath,
|
||||
getEditPath,
|
||||
} from './routes';
|
||||
|
||||
const TestComponent = ({ id, isOrg }: { id: string; isOrg?: boolean }) => {
|
||||
|
@ -86,3 +88,32 @@ describe('getReindexJobRoute', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAddPath', () => {
|
||||
it('should handle a service type', () => {
|
||||
expect(getAddPath('share_point')).toEqual('/sources/add/share_point');
|
||||
});
|
||||
|
||||
it('should should handle an external service type with no base service type', () => {
|
||||
expect(getAddPath('external')).toEqual('/sources/add/external');
|
||||
});
|
||||
|
||||
it('should should handle an external service type with a base service type', () => {
|
||||
expect(getAddPath('external', 'share_point')).toEqual('/sources/add/share_point/external');
|
||||
});
|
||||
it('should should handle a custom service type with no base service type', () => {
|
||||
expect(getAddPath('external')).toEqual('/sources/add/external');
|
||||
});
|
||||
|
||||
it('should should handle a custom service type with a base service type', () => {
|
||||
expect(getAddPath('custom', 'share_point_server')).toEqual(
|
||||
'/sources/add/share_point_server/custom'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEditPath', () => {
|
||||
it('should handle a service type', () => {
|
||||
expect(getEditPath('share_point')).toEqual('/settings/connectors/share_point/edit');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,6 +77,14 @@ export const getReindexJobRoute = (
|
|||
isOrganization: boolean
|
||||
) =>
|
||||
getSourcesPath(generatePath(REINDEX_JOB_PATH, { sourceId, activeReindexJobId }), isOrganization);
|
||||
export const getAddPath = (serviceType: string): string => `${SOURCES_PATH}/add/${serviceType}`;
|
||||
|
||||
export const getAddPath = (serviceType: string, baseServiceType?: string): string => {
|
||||
const baseServiceTypePath = baseServiceType
|
||||
? `${baseServiceType}/${serviceType}`
|
||||
: `${serviceType}`;
|
||||
return `${SOURCES_PATH}/add/${baseServiceTypePath}`;
|
||||
};
|
||||
|
||||
// TODO this should handle base service type once we are getting it back from registered external connectors
|
||||
export const getEditPath = (serviceType: string): string =>
|
||||
`${ORG_SETTINGS_CONNECTORS_PATH}/${serviceType}/edit`;
|
||||
|
|
|
@ -72,18 +72,14 @@ export interface Configuration {
|
|||
|
||||
export interface SourceDataItem {
|
||||
name: string;
|
||||
iconName: string;
|
||||
categories?: string[];
|
||||
serviceType: string;
|
||||
baseServiceType?: string;
|
||||
configuration: Configuration;
|
||||
configured?: boolean;
|
||||
connected?: boolean;
|
||||
features?: Features;
|
||||
objTypes?: string[];
|
||||
accountContextOnly: boolean;
|
||||
internalConnectorAvailable?: boolean;
|
||||
externalConnectorAvailable?: boolean;
|
||||
customConnectorAvailable?: boolean;
|
||||
isBeta?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* 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 { SourceDataItem } from '../types';
|
||||
|
||||
export const hasMultipleConnectorOptions = ({
|
||||
internalConnectorAvailable,
|
||||
externalConnectorAvailable,
|
||||
customConnectorAvailable,
|
||||
}: SourceDataItem) =>
|
||||
[externalConnectorAvailable, internalConnectorAvailable, customConnectorAvailable].filter(
|
||||
(available) => !!available
|
||||
).length > 1;
|
|
@ -11,6 +11,5 @@ export { mimeType } from './mime_types';
|
|||
export { readUploadedFileAsBase64 } from './read_uploaded_file_as_base64';
|
||||
export { readUploadedFileAsText } from './read_uploaded_file_as_text';
|
||||
export { handlePrivateKeyUpload } from './handle_private_key_upload';
|
||||
export { hasMultipleConnectorOptions } from './has_multiple_connector_options';
|
||||
export { isNotNullish } from './is_not_nullish';
|
||||
export { sortByName } from './sort_by_name';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import '../../../../../../__mocks__/shallow_useeffect.mock';
|
||||
import { setMockValues } from '../../../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../../../__mocks__/react_router';
|
||||
import { sourceConfigData } from '../../../../../__mocks__/content_sources.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
@ -17,7 +18,6 @@ import {
|
|||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../../components/layout';
|
||||
import { staticSourceData } from '../../../source_data';
|
||||
|
||||
import { AddCustomSource } from './add_custom_source';
|
||||
import { AddCustomSourceSteps } from './add_custom_source_logic';
|
||||
|
@ -25,11 +25,6 @@ import { ConfigureCustom } from './configure_custom';
|
|||
import { SaveCustom } from './save_custom';
|
||||
|
||||
describe('AddCustomSource', () => {
|
||||
const props = {
|
||||
sourceData: staticSourceData[0],
|
||||
initialValues: undefined,
|
||||
};
|
||||
|
||||
const values = {
|
||||
sourceConfigData,
|
||||
isOrganization: true,
|
||||
|
@ -37,17 +32,26 @@ describe('AddCustomSource', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
setMockValues({ ...values });
|
||||
mockUseParams.mockReturnValue({ baseServiceType: 'share_point_server' });
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<AddCustomSource {...props} />);
|
||||
const wrapper = shallow(<AddCustomSource />);
|
||||
|
||||
expect(wrapper.find(WorkplaceSearchPageTemplate)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns null if there is no matching source data for the service type', () => {
|
||||
mockUseParams.mockReturnValue({ baseServiceType: 'doesnt_exist' });
|
||||
|
||||
const wrapper = shallow(<AddCustomSource />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('should show correct layout for personal dashboard', () => {
|
||||
setMockValues({ isOrganization: false });
|
||||
const wrapper = shallow(<AddCustomSource {...props} />);
|
||||
const wrapper = shallow(<AddCustomSource />);
|
||||
|
||||
expect(wrapper.find(WorkplaceSearchPageTemplate)).toHaveLength(0);
|
||||
expect(wrapper.find(PersonalDashboardLayout)).toHaveLength(1);
|
||||
|
@ -55,14 +59,14 @@ describe('AddCustomSource', () => {
|
|||
|
||||
it('should show Configure Custom for custom configuration step', () => {
|
||||
setMockValues({ currentStep: AddCustomSourceSteps.ConfigureCustomStep });
|
||||
const wrapper = shallow(<AddCustomSource {...props} />);
|
||||
const wrapper = shallow(<AddCustomSource />);
|
||||
|
||||
expect(wrapper.find(ConfigureCustom)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should show Save Custom for save custom step', () => {
|
||||
setMockValues({ currentStep: AddCustomSourceSteps.SaveCustomStep });
|
||||
const wrapper = shallow(<AddCustomSource {...props} />);
|
||||
const wrapper = shallow(<AddCustomSource />);
|
||||
|
||||
expect(wrapper.find(SaveCustom)).toHaveLength(1);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { AppLogic } from '../../../../../app_logic';
|
||||
|
@ -16,27 +18,38 @@ import {
|
|||
} from '../../../../../components/layout';
|
||||
import { NAV } from '../../../../../constants';
|
||||
|
||||
import { SourceDataItem } from '../../../../../types';
|
||||
import { getSourceData } from '../../../source_data';
|
||||
|
||||
import { AddCustomSourceLogic, AddCustomSourceSteps } from './add_custom_source_logic';
|
||||
import { ConfigureCustom } from './configure_custom';
|
||||
import { SaveCustom } from './save_custom';
|
||||
|
||||
interface Props {
|
||||
sourceData: SourceDataItem;
|
||||
initialValue?: string;
|
||||
}
|
||||
export const AddCustomSource: React.FC<Props> = ({ sourceData, initialValue = '' }) => {
|
||||
const addCustomSourceLogic = AddCustomSourceLogic({ sourceData, initialValue });
|
||||
export const AddCustomSource: React.FC = () => {
|
||||
const { baseServiceType } = useParams<{ baseServiceType?: string }>();
|
||||
const sourceData = getSourceData('custom', baseServiceType);
|
||||
|
||||
const addCustomSourceLogic = AddCustomSourceLogic({
|
||||
baseServiceType,
|
||||
initialValue: sourceData?.name,
|
||||
});
|
||||
|
||||
const { currentStep } = useValues(addCustomSourceLogic);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
|
||||
|
||||
return (
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, sourceData.name || '...']}>
|
||||
{currentStep === AddCustomSourceSteps.ConfigureCustomStep && <ConfigureCustom />}
|
||||
{currentStep === AddCustomSourceSteps.SaveCustomStep && <SaveCustom />}
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, sourceData.name]}>
|
||||
{currentStep === AddCustomSourceSteps.ConfigureCustomStep && (
|
||||
<ConfigureCustom sourceData={sourceData} />
|
||||
)}
|
||||
{currentStep === AddCustomSourceSteps.SaveCustomStep && (
|
||||
<SaveCustom sourceData={sourceData} />
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,7 +14,6 @@ import { sourceConfigData } from '../../../../../__mocks__/content_sources.mock'
|
|||
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { docLinks } from '../../../../../../shared/doc_links';
|
||||
import { itShowsServerErrorAsFlashMessage } from '../../../../../../test_helpers';
|
||||
|
||||
jest.mock('../../../../../app_logic', () => ({
|
||||
|
@ -22,35 +21,17 @@ jest.mock('../../../../../app_logic', () => ({
|
|||
}));
|
||||
import { AppLogic } from '../../../../../app_logic';
|
||||
|
||||
import { SOURCE_NAMES } from '../../../../../constants';
|
||||
import { CustomSource, SourceDataItem } from '../../../../../types';
|
||||
import { CustomSource } from '../../../../../types';
|
||||
|
||||
import { AddCustomSourceLogic, AddCustomSourceSteps } from './add_custom_source_logic';
|
||||
|
||||
const CUSTOM_SOURCE_DATA_ITEM: SourceDataItem = {
|
||||
name: SOURCE_NAMES.CUSTOM,
|
||||
iconName: SOURCE_NAMES.CUSTOM,
|
||||
serviceType: 'custom',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: false,
|
||||
needsBaseUrl: false,
|
||||
documentationUrl: docLinks.workplaceSearchCustomSources,
|
||||
applicationPortalUrl: '',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
};
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
currentStep: AddCustomSourceSteps.ConfigureCustomStep,
|
||||
buttonLoading: false,
|
||||
customSourceNameValue: '',
|
||||
newCustomSource: {} as CustomSource,
|
||||
sourceData: CUSTOM_SOURCE_DATA_ITEM,
|
||||
};
|
||||
|
||||
const MOCK_PROPS = { initialValue: '', sourceData: CUSTOM_SOURCE_DATA_ITEM };
|
||||
|
||||
const MOCK_NAME = 'name';
|
||||
|
||||
describe('AddCustomSourceLogic', () => {
|
||||
|
@ -60,7 +41,7 @@ describe('AddCustomSourceLogic', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mount({}, MOCK_PROPS);
|
||||
mount({});
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
|
@ -112,12 +93,9 @@ describe('AddCustomSourceLogic', () => {
|
|||
|
||||
describe('listeners', () => {
|
||||
beforeEach(() => {
|
||||
mount(
|
||||
{
|
||||
customSourceNameValue: MOCK_NAME,
|
||||
},
|
||||
MOCK_PROPS
|
||||
);
|
||||
mount({
|
||||
customSourceNameValue: MOCK_NAME,
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization context', () => {
|
||||
|
@ -151,11 +129,7 @@ describe('AddCustomSourceLogic', () => {
|
|||
customSourceNameValue: MOCK_NAME,
|
||||
},
|
||||
{
|
||||
...MOCK_PROPS,
|
||||
sourceData: {
|
||||
...CUSTOM_SOURCE_DATA_ITEM,
|
||||
serviceType: 'sharepoint-server',
|
||||
},
|
||||
baseServiceType: 'share_point_server',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -165,7 +139,7 @@ describe('AddCustomSourceLogic', () => {
|
|||
body: JSON.stringify({
|
||||
service_type: 'custom',
|
||||
name: MOCK_NAME,
|
||||
base_service_type: 'sharepoint-server',
|
||||
base_service_type: 'share_point_server',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
@ -199,11 +173,7 @@ describe('AddCustomSourceLogic', () => {
|
|||
customSourceNameValue: MOCK_NAME,
|
||||
},
|
||||
{
|
||||
...MOCK_PROPS,
|
||||
sourceData: {
|
||||
...CUSTOM_SOURCE_DATA_ITEM,
|
||||
serviceType: 'sharepoint-server',
|
||||
},
|
||||
baseServiceType: 'share_point_server',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -215,7 +185,7 @@ describe('AddCustomSourceLogic', () => {
|
|||
body: JSON.stringify({
|
||||
service_type: 'custom',
|
||||
name: MOCK_NAME,
|
||||
base_service_type: 'sharepoint-server',
|
||||
base_service_type: 'share_point_server',
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,11 +10,11 @@ import { kea, MakeLogicType } from 'kea';
|
|||
import { flashAPIErrors, clearFlashMessages } from '../../../../../../shared/flash_messages';
|
||||
import { HttpLogic } from '../../../../../../shared/http';
|
||||
import { AppLogic } from '../../../../../app_logic';
|
||||
import { CustomSource, SourceDataItem } from '../../../../../types';
|
||||
import { CustomSource } from '../../../../../types';
|
||||
|
||||
export interface AddCustomSourceProps {
|
||||
sourceData: SourceDataItem;
|
||||
initialValue: string;
|
||||
baseServiceType?: string;
|
||||
initialValue?: string;
|
||||
}
|
||||
|
||||
export enum AddCustomSourceSteps {
|
||||
|
@ -34,7 +34,6 @@ interface AddCustomSourceValues {
|
|||
currentStep: AddCustomSourceSteps;
|
||||
customSourceNameValue: string;
|
||||
newCustomSource: CustomSource;
|
||||
sourceData: SourceDataItem;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +66,7 @@ export const AddCustomSourceLogic = kea<
|
|||
},
|
||||
],
|
||||
customSourceNameValue: [
|
||||
props.initialValue,
|
||||
props.initialValue || '',
|
||||
{
|
||||
setCustomSourceNameValue: (_, customSourceNameValue) => customSourceNameValue,
|
||||
},
|
||||
|
@ -78,7 +77,6 @@ export const AddCustomSourceLogic = kea<
|
|||
setNewCustomSource: (_, newCustomSource) => newCustomSource,
|
||||
},
|
||||
],
|
||||
sourceData: [props.sourceData],
|
||||
}),
|
||||
listeners: ({ actions, values, props }) => ({
|
||||
createContentSource: async () => {
|
||||
|
@ -90,21 +88,12 @@ export const AddCustomSourceLogic = kea<
|
|||
|
||||
const { customSourceNameValue } = values;
|
||||
|
||||
const baseParams = {
|
||||
const params = {
|
||||
service_type: 'custom',
|
||||
name: customSourceNameValue,
|
||||
base_service_type: props.baseServiceType,
|
||||
};
|
||||
|
||||
// pre-configured custom sources have a serviceType reflecting their target service
|
||||
// we submit this as `base_service_type` to keep track of
|
||||
const params =
|
||||
props.sourceData.serviceType === 'custom'
|
||||
? baseParams
|
||||
: {
|
||||
...baseParams,
|
||||
base_service_type: props.sourceData.serviceType,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.post<CustomSource>(route, {
|
||||
body: JSON.stringify(params),
|
||||
|
|
|
@ -21,24 +21,24 @@ import { ConfigureCustom } from './configure_custom';
|
|||
describe('ConfigureCustom', () => {
|
||||
const setCustomSourceNameValue = jest.fn();
|
||||
const createContentSource = jest.fn();
|
||||
const sourceData = staticSourceData[1];
|
||||
|
||||
beforeEach(() => {
|
||||
setMockActions({ setCustomSourceNameValue, createContentSource });
|
||||
setMockValues({
|
||||
customSourceNameValue: 'name',
|
||||
buttonLoading: false,
|
||||
sourceData: staticSourceData[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<ConfigureCustom />);
|
||||
const wrapper = shallow(<ConfigureCustom sourceData={sourceData} />);
|
||||
|
||||
expect(wrapper.find(EuiForm)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('handles input change', () => {
|
||||
const wrapper = shallow(<ConfigureCustom />);
|
||||
const wrapper = shallow(<ConfigureCustom sourceData={sourceData} />);
|
||||
const text = 'changed for the better';
|
||||
const input = wrapper.find(EuiFieldText);
|
||||
input.simulate('change', { target: { value: text } });
|
||||
|
@ -47,7 +47,7 @@ describe('ConfigureCustom', () => {
|
|||
});
|
||||
|
||||
it('handles form submission', () => {
|
||||
const wrapper = shallow(<ConfigureCustom />);
|
||||
const wrapper = shallow(<ConfigureCustom sourceData={sourceData} />);
|
||||
|
||||
const preventDefault = jest.fn();
|
||||
wrapper.find('EuiForm').simulate('submit', { preventDefault });
|
||||
|
|
|
@ -21,11 +21,13 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { docLinks } from '../../../../../../shared/doc_links';
|
||||
|
||||
import connectionIllustration from '../../../../../assets/connection_illustration.svg';
|
||||
import { SourceDataItem } from '../../../../../types';
|
||||
import { SOURCE_NAME_LABEL } from '../../../constants';
|
||||
|
||||
import { AddSourceHeader } from '../add_source_header';
|
||||
|
@ -33,9 +35,13 @@ import { CONFIG_CUSTOM_BUTTON, CONFIG_CUSTOM_LINK_TEXT, CONFIG_INTRO_ALT_TEXT }
|
|||
|
||||
import { AddCustomSourceLogic } from './add_custom_source_logic';
|
||||
|
||||
export const ConfigureCustom: React.FC = () => {
|
||||
interface ConfigureCustomProps {
|
||||
sourceData: SourceDataItem;
|
||||
}
|
||||
|
||||
export const ConfigureCustom: React.FC<ConfigureCustomProps> = ({ sourceData }) => {
|
||||
const { setCustomSourceNameValue, createContentSource } = useActions(AddCustomSourceLogic);
|
||||
const { customSourceNameValue, buttonLoading, sourceData } = useValues(AddCustomSourceLogic);
|
||||
const { customSourceNameValue, buttonLoading } = useValues(AddCustomSourceLogic);
|
||||
|
||||
const handleFormSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -25,18 +25,21 @@ const mockValues = {
|
|||
accessToken: 'token',
|
||||
name: 'name',
|
||||
},
|
||||
sourceData: staticCustomSourceData,
|
||||
};
|
||||
|
||||
const sourceData = staticCustomSourceData;
|
||||
|
||||
describe('SaveCustom', () => {
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(mockValues);
|
||||
});
|
||||
|
||||
describe('default behavior', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(mockValues);
|
||||
|
||||
wrapper = shallow(<SaveCustom />);
|
||||
wrapper = shallow(<SaveCustom sourceData={sourceData} />);
|
||||
});
|
||||
|
||||
it('contains a button back to the sources list', () => {
|
||||
|
@ -52,20 +55,14 @@ describe('SaveCustom', () => {
|
|||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
sourceData: {
|
||||
...staticCustomSourceData,
|
||||
serviceType: 'sharepoint-server',
|
||||
configuration: {
|
||||
...staticCustomSourceData.configuration,
|
||||
githubRepository: 'elastic/sharepoint-server-connector',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
wrapper = shallow(<SaveCustom />);
|
||||
wrapper = shallow(
|
||||
<SaveCustom
|
||||
sourceData={{
|
||||
...sourceData,
|
||||
baseServiceType: 'share_point_server',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('includes a link to provide feedback', () => {
|
||||
|
|
|
@ -21,12 +21,14 @@ import {
|
|||
EuiCallOut,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { EuiButtonTo } from '../../../../../../shared/react_router_helpers';
|
||||
import { AppLogic } from '../../../../../app_logic';
|
||||
import { SOURCES_PATH, getSourcesPath } from '../../../../../routes';
|
||||
import { SourceDataItem } from '../../../../../types';
|
||||
|
||||
import { CustomSourceDeployment } from '../../custom_source_deployment';
|
||||
|
||||
|
@ -35,10 +37,14 @@ import { SAVE_CUSTOM_BODY1 as READY_TO_ACCEPT_REQUESTS_LABEL } from '../constant
|
|||
|
||||
import { AddCustomSourceLogic } from './add_custom_source_logic';
|
||||
|
||||
export const SaveCustom: React.FC = () => {
|
||||
const { newCustomSource, sourceData } = useValues(AddCustomSourceLogic);
|
||||
interface SaveCustomProps {
|
||||
sourceData: SourceDataItem;
|
||||
}
|
||||
|
||||
export const SaveCustom: React.FC<SaveCustomProps> = ({ sourceData }) => {
|
||||
const { newCustomSource } = useValues(AddCustomSourceLogic);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
const { serviceType, name, categories = [] } = sourceData;
|
||||
const { serviceType, baseServiceType, name, categories = [] } = sourceData;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -92,10 +98,10 @@ export const SaveCustom: React.FC = () => {
|
|||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<CustomSourceDeployment source={newCustomSource} sourceData={sourceData} />
|
||||
<CustomSourceDeployment source={newCustomSource} baseServiceType={baseServiceType} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{serviceType !== 'custom' && (
|
||||
{baseServiceType && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import '../../../../../../__mocks__/shallow_useeffect.mock';
|
||||
import { setMockActions, setMockValues } from '../../../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../../../__mocks__/react_router';
|
||||
import { sourceConfigData } from '../../../../../__mocks__/content_sources.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
@ -19,24 +20,15 @@ import {
|
|||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../../components/layout';
|
||||
import { staticSourceData } from '../../../source_data';
|
||||
|
||||
import { ExternalConnectorConfig } from './external_connector_config';
|
||||
import { ExternalConnectorFormFields } from './external_connector_form_fields';
|
||||
|
||||
describe('ExternalConnectorConfig', () => {
|
||||
const goBack = jest.fn();
|
||||
const onDeleteConfig = jest.fn();
|
||||
const setExternalConnectorApiKey = jest.fn();
|
||||
const setExternalConnectorUrl = jest.fn();
|
||||
const saveExternalConnectorConfig = jest.fn();
|
||||
|
||||
const props = {
|
||||
sourceData: staticSourceData[0],
|
||||
goBack,
|
||||
onDeleteConfig,
|
||||
};
|
||||
|
||||
const values = {
|
||||
sourceConfigData,
|
||||
buttonLoading: false,
|
||||
|
@ -48,37 +40,47 @@ describe('ExternalConnectorConfig', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockActions({
|
||||
setExternalConnectorApiKey,
|
||||
setExternalConnectorUrl,
|
||||
saveExternalConnectorConfig,
|
||||
});
|
||||
setMockValues({ ...values });
|
||||
mockUseParams.mockReturnValue({});
|
||||
});
|
||||
|
||||
it('returns null if there is no matching source data for the service type', () => {
|
||||
mockUseParams.mockReturnValue({ baseServiceType: 'doesnt_exist' });
|
||||
|
||||
const wrapper = shallow(<ExternalConnectorConfig />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<ExternalConnectorConfig {...props} />);
|
||||
const wrapper = shallow(<ExternalConnectorConfig />);
|
||||
|
||||
expect(wrapper.find(EuiSteps)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiSteps).dive().find(ExternalConnectorFormFields)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders organizstion layout', () => {
|
||||
const wrapper = shallow(<ExternalConnectorConfig {...props} />);
|
||||
const wrapper = shallow(<ExternalConnectorConfig />);
|
||||
|
||||
expect(wrapper.find(WorkplaceSearchPageTemplate)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should show correct layout for personal dashboard', () => {
|
||||
setMockValues({ ...values, isOrganization: false });
|
||||
const wrapper = shallow(<ExternalConnectorConfig {...props} />);
|
||||
const wrapper = shallow(<ExternalConnectorConfig />);
|
||||
|
||||
expect(wrapper.find(WorkplaceSearchPageTemplate)).toHaveLength(0);
|
||||
expect(wrapper.find(PersonalDashboardLayout)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('handles form submission', () => {
|
||||
const wrapper = shallow(<ExternalConnectorConfig {...props} />);
|
||||
const wrapper = shallow(<ExternalConnectorConfig />);
|
||||
|
||||
const preventDefault = jest.fn();
|
||||
wrapper.find('form').simulate('submit', { preventDefault });
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
|
||||
import React, { FormEvent } from 'react';
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
|
@ -26,56 +27,41 @@ import {
|
|||
PersonalDashboardLayout,
|
||||
WorkplaceSearchPageTemplate,
|
||||
} from '../../../../../components/layout';
|
||||
import { NAV, REMOVE_BUTTON } from '../../../../../constants';
|
||||
import { SourceDataItem } from '../../../../../types';
|
||||
|
||||
import { staticExternalSourceData } from '../../../source_data';
|
||||
import { NAV } from '../../../../../constants';
|
||||
|
||||
import { getSourceData } from '../../../source_data';
|
||||
import { AddSourceHeader } from '../add_source_header';
|
||||
import { ConfigDocsLinks } from '../config_docs_links';
|
||||
import { OAUTH_SAVE_CONFIG_BUTTON, OAUTH_BACK_BUTTON } from '../constants';
|
||||
import { OAUTH_SAVE_CONFIG_BUTTON } from '../constants';
|
||||
|
||||
import { ExternalConnectorDocumentation } from './external_connector_documentation';
|
||||
import { ExternalConnectorFormFields } from './external_connector_form_fields';
|
||||
import { ExternalConnectorLogic } from './external_connector_logic';
|
||||
|
||||
interface SaveConfigProps {
|
||||
sourceData: SourceDataItem;
|
||||
goBack?: () => void;
|
||||
onDeleteConfig?: () => void;
|
||||
}
|
||||
|
||||
export const ExternalConnectorConfig: React.FC<SaveConfigProps> = ({
|
||||
sourceData,
|
||||
goBack,
|
||||
onDeleteConfig,
|
||||
}) => {
|
||||
const serviceType = 'external';
|
||||
export const ExternalConnectorConfig: React.FC = () => {
|
||||
const { baseServiceType } = useParams<{ baseServiceType?: string }>();
|
||||
const sourceData = getSourceData('external', baseServiceType);
|
||||
const { saveExternalConnectorConfig } = useActions(ExternalConnectorLogic);
|
||||
|
||||
const {
|
||||
formDisabled,
|
||||
buttonLoading,
|
||||
externalConnectorUrl,
|
||||
externalConnectorApiKey,
|
||||
sourceConfigData,
|
||||
urlValid,
|
||||
} = useValues(ExternalConnectorLogic);
|
||||
const { formDisabled, buttonLoading, externalConnectorUrl, externalConnectorApiKey, urlValid } =
|
||||
useValues(ExternalConnectorLogic);
|
||||
|
||||
const handleFormSubmission = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
saveExternalConnectorConfig({ url: externalConnectorUrl, apiKey: externalConnectorApiKey });
|
||||
};
|
||||
|
||||
const { name, categories } = sourceConfigData;
|
||||
const {
|
||||
configuration: { applicationLinkTitle, applicationPortalUrl },
|
||||
} = sourceData;
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
configuration: { documentationUrl },
|
||||
} = staticExternalSourceData;
|
||||
name,
|
||||
categories = [],
|
||||
configuration: { applicationLinkTitle, applicationPortalUrl, documentationUrl },
|
||||
} = sourceData;
|
||||
|
||||
const saveButton = (
|
||||
<EuiButton color="primary" fill isLoading={buttonLoading} disabled={formDisabled} type="submit">
|
||||
|
@ -83,22 +69,10 @@ export const ExternalConnectorConfig: React.FC<SaveConfigProps> = ({
|
|||
</EuiButton>
|
||||
);
|
||||
|
||||
const deleteButton = (
|
||||
<EuiButton color="danger" fill disabled={buttonLoading} onClick={onDeleteConfig}>
|
||||
{REMOVE_BUTTON}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
const backButton = <EuiButtonEmpty onClick={goBack}>{OAUTH_BACK_BUTTON}</EuiButtonEmpty>;
|
||||
|
||||
const formActions = (
|
||||
<EuiFormRow>
|
||||
<EuiFlexGroup justifyContent="flexStart" gutterSize="m" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{saveButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{goBack && backButton}
|
||||
{onDeleteConfig && deleteButton}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
@ -132,11 +106,17 @@ export const ExternalConnectorConfig: React.FC<SaveConfigProps> = ({
|
|||
},
|
||||
];
|
||||
|
||||
const header = <AddSourceHeader name={name} serviceType={serviceType} categories={categories} />;
|
||||
const header = (
|
||||
<AddSourceHeader
|
||||
name={name}
|
||||
serviceType={baseServiceType || 'external'}
|
||||
categories={categories}
|
||||
/>
|
||||
);
|
||||
const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
|
||||
|
||||
return (
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, name || '...']} isLoading={false}>
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, name]} isLoading={false}>
|
||||
{header}
|
||||
<EuiSpacer size="l" />
|
||||
<ExternalConnectorDocumentation name={name} documentationUrl={documentationUrl} />
|
||||
|
|
|
@ -36,10 +36,6 @@ describe('ExternalConnectorLogic', () => {
|
|||
formDisabled: true,
|
||||
externalConnectorUrl: '',
|
||||
externalConnectorApiKey: '',
|
||||
sourceConfigData: {
|
||||
name: '',
|
||||
categories: [],
|
||||
},
|
||||
urlValid: true,
|
||||
showInsecureUrlCallout: false,
|
||||
insecureUrl: true,
|
||||
|
@ -52,7 +48,6 @@ describe('ExternalConnectorLogic', () => {
|
|||
formDisabled: false,
|
||||
insecureUrl: false,
|
||||
dataLoading: false,
|
||||
sourceConfigData,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -87,7 +82,6 @@ describe('ExternalConnectorLogic', () => {
|
|||
it('saves the source config', () => {
|
||||
expect(ExternalConnectorLogic.values).toEqual({
|
||||
...DEFAULT_VALUES_SUCCESS,
|
||||
sourceConfigData,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -104,7 +98,6 @@ describe('ExternalConnectorLogic', () => {
|
|||
...DEFAULT_VALUES_SUCCESS,
|
||||
externalConnectorUrl: '',
|
||||
insecureUrl: true,
|
||||
sourceConfigData: newSourceConfigData,
|
||||
});
|
||||
});
|
||||
it('sets undefined api key to empty string', () => {
|
||||
|
@ -119,7 +112,6 @@ describe('ExternalConnectorLogic', () => {
|
|||
expect(ExternalConnectorLogic.values).toEqual({
|
||||
...DEFAULT_VALUES_SUCCESS,
|
||||
externalConnectorApiKey: '',
|
||||
sourceConfigData: newSourceConfigData,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,7 +48,6 @@ export interface ExternalConnectorValues {
|
|||
externalConnectorApiKey: string;
|
||||
externalConnectorUrl: string;
|
||||
urlValid: boolean;
|
||||
sourceConfigData: SourceConfigData | Pick<SourceConfigData, 'name' | 'categories'>;
|
||||
insecureUrl: boolean;
|
||||
showInsecureUrlCallout: boolean;
|
||||
}
|
||||
|
@ -107,12 +106,6 @@ export const ExternalConnectorLogic = kea<
|
|||
setShowInsecureUrlCallout: (_, showCallout) => showCallout,
|
||||
},
|
||||
],
|
||||
sourceConfigData: [
|
||||
{ name: '', categories: [] },
|
||||
{
|
||||
fetchExternalSourceSuccess: (_, sourceConfigData) => sourceConfigData,
|
||||
},
|
||||
],
|
||||
urlValid: [
|
||||
true,
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
setMockActions,
|
||||
setMockValues,
|
||||
} from '../../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../../__mocks__/react_router';
|
||||
import { sourceConfigData } from '../../../../__mocks__/content_sources.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
@ -22,13 +23,9 @@ import {
|
|||
PersonalDashboardLayout,
|
||||
} from '../../../../components/layout';
|
||||
|
||||
import { staticSourceData } from '../../source_data';
|
||||
|
||||
import { AddSource } from './add_source';
|
||||
import { AddSourceSteps } from './add_source_logic';
|
||||
import { ConfigCompleted } from './config_completed';
|
||||
import { ConfigurationChoice } from './configuration_choice';
|
||||
import { ConfigurationIntro } from './configuration_intro';
|
||||
import { ConfigureOauth } from './configure_oauth';
|
||||
import { ConnectInstance } from './connect_instance';
|
||||
import { Reauthenticate } from './reauthenticate';
|
||||
|
@ -36,7 +33,7 @@ import { SaveConfig } from './save_config';
|
|||
|
||||
describe('AddSourceList', () => {
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
const initializeAddSource = jest.fn();
|
||||
const getSourceConfigData = jest.fn();
|
||||
const setAddSourceStep = jest.fn();
|
||||
const saveSourceConfig = jest.fn((_, setConfigCompletedStep) => {
|
||||
setConfigCompletedStep();
|
||||
|
@ -47,7 +44,7 @@ describe('AddSourceList', () => {
|
|||
const resetSourcesState = jest.fn();
|
||||
|
||||
const mockValues = {
|
||||
addSourceCurrentStep: AddSourceSteps.ConfigIntroStep,
|
||||
addSourceCurrentStep: null,
|
||||
sourceConfigData,
|
||||
dataLoading: false,
|
||||
newCustomSource: {},
|
||||
|
@ -56,68 +53,29 @@ describe('AddSourceList', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockActions({
|
||||
initializeAddSource,
|
||||
getSourceConfigData,
|
||||
setAddSourceStep,
|
||||
saveSourceConfig,
|
||||
createContentSource,
|
||||
resetSourcesState,
|
||||
});
|
||||
setMockValues(mockValues);
|
||||
});
|
||||
|
||||
it('renders default state', () => {
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[0]} />);
|
||||
wrapper.find(ConfigurationIntro).prop('advanceStep')();
|
||||
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
|
||||
expect(initializeAddSource).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders default state correctly when there are multiple connector options', () => {
|
||||
const wrapper = shallow(
|
||||
<AddSource
|
||||
sourceData={{
|
||||
...staticSourceData[0],
|
||||
externalConnectorAvailable: true,
|
||||
customConnectorAvailable: true,
|
||||
internalConnectorAvailable: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
wrapper.find(ConfigurationIntro).prop('advanceStep')();
|
||||
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ChoiceStep);
|
||||
});
|
||||
|
||||
it('renders default state correctly when there are multiple connector options but external connector is configured', () => {
|
||||
setMockValues({ ...mockValues, externalConfigured: true });
|
||||
const wrapper = shallow(
|
||||
<AddSource
|
||||
sourceData={{
|
||||
...staticSourceData[0],
|
||||
externalConnectorAvailable: true,
|
||||
customConnectorAvailable: true,
|
||||
internalConnectorAvailable: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
wrapper.find(ConfigurationIntro).prop('advanceStep')();
|
||||
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
|
||||
mockUseParams.mockReturnValue({ serviceType: 'confluence_cloud' });
|
||||
});
|
||||
|
||||
describe('layout', () => {
|
||||
it('renders the default workplace search layout when on an organization view', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: true });
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
|
||||
expect(wrapper.type()).toEqual(WorkplaceSearchPageTemplate);
|
||||
});
|
||||
|
||||
it('renders the personal dashboard layout when not in an organization', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: false });
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
|
||||
expect(wrapper.type()).toEqual(PersonalDashboardLayout);
|
||||
});
|
||||
|
@ -125,7 +83,7 @@ describe('AddSourceList', () => {
|
|||
|
||||
it('renders a breadcrumb fallback while data is loading', () => {
|
||||
setMockValues({ ...mockValues, dataLoading: true, sourceConfigData: {} });
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
|
||||
expect(wrapper.prop('pageChrome')).toEqual(['Sources', 'Add Source', '...']);
|
||||
});
|
||||
|
@ -135,26 +93,24 @@ describe('AddSourceList', () => {
|
|||
...mockValues,
|
||||
addSourceCurrentStep: AddSourceSteps.ConfigCompletedStep,
|
||||
});
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
expect(wrapper.find(ConfigCompleted).prop('showFeedbackLink')).toEqual(false);
|
||||
wrapper.find(ConfigCompleted).prop('advanceStep')();
|
||||
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/connect');
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep);
|
||||
});
|
||||
|
||||
it('renders Config Completed step with feedback for external connectors', () => {
|
||||
mockUseParams.mockReturnValue({ serviceType: 'external' });
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
sourceConfigData: { ...sourceConfigData, serviceType: 'external' },
|
||||
addSourceCurrentStep: AddSourceSteps.ConfigCompletedStep,
|
||||
});
|
||||
const wrapper = shallow(
|
||||
<AddSource sourceData={{ ...staticSourceData[1], serviceType: 'external' }} />
|
||||
);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
expect(wrapper.find(ConfigCompleted).prop('showFeedbackLink')).toEqual(true);
|
||||
wrapper.find(ConfigCompleted).prop('advanceStep')();
|
||||
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/connect');
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep);
|
||||
});
|
||||
|
||||
|
@ -163,13 +119,13 @@ describe('AddSourceList', () => {
|
|||
...mockValues,
|
||||
addSourceCurrentStep: AddSourceSteps.SaveConfigStep,
|
||||
});
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
const saveConfig = wrapper.find(SaveConfig);
|
||||
saveConfig.prop('advanceStep')();
|
||||
saveConfig.prop('goBackStep')!();
|
||||
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConfigIntroStep);
|
||||
expect(saveSourceConfig).toHaveBeenCalled();
|
||||
|
||||
saveConfig.prop('goBackStep')!();
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/intro');
|
||||
});
|
||||
|
||||
it('renders Connect Instance step', () => {
|
||||
|
@ -178,10 +134,11 @@ describe('AddSourceList', () => {
|
|||
sourceConfigData,
|
||||
addSourceCurrentStep: AddSourceSteps.ConnectInstanceStep,
|
||||
});
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} connect />);
|
||||
|
||||
const wrapper = shallow(<AddSource />);
|
||||
wrapper.find(ConnectInstance).prop('onFormCreated')('foo');
|
||||
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/connect');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources');
|
||||
});
|
||||
|
||||
it('renders Configure Oauth step', () => {
|
||||
|
@ -189,11 +146,11 @@ describe('AddSourceList', () => {
|
|||
...mockValues,
|
||||
addSourceCurrentStep: AddSourceSteps.ConfigureOauthStep,
|
||||
});
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
|
||||
wrapper.find(ConfigureOauth).prop('onFormCreated')('foo');
|
||||
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/connect');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources');
|
||||
});
|
||||
|
||||
it('renders Reauthenticate step', () => {
|
||||
|
@ -201,23 +158,8 @@ describe('AddSourceList', () => {
|
|||
...mockValues,
|
||||
addSourceCurrentStep: AddSourceSteps.ReauthenticateStep,
|
||||
});
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<AddSource />);
|
||||
|
||||
expect(wrapper.find(Reauthenticate)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders Config Choice step', () => {
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
addSourceCurrentStep: AddSourceSteps.ChoiceStep,
|
||||
});
|
||||
const wrapper = shallow(<AddSource sourceData={staticSourceData[1]} />);
|
||||
const advance = wrapper.find(ConfigurationChoice).prop('goToInternalStep');
|
||||
expect(advance).toBeDefined();
|
||||
if (advance) {
|
||||
advance();
|
||||
}
|
||||
|
||||
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,29 +7,28 @@
|
|||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { flashSuccessToast } from '../../../../../shared/flash_messages';
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
import { AppLogic } from '../../../../app_logic';
|
||||
import {
|
||||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../components/layout';
|
||||
import { NAV } from '../../../../constants';
|
||||
import { SOURCES_PATH, getSourcesPath, getAddPath } from '../../../../routes';
|
||||
import { SOURCES_PATH, getSourcesPath, getAddPath, ADD_SOURCE_PATH } from '../../../../routes';
|
||||
|
||||
import { hasMultipleConnectorOptions } from '../../../../utils';
|
||||
|
||||
import { SourcesLogic } from '../../sources_logic';
|
||||
import { getSourceData } from '../../source_data';
|
||||
|
||||
import { AddSourceHeader } from './add_source_header';
|
||||
import { AddSourceLogic, AddSourceProps, AddSourceSteps } from './add_source_logic';
|
||||
import { AddSourceLogic, AddSourceSteps } from './add_source_logic';
|
||||
import { ConfigCompleted } from './config_completed';
|
||||
import { ConfigurationChoice } from './configuration_choice';
|
||||
import { ConfigurationIntro } from './configuration_intro';
|
||||
import { ConfigureOauth } from './configure_oauth';
|
||||
import { ConnectInstance } from './connect_instance';
|
||||
import { Reauthenticate } from './reauthenticate';
|
||||
|
@ -37,27 +36,42 @@ import { SaveConfig } from './save_config';
|
|||
|
||||
import './add_source.scss';
|
||||
|
||||
export const AddSource: React.FC<AddSourceProps> = (props) => {
|
||||
const { initializeAddSource, setAddSourceStep, saveSourceConfig, resetSourceState } =
|
||||
useActions(AddSourceLogic);
|
||||
const { addSourceCurrentStep, sourceConfigData, dataLoading } = useValues(AddSourceLogic);
|
||||
const { name, categories, needsPermissions, accountContextOnly, privateSourcesEnabled } =
|
||||
sourceConfigData;
|
||||
const { serviceType, configuration, features, objTypes } = props.sourceData;
|
||||
const addPath = getAddPath(serviceType);
|
||||
export const AddSource: React.FC = () => {
|
||||
const { serviceType, initialStep } = useParams<{ serviceType: string; initialStep?: string }>();
|
||||
const addSourceLogic = AddSourceLogic({ serviceType, initialStep });
|
||||
const { getSourceConfigData, setAddSourceStep, saveSourceConfig, resetSourceState } =
|
||||
useActions(addSourceLogic);
|
||||
const { addSourceCurrentStep, sourceConfigData, dataLoading } = useValues(addSourceLogic);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
const { externalConfigured } = useValues(SourcesLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { navigateToUrl } = useValues(KibanaLogic);
|
||||
|
||||
useEffect(() => {
|
||||
initializeAddSource(props);
|
||||
getSourceConfigData();
|
||||
return resetSourceState;
|
||||
}, []);
|
||||
}, [serviceType]);
|
||||
|
||||
const goToConfigurationIntro = () => setAddSourceStep(AddSourceSteps.ConfigIntroStep);
|
||||
const goToSaveConfig = () => setAddSourceStep(AddSourceSteps.SaveConfigStep);
|
||||
const sourceData = getSourceData(serviceType);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { configuration, features, objTypes } = sourceData;
|
||||
|
||||
const { name, categories, needsPermissions, accountContextOnly, privateSourcesEnabled } =
|
||||
sourceConfigData;
|
||||
|
||||
if (!hasPlatinumLicense && accountContextOnly) {
|
||||
navigateToUrl(getSourcesPath(ADD_SOURCE_PATH, isOrganization));
|
||||
}
|
||||
|
||||
const goToConfigurationIntro = () =>
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
`${getSourcesPath(getAddPath(serviceType), isOrganization)}/intro`
|
||||
);
|
||||
const setConfigCompletedStep = () => setAddSourceStep(AddSourceSteps.ConfigCompletedStep);
|
||||
const goToConfigCompleted = () => saveSourceConfig(false, setConfigCompletedStep);
|
||||
const goToChoice = () => setAddSourceStep(AddSourceSteps.ChoiceStep);
|
||||
const FORM_SOURCE_ADDED_SUCCESS_MESSAGE = i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.contentSource.formSourceAddedSuccessMessage',
|
||||
{
|
||||
|
@ -66,11 +80,7 @@ export const AddSource: React.FC<AddSourceProps> = (props) => {
|
|||
}
|
||||
);
|
||||
|
||||
const goToConnectInstance = () => {
|
||||
setAddSourceStep(AddSourceSteps.ConnectInstanceStep);
|
||||
KibanaLogic.values.navigateToUrl(`${getSourcesPath(addPath, isOrganization)}/connect`);
|
||||
};
|
||||
|
||||
const goToConnectInstance = () => setAddSourceStep(AddSourceSteps.ConnectInstanceStep);
|
||||
const goToFormSourceCreated = () => {
|
||||
KibanaLogic.values.navigateToUrl(`${getSourcesPath(SOURCES_PATH, isOrganization)}`);
|
||||
flashSuccessToast(FORM_SOURCE_ADDED_SUCCESS_MESSAGE);
|
||||
|
@ -81,18 +91,6 @@ export const AddSource: React.FC<AddSourceProps> = (props) => {
|
|||
|
||||
return (
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, name || '...']} isLoading={dataLoading}>
|
||||
{addSourceCurrentStep === AddSourceSteps.ConfigIntroStep && (
|
||||
<ConfigurationIntro
|
||||
name={name}
|
||||
// TODO: Remove this once we can support multiple external connectors
|
||||
advanceStep={
|
||||
hasMultipleConnectorOptions(props.sourceData) && !externalConfigured
|
||||
? goToChoice
|
||||
: goToSaveConfig
|
||||
}
|
||||
header={header}
|
||||
/>
|
||||
)}
|
||||
{addSourceCurrentStep === AddSourceSteps.SaveConfigStep && (
|
||||
<SaveConfig
|
||||
name={name}
|
||||
|
@ -130,9 +128,6 @@ export const AddSource: React.FC<AddSourceProps> = (props) => {
|
|||
{addSourceCurrentStep === AddSourceSteps.ReauthenticateStep && (
|
||||
<Reauthenticate name={name} header={header} />
|
||||
)}
|
||||
{addSourceCurrentStep === AddSourceSteps.ChoiceStep && (
|
||||
<ConfigurationChoice sourceData={props.sourceData} goToInternalStep={goToSaveConfig} />
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 '../../../../../__mocks__/shallow_useeffect.mock';
|
||||
import { setMockValues, mockKibanaValues } from '../../../../../__mocks__/kea_logic';
|
||||
|
||||
import { mockUseParams } from '../../../../../__mocks__/react_router';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../components/layout';
|
||||
|
||||
import { getSourceData } from '../../source_data';
|
||||
|
||||
import { AddSourceChoice } from './add_source_choice';
|
||||
import { ConfigurationChoice } from './configuration_choice';
|
||||
|
||||
describe('AddSourceChoice', () => {
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
|
||||
const mockValues = {
|
||||
isOrganization: true,
|
||||
hasPlatinumLicense: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseParams.mockReturnValue({ serviceType: 'share_point' });
|
||||
});
|
||||
|
||||
it('returns null if there is no matching source data for the service type', () => {
|
||||
mockUseParams.mockReturnValue({ serviceType: 'doesnt_exist' });
|
||||
setMockValues(mockValues);
|
||||
|
||||
const wrapper = shallow(<AddSourceChoice />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('redirects to root add source path if user does not have a platinum license and the service is account context only', () => {
|
||||
mockUseParams.mockReturnValue({ serviceType: 'slack' });
|
||||
setMockValues({ ...mockValues, hasPlatinumLicense: false });
|
||||
|
||||
shallow(<AddSourceChoice />);
|
||||
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add');
|
||||
});
|
||||
|
||||
describe('layout', () => {
|
||||
it('renders the default workplace search layout when on an organization view', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: true });
|
||||
const wrapper = shallow(<AddSourceChoice />);
|
||||
|
||||
expect(wrapper.type()).toEqual(WorkplaceSearchPageTemplate);
|
||||
});
|
||||
|
||||
it('renders the personal dashboard layout when not in an organization', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: false });
|
||||
const wrapper = shallow(<AddSourceChoice />);
|
||||
|
||||
expect(wrapper.type()).toEqual(PersonalDashboardLayout);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders Config Choice step', () => {
|
||||
setMockValues(mockValues);
|
||||
const wrapper = shallow(<AddSourceChoice />);
|
||||
|
||||
expect(wrapper.find(ConfigurationChoice).prop('sourceData')).toEqual(
|
||||
getSourceData('share_point')
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
|
||||
import { AppLogic } from '../../../../app_logic';
|
||||
import {
|
||||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../components/layout';
|
||||
import { NAV } from '../../../../constants';
|
||||
|
||||
import { getSourcesPath, ADD_SOURCE_PATH } from '../../../../routes';
|
||||
|
||||
import { getSourceData } from '../../source_data';
|
||||
|
||||
import { ConfigurationChoice } from './configuration_choice';
|
||||
|
||||
import './add_source.scss';
|
||||
|
||||
export const AddSourceChoice: React.FC = () => {
|
||||
const { serviceType } = useParams<{ serviceType: string }>();
|
||||
const sourceData = getSourceData(serviceType);
|
||||
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { navigateToUrl } = useValues(KibanaLogic);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name, accountContextOnly } = sourceData;
|
||||
|
||||
if (!hasPlatinumLicense && accountContextOnly) {
|
||||
navigateToUrl(getSourcesPath(ADD_SOURCE_PATH, isOrganization));
|
||||
}
|
||||
|
||||
const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
|
||||
|
||||
return (
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, name]}>
|
||||
<ConfigurationChoice sourceData={sourceData} />
|
||||
</Layout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 '../../../../../__mocks__/shallow_useeffect.mock';
|
||||
import { setMockValues } from '../../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../../__mocks__/react_router';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../components/layout';
|
||||
|
||||
import { AddSourceIntro } from './add_source_intro';
|
||||
import { ConfigurationIntro } from './configuration_intro';
|
||||
|
||||
describe('AddSourceList', () => {
|
||||
const mockValues = {
|
||||
isOrganization: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseParams.mockReturnValue({ serviceType: 'share_point' });
|
||||
});
|
||||
|
||||
it('returns null if there is no matching source data for the service type', () => {
|
||||
mockUseParams.mockReturnValue({ serviceType: 'doesnt_exist' });
|
||||
setMockValues(mockValues);
|
||||
|
||||
const wrapper = shallow(<AddSourceIntro />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('sends the user to a choice view when there are multiple connector options', () => {
|
||||
setMockValues(mockValues);
|
||||
|
||||
const wrapper = shallow(<AddSourceIntro />);
|
||||
|
||||
expect(wrapper.find(ConfigurationIntro).prop('advanceStepTo')).toEqual(
|
||||
'/sources/add/share_point/choice'
|
||||
);
|
||||
});
|
||||
|
||||
it('sends the user to the add source view by default', () => {
|
||||
mockUseParams.mockReturnValue({ serviceType: 'slack' });
|
||||
setMockValues(mockValues);
|
||||
|
||||
const wrapper = shallow(<AddSourceIntro />);
|
||||
|
||||
expect(wrapper.find(ConfigurationIntro).prop('advanceStepTo')).toEqual('/sources/add/slack/');
|
||||
});
|
||||
|
||||
describe('layout', () => {
|
||||
it('renders the default workplace search layout when on an organization view', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: true });
|
||||
|
||||
const wrapper = shallow(<AddSourceIntro />);
|
||||
|
||||
expect(wrapper.type()).toEqual(WorkplaceSearchPageTemplate);
|
||||
});
|
||||
|
||||
it('renders the personal dashboard layout when not in an organization', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: false });
|
||||
|
||||
const wrapper = shallow(<AddSourceIntro />);
|
||||
|
||||
expect(wrapper.type()).toEqual(PersonalDashboardLayout);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
|
||||
import { AppLogic } from '../../../../app_logic';
|
||||
import {
|
||||
WorkplaceSearchPageTemplate,
|
||||
PersonalDashboardLayout,
|
||||
} from '../../../../components/layout';
|
||||
import { NAV } from '../../../../constants';
|
||||
import { getSourcesPath, ADD_SOURCE_PATH, getAddPath } from '../../../../routes';
|
||||
|
||||
import { getSourceData, hasMultipleConnectorOptions } from '../../source_data';
|
||||
|
||||
import { AddSourceHeader } from './add_source_header';
|
||||
import { ConfigurationIntro } from './configuration_intro';
|
||||
|
||||
import './add_source.scss';
|
||||
|
||||
export const AddSourceIntro: React.FC = () => {
|
||||
const { serviceType } = useParams<{ serviceType: string }>();
|
||||
const sourceData = getSourceData(serviceType);
|
||||
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { navigateToUrl } = useValues(KibanaLogic);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name, categories = [], accountContextOnly } = sourceData;
|
||||
|
||||
if (!hasPlatinumLicense && accountContextOnly) {
|
||||
navigateToUrl(getSourcesPath(ADD_SOURCE_PATH, isOrganization));
|
||||
}
|
||||
|
||||
const header = <AddSourceHeader name={name} serviceType={serviceType} categories={categories} />;
|
||||
const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
|
||||
const to =
|
||||
`${getSourcesPath(getAddPath(serviceType), isOrganization)}/` +
|
||||
(hasMultipleConnectorOptions(serviceType) ? 'choice' : '');
|
||||
return (
|
||||
<Layout pageChrome={[NAV.SOURCES, NAV.ADD_SOURCE, name]}>
|
||||
<ConfigurationIntro name={name} advanceStepTo={to} header={header} />
|
||||
</Layout>
|
||||
);
|
||||
};
|
|
@ -15,7 +15,6 @@ import { sourceConfigData } from '../../../../__mocks__/content_sources.mock';
|
|||
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { docLinks } from '../../../../../shared/doc_links';
|
||||
import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers';
|
||||
|
||||
jest.mock('../../../../app_logic', () => ({
|
||||
|
@ -23,10 +22,9 @@ jest.mock('../../../../app_logic', () => ({
|
|||
}));
|
||||
import { AppLogic } from '../../../../app_logic';
|
||||
|
||||
import { SOURCE_NAMES, SOURCE_OBJ_TYPES } from '../../../../constants';
|
||||
import { SOURCES_PATH, PRIVATE_SOURCES_PATH, getSourcesPath } from '../../../../routes';
|
||||
import { FeatureIds } from '../../../../types';
|
||||
import { PERSONAL_DASHBOARD_SOURCE_ERROR } from '../../constants';
|
||||
import { staticSourceData } from '../../source_data';
|
||||
import { SourcesLogic } from '../../sources_logic';
|
||||
|
||||
import { ExternalConnectorLogic } from './add_external_connector/external_connector_logic';
|
||||
|
@ -37,7 +35,6 @@ import {
|
|||
SourceConnectData,
|
||||
OrganizationsMap,
|
||||
AddSourceValues,
|
||||
AddSourceProps,
|
||||
} from './add_source_logic';
|
||||
|
||||
describe('AddSourceLogic', () => {
|
||||
|
@ -47,8 +44,7 @@ describe('AddSourceLogic', () => {
|
|||
const { clearFlashMessages, flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers;
|
||||
|
||||
const DEFAULT_VALUES: AddSourceValues = {
|
||||
addSourceCurrentStep: AddSourceSteps.ConfigIntroStep,
|
||||
addSourceProps: {} as AddSourceProps,
|
||||
addSourceCurrentStep: null,
|
||||
dataLoading: true,
|
||||
sectionLoading: true,
|
||||
buttonLoading: false,
|
||||
|
@ -62,11 +58,11 @@ describe('AddSourceLogic', () => {
|
|||
sourceConfigData: {} as SourceConfigData,
|
||||
sourceConnectData: {} as SourceConnectData,
|
||||
oauthConfigCompleted: false,
|
||||
currentServiceType: '',
|
||||
githubOrganizations: [],
|
||||
selectedGithubOrganizationsMap: {} as OrganizationsMap,
|
||||
selectedGithubOrganizations: [],
|
||||
preContentSourceId: '',
|
||||
sourceData: staticSourceData[0],
|
||||
};
|
||||
|
||||
const sourceConnectData = {
|
||||
|
@ -79,40 +75,13 @@ describe('AddSourceLogic', () => {
|
|||
serviceType: 'github',
|
||||
githubOrganizations: ['foo', 'bar'],
|
||||
};
|
||||
const DEFAULT_SERVICE_TYPE = {
|
||||
name: SOURCE_NAMES.BOX,
|
||||
iconName: SOURCE_NAMES.BOX,
|
||||
serviceType: 'box',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: true,
|
||||
needsBaseUrl: false,
|
||||
documentationUrl: docLinks.workplaceSearchBox,
|
||||
applicationPortalUrl: 'https://app.box.com/developers/console',
|
||||
},
|
||||
objTypes: [SOURCE_OBJ_TYPES.FOLDERS, SOURCE_OBJ_TYPES.ALL_FILES],
|
||||
features: {
|
||||
basicOrgContext: [
|
||||
FeatureIds.SyncFrequency,
|
||||
FeatureIds.SyncedItems,
|
||||
FeatureIds.GlobalAccessPermissions,
|
||||
],
|
||||
basicOrgContextExcludedFeatures: [FeatureIds.DocumentLevelPermissions],
|
||||
platinumOrgContext: [FeatureIds.SyncFrequency, FeatureIds.SyncedItems],
|
||||
platinumPrivateContext: [
|
||||
FeatureIds.Private,
|
||||
FeatureIds.SyncFrequency,
|
||||
FeatureIds.SyncedItems,
|
||||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
};
|
||||
const DEFAULT_SERVICE_TYPE = 'box';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
ExternalConnectorLogic.mount();
|
||||
SourcesLogic.mount();
|
||||
mount();
|
||||
mount({}, { serviceType: 'box' });
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
|
@ -215,7 +184,6 @@ describe('AddSourceLogic', () => {
|
|||
oauthConfigCompleted: true,
|
||||
dataLoading: false,
|
||||
sectionLoading: false,
|
||||
currentServiceType: config.serviceType,
|
||||
githubOrganizations: config.githubOrganizations,
|
||||
});
|
||||
});
|
||||
|
@ -286,140 +254,90 @@ describe('AddSourceLogic', () => {
|
|||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('initializeAddSource', () => {
|
||||
const addSourceProps = { sourceData: DEFAULT_SERVICE_TYPE };
|
||||
const getSourceConfigDataSpy = jest.spyOn(AddSourceLogic.actions, 'getSourceConfigData');
|
||||
const setAddSourcePropsSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceProps');
|
||||
|
||||
AddSourceLogic.actions.initializeAddSource(addSourceProps);
|
||||
|
||||
expect(setAddSourcePropsSpy).toHaveBeenCalledWith({ addSourceProps });
|
||||
expect(getSourceConfigDataSpy).toHaveBeenCalledWith('box', addSourceProps);
|
||||
});
|
||||
|
||||
describe('setFirstStep', () => {
|
||||
it('sets intro as first step', () => {
|
||||
it('sets save config as first step if unconfigured', () => {
|
||||
mount(
|
||||
{
|
||||
sourceConfigData: {
|
||||
...sourceConfigData,
|
||||
configured: false,
|
||||
},
|
||||
},
|
||||
{ serviceType: DEFAULT_SERVICE_TYPE }
|
||||
);
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = { sourceData: DEFAULT_SERVICE_TYPE };
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConfigIntroStep);
|
||||
AddSourceLogic.actions.setFirstStep();
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
|
||||
});
|
||||
|
||||
it('sets connect as first step', () => {
|
||||
mount({ sourceConfigData }, { serviceType: DEFAULT_SERVICE_TYPE, initialStep: 'connect' });
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = { sourceData: DEFAULT_SERVICE_TYPE, connect: true };
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
|
||||
AddSourceLogic.actions.setFirstStep();
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep);
|
||||
});
|
||||
|
||||
it('sets configure as first step', () => {
|
||||
mount(
|
||||
{ sourceConfigData },
|
||||
{ serviceType: DEFAULT_SERVICE_TYPE, initialStep: 'configure' }
|
||||
);
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = { sourceData: DEFAULT_SERVICE_TYPE, configure: true };
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
|
||||
AddSourceLogic.actions.setFirstStep();
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConfigureOauthStep);
|
||||
});
|
||||
|
||||
it('sets reAuthenticate as first step', () => {
|
||||
it('sets reauthenticate as first step', () => {
|
||||
mount(
|
||||
{ sourceConfigData },
|
||||
{ serviceType: DEFAULT_SERVICE_TYPE, initialStep: 'reauthenticate' }
|
||||
);
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = { sourceData: DEFAULT_SERVICE_TYPE, reAuthenticate: true };
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
|
||||
AddSourceLogic.actions.setFirstStep();
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ReauthenticateStep);
|
||||
});
|
||||
it('sets SaveConfig as first step for external connectors', () => {
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = {
|
||||
sourceData: {
|
||||
...DEFAULT_SERVICE_TYPE,
|
||||
serviceType: 'external',
|
||||
},
|
||||
};
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
|
||||
});
|
||||
it('sets SaveConfigStep for when external connector is available and configured', () => {
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = {
|
||||
sourceData: {
|
||||
...DEFAULT_SERVICE_TYPE,
|
||||
externalConnectorAvailable: true,
|
||||
},
|
||||
};
|
||||
AddSourceLogic.actions.setSourceConfigData({
|
||||
...sourceConfigData,
|
||||
serviceType: 'external',
|
||||
configured: false,
|
||||
});
|
||||
SourcesLogic.mount();
|
||||
SourcesLogic.actions.onInitializeSources({
|
||||
contentSources: [],
|
||||
serviceTypes: [
|
||||
{
|
||||
serviceType: 'external',
|
||||
it('sets connect step if configured', () => {
|
||||
mount(
|
||||
{
|
||||
sourceConfigData: {
|
||||
...sourceConfigData,
|
||||
configured: true,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
|
||||
});
|
||||
it('sets Connect step when configured and external connector is available and configured', () => {
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = {
|
||||
sourceData: {
|
||||
...DEFAULT_SERVICE_TYPE,
|
||||
externalConnectorAvailable: true,
|
||||
configured: true,
|
||||
},
|
||||
};
|
||||
AddSourceLogic.actions.setSourceConfigData({
|
||||
...sourceConfigData,
|
||||
serviceType: 'external',
|
||||
configured: true,
|
||||
});
|
||||
SourcesLogic.mount();
|
||||
SourcesLogic.actions.onInitializeSources({
|
||||
contentSources: [],
|
||||
serviceTypes: [
|
||||
{
|
||||
serviceType: 'external',
|
||||
configured: true,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
{ serviceType: DEFAULT_SERVICE_TYPE }
|
||||
);
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
AddSourceLogic.actions.setFirstStep();
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep);
|
||||
});
|
||||
it('sets Connect step when external and fully configured', () => {
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
const addSourceProps = {
|
||||
sourceData: {
|
||||
...DEFAULT_SERVICE_TYPE,
|
||||
serviceType: 'external',
|
||||
},
|
||||
};
|
||||
AddSourceLogic.actions.setSourceConfigData({
|
||||
...sourceConfigData,
|
||||
configured: true,
|
||||
serviceType: 'external',
|
||||
configuredFields: { clientId: 'a', clientSecret: 'b' },
|
||||
});
|
||||
SourcesLogic.mount();
|
||||
SourcesLogic.actions.onInitializeSources({
|
||||
contentSources: [],
|
||||
serviceTypes: [
|
||||
{
|
||||
|
||||
it('sets connect step if external connector has client id and secret', () => {
|
||||
mount(
|
||||
{
|
||||
sourceConfigData: {
|
||||
...sourceConfigData,
|
||||
serviceType: 'external',
|
||||
configured: true,
|
||||
configuredFields: {
|
||||
clientId: 'test-client-id',
|
||||
clientSecret: 'test-client-secret',
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
AddSourceLogic.actions.setFirstStep(addSourceProps);
|
||||
},
|
||||
{ serviceType: DEFAULT_SERVICE_TYPE }
|
||||
);
|
||||
const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep');
|
||||
|
||||
AddSourceLogic.actions.setFirstStep();
|
||||
|
||||
expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep);
|
||||
});
|
||||
|
@ -541,30 +459,33 @@ describe('AddSourceLogic', () => {
|
|||
const setSourceConfigDataSpy = jest.spyOn(AddSourceLogic.actions, 'setSourceConfigData');
|
||||
http.get.mockReturnValue(Promise.resolve(sourceConfigData));
|
||||
|
||||
AddSourceLogic.actions.getSourceConfigData('github');
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/org/settings/connectors/github'
|
||||
);
|
||||
AddSourceLogic.actions.getSourceConfigData();
|
||||
await nextTick();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/org/settings/connectors/box'
|
||||
);
|
||||
expect(setSourceConfigDataSpy).toHaveBeenCalledWith(sourceConfigData);
|
||||
});
|
||||
|
||||
it('calls API and sets values and calls setFirstStep if AddSourceProps is provided', async () => {
|
||||
const setSourceConfigDataSpy = jest.spyOn(AddSourceLogic.actions, 'setSourceConfigData');
|
||||
const setFirstStepSpy = jest.spyOn(AddSourceLogic.actions, 'setFirstStep');
|
||||
const addSourceProps = { sourceData: DEFAULT_SERVICE_TYPE };
|
||||
|
||||
http.get.mockReturnValue(Promise.resolve(sourceConfigData));
|
||||
|
||||
AddSourceLogic.actions.getSourceConfigData('github', addSourceProps);
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/org/settings/connectors/github'
|
||||
);
|
||||
AddSourceLogic.actions.getSourceConfigData();
|
||||
await nextTick();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/org/settings/connectors/box'
|
||||
);
|
||||
expect(setSourceConfigDataSpy).toHaveBeenCalledWith(sourceConfigData);
|
||||
expect(setFirstStepSpy).toHaveBeenCalledWith(addSourceProps);
|
||||
expect(setFirstStepSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
itShowsServerErrorAsFlashMessage(http.get, () => {
|
||||
AddSourceLogic.actions.getSourceConfigData('github');
|
||||
AddSourceLogic.actions.getSourceConfigData();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -579,7 +500,7 @@ describe('AddSourceLogic', () => {
|
|||
);
|
||||
http.get.mockReturnValue(Promise.resolve(sourceConnectData));
|
||||
|
||||
AddSourceLogic.actions.getSourceConnectData('github', successCallback);
|
||||
AddSourceLogic.actions.getSourceConnectData(successCallback);
|
||||
|
||||
const query = {
|
||||
index_permissions: false,
|
||||
|
@ -588,7 +509,7 @@ describe('AddSourceLogic', () => {
|
|||
expect(clearFlashMessages).toHaveBeenCalled();
|
||||
expect(AddSourceLogic.values.buttonLoading).toEqual(true);
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/org/sources/github/prepare',
|
||||
'/internal/workplace_search/org/sources/box/prepare',
|
||||
{
|
||||
query,
|
||||
}
|
||||
|
@ -602,7 +523,7 @@ describe('AddSourceLogic', () => {
|
|||
it('passes query params', () => {
|
||||
AddSourceLogic.actions.setSourceSubdomainValue('subdomain');
|
||||
AddSourceLogic.actions.setSourceIndexPermissionsValue(true);
|
||||
AddSourceLogic.actions.getSourceConnectData('github', successCallback);
|
||||
AddSourceLogic.actions.getSourceConnectData(successCallback);
|
||||
|
||||
const query = {
|
||||
index_permissions: true,
|
||||
|
@ -610,7 +531,7 @@ describe('AddSourceLogic', () => {
|
|||
};
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/org/sources/github/prepare',
|
||||
'/internal/workplace_search/org/sources/box/prepare',
|
||||
{
|
||||
query,
|
||||
}
|
||||
|
@ -618,7 +539,7 @@ describe('AddSourceLogic', () => {
|
|||
});
|
||||
|
||||
itShowsServerErrorAsFlashMessage(http.get, () => {
|
||||
AddSourceLogic.actions.getSourceConnectData('github', successCallback);
|
||||
AddSourceLogic.actions.getSourceConnectData(successCallback);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -833,7 +754,7 @@ describe('AddSourceLogic', () => {
|
|||
const successCallback = jest.fn();
|
||||
const errorCallback = jest.fn();
|
||||
|
||||
const serviceType = 'zendesk';
|
||||
const serviceType = 'box';
|
||||
const login = 'login';
|
||||
const password = 'password';
|
||||
const indexPermissions = false;
|
||||
|
@ -859,7 +780,7 @@ describe('AddSourceLogic', () => {
|
|||
const setButtonNotLoadingSpy = jest.spyOn(AddSourceLogic.actions, 'setButtonNotLoading');
|
||||
http.post.mockReturnValue(Promise.resolve());
|
||||
|
||||
AddSourceLogic.actions.createContentSource(serviceType, successCallback, errorCallback);
|
||||
AddSourceLogic.actions.createContentSource(successCallback, errorCallback);
|
||||
|
||||
expect(clearFlashMessages).toHaveBeenCalled();
|
||||
expect(AddSourceLogic.values.buttonLoading).toEqual(true);
|
||||
|
@ -875,7 +796,7 @@ describe('AddSourceLogic', () => {
|
|||
const setButtonNotLoadingSpy = jest.spyOn(AddSourceLogic.actions, 'setButtonNotLoading');
|
||||
http.post.mockReturnValue(Promise.reject('this is an error'));
|
||||
|
||||
AddSourceLogic.actions.createContentSource(serviceType, successCallback, errorCallback);
|
||||
AddSourceLogic.actions.createContentSource(successCallback, errorCallback);
|
||||
await nextTick();
|
||||
|
||||
expect(setButtonNotLoadingSpy).toHaveBeenCalled();
|
||||
|
@ -891,10 +812,10 @@ describe('AddSourceLogic', () => {
|
|||
});
|
||||
|
||||
it('getSourceConnectData', () => {
|
||||
AddSourceLogic.actions.getSourceConnectData('github', jest.fn());
|
||||
AddSourceLogic.actions.getSourceConnectData(jest.fn());
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/workplace_search/account/sources/github/prepare',
|
||||
'/internal/workplace_search/account/sources/box/prepare',
|
||||
{ query: {} }
|
||||
);
|
||||
});
|
||||
|
@ -915,10 +836,10 @@ describe('AddSourceLogic', () => {
|
|||
});
|
||||
|
||||
it('createContentSource', () => {
|
||||
AddSourceLogic.actions.createContentSource('github', jest.fn());
|
||||
AddSourceLogic.actions.createContentSource(jest.fn());
|
||||
|
||||
expect(http.post).toHaveBeenCalledWith('/internal/workplace_search/account/create_source', {
|
||||
body: JSON.stringify({ service_type: 'github' }),
|
||||
body: JSON.stringify({ service_type: 'box' }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ import { AppLogic } from '../../../../app_logic';
|
|||
import { SOURCES_PATH, PRIVATE_SOURCES_PATH, getSourcesPath, getAddPath } from '../../../../routes';
|
||||
import { SourceDataItem } from '../../../../types';
|
||||
import { PERSONAL_DASHBOARD_SOURCE_ERROR } from '../../constants';
|
||||
import { getSourceData } from '../../source_data';
|
||||
import { SourcesLogic } from '../../sources_logic';
|
||||
|
||||
import {
|
||||
|
@ -31,20 +32,16 @@ import {
|
|||
} from './add_external_connector/external_connector_logic';
|
||||
|
||||
export interface AddSourceProps {
|
||||
sourceData: SourceDataItem;
|
||||
connect?: boolean;
|
||||
configure?: boolean;
|
||||
reAuthenticate?: boolean;
|
||||
serviceType: string;
|
||||
initialStep?: string;
|
||||
}
|
||||
|
||||
export enum AddSourceSteps {
|
||||
ConfigIntroStep = 'Config Intro',
|
||||
SaveConfigStep = 'Save Config',
|
||||
ConfigCompletedStep = 'Config Completed',
|
||||
ConnectInstanceStep = 'Connect Instance',
|
||||
ConfigureOauthStep = 'Configure Oauth',
|
||||
ReauthenticateStep = 'Reauthenticate',
|
||||
ChoiceStep = 'Choice',
|
||||
}
|
||||
|
||||
export interface OauthParams {
|
||||
|
@ -57,10 +54,6 @@ export interface OauthParams {
|
|||
}
|
||||
|
||||
export interface AddSourceActions {
|
||||
initializeAddSource: (addSourceProps: AddSourceProps) => { addSourceProps: AddSourceProps };
|
||||
setAddSourceProps: ({ addSourceProps }: { addSourceProps: AddSourceProps }) => {
|
||||
addSourceProps: AddSourceProps;
|
||||
};
|
||||
setAddSourceStep(addSourceCurrentStep: AddSourceSteps): AddSourceSteps;
|
||||
setSourceConfigData(sourceConfigData: SourceConfigData): SourceConfigData;
|
||||
setSourceConnectData(sourceConnectData: SourceConnectData): SourceConnectData;
|
||||
|
@ -76,10 +69,9 @@ export interface AddSourceActions {
|
|||
setSelectedGithubOrganizations(option: string): string;
|
||||
resetSourceState(): void;
|
||||
createContentSource(
|
||||
serviceType: string,
|
||||
successCallback: () => void,
|
||||
errorCallback?: () => void
|
||||
): { serviceType: string; successCallback(): void; errorCallback?(): void };
|
||||
): { successCallback(): void; errorCallback?(): void };
|
||||
saveSourceConfig(
|
||||
isUpdating: boolean,
|
||||
successCallback?: () => void
|
||||
|
@ -89,24 +81,22 @@ export interface AddSourceActions {
|
|||
params: OauthParams,
|
||||
isOrganization: boolean
|
||||
): { search: Search; params: OauthParams; isOrganization: boolean };
|
||||
getSourceConfigData(
|
||||
serviceType: string,
|
||||
addSourceProps?: AddSourceProps
|
||||
): { serviceType: string; addSourceProps: AddSourceProps | undefined };
|
||||
getSourceConnectData(
|
||||
serviceType: string,
|
||||
successCallback: (oauthUrl: string) => void
|
||||
): { serviceType: string; successCallback(oauthUrl: string): void };
|
||||
getSourceConfigData(): void;
|
||||
getSourceConnectData(successCallback: (oauthUrl: string) => void): {
|
||||
successCallback(oauthUrl: string): void;
|
||||
};
|
||||
getSourceReConnectData(sourceId: string): { sourceId: string };
|
||||
getPreContentSourceConfigData(): void;
|
||||
setButtonNotLoading(): void;
|
||||
setFirstStep(addSourceProps: AddSourceProps): { addSourceProps: AddSourceProps };
|
||||
setFirstStep(): void;
|
||||
}
|
||||
|
||||
export interface SourceConfigData {
|
||||
serviceType: string;
|
||||
baseServiceType?: string;
|
||||
name: string;
|
||||
configured: boolean;
|
||||
externalConnectorServiceDescribed?: boolean;
|
||||
categories: string[];
|
||||
needsPermissions?: boolean;
|
||||
privateSourcesEnabled: boolean;
|
||||
|
@ -133,8 +123,7 @@ export interface OrganizationsMap {
|
|||
}
|
||||
|
||||
export interface AddSourceValues {
|
||||
addSourceProps: AddSourceProps;
|
||||
addSourceCurrentStep: AddSourceSteps;
|
||||
addSourceCurrentStep: AddSourceSteps | null;
|
||||
dataLoading: boolean;
|
||||
sectionLoading: boolean;
|
||||
buttonLoading: boolean;
|
||||
|
@ -147,12 +136,12 @@ export interface AddSourceValues {
|
|||
indexPermissionsValue: boolean;
|
||||
sourceConfigData: SourceConfigData;
|
||||
sourceConnectData: SourceConnectData;
|
||||
currentServiceType: string;
|
||||
githubOrganizations: string[];
|
||||
selectedGithubOrganizationsMap: OrganizationsMap;
|
||||
selectedGithubOrganizations: string[];
|
||||
preContentSourceId: string;
|
||||
oauthConfigCompleted: boolean;
|
||||
sourceData: SourceDataItem | null;
|
||||
}
|
||||
|
||||
interface PreContentSourceResponse {
|
||||
|
@ -161,471 +150,436 @@ interface PreContentSourceResponse {
|
|||
githubOrganizations: string[];
|
||||
}
|
||||
|
||||
export const AddSourceLogic = kea<MakeLogicType<AddSourceValues, AddSourceActions>>({
|
||||
path: ['enterprise_search', 'workplace_search', 'add_source_logic'],
|
||||
actions: {
|
||||
initializeAddSource: (addSourceProps: AddSourceProps) => ({ addSourceProps }),
|
||||
setAddSourceProps: ({ addSourceProps }: { addSourceProps: AddSourceProps }) => ({
|
||||
addSourceProps,
|
||||
}),
|
||||
setAddSourceStep: (addSourceCurrentStep: AddSourceSteps) => addSourceCurrentStep,
|
||||
setSourceConfigData: (sourceConfigData: SourceConfigData) => sourceConfigData,
|
||||
setSourceConnectData: (sourceConnectData: SourceConnectData) => sourceConnectData,
|
||||
setClientIdValue: (clientIdValue: string) => clientIdValue,
|
||||
setClientSecretValue: (clientSecretValue: string) => clientSecretValue,
|
||||
setBaseUrlValue: (baseUrlValue: string) => baseUrlValue,
|
||||
setSourceLoginValue: (loginValue: string) => loginValue,
|
||||
setSourcePasswordValue: (passwordValue: string) => passwordValue,
|
||||
setSourceSubdomainValue: (subdomainValue: string) => subdomainValue,
|
||||
setSourceIndexPermissionsValue: (indexPermissionsValue: boolean) => indexPermissionsValue,
|
||||
setPreContentSourceConfigData: (data: PreContentSourceResponse) => data,
|
||||
setPreContentSourceId: (preContentSourceId: string) => preContentSourceId,
|
||||
setSelectedGithubOrganizations: (option: string) => option,
|
||||
getSourceConfigData: (serviceType: string, addSourceProps?: AddSourceProps) => ({
|
||||
serviceType,
|
||||
addSourceProps,
|
||||
}),
|
||||
getSourceConnectData: (serviceType: string, successCallback: (oauthUrl: string) => string) => ({
|
||||
serviceType,
|
||||
successCallback,
|
||||
}),
|
||||
getSourceReConnectData: (sourceId: string) => ({ sourceId }),
|
||||
getPreContentSourceConfigData: () => true,
|
||||
saveSourceConfig: (isUpdating: boolean, successCallback?: () => void) => ({
|
||||
isUpdating,
|
||||
successCallback,
|
||||
}),
|
||||
saveSourceParams: (search: Search, params: OauthParams, isOrganization: boolean) => ({
|
||||
search,
|
||||
params,
|
||||
isOrganization,
|
||||
}),
|
||||
createContentSource: (
|
||||
serviceType: string,
|
||||
successCallback: () => void,
|
||||
errorCallback?: () => void
|
||||
) => ({ serviceType, successCallback, errorCallback }),
|
||||
resetSourceState: () => true,
|
||||
setButtonNotLoading: () => false,
|
||||
setFirstStep: (addSourceProps) => ({ addSourceProps }),
|
||||
},
|
||||
reducers: {
|
||||
addSourceProps: [
|
||||
{} as AddSourceProps,
|
||||
{
|
||||
setAddSourceProps: (_, { addSourceProps }) => addSourceProps,
|
||||
},
|
||||
],
|
||||
addSourceCurrentStep: [
|
||||
AddSourceSteps.ConfigIntroStep,
|
||||
{
|
||||
setAddSourceStep: (_, addSourceCurrentStep) => addSourceCurrentStep,
|
||||
},
|
||||
],
|
||||
sourceConfigData: [
|
||||
{} as SourceConfigData,
|
||||
{
|
||||
setSourceConfigData: (_, sourceConfigData) => sourceConfigData,
|
||||
},
|
||||
],
|
||||
sourceConnectData: [
|
||||
{} as SourceConnectData,
|
||||
{
|
||||
setSourceConnectData: (_, sourceConnectData) => sourceConnectData,
|
||||
},
|
||||
],
|
||||
dataLoading: [
|
||||
true,
|
||||
{
|
||||
setSourceConfigData: () => false,
|
||||
resetSourceState: () => false,
|
||||
setPreContentSourceConfigData: () => false,
|
||||
getSourceConfigData: () => true,
|
||||
},
|
||||
],
|
||||
buttonLoading: [
|
||||
false,
|
||||
{
|
||||
setButtonNotLoading: () => false,
|
||||
setSourceConnectData: () => false,
|
||||
setSourceConfigData: () => false,
|
||||
resetSourceState: () => false,
|
||||
saveSourceConfig: () => true,
|
||||
getSourceConnectData: () => true,
|
||||
createContentSource: () => true,
|
||||
},
|
||||
],
|
||||
sectionLoading: [
|
||||
true,
|
||||
{
|
||||
getPreContentSourceConfigData: () => true,
|
||||
setPreContentSourceConfigData: () => false,
|
||||
},
|
||||
],
|
||||
clientIdValue: [
|
||||
'',
|
||||
{
|
||||
setClientIdValue: (_, clientIdValue) => clientIdValue,
|
||||
setSourceConfigData: (_, { configuredFields: { clientId } }) => clientId || '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
clientSecretValue: [
|
||||
'',
|
||||
{
|
||||
setClientSecretValue: (_, clientSecretValue) => clientSecretValue,
|
||||
setSourceConfigData: (_, { configuredFields: { clientSecret } }) => clientSecret || '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
baseUrlValue: [
|
||||
'',
|
||||
{
|
||||
setBaseUrlValue: (_, baseUrlValue) => baseUrlValue,
|
||||
setSourceConfigData: (_, { configuredFields: { baseUrl } }) => baseUrl || '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
loginValue: [
|
||||
'',
|
||||
{
|
||||
setSourceLoginValue: (_, loginValue) => loginValue,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
passwordValue: [
|
||||
'',
|
||||
{
|
||||
setSourcePasswordValue: (_, passwordValue) => passwordValue,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
subdomainValue: [
|
||||
'',
|
||||
{
|
||||
setSourceSubdomainValue: (_, subdomainValue) => subdomainValue,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
indexPermissionsValue: [
|
||||
false,
|
||||
{
|
||||
setSourceIndexPermissionsValue: (_, indexPermissionsValue) => indexPermissionsValue,
|
||||
resetSourceState: () => false,
|
||||
},
|
||||
],
|
||||
currentServiceType: [
|
||||
'',
|
||||
{
|
||||
setPreContentSourceConfigData: (_, { serviceType }) => serviceType,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
githubOrganizations: [
|
||||
[],
|
||||
{
|
||||
setPreContentSourceConfigData: (_, { githubOrganizations }) => githubOrganizations,
|
||||
resetSourceState: () => [],
|
||||
},
|
||||
],
|
||||
selectedGithubOrganizationsMap: [
|
||||
{} as OrganizationsMap,
|
||||
{
|
||||
setSelectedGithubOrganizations: (state, option) => ({
|
||||
...state,
|
||||
...{ [option]: !state[option] },
|
||||
}),
|
||||
resetSourceState: () => ({}),
|
||||
},
|
||||
],
|
||||
preContentSourceId: [
|
||||
'',
|
||||
{
|
||||
setPreContentSourceId: (_, preContentSourceId) => preContentSourceId,
|
||||
setPreContentSourceConfigData: () => '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
oauthConfigCompleted: [
|
||||
false,
|
||||
{
|
||||
setPreContentSourceConfigData: () => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
selectors: ({ selectors }) => ({
|
||||
selectedGithubOrganizations: [
|
||||
() => [selectors.selectedGithubOrganizationsMap],
|
||||
(orgsMap) => keys(pickBy(orgsMap)),
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
initializeAddSource: ({ addSourceProps }) => {
|
||||
const { serviceType } = addSourceProps.sourceData;
|
||||
actions.setAddSourceProps({ addSourceProps });
|
||||
actions.getSourceConfigData(serviceType, addSourceProps);
|
||||
export const AddSourceLogic = kea<MakeLogicType<AddSourceValues, AddSourceActions, AddSourceProps>>(
|
||||
{
|
||||
path: ['enterprise_search', 'workplace_search', 'add_source_logic'],
|
||||
actions: {
|
||||
setAddSourceStep: (addSourceCurrentStep: AddSourceSteps) => addSourceCurrentStep,
|
||||
setSourceConfigData: (sourceConfigData: SourceConfigData) => sourceConfigData,
|
||||
setSourceConnectData: (sourceConnectData: SourceConnectData) => sourceConnectData,
|
||||
setClientIdValue: (clientIdValue: string) => clientIdValue,
|
||||
setClientSecretValue: (clientSecretValue: string) => clientSecretValue,
|
||||
setBaseUrlValue: (baseUrlValue: string) => baseUrlValue,
|
||||
setSourceLoginValue: (loginValue: string) => loginValue,
|
||||
setSourcePasswordValue: (passwordValue: string) => passwordValue,
|
||||
setSourceSubdomainValue: (subdomainValue: string) => subdomainValue,
|
||||
setSourceIndexPermissionsValue: (indexPermissionsValue: boolean) => indexPermissionsValue,
|
||||
setPreContentSourceConfigData: (data: PreContentSourceResponse) => data,
|
||||
setPreContentSourceId: (preContentSourceId: string) => preContentSourceId,
|
||||
setSelectedGithubOrganizations: (option: string) => option,
|
||||
getSourceConfigData: () => true,
|
||||
getSourceConnectData: (successCallback: (oauthUrl: string) => string) => ({
|
||||
successCallback,
|
||||
}),
|
||||
getSourceReConnectData: (sourceId: string) => ({ sourceId }),
|
||||
getPreContentSourceConfigData: () => true,
|
||||
saveSourceConfig: (isUpdating: boolean, successCallback?: () => void) => ({
|
||||
isUpdating,
|
||||
successCallback,
|
||||
}),
|
||||
saveSourceParams: (search: Search, params: OauthParams, isOrganization: boolean) => ({
|
||||
search,
|
||||
params,
|
||||
isOrganization,
|
||||
}),
|
||||
createContentSource: (successCallback: () => void, errorCallback?: () => void) => ({
|
||||
successCallback,
|
||||
errorCallback,
|
||||
}),
|
||||
resetSourceState: () => true,
|
||||
setButtonNotLoading: () => true,
|
||||
setFirstStep: () => true,
|
||||
},
|
||||
getSourceConfigData: async ({ serviceType, addSourceProps }) => {
|
||||
const route = `/internal/workplace_search/org/settings/connectors/${serviceType}`;
|
||||
reducers: ({ props }) => ({
|
||||
addSourceCurrentStep: [
|
||||
null,
|
||||
{
|
||||
setAddSourceStep: (_, addSourceCurrentStep) => addSourceCurrentStep,
|
||||
},
|
||||
],
|
||||
sourceConfigData: [
|
||||
{} as SourceConfigData,
|
||||
{
|
||||
setSourceConfigData: (_, sourceConfigData) => sourceConfigData,
|
||||
},
|
||||
],
|
||||
sourceConnectData: [
|
||||
{} as SourceConnectData,
|
||||
{
|
||||
setSourceConnectData: (_, sourceConnectData) => sourceConnectData,
|
||||
},
|
||||
],
|
||||
dataLoading: [
|
||||
true,
|
||||
{
|
||||
setSourceConfigData: () => false,
|
||||
resetSourceState: () => false,
|
||||
setPreContentSourceConfigData: () => false,
|
||||
getSourceConfigData: () => true,
|
||||
},
|
||||
],
|
||||
buttonLoading: [
|
||||
false,
|
||||
{
|
||||
setButtonNotLoading: () => false,
|
||||
setSourceConnectData: () => false,
|
||||
setSourceConfigData: () => false,
|
||||
resetSourceState: () => false,
|
||||
saveSourceConfig: () => true,
|
||||
getSourceConnectData: () => true,
|
||||
createContentSource: () => true,
|
||||
},
|
||||
],
|
||||
sectionLoading: [
|
||||
true,
|
||||
{
|
||||
getPreContentSourceConfigData: () => true,
|
||||
setPreContentSourceConfigData: () => false,
|
||||
},
|
||||
],
|
||||
clientIdValue: [
|
||||
'',
|
||||
{
|
||||
setClientIdValue: (_, clientIdValue) => clientIdValue,
|
||||
setSourceConfigData: (_, { configuredFields: { clientId } }) => clientId || '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
clientSecretValue: [
|
||||
'',
|
||||
{
|
||||
setClientSecretValue: (_, clientSecretValue) => clientSecretValue,
|
||||
setSourceConfigData: (_, { configuredFields: { clientSecret } }) => clientSecret || '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
baseUrlValue: [
|
||||
'',
|
||||
{
|
||||
setBaseUrlValue: (_, baseUrlValue) => baseUrlValue,
|
||||
setSourceConfigData: (_, { configuredFields: { baseUrl } }) => baseUrl || '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
loginValue: [
|
||||
'',
|
||||
{
|
||||
setSourceLoginValue: (_, loginValue) => loginValue,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
passwordValue: [
|
||||
'',
|
||||
{
|
||||
setSourcePasswordValue: (_, passwordValue) => passwordValue,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
subdomainValue: [
|
||||
'',
|
||||
{
|
||||
setSourceSubdomainValue: (_, subdomainValue) => subdomainValue,
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
indexPermissionsValue: [
|
||||
false,
|
||||
{
|
||||
setSourceIndexPermissionsValue: (_, indexPermissionsValue) => indexPermissionsValue,
|
||||
resetSourceState: () => false,
|
||||
},
|
||||
],
|
||||
githubOrganizations: [
|
||||
[],
|
||||
{
|
||||
setPreContentSourceConfigData: (_, { githubOrganizations }) => githubOrganizations,
|
||||
resetSourceState: () => [],
|
||||
},
|
||||
],
|
||||
selectedGithubOrganizationsMap: [
|
||||
{} as OrganizationsMap,
|
||||
{
|
||||
setSelectedGithubOrganizations: (state, option) => ({
|
||||
...state,
|
||||
...{ [option]: !state[option] },
|
||||
}),
|
||||
resetSourceState: () => ({}),
|
||||
},
|
||||
],
|
||||
preContentSourceId: [
|
||||
'',
|
||||
{
|
||||
setPreContentSourceId: (_, preContentSourceId) => preContentSourceId,
|
||||
setPreContentSourceConfigData: () => '',
|
||||
resetSourceState: () => '',
|
||||
},
|
||||
],
|
||||
oauthConfigCompleted: [
|
||||
false,
|
||||
{
|
||||
setPreContentSourceConfigData: () => true,
|
||||
},
|
||||
],
|
||||
sourceData: [getSourceData(props.serviceType) || null, {}],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
selectedGithubOrganizations: [
|
||||
() => [selectors.selectedGithubOrganizationsMap],
|
||||
(orgsMap) => keys(pickBy(orgsMap)),
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions, values, props }) => ({
|
||||
getSourceConfigData: async () => {
|
||||
const { serviceType } = props;
|
||||
// TODO: Once multi-config support for connectors is added, this request url will need to include an ID
|
||||
const route = `/internal/workplace_search/org/settings/connectors/${serviceType}`;
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<SourceConfigData>(route);
|
||||
actions.setSourceConfigData(response);
|
||||
if (addSourceProps) {
|
||||
actions.setFirstStep(addSourceProps);
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<SourceConfigData>(route);
|
||||
actions.setSourceConfigData(response);
|
||||
actions.setFirstStep();
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
getSourceConnectData: async ({ serviceType, successCallback }) => {
|
||||
clearFlashMessages();
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const { subdomainValue: subdomain, indexPermissionsValue: indexPermissions } = values;
|
||||
},
|
||||
getSourceConnectData: async ({ successCallback }) => {
|
||||
const { serviceType } = props;
|
||||
clearFlashMessages();
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const { subdomainValue: subdomain, indexPermissionsValue: indexPermissions } = values;
|
||||
|
||||
const route = isOrganization
|
||||
? `/internal/workplace_search/org/sources/${serviceType}/prepare`
|
||||
: `/internal/workplace_search/account/sources/${serviceType}/prepare`;
|
||||
const route = isOrganization
|
||||
? `/internal/workplace_search/org/sources/${serviceType}/prepare`
|
||||
: `/internal/workplace_search/account/sources/${serviceType}/prepare`;
|
||||
|
||||
const indexPermissionsQuery = isOrganization
|
||||
? { index_permissions: indexPermissions }
|
||||
: undefined;
|
||||
const indexPermissionsQuery = isOrganization
|
||||
? { index_permissions: indexPermissions }
|
||||
: undefined;
|
||||
|
||||
const query = subdomain
|
||||
? {
|
||||
...indexPermissionsQuery,
|
||||
subdomain,
|
||||
const query = subdomain
|
||||
? {
|
||||
...indexPermissionsQuery,
|
||||
subdomain,
|
||||
}
|
||||
: { ...indexPermissionsQuery };
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<SourceConnectData>(route, {
|
||||
query,
|
||||
});
|
||||
actions.setSourceConnectData(response);
|
||||
successCallback(response.oauthUrl);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
} finally {
|
||||
actions.setButtonNotLoading();
|
||||
}
|
||||
},
|
||||
getSourceReConnectData: async ({ sourceId }) => {
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const route = isOrganization
|
||||
? `/internal/workplace_search/org/sources/${sourceId}/reauth_prepare`
|
||||
: `/internal/workplace_search/account/sources/${sourceId}/reauth_prepare`;
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<SourceConnectData>(route);
|
||||
actions.setSourceConnectData(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
getPreContentSourceConfigData: async () => {
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const { preContentSourceId } = values;
|
||||
const route = isOrganization
|
||||
? `/internal/workplace_search/org/pre_sources/${preContentSourceId}`
|
||||
: `/internal/workplace_search/account/pre_sources/${preContentSourceId}`;
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<PreContentSourceResponse>(route);
|
||||
actions.setPreContentSourceConfigData(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
saveSourceConfig: async ({ isUpdating, successCallback }) => {
|
||||
clearFlashMessages();
|
||||
const {
|
||||
sourceConfigData: { serviceType },
|
||||
baseUrlValue,
|
||||
clientIdValue,
|
||||
clientSecretValue,
|
||||
sourceConfigData,
|
||||
} = values;
|
||||
|
||||
const { externalConnectorUrl, externalConnectorApiKey } = ExternalConnectorLogic.values;
|
||||
if (
|
||||
serviceType === 'external' &&
|
||||
externalConnectorUrl &&
|
||||
!isValidExternalUrl(externalConnectorUrl)
|
||||
) {
|
||||
ExternalConnectorLogic.actions.setUrlValidation(false);
|
||||
actions.setButtonNotLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
const route = isUpdating
|
||||
? `/internal/workplace_search/org/settings/connectors/${serviceType}`
|
||||
: '/internal/workplace_search/org/settings/connectors';
|
||||
|
||||
const http = isUpdating ? HttpLogic.values.http.put : HttpLogic.values.http.post;
|
||||
|
||||
const params = {
|
||||
base_url: baseUrlValue || undefined,
|
||||
client_id: clientIdValue || undefined,
|
||||
client_secret: clientSecretValue || undefined,
|
||||
service_type: serviceType,
|
||||
private_key: sourceConfigData.configuredFields?.privateKey,
|
||||
public_key: sourceConfigData.configuredFields?.publicKey,
|
||||
consumer_key: sourceConfigData.configuredFields?.consumerKey,
|
||||
external_connector_url: (serviceType === 'external' && externalConnectorUrl) || undefined,
|
||||
external_connector_api_key:
|
||||
(serviceType === 'external' && externalConnectorApiKey) || undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await http<SourceConfigData>(route, {
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
if (successCallback) successCallback();
|
||||
if (isUpdating) {
|
||||
flashSuccessToast(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConfigUpdated',
|
||||
{
|
||||
defaultMessage: 'Successfully updated configuration.',
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
: { ...indexPermissionsQuery };
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<SourceConnectData>(route, {
|
||||
query,
|
||||
});
|
||||
actions.setSourceConnectData(response);
|
||||
successCallback(response.oauthUrl);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
} finally {
|
||||
actions.setButtonNotLoading();
|
||||
}
|
||||
},
|
||||
getSourceReConnectData: async ({ sourceId }) => {
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const route = isOrganization
|
||||
? `/internal/workplace_search/org/sources/${sourceId}/reauth_prepare`
|
||||
: `/internal/workplace_search/account/sources/${sourceId}/reauth_prepare`;
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<SourceConnectData>(route);
|
||||
actions.setSourceConnectData(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
getPreContentSourceConfigData: async () => {
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const { preContentSourceId } = values;
|
||||
const route = isOrganization
|
||||
? `/internal/workplace_search/org/pre_sources/${preContentSourceId}`
|
||||
: `/internal/workplace_search/account/pre_sources/${preContentSourceId}`;
|
||||
|
||||
try {
|
||||
const response = await HttpLogic.values.http.get<PreContentSourceResponse>(route);
|
||||
actions.setPreContentSourceConfigData(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
saveSourceConfig: async ({ isUpdating, successCallback }) => {
|
||||
clearFlashMessages();
|
||||
const {
|
||||
sourceConfigData: { serviceType },
|
||||
baseUrlValue,
|
||||
clientIdValue,
|
||||
clientSecretValue,
|
||||
sourceConfigData,
|
||||
} = values;
|
||||
|
||||
const { externalConnectorUrl, externalConnectorApiKey } = ExternalConnectorLogic.values;
|
||||
if (
|
||||
serviceType === 'external' &&
|
||||
externalConnectorUrl &&
|
||||
!isValidExternalUrl(externalConnectorUrl)
|
||||
) {
|
||||
ExternalConnectorLogic.actions.setUrlValidation(false);
|
||||
actions.setButtonNotLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
const route = isUpdating
|
||||
? `/internal/workplace_search/org/settings/connectors/${serviceType}`
|
||||
: '/internal/workplace_search/org/settings/connectors';
|
||||
|
||||
const http = isUpdating ? HttpLogic.values.http.put : HttpLogic.values.http.post;
|
||||
|
||||
const params = {
|
||||
base_url: baseUrlValue || undefined,
|
||||
client_id: clientIdValue || undefined,
|
||||
client_secret: clientSecretValue || undefined,
|
||||
service_type: serviceType,
|
||||
private_key: sourceConfigData.configuredFields?.privateKey,
|
||||
public_key: sourceConfigData.configuredFields?.publicKey,
|
||||
consumer_key: sourceConfigData.configuredFields?.consumerKey,
|
||||
external_connector_url: (serviceType === 'external' && externalConnectorUrl) || undefined,
|
||||
external_connector_api_key:
|
||||
(serviceType === 'external' && externalConnectorApiKey) || undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await http<SourceConfigData>(route, {
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
if (successCallback) successCallback();
|
||||
if (isUpdating) {
|
||||
flashSuccessToast(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConfigUpdated',
|
||||
{
|
||||
defaultMessage: 'Successfully updated configuration.',
|
||||
}
|
||||
)
|
||||
);
|
||||
actions.setSourceConfigData(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
} finally {
|
||||
actions.setButtonNotLoading();
|
||||
}
|
||||
actions.setSourceConfigData(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
} finally {
|
||||
actions.setButtonNotLoading();
|
||||
}
|
||||
},
|
||||
saveSourceParams: async ({ search, params, isOrganization }) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { navigateToUrl } = KibanaLogic.values;
|
||||
const { setAddedSource } = SourcesLogic.actions;
|
||||
const query = { ...params };
|
||||
const route = '/internal/workplace_search/sources/create';
|
||||
},
|
||||
saveSourceParams: async ({ search, params, isOrganization }) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { navigateToUrl } = KibanaLogic.values;
|
||||
const { setAddedSource } = SourcesLogic.actions;
|
||||
const query = { ...params };
|
||||
const route = '/internal/workplace_search/sources/create';
|
||||
|
||||
/**
|
||||
/**
|
||||
There is an extreme edge case where the user is trying to connect Github as source from ent-search,
|
||||
after configuring it in Kibana. When this happens, Github redirects the user from ent-search to Kibana
|
||||
with special error properties in the query params. In this case we need to redirect the user to the
|
||||
app home page and display the error message, and not persist the other query params to the server.
|
||||
*/
|
||||
if (params.error_description) {
|
||||
navigateToUrl(isOrganization ? '/' : PRIVATE_SOURCES_PATH);
|
||||
setErrorMessage(
|
||||
isOrganization
|
||||
? params.error_description
|
||||
: PERSONAL_DASHBOARD_SOURCE_ERROR(params.error_description)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await http.get<{
|
||||
serviceName: string;
|
||||
indexPermissions: boolean;
|
||||
serviceType: string;
|
||||
preContentSourceId: string;
|
||||
hasConfigureStep: boolean;
|
||||
}>(route, { query });
|
||||
const { serviceName, indexPermissions, serviceType, preContentSourceId, hasConfigureStep } =
|
||||
response;
|
||||
|
||||
// GitHub requires an intermediate configuration step, where we collect the repos to index.
|
||||
if (hasConfigureStep && !values.oauthConfigCompleted) {
|
||||
actions.setPreContentSourceId(preContentSourceId);
|
||||
navigateToUrl(
|
||||
getSourcesPath(`${getAddPath('github')}/configure${search}`, isOrganization)
|
||||
if (params.error_description) {
|
||||
navigateToUrl(isOrganization ? '/' : PRIVATE_SOURCES_PATH);
|
||||
setErrorMessage(
|
||||
isOrganization
|
||||
? params.error_description
|
||||
: PERSONAL_DASHBOARD_SOURCE_ERROR(params.error_description)
|
||||
);
|
||||
} else {
|
||||
setAddedSource(serviceName, indexPermissions, serviceType);
|
||||
navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization));
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
setFirstStep: ({ addSourceProps }) => {
|
||||
const firstStep = getFirstStep(
|
||||
addSourceProps,
|
||||
values.sourceConfigData,
|
||||
SourcesLogic.values.externalConfigured
|
||||
);
|
||||
actions.setAddSourceStep(firstStep);
|
||||
},
|
||||
createContentSource: async ({ serviceType, successCallback, errorCallback }) => {
|
||||
clearFlashMessages();
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const route = isOrganization
|
||||
? '/internal/workplace_search/org/create_source'
|
||||
: '/internal/workplace_search/account/create_source';
|
||||
|
||||
const {
|
||||
selectedGithubOrganizations: githubOrganizations,
|
||||
loginValue,
|
||||
passwordValue,
|
||||
indexPermissionsValue,
|
||||
} = values;
|
||||
try {
|
||||
const response = await http.get<{
|
||||
serviceName: string;
|
||||
indexPermissions: boolean;
|
||||
serviceType: string;
|
||||
preContentSourceId: string;
|
||||
hasConfigureStep: boolean;
|
||||
}>(route, { query });
|
||||
const {
|
||||
serviceName,
|
||||
indexPermissions,
|
||||
serviceType,
|
||||
preContentSourceId,
|
||||
hasConfigureStep,
|
||||
} = response;
|
||||
|
||||
const params = {
|
||||
service_type: serviceType,
|
||||
login: loginValue || undefined,
|
||||
password: passwordValue || undefined,
|
||||
organizations: githubOrganizations.length > 0 ? githubOrganizations : undefined,
|
||||
index_permissions: indexPermissionsValue || undefined,
|
||||
} as {
|
||||
[key: string]: string | string[] | undefined;
|
||||
};
|
||||
// GitHub requires an intermediate configuration step, where we collect the repos to index.
|
||||
if (hasConfigureStep && !values.oauthConfigCompleted) {
|
||||
actions.setPreContentSourceId(preContentSourceId);
|
||||
navigateToUrl(
|
||||
getSourcesPath(`${getAddPath('github')}/configure${search}`, isOrganization)
|
||||
);
|
||||
} else {
|
||||
setAddedSource(serviceName, indexPermissions, serviceType);
|
||||
navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization));
|
||||
}
|
||||
} catch (e) {
|
||||
navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization));
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
setFirstStep: () => {
|
||||
const firstStep = getFirstStep(values.sourceConfigData, props.initialStep);
|
||||
actions.setAddSourceStep(firstStep);
|
||||
},
|
||||
createContentSource: async ({ successCallback, errorCallback }) => {
|
||||
const { serviceType } = props;
|
||||
clearFlashMessages();
|
||||
const { isOrganization } = AppLogic.values;
|
||||
const route = isOrganization
|
||||
? '/internal/workplace_search/org/create_source'
|
||||
: '/internal/workplace_search/account/create_source';
|
||||
|
||||
// Remove undefined values from params
|
||||
Object.keys(params).forEach((key) => params[key] === undefined && delete params[key]);
|
||||
const {
|
||||
selectedGithubOrganizations: githubOrganizations,
|
||||
loginValue,
|
||||
passwordValue,
|
||||
indexPermissionsValue,
|
||||
} = values;
|
||||
|
||||
try {
|
||||
await HttpLogic.values.http.post(route, {
|
||||
body: JSON.stringify({ ...params }),
|
||||
});
|
||||
successCallback();
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
if (errorCallback) errorCallback();
|
||||
} finally {
|
||||
actions.setButtonNotLoading();
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
const params = {
|
||||
service_type: serviceType,
|
||||
login: loginValue || undefined,
|
||||
password: passwordValue || undefined,
|
||||
organizations: githubOrganizations.length > 0 ? githubOrganizations : undefined,
|
||||
index_permissions: indexPermissionsValue || undefined,
|
||||
} as {
|
||||
[key: string]: string | string[] | undefined;
|
||||
};
|
||||
|
||||
const getFirstStep = (
|
||||
props: AddSourceProps,
|
||||
sourceConfigData: SourceConfigData,
|
||||
externalConfigured: boolean
|
||||
): AddSourceSteps => {
|
||||
const {
|
||||
connect,
|
||||
configure,
|
||||
reAuthenticate,
|
||||
sourceData: { serviceType, externalConnectorAvailable },
|
||||
} = props;
|
||||
// We can land on this page from a choice page for multiple types of connectors
|
||||
// If that's the case we want to skip the intro and configuration, if the external & internal connector have already been configured
|
||||
const { configuredFields, configured } = sourceConfigData;
|
||||
if (externalConnectorAvailable && configured && externalConfigured)
|
||||
return AddSourceSteps.ConnectInstanceStep;
|
||||
if (externalConnectorAvailable && !configured && externalConfigured)
|
||||
return AddSourceSteps.SaveConfigStep;
|
||||
if (serviceType === 'external') {
|
||||
// external connectors can be partially configured, so we need to check which fields are filled
|
||||
if (configuredFields?.clientId && configuredFields?.clientSecret) {
|
||||
return AddSourceSteps.ConnectInstanceStep;
|
||||
}
|
||||
// Unconfigured external connectors have already shown the intro step before the choice page, so we don't want to show it again
|
||||
return AddSourceSteps.SaveConfigStep;
|
||||
// Remove undefined values from params
|
||||
Object.keys(params).forEach((key) => params[key] === undefined && delete params[key]);
|
||||
|
||||
try {
|
||||
await HttpLogic.values.http.post(route, {
|
||||
body: JSON.stringify({ ...params }),
|
||||
});
|
||||
successCallback();
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
if (errorCallback) errorCallback();
|
||||
} finally {
|
||||
actions.setButtonNotLoading();
|
||||
}
|
||||
},
|
||||
}),
|
||||
}
|
||||
if (connect) return AddSourceSteps.ConnectInstanceStep;
|
||||
if (configure) return AddSourceSteps.ConfigureOauthStep;
|
||||
if (reAuthenticate) return AddSourceSteps.ReauthenticateStep;
|
||||
return AddSourceSteps.ConfigIntroStep;
|
||||
);
|
||||
|
||||
const getFirstStep = (sourceConfigData: SourceConfigData, initialStep?: string): AddSourceSteps => {
|
||||
const {
|
||||
serviceType,
|
||||
configured,
|
||||
configuredFields: { clientId, clientSecret },
|
||||
} = sourceConfigData;
|
||||
if (initialStep === 'connect') return AddSourceSteps.ConnectInstanceStep;
|
||||
if (initialStep === 'configure') return AddSourceSteps.ConfigureOauthStep;
|
||||
if (initialStep === 'reauthenticate') return AddSourceSteps.ReauthenticateStep;
|
||||
if (serviceType !== 'external' && configured) return AddSourceSteps.ConnectInstanceStep;
|
||||
|
||||
// TODO remove this once external/BYO connectors track `configured` properly
|
||||
if (serviceType === 'external' && clientId && clientSecret)
|
||||
return AddSourceSteps.ConnectInstanceStep;
|
||||
|
||||
return AddSourceSteps.SaveConfigStep;
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('AvailableSourcesList', () => {
|
|||
const wrapper = shallow(<AvailableSourcesList sources={mergedAvailableSources} />);
|
||||
|
||||
expect(wrapper.find(EuiTitle)).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="AvailableSourceListItem"]')).toHaveLength(24);
|
||||
expect(wrapper.find('[data-test-subj="AvailableSourceListItem"]')).toHaveLength(25);
|
||||
expect(wrapper.find('[data-test-subj="CustomAPISourceLink"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
EuiTitle,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -43,8 +44,13 @@ interface AvailableSourcesListProps {
|
|||
export const AvailableSourcesList: React.FC<AvailableSourcesListProps> = ({ sources }) => {
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
|
||||
const getSourceCard = ({ name, serviceType, accountContextOnly }: SourceDataItem) => {
|
||||
const addPath = getAddPath(serviceType);
|
||||
const getSourceCard = ({
|
||||
accountContextOnly,
|
||||
baseServiceType,
|
||||
name,
|
||||
serviceType,
|
||||
}: SourceDataItem) => {
|
||||
const addPath = getAddPath(serviceType, baseServiceType);
|
||||
const disabled = !hasPlatinumLicense && accountContextOnly;
|
||||
|
||||
const connectButton = () => {
|
||||
|
@ -61,15 +67,30 @@ export const AvailableSourcesList: React.FC<AvailableSourcesListProps> = ({ sour
|
|||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonEmptyTo disabled={disabled} to={getSourcesPath(addPath, true)}>
|
||||
Connect
|
||||
</EuiButtonEmptyTo>
|
||||
<EuiButtonEmpty disabled>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.connectButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Connect',
|
||||
}
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiToolTip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiButtonEmptyTo disabled={disabled} to={getSourcesPath(addPath, true)}>
|
||||
Connect
|
||||
<EuiButtonEmptyTo
|
||||
to={
|
||||
getSourcesPath(addPath, true) +
|
||||
(serviceType === 'custom' || serviceType === 'external' ? '' : '/intro')
|
||||
}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.connectButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Connect',
|
||||
}
|
||||
)}
|
||||
</EuiButtonEmptyTo>
|
||||
);
|
||||
}
|
||||
|
@ -79,7 +100,7 @@ export const AvailableSourcesList: React.FC<AvailableSourcesListProps> = ({ sour
|
|||
<>
|
||||
<EuiFlexGroup alignItems="center" responsive={false} gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SourceIcon serviceType={serviceType} name={name} size="l" />
|
||||
<SourceIcon serviceType={baseServiceType || serviceType} name={name} size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="m">{name}</EuiText>
|
||||
|
|
|
@ -5,20 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockKibanaValues, setMockValues } from '../../../../../__mocks__/kea_logic';
|
||||
import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
|
||||
|
||||
import { staticSourceData } from '../../source_data';
|
||||
|
||||
import { ConfigurationChoice } from './configuration_choice';
|
||||
|
||||
describe('ConfigurationChoice', () => {
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
const props = {
|
||||
sourceData: staticSourceData[0],
|
||||
};
|
||||
|
@ -28,31 +27,23 @@ describe('ConfigurationChoice', () => {
|
|||
categories: [],
|
||||
},
|
||||
};
|
||||
const mockActions = {
|
||||
initializeSources: jest.fn(),
|
||||
resetSourcesState: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setMockValues(mockValues);
|
||||
jest.clearAllMocks();
|
||||
setMockValues(mockValues);
|
||||
setMockActions(mockActions);
|
||||
});
|
||||
|
||||
it('renders internal connector if available', () => {
|
||||
const wrapper = mount(<ConfigurationChoice {...{ ...props }} />);
|
||||
|
||||
expect(wrapper.find('EuiCard')).toHaveLength(1);
|
||||
expect(wrapper.find(EuiButton)).toHaveLength(1);
|
||||
});
|
||||
it('should navigate to internal connector on internal connector click', () => {
|
||||
const wrapper = mount(<ConfigurationChoice {...props} />);
|
||||
const button = wrapper.find(EuiButton);
|
||||
button.simulate('click');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/box/internal/');
|
||||
});
|
||||
it('should call prop function when provided on internal connector click', () => {
|
||||
const advanceSpy = jest.fn();
|
||||
const wrapper = mount(<ConfigurationChoice {...{ ...props, goToInternalStep: advanceSpy }} />);
|
||||
const button = wrapper.find(EuiButton);
|
||||
button.simulate('click');
|
||||
expect(navigateToUrl).not.toHaveBeenCalled();
|
||||
expect(advanceSpy).toHaveBeenCalled();
|
||||
const internalConnectorCard = wrapper.find('[data-test-subj="InternalConnectorCard"]');
|
||||
expect(internalConnectorCard).toHaveLength(1);
|
||||
expect(internalConnectorCard.find(EuiButtonTo).prop('to')).toEqual('/sources/add/box/');
|
||||
});
|
||||
|
||||
it('renders external connector if available', () => {
|
||||
|
@ -62,32 +53,36 @@ describe('ConfigurationChoice', () => {
|
|||
...props,
|
||||
sourceData: {
|
||||
...props.sourceData,
|
||||
internalConnectorAvailable: false,
|
||||
externalConnectorAvailable: true,
|
||||
serviceType: 'share_point',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find('EuiCard')).toHaveLength(1);
|
||||
expect(wrapper.find(EuiButton)).toHaveLength(1);
|
||||
const externalConnectorCard = wrapper.find('[data-test-subj="ExternalConnectorCard"]');
|
||||
expect(externalConnectorCard).toHaveLength(1);
|
||||
expect(externalConnectorCard.find(EuiButtonTo).prop('to')).toEqual(
|
||||
'/sources/add/share_point/external/connector_registration'
|
||||
);
|
||||
});
|
||||
it('should navigate to external connector on external connector click', () => {
|
||||
|
||||
it('renders disabled message if external connector is available but user has already configured', () => {
|
||||
setMockValues({ ...mockValues, externalConfigured: true });
|
||||
|
||||
const wrapper = mount(
|
||||
<ConfigurationChoice
|
||||
{...{
|
||||
...props,
|
||||
sourceData: {
|
||||
...props.sourceData,
|
||||
internalConnectorAvailable: false,
|
||||
externalConnectorAvailable: true,
|
||||
serviceType: 'share_point',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const button = wrapper.find(EuiButton);
|
||||
button.simulate('click');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/box/external/');
|
||||
|
||||
const externalConnectorCard = wrapper.find('[data-test-subj="ExternalConnectorCard"]');
|
||||
expect(externalConnectorCard.prop('disabledMessage')).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders custom connector if available', () => {
|
||||
|
@ -97,33 +92,16 @@ describe('ConfigurationChoice', () => {
|
|||
...props,
|
||||
sourceData: {
|
||||
...props.sourceData,
|
||||
internalConnectorAvailable: false,
|
||||
externalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
serviceType: 'share_point_server',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find('EuiCard')).toHaveLength(1);
|
||||
expect(wrapper.find(EuiButton)).toHaveLength(1);
|
||||
});
|
||||
it('should navigate to custom connector on custom connector click', () => {
|
||||
const wrapper = mount(
|
||||
<ConfigurationChoice
|
||||
{...{
|
||||
...props,
|
||||
sourceData: {
|
||||
...props.sourceData,
|
||||
internalConnectorAvailable: false,
|
||||
externalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
const customConnectorCard = wrapper.find('[data-test-subj="CustomConnectorCard"]');
|
||||
expect(customConnectorCard).toHaveLength(1);
|
||||
expect(customConnectorCard.find(EuiButtonTo).prop('to')).toEqual(
|
||||
'/sources/add/share_point_server/custom'
|
||||
);
|
||||
const button = wrapper.find(EuiButton);
|
||||
button.simulate('click');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/box/custom/');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,92 +5,85 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { EuiButton, EuiCard, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
|
||||
import { AppLogic } from '../../../../app_logic';
|
||||
import { getAddPath, getSourcesPath } from '../../../../routes';
|
||||
import { SourceDataItem } from '../../../../types';
|
||||
|
||||
import { AddSourceHeader } from './add_source_header';
|
||||
import { AddSourceLogic } from './add_source_logic';
|
||||
import { hasCustomConnectorOption, hasExternalConnectorOption } from '../../source_data';
|
||||
|
||||
interface ConfigurationChoiceProps {
|
||||
sourceData: SourceDataItem;
|
||||
goToInternalStep?: () => void;
|
||||
}
|
||||
import { SourcesLogic } from '../../sources_logic';
|
||||
|
||||
import { AddSourceHeader } from './add_source_header';
|
||||
|
||||
interface CardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
onClick: () => void;
|
||||
to: string;
|
||||
badgeLabel?: string;
|
||||
disabledMessage?: string;
|
||||
}
|
||||
|
||||
const ConnectorCard: React.FC<CardProps> = ({
|
||||
title,
|
||||
description,
|
||||
buttonText,
|
||||
to,
|
||||
badgeLabel,
|
||||
disabledMessage,
|
||||
}: CardProps) => (
|
||||
<EuiFlexItem grow>
|
||||
<EuiCard
|
||||
isDisabled={!!disabledMessage}
|
||||
hasBorder
|
||||
title={title}
|
||||
description={disabledMessage || description}
|
||||
betaBadgeProps={{ label: badgeLabel }}
|
||||
footer={
|
||||
<EuiButtonTo color="primary" to={to} isDisabled={!!disabledMessage}>
|
||||
{buttonText}
|
||||
</EuiButtonTo>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
interface ConfigurationChoiceProps {
|
||||
sourceData: SourceDataItem;
|
||||
}
|
||||
|
||||
export const ConfigurationChoice: React.FC<ConfigurationChoiceProps> = ({
|
||||
sourceData: {
|
||||
name,
|
||||
serviceType,
|
||||
externalConnectorAvailable,
|
||||
internalConnectorAvailable,
|
||||
customConnectorAvailable,
|
||||
},
|
||||
goToInternalStep,
|
||||
sourceData: { name, categories = [], serviceType },
|
||||
}) => {
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
const { sourceConfigData } = useValues(AddSourceLogic);
|
||||
const { categories } = sourceConfigData;
|
||||
const goToInternal = goToInternalStep
|
||||
? goToInternalStep
|
||||
: () =>
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
`${getSourcesPath(
|
||||
`${getSourcesPath(getAddPath(serviceType), isOrganization)}/internal`,
|
||||
isOrganization
|
||||
)}/`
|
||||
);
|
||||
const goToExternal = () =>
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
`${getSourcesPath(
|
||||
`${getSourcesPath(getAddPath(serviceType), isOrganization)}/external`,
|
||||
isOrganization
|
||||
)}/`
|
||||
);
|
||||
const goToCustom = () =>
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
`${getSourcesPath(
|
||||
`${getSourcesPath(getAddPath(serviceType), isOrganization)}/custom`,
|
||||
isOrganization
|
||||
)}/`
|
||||
);
|
||||
const externalConnectorAvailable = hasExternalConnectorOption(serviceType);
|
||||
const customConnectorAvailable = hasCustomConnectorOption(serviceType);
|
||||
|
||||
const ConnectorCard: React.FC<CardProps> = ({
|
||||
title,
|
||||
description,
|
||||
buttonText,
|
||||
onClick,
|
||||
badgeLabel,
|
||||
}: CardProps) => (
|
||||
<EuiFlexItem grow>
|
||||
<EuiCard
|
||||
hasBorder
|
||||
title={title}
|
||||
description={description}
|
||||
betaBadgeProps={{ label: badgeLabel }}
|
||||
footer={
|
||||
<EuiButton color="primary" onClick={onClick}>
|
||||
{buttonText}
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
|
||||
const { initializeSources, resetSourcesState } = useActions(SourcesLogic);
|
||||
|
||||
const { externalConfigured } = useValues(SourcesLogic);
|
||||
|
||||
useEffect(() => {
|
||||
initializeSources();
|
||||
return resetSourcesState;
|
||||
}, []);
|
||||
|
||||
const internalTo = `${getSourcesPath(getAddPath(serviceType), isOrganization)}/`;
|
||||
const externalTo = `${getSourcesPath(
|
||||
getAddPath('external', serviceType),
|
||||
isOrganization
|
||||
)}/connector_registration`;
|
||||
const customTo = `${getSourcesPath(getAddPath('custom', serviceType), isOrganization)}`;
|
||||
|
||||
const internalConnectorProps: CardProps = {
|
||||
title: i18n.translate(
|
||||
|
@ -118,7 +111,7 @@ export const ConfigurationChoice: React.FC<ConfigurationChoiceProps> = ({
|
|||
defaultMessage: 'Recommended',
|
||||
}
|
||||
),
|
||||
onClick: goToInternal,
|
||||
to: internalTo,
|
||||
};
|
||||
|
||||
const externalConnectorProps: CardProps = {
|
||||
|
@ -141,7 +134,7 @@ export const ConfigurationChoice: React.FC<ConfigurationChoiceProps> = ({
|
|||
defaultMessage: 'Instructions',
|
||||
}
|
||||
),
|
||||
onClick: goToExternal,
|
||||
to: externalTo,
|
||||
badgeLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.betaLabel',
|
||||
{
|
||||
|
@ -169,7 +162,7 @@ export const ConfigurationChoice: React.FC<ConfigurationChoiceProps> = ({
|
|||
defaultMessage: 'Instructions',
|
||||
}
|
||||
),
|
||||
onClick: goToCustom,
|
||||
to: customTo,
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -177,9 +170,26 @@ export const ConfigurationChoice: React.FC<ConfigurationChoiceProps> = ({
|
|||
<AddSourceHeader name={name} serviceType={serviceType} categories={categories} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup justifyContent="flexStart" direction="row" responsive={false}>
|
||||
{internalConnectorAvailable && <ConnectorCard {...internalConnectorProps} />}
|
||||
{externalConnectorAvailable && <ConnectorCard {...externalConnectorProps} />}
|
||||
{customConnectorAvailable && <ConnectorCard {...customConnectorProps} />}
|
||||
<ConnectorCard {...internalConnectorProps} data-test-subj="InternalConnectorCard" />
|
||||
{externalConnectorAvailable && (
|
||||
<ConnectorCard
|
||||
{...externalConnectorProps}
|
||||
disabledMessage={
|
||||
externalConfigured
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.alreadyConfiguredMessage',
|
||||
{
|
||||
defaultMessage: "You've already configured an external connector",
|
||||
}
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
data-test-subj="ExternalConnectorCard"
|
||||
/>
|
||||
)}
|
||||
{customConnectorAvailable && (
|
||||
<ConnectorCard {...customConnectorProps} data-test-subj="CustomConnectorCard" />
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -14,11 +14,10 @@ import { EuiText, EuiTitle } from '@elastic/eui';
|
|||
import { ConfigurationIntro } from './configuration_intro';
|
||||
|
||||
describe('ConfigurationIntro', () => {
|
||||
const advanceStep = jest.fn();
|
||||
const props = {
|
||||
header: <h1>Header</h1>,
|
||||
name: 'foo',
|
||||
advanceStep,
|
||||
advanceStepTo: '',
|
||||
};
|
||||
|
||||
it('renderscontext', () => {
|
||||
|
|
|
@ -9,7 +9,6 @@ import React from 'react';
|
|||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
|
@ -18,9 +17,12 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
|
||||
|
||||
import connectionIllustration from '../../../../assets/connection_illustration.svg';
|
||||
|
||||
import {
|
||||
|
@ -37,12 +39,12 @@ import {
|
|||
interface ConfigurationIntroProps {
|
||||
header: React.ReactNode;
|
||||
name: string;
|
||||
advanceStep(): void;
|
||||
advanceStepTo: string;
|
||||
}
|
||||
|
||||
export const ConfigurationIntro: React.FC<ConfigurationIntroProps> = ({
|
||||
name,
|
||||
advanceStep,
|
||||
advanceStepTo,
|
||||
header,
|
||||
}) => (
|
||||
<>
|
||||
|
@ -144,11 +146,11 @@ export const ConfigurationIntro: React.FC<ConfigurationIntroProps> = ({
|
|||
<EuiFlexItem>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFormRow>
|
||||
<EuiButton
|
||||
<EuiButtonTo
|
||||
color="primary"
|
||||
data-test-subj="ConfigureStepButton"
|
||||
fill
|
||||
onClick={advanceStep}
|
||||
to={advanceStepTo}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button',
|
||||
|
@ -157,7 +159,7 @@ export const ConfigurationIntro: React.FC<ConfigurationIntroProps> = ({
|
|||
values: { name },
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiButtonTo>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="xl" />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('ConfigureOauth', () => {
|
|||
const onFormCreated = jest.fn();
|
||||
const getPreContentSourceConfigData = jest.fn();
|
||||
const setSelectedGithubOrganizations = jest.fn();
|
||||
const createContentSource = jest.fn((_, formSubmitSuccess, handleFormSubmitError) => {
|
||||
const createContentSource = jest.fn((formSubmitSuccess, handleFormSubmitError) => {
|
||||
formSubmitSuccess();
|
||||
handleFormSubmitError();
|
||||
});
|
||||
|
|
|
@ -35,12 +35,8 @@ export const ConfigureOauth: React.FC<ConfigureOauthProps> = ({ name, onFormCrea
|
|||
|
||||
const { getPreContentSourceConfigData, setSelectedGithubOrganizations, createContentSource } =
|
||||
useActions(AddSourceLogic);
|
||||
const {
|
||||
currentServiceType,
|
||||
githubOrganizations,
|
||||
selectedGithubOrganizationsMap,
|
||||
sectionLoading,
|
||||
} = useValues(AddSourceLogic);
|
||||
const { githubOrganizations, selectedGithubOrganizationsMap, sectionLoading } =
|
||||
useValues(AddSourceLogic);
|
||||
|
||||
const checkboxOptions = githubOrganizations.map((item) => ({ id: item, label: item }));
|
||||
|
||||
|
@ -54,7 +50,7 @@ export const ConfigureOauth: React.FC<ConfigureOauthProps> = ({ name, onFormCrea
|
|||
const handleFormSubmit = (e: FormEvent) => {
|
||||
setFormLoading(true);
|
||||
e.preventDefault();
|
||||
createContentSource(currentServiceType, formSubmitSuccess, handleFormSubmitError);
|
||||
createContentSource(formSubmitSuccess, handleFormSubmitError);
|
||||
};
|
||||
|
||||
const configfieldsForm = (
|
||||
|
|
|
@ -11,6 +11,8 @@ import React from 'react';
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
import { EuiButtonEmptyTo } from '../../../../../shared/react_router_helpers';
|
||||
|
||||
import { ConfiguredSourcesList } from './configured_sources_list';
|
||||
|
@ -24,47 +26,19 @@ describe('ConfiguredSourcesList', () => {
|
|||
it('renders', () => {
|
||||
const wrapper = shallow(<ConfiguredSourcesList {...props} />);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="UnConnectedTooltip"]')).toHaveLength(20);
|
||||
expect(wrapper.find('[data-test-subj="UnConnectedTooltip"]')).toHaveLength(21);
|
||||
expect(wrapper.find('[data-test-subj="AccountOnlyTooltip"]')).toHaveLength(2);
|
||||
expect(wrapper.find('[data-test-subj="ConfiguredSourcesListItem"]')).toHaveLength(23);
|
||||
expect(wrapper.find('[data-test-subj="ConfiguredSourcesListItem"]')).toHaveLength(24);
|
||||
});
|
||||
|
||||
it('does show connect button for a connected external source', () => {
|
||||
const wrapper = shallow(
|
||||
<ConfiguredSourcesList
|
||||
{...{
|
||||
sources: [{ ...mergedConfiguredSources[0], connected: true, serviceType: 'external' }],
|
||||
isOrganization: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find(EuiButtonEmptyTo)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does show connect button for an unconnected external source', () => {
|
||||
const wrapper = shallow(
|
||||
<ConfiguredSourcesList
|
||||
{...{
|
||||
sources: [{ ...mergedConfiguredSources[0], connected: false, serviceType: 'external' }],
|
||||
isOrganization: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const button = wrapper.find(EuiButtonEmptyTo);
|
||||
expect(button).toHaveLength(1);
|
||||
expect(button.prop('to')).toEqual('/sources/add/external/connect');
|
||||
});
|
||||
|
||||
it('connect button for an unconnected source with multiple connector options routes to choice page', () => {
|
||||
it('shows connect button for an source with multiple connector options that routes to choice page', () => {
|
||||
const wrapper = shallow(
|
||||
<ConfiguredSourcesList
|
||||
{...{
|
||||
sources: [
|
||||
{
|
||||
...mergedConfiguredSources[0],
|
||||
connected: false,
|
||||
serviceType: 'share_point',
|
||||
externalConnectorAvailable: true,
|
||||
},
|
||||
],
|
||||
isOrganization: true,
|
||||
|
@ -73,28 +47,46 @@ describe('ConfiguredSourcesList', () => {
|
|||
);
|
||||
const button = wrapper.find(EuiButtonEmptyTo);
|
||||
expect(button).toHaveLength(1);
|
||||
expect(button.prop('to')).toEqual('/sources/add/share_point/');
|
||||
expect(button.prop('to')).toEqual('/sources/add/share_point/choice');
|
||||
});
|
||||
|
||||
it('connect button for a source with multiple connector options routes to connect page for private sources', () => {
|
||||
it('shows connect button for a source without multiple connector options that routes to add page', () => {
|
||||
const wrapper = shallow(
|
||||
<ConfiguredSourcesList
|
||||
{...{
|
||||
sources: [
|
||||
{
|
||||
...mergedConfiguredSources[0],
|
||||
connected: false,
|
||||
serviceType: 'share_point',
|
||||
externalConnectorAvailable: true,
|
||||
serviceType: 'slack',
|
||||
},
|
||||
],
|
||||
isOrganization: false,
|
||||
isOrganization: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const button = wrapper.find(EuiButtonEmptyTo);
|
||||
expect(button).toHaveLength(1);
|
||||
expect(button.prop('to')).toEqual('/p/sources/add/share_point/connect');
|
||||
expect(button.prop('to')).toEqual('/sources/add/slack/');
|
||||
});
|
||||
|
||||
it('disabled when in organization mode and connector is account context only', () => {
|
||||
const wrapper = shallow(
|
||||
<ConfiguredSourcesList
|
||||
{...{
|
||||
sources: [
|
||||
{
|
||||
...mergedConfiguredSources[0],
|
||||
serviceType: 'gmail',
|
||||
accountContextOnly: true,
|
||||
},
|
||||
],
|
||||
isOrganization: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const button = wrapper.find(EuiButtonEmpty);
|
||||
expect(button).toHaveLength(1);
|
||||
expect(button.prop('isDisabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('handles empty state', () => {
|
||||
|
|
|
@ -27,7 +27,8 @@ import { EuiButtonEmptyTo } from '../../../../../shared/react_router_helpers';
|
|||
import { SourceIcon } from '../../../../components/shared/source_icon';
|
||||
import { getAddPath, getSourcesPath } from '../../../../routes';
|
||||
import { SourceDataItem } from '../../../../types';
|
||||
import { hasMultipleConnectorOptions } from '../../../../utils';
|
||||
|
||||
import { hasMultipleConnectorOptions } from '../../source_data';
|
||||
|
||||
import {
|
||||
CONFIGURED_SOURCES_LIST_UNCONNECTED_TOOLTIP,
|
||||
|
@ -72,7 +73,8 @@ export const ConfiguredSourcesList: React.FC<ConfiguredSourcesProps> = ({
|
|||
const visibleSources = (
|
||||
<EuiFlexGrid columns={3} gutterSize="m" className="source-grid-configured">
|
||||
{sources.map((sourceData, i) => {
|
||||
const { connected, accountContextOnly, name, serviceType, isBeta } = sourceData;
|
||||
const { connected, accountContextOnly, name, serviceType, isBeta, baseServiceType } =
|
||||
sourceData;
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<EuiFlexItem
|
||||
|
@ -107,7 +109,11 @@ export const ConfiguredSourcesList: React.FC<ConfiguredSourcesProps> = ({
|
|||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SourceIcon serviceType={serviceType} name={name} size="xxl" />
|
||||
<SourceIcon
|
||||
serviceType={baseServiceType ?? serviceType}
|
||||
name={name}
|
||||
size="xxl"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
|
@ -128,7 +134,7 @@ export const ConfiguredSourcesList: React.FC<ConfiguredSourcesProps> = ({
|
|||
<EuiButtonEmptyTo
|
||||
className="eui-fullWidth"
|
||||
to={`${getSourcesPath(getAddPath(serviceType), isOrganization)}/${
|
||||
hasMultipleConnectorOptions(sourceData) && isOrganization ? '' : 'connect'
|
||||
hasMultipleConnectorOptions(serviceType) && isOrganization ? 'choice' : ''
|
||||
}`}
|
||||
>
|
||||
{!connected
|
||||
|
|
|
@ -33,10 +33,10 @@ describe('ConnectInstance', () => {
|
|||
const setSourcePasswordValue = jest.fn();
|
||||
const setSourceSubdomainValue = jest.fn();
|
||||
const setSourceIndexPermissionsValue = jest.fn();
|
||||
const getSourceConnectData = jest.fn((_, redirectOauth) => {
|
||||
const getSourceConnectData = jest.fn((redirectOauth) => {
|
||||
redirectOauth();
|
||||
});
|
||||
const createContentSource = jest.fn((_, redirectFormCreated) => {
|
||||
const createContentSource = jest.fn((redirectFormCreated) => {
|
||||
redirectFormCreated();
|
||||
});
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ export const ConnectInstance: React.FC<ConnectInstanceProps> = ({
|
|||
features,
|
||||
objTypes,
|
||||
name,
|
||||
serviceType,
|
||||
needsPermissions,
|
||||
onFormCreated,
|
||||
header,
|
||||
|
@ -74,8 +73,8 @@ export const ConnectInstance: React.FC<ConnectInstanceProps> = ({
|
|||
|
||||
const redirectOauth = (oauthUrl: string) => window.location.replace(oauthUrl);
|
||||
const redirectFormCreated = () => onFormCreated(name);
|
||||
const onOauthFormSubmit = () => getSourceConnectData(serviceType, redirectOauth);
|
||||
const onCredentialsFormSubmit = () => createContentSource(serviceType, redirectFormCreated);
|
||||
const onOauthFormSubmit = () => getSourceConnectData(redirectOauth);
|
||||
const onCredentialsFormSubmit = () => createContentSource(redirectFormCreated);
|
||||
|
||||
const handleFormSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE_DESCRIPTION,
|
||||
} from './constants';
|
||||
|
||||
interface ConnectInstanceProps {
|
||||
interface SourceFeatureProps {
|
||||
features?: Features;
|
||||
objTypes?: string[];
|
||||
name: string;
|
||||
|
@ -47,7 +47,7 @@ interface ConnectInstanceProps {
|
|||
|
||||
type IncludedFeatureIds = Exclude<FeatureIds, FeatureIds.DocumentLevelPermissions>;
|
||||
|
||||
export const SourceFeatures: React.FC<ConnectInstanceProps> = ({ features, objTypes, name }) => {
|
||||
export const SourceFeatures: React.FC<SourceFeatureProps> = ({ features, objTypes, name }) => {
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
|
||||
|
|
|
@ -24,14 +24,6 @@ const customSource = {
|
|||
name: 'name',
|
||||
};
|
||||
|
||||
const preconfiguredSourceData = {
|
||||
...staticCustomSourceData,
|
||||
serviceType: 'sharepoint-server',
|
||||
configuration: {
|
||||
...staticCustomSourceData.configuration,
|
||||
githubRepository: 'elastic/sharepoint-server-connector',
|
||||
},
|
||||
};
|
||||
const mockValues = {
|
||||
sourceData: staticCustomSourceData,
|
||||
};
|
||||
|
@ -44,9 +36,7 @@ describe('CustomSourceDeployment', () => {
|
|||
jest.clearAllMocks();
|
||||
setMockValues(mockValues);
|
||||
|
||||
wrapper = shallow(
|
||||
<CustomSourceDeployment source={customSource} sourceData={staticCustomSourceData} />
|
||||
);
|
||||
wrapper = shallow(<CustomSourceDeployment source={customSource} />);
|
||||
});
|
||||
|
||||
it('contains a source identifier', () => {
|
||||
|
@ -69,7 +59,7 @@ describe('CustomSourceDeployment', () => {
|
|||
});
|
||||
|
||||
wrapper = shallow(
|
||||
<CustomSourceDeployment source={customSource} sourceData={preconfiguredSourceData} />
|
||||
<CustomSourceDeployment source={customSource} baseServiceType={'share_point_server'} />
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -86,9 +76,7 @@ describe('CustomSourceDeployment', () => {
|
|||
jest.clearAllMocks();
|
||||
setMockValues(mockValues);
|
||||
|
||||
const wrapper = shallow(
|
||||
<CustomSourceDeployment small source={customSource} sourceData={staticCustomSourceData} />
|
||||
);
|
||||
const wrapper = shallow(<CustomSourceDeployment small source={customSource} />);
|
||||
|
||||
expect(wrapper.find(EuiPanel).prop('paddingSize')).toEqual('m');
|
||||
|
||||
|
|
|
@ -14,17 +14,30 @@ import { EuiLinkTo } from '../../../../shared/react_router_helpers';
|
|||
import { API_KEY_LABEL } from '../../../constants';
|
||||
import { API_KEYS_PATH } from '../../../routes';
|
||||
|
||||
import { ContentSource, CustomSource, SourceDataItem } from '../../../types';
|
||||
import { ContentSource, CustomSource } from '../../../types';
|
||||
|
||||
import { getSourceData } from '../source_data';
|
||||
|
||||
import { SourceIdentifier } from './source_identifier';
|
||||
|
||||
interface Props {
|
||||
source: ContentSource | CustomSource;
|
||||
sourceData: SourceDataItem;
|
||||
baseServiceType?: string;
|
||||
small?: boolean;
|
||||
}
|
||||
export const CustomSourceDeployment: React.FC<Props> = ({ source, sourceData, small = false }) => {
|
||||
export const CustomSourceDeployment: React.FC<Props> = ({
|
||||
source,
|
||||
baseServiceType,
|
||||
small = false,
|
||||
}) => {
|
||||
const { name, id } = source;
|
||||
|
||||
const sourceData = getSourceData('custom', baseServiceType);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
configuration: { documentationUrl, githubRepository },
|
||||
} = sourceData;
|
||||
|
|
|
@ -16,8 +16,6 @@ import { EuiCallOut, EuiConfirmModal, EuiEmptyPrompt, EuiTable } from '@elastic/
|
|||
|
||||
import { ComponentLoader } from '../../../components/shared/component_loader';
|
||||
|
||||
import * as SourceData from '../source_data';
|
||||
|
||||
import { CustomSourceDeployment } from './custom_source_deployment';
|
||||
|
||||
import { Overview } from './overview';
|
||||
|
@ -144,33 +142,6 @@ describe('Overview', () => {
|
|||
expect(initializeSourceSynchronization).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses a base service type if one is provided', () => {
|
||||
jest.spyOn(SourceData, 'getSourceData');
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
contentSource: {
|
||||
...fullContentSources[0],
|
||||
baseServiceType: 'share_point_server',
|
||||
},
|
||||
});
|
||||
|
||||
shallow(<Overview />);
|
||||
|
||||
expect(SourceData.getSourceData).toHaveBeenCalledWith('share_point_server');
|
||||
});
|
||||
|
||||
it('defaults to the regular service tye', () => {
|
||||
jest.spyOn(SourceData, 'getSourceData');
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
contentSource: fullContentSources[0],
|
||||
});
|
||||
|
||||
shallow(<Overview />);
|
||||
|
||||
expect(SourceData.getSourceData).toHaveBeenCalledWith('custom');
|
||||
});
|
||||
|
||||
describe('custom sources', () => {
|
||||
it('includes deployment instructions', () => {
|
||||
setMockValues({
|
||||
|
|
|
@ -81,7 +81,6 @@ import {
|
|||
SOURCE_SYNC_CONFIRM_TITLE,
|
||||
SOURCE_SYNC_CONFIRM_MESSAGE,
|
||||
} from '../constants';
|
||||
import { getSourceData } from '../source_data';
|
||||
import { SourceLogic } from '../source_logic';
|
||||
|
||||
import { CustomSourceDeployment } from './custom_source_deployment';
|
||||
|
@ -106,12 +105,10 @@ export const Overview: React.FC = () => {
|
|||
isFederatedSource,
|
||||
isIndexedSource,
|
||||
name,
|
||||
serviceType,
|
||||
baseServiceType,
|
||||
} = contentSource;
|
||||
|
||||
const serviceType = contentSource.baseServiceType || contentSource.serviceType;
|
||||
|
||||
const sourceData = getSourceData(serviceType);
|
||||
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const closeModal = () => setIsModalVisible(false);
|
||||
|
@ -431,7 +428,7 @@ export const Overview: React.FC = () => {
|
|||
</h6>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<CustomSourceDeployment source={contentSource} sourceData={sourceData} small />
|
||||
<CustomSourceDeployment source={contentSource} baseServiceType={baseServiceType} small />
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ export const SourceSettings: React.FC = () => {
|
|||
const { isOrganization } = useValues(AppLogic);
|
||||
|
||||
useEffect(() => {
|
||||
getSourceConfigData(serviceType);
|
||||
getSourceConfigData();
|
||||
}, []);
|
||||
|
||||
const isGithubApp =
|
||||
|
|
|
@ -17,9 +17,10 @@ import {
|
|||
} from '../../constants';
|
||||
import { FeatureIds, SourceDataItem } from '../../types';
|
||||
|
||||
export const staticExternalSourceData: SourceDataItem = {
|
||||
// TODO remove Sharepoint-specific content after BYO connector support
|
||||
export const staticGenericExternalSourceData: SourceDataItem = {
|
||||
name: SOURCE_NAMES.SHAREPOINT,
|
||||
iconName: SOURCE_NAMES.SHAREPOINT,
|
||||
categories: [],
|
||||
serviceType: 'external',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -40,16 +41,12 @@ export const staticExternalSourceData: SourceDataItem = {
|
|||
platinumPrivateContext: [FeatureIds.Private, FeatureIds.SyncFrequency, FeatureIds.SyncedItems],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
externalConnectorAvailable: false,
|
||||
customConnectorAvailable: false,
|
||||
isBeta: true,
|
||||
};
|
||||
|
||||
export const staticSourceData: SourceDataItem[] = [
|
||||
{
|
||||
name: SOURCE_NAMES.BOX,
|
||||
iconName: SOURCE_NAMES.BOX,
|
||||
serviceType: 'box',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -74,11 +71,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.CONFLUENCE,
|
||||
iconName: SOURCE_NAMES.CONFLUENCE,
|
||||
serviceType: 'confluence_cloud',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -108,11 +103,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.CONFLUENCE_SERVER,
|
||||
iconName: SOURCE_NAMES.CONFLUENCE_SERVER,
|
||||
serviceType: 'confluence_server',
|
||||
configuration: {
|
||||
isPublicKey: true,
|
||||
|
@ -140,11 +133,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.DROPBOX,
|
||||
iconName: SOURCE_NAMES.DROPBOX,
|
||||
serviceType: 'dropbox',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -169,11 +160,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.GITHUB,
|
||||
iconName: SOURCE_NAMES.GITHUB,
|
||||
serviceType: 'github',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -205,11 +194,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.GITHUB_ENTERPRISE,
|
||||
iconName: SOURCE_NAMES.GITHUB_ENTERPRISE,
|
||||
serviceType: 'github_enterprise_server',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -247,11 +234,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.GMAIL,
|
||||
iconName: SOURCE_NAMES.GMAIL,
|
||||
serviceType: 'gmail',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -265,11 +250,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
platinumPrivateContext: [FeatureIds.Remote, FeatureIds.Private, FeatureIds.SearchableContent],
|
||||
},
|
||||
accountContextOnly: true,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.GOOGLE_DRIVE,
|
||||
iconName: SOURCE_NAMES.GOOGLE_DRIVE,
|
||||
serviceType: 'google_drive',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -298,11 +281,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.JIRA,
|
||||
iconName: SOURCE_NAMES.JIRA,
|
||||
serviceType: 'jira_cloud',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -334,11 +315,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.JIRA_SERVER,
|
||||
iconName: SOURCE_NAMES.JIRA_SERVER,
|
||||
serviceType: 'jira_server',
|
||||
configuration: {
|
||||
isPublicKey: true,
|
||||
|
@ -369,13 +348,12 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.NETWORK_DRVE,
|
||||
iconName: SOURCE_NAMES.NETWORK_DRVE,
|
||||
categories: [SOURCE_CATEGORIES.STORAGE],
|
||||
serviceType: 'network_drive', // this doesn't exist on the BE
|
||||
serviceType: 'custom',
|
||||
baseServiceType: 'network_drive',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: false,
|
||||
|
@ -385,12 +363,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
githubRepository: 'elastic/enterprise-search-network-drive-connector',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.ONEDRIVE,
|
||||
iconName: SOURCE_NAMES.ONEDRIVE,
|
||||
serviceType: 'one_drive',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -415,17 +390,16 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.OUTLOOK,
|
||||
iconName: SOURCE_NAMES.OUTLOOK,
|
||||
categories: [
|
||||
SOURCE_CATEGORIES.COMMUNICATIONS,
|
||||
SOURCE_CATEGORIES.PRODUCTIVITY,
|
||||
SOURCE_CATEGORIES.MICROSOFT,
|
||||
],
|
||||
serviceType: 'outlook', // this doesn't exist on the BE
|
||||
serviceType: 'custom',
|
||||
baseServiceType: 'outlook',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: false,
|
||||
|
@ -435,12 +409,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
githubRepository: 'elastic/enterprise-search-outlook-connector',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.SALESFORCE,
|
||||
iconName: SOURCE_NAMES.SALESFORCE,
|
||||
serviceType: 'salesforce',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -472,11 +443,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.SALESFORCE_SANDBOX,
|
||||
iconName: SOURCE_NAMES.SALESFORCE_SANDBOX,
|
||||
serviceType: 'salesforce_sandbox',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -508,11 +477,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.SERVICENOW,
|
||||
iconName: SOURCE_NAMES.SERVICENOW,
|
||||
serviceType: 'service_now',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -541,11 +508,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.SHAREPOINT,
|
||||
iconName: SOURCE_NAMES.SHAREPOINT,
|
||||
serviceType: 'share_point',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -570,13 +535,39 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
externalConnectorAvailable: true,
|
||||
},
|
||||
staticExternalSourceData,
|
||||
{
|
||||
name: SOURCE_NAMES.SHAREPOINT,
|
||||
categories: [],
|
||||
serviceType: 'external',
|
||||
baseServiceType: 'share_point',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: true,
|
||||
needsBaseUrl: false,
|
||||
documentationUrl: docLinks.workplaceSearchExternalSharePointOnline,
|
||||
applicationPortalUrl: 'https://portal.azure.com/',
|
||||
},
|
||||
objTypes: [SOURCE_OBJ_TYPES.ALL_STORED_FILES],
|
||||
features: {
|
||||
basicOrgContext: [
|
||||
FeatureIds.SyncFrequency,
|
||||
FeatureIds.SyncedItems,
|
||||
FeatureIds.GlobalAccessPermissions,
|
||||
],
|
||||
basicOrgContextExcludedFeatures: [FeatureIds.DocumentLevelPermissions],
|
||||
platinumOrgContext: [FeatureIds.SyncFrequency, FeatureIds.SyncedItems],
|
||||
platinumPrivateContext: [
|
||||
FeatureIds.Private,
|
||||
FeatureIds.SyncFrequency,
|
||||
FeatureIds.SyncedItems,
|
||||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
isBeta: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.SHAREPOINT_SERVER,
|
||||
iconName: SOURCE_NAMES.SHAREPOINT_SERVER,
|
||||
categories: [
|
||||
SOURCE_CATEGORIES.FILE_SHARING,
|
||||
SOURCE_CATEGORIES.STORAGE,
|
||||
|
@ -584,7 +575,8 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
SOURCE_CATEGORIES.MICROSOFT,
|
||||
SOURCE_CATEGORIES.OFFICE_365,
|
||||
],
|
||||
serviceType: 'share_point_server', // this doesn't exist on the BE
|
||||
serviceType: 'custom',
|
||||
baseServiceType: 'share_point_server',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: false,
|
||||
|
@ -594,12 +586,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
githubRepository: 'elastic/enterprise-search-sharepoint-server-connector',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.SLACK,
|
||||
iconName: SOURCE_NAMES.SLACK,
|
||||
serviceType: 'slack',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -617,17 +606,16 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
platinumPrivateContext: [FeatureIds.Remote, FeatureIds.Private, FeatureIds.SearchableContent],
|
||||
},
|
||||
accountContextOnly: true,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.TEAMS,
|
||||
iconName: SOURCE_NAMES.TEAMS,
|
||||
categories: [
|
||||
SOURCE_CATEGORIES.COMMUNICATIONS,
|
||||
SOURCE_CATEGORIES.PRODUCTIVITY,
|
||||
SOURCE_CATEGORIES.MICROSOFT,
|
||||
],
|
||||
serviceType: 'teams', // this doesn't exist on the BE
|
||||
serviceType: 'custom',
|
||||
baseServiceType: 'teams',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: false,
|
||||
|
@ -637,12 +625,9 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
githubRepository: 'elastic/enterprise-search-teams-connector',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.ZENDESK,
|
||||
iconName: SOURCE_NAMES.ZENDESK,
|
||||
serviceType: 'zendesk',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
|
@ -667,13 +652,12 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
],
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: true,
|
||||
},
|
||||
{
|
||||
name: SOURCE_NAMES.ZOOM,
|
||||
iconName: SOURCE_NAMES.ZOOM,
|
||||
categories: [SOURCE_CATEGORIES.COMMUNICATIONS, SOURCE_CATEGORIES.PRODUCTIVITY],
|
||||
serviceType: 'zoom', // this doesn't exist on the BE
|
||||
serviceType: 'custom',
|
||||
baseServiceType: 'zoom',
|
||||
configuration: {
|
||||
isPublicKey: false,
|
||||
hasOauthRedirect: false,
|
||||
|
@ -683,14 +667,12 @@ export const staticSourceData: SourceDataItem[] = [
|
|||
githubRepository: 'elastic/enterprise-search-zoom-connector',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
internalConnectorAvailable: false,
|
||||
customConnectorAvailable: true,
|
||||
},
|
||||
staticGenericExternalSourceData,
|
||||
];
|
||||
|
||||
export const staticCustomSourceData: SourceDataItem = {
|
||||
name: SOURCE_NAMES.CUSTOM,
|
||||
iconName: SOURCE_NAMES.CUSTOM,
|
||||
categories: ['API', 'Custom'],
|
||||
serviceType: 'custom',
|
||||
configuration: {
|
||||
|
@ -701,12 +683,26 @@ export const staticCustomSourceData: SourceDataItem = {
|
|||
applicationPortalUrl: '',
|
||||
},
|
||||
accountContextOnly: false,
|
||||
customConnectorAvailable: true,
|
||||
};
|
||||
|
||||
export const getSourceData = (serviceType: string): SourceDataItem => {
|
||||
return (
|
||||
staticSourceData.find((staticSource) => staticSource.serviceType === serviceType) ||
|
||||
staticCustomSourceData
|
||||
export const getSourceData = (
|
||||
serviceType: string,
|
||||
baseServiceType?: string
|
||||
): SourceDataItem | undefined => {
|
||||
if (serviceType === 'custom' && typeof baseServiceType === 'undefined') {
|
||||
return staticCustomSourceData;
|
||||
}
|
||||
return staticSourceData.find(
|
||||
(staticSource) =>
|
||||
staticSource.serviceType === serviceType && staticSource.baseServiceType === baseServiceType
|
||||
);
|
||||
};
|
||||
|
||||
export const hasExternalConnectorOption = (serviceType: string): boolean =>
|
||||
!!getSourceData('external', serviceType);
|
||||
|
||||
export const hasCustomConnectorOption = (serviceType: string): boolean =>
|
||||
!!getSourceData('custom', serviceType);
|
||||
|
||||
export const hasMultipleConnectorOptions = (serviceType: string): boolean =>
|
||||
hasExternalConnectorOption(serviceType) || hasCustomConnectorOption(serviceType);
|
||||
|
|
|
@ -23,7 +23,12 @@ import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers';
|
|||
import { AppLogic } from '../../app_logic';
|
||||
|
||||
import { staticSourceData } from './source_data';
|
||||
import { SourcesLogic, fetchSourceStatuses, POLLING_INTERVAL } from './sources_logic';
|
||||
import {
|
||||
SourcesLogic,
|
||||
fetchSourceStatuses,
|
||||
POLLING_INTERVAL,
|
||||
mergeServerAndStaticData,
|
||||
} from './sources_logic';
|
||||
|
||||
describe('SourcesLogic', () => {
|
||||
const { http } = mockHttpValues;
|
||||
|
@ -37,8 +42,14 @@ describe('SourcesLogic', () => {
|
|||
const defaultValues = {
|
||||
contentSources: [],
|
||||
privateContentSources: [],
|
||||
sourceData: staticSourceData.map((data) => ({ ...data, connected: false })),
|
||||
availableSources: staticSourceData.map((data) => ({ ...data, connected: false })),
|
||||
sourceData: mergeServerAndStaticData([], staticSourceData, []).map((data) => ({
|
||||
...data,
|
||||
connected: false,
|
||||
})),
|
||||
availableSources: mergeServerAndStaticData([], staticSourceData, []).map((data) => ({
|
||||
...data,
|
||||
connected: false,
|
||||
})),
|
||||
configuredSources: [],
|
||||
serviceTypes: [],
|
||||
permissionsModal: null,
|
||||
|
@ -322,7 +333,7 @@ describe('SourcesLogic', () => {
|
|||
it('availableSources & configuredSources have correct length', () => {
|
||||
SourcesLogic.actions.onInitializeSources(serverResponse);
|
||||
|
||||
expect(SourcesLogic.values.availableSources).toHaveLength(18);
|
||||
expect(SourcesLogic.values.availableSources).toHaveLength(19);
|
||||
expect(SourcesLogic.values.configuredSources).toHaveLength(5);
|
||||
});
|
||||
it('externalConfigured is set to true if external is configured', () => {
|
||||
|
|
|
@ -51,7 +51,7 @@ export interface IPermissionsModalProps {
|
|||
additionalConfiguration: boolean;
|
||||
}
|
||||
|
||||
type CombinedDataItem = SourceDataItem & { connected: boolean };
|
||||
type CombinedDataItem = SourceDataItem & Partial<Connector> & { connected: boolean };
|
||||
|
||||
export interface ISourcesValues {
|
||||
contentSources: ContentSourceDetails[];
|
||||
|
@ -145,17 +145,17 @@ export const SourcesLogic = kea<MakeLogicType<ISourcesValues, ISourcesActions>>(
|
|||
selectors: ({ selectors }) => ({
|
||||
availableSources: [
|
||||
() => [selectors.sourceData],
|
||||
(sourceData: SourceDataItem[]) =>
|
||||
(sourceData: CombinedDataItem[]) =>
|
||||
sortByName(sourceData.filter(({ configured }) => !configured)),
|
||||
],
|
||||
configuredSources: [
|
||||
() => [selectors.sourceData],
|
||||
(sourceData: SourceDataItem[]) =>
|
||||
(sourceData: CombinedDataItem[]) =>
|
||||
sortByName(sourceData.filter(({ configured }) => configured)),
|
||||
],
|
||||
externalConfigured: [
|
||||
() => [selectors.configuredSources],
|
||||
(configuredSources: SourceDataItem[]) =>
|
||||
(configuredSources: CombinedDataItem[]) =>
|
||||
!!configuredSources.find((item) => item.serviceType === 'external'),
|
||||
],
|
||||
sourceData: [
|
||||
|
@ -312,9 +312,12 @@ export const mergeServerAndStaticData = (
|
|||
contentSources: ContentSourceDetails[]
|
||||
): CombinedDataItem[] => {
|
||||
const unsortedData = staticData.map((staticItem) => {
|
||||
const serverItem = serverData.find(({ serviceType }) => serviceType === staticItem.serviceType);
|
||||
const serverItem = staticItem.baseServiceType
|
||||
? undefined // static items with base service types will never have matching external connectors, BE doesn't pass us a baseServiceType
|
||||
: serverData.find(({ serviceType }) => serviceType === staticItem.serviceType);
|
||||
const connectedSource = contentSources.find(
|
||||
({ serviceType }) => serviceType === staticItem.serviceType
|
||||
({ baseServiceType, serviceType }) =>
|
||||
serviceType === staticItem.serviceType && baseServiceType === staticItem.baseServiceType
|
||||
);
|
||||
return {
|
||||
...staticItem,
|
||||
|
|
|
@ -10,11 +10,11 @@ import '../../../__mocks__/shallow_useeffect.mock';
|
|||
import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
import { Route, Switch, Redirect } from 'react-router-dom';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { ADD_SOURCE_PATH, PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes';
|
||||
import { ADD_SOURCE_PATH, PRIVATE_SOURCES_PATH, getSourcesPath } from '../../routes';
|
||||
|
||||
import { SourcesRouter } from './sources_router';
|
||||
|
||||
|
@ -34,19 +34,13 @@ describe('SourcesRouter', () => {
|
|||
});
|
||||
|
||||
it('renders sources routes', () => {
|
||||
const TOTAL_ROUTES = 103;
|
||||
const wrapper = shallow(<SourcesRouter />);
|
||||
|
||||
expect(wrapper.find(Switch)).toHaveLength(1);
|
||||
expect(wrapper.find(Route)).toHaveLength(TOTAL_ROUTES);
|
||||
});
|
||||
|
||||
it('redirects when nonplatinum license and accountOnly context', () => {
|
||||
setMockValues({ ...mockValues, hasPlatinumLicense: false });
|
||||
const wrapper = shallow(<SourcesRouter />);
|
||||
|
||||
expect(wrapper.find(Redirect).last().prop('from')).toEqual(ADD_SOURCE_PATH);
|
||||
expect(wrapper.find(Redirect).last().prop('to')).toEqual(SOURCES_PATH);
|
||||
expect(wrapper.find('[data-test-subj="ConnectorIntroRoute"]')).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="ConnectorChoiceRoute"]')).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="ExternalConnectorConfigRoute"]')).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="AddCustomSourceRoute"]')).toHaveLength(2);
|
||||
expect(wrapper.find('[data-test-subj="AddSourceRoute"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('redirects when cannot create sources', () => {
|
||||
|
|
|
@ -11,7 +11,6 @@ import { Redirect, Route, Switch, useLocation } from 'react-router-dom';
|
|||
import { Location } from 'history';
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { LicensingLogic } from '../../../shared/licensing';
|
||||
import { AppLogic } from '../../app_logic';
|
||||
import {
|
||||
GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE,
|
||||
|
@ -24,17 +23,15 @@ import {
|
|||
SOURCES_PATH,
|
||||
getSourcesPath,
|
||||
getAddPath,
|
||||
ADD_CUSTOM_PATH,
|
||||
} from '../../routes';
|
||||
import { hasMultipleConnectorOptions } from '../../utils';
|
||||
|
||||
import { AddSource, AddSourceList, GitHubViaApp } from './components/add_source';
|
||||
import { AddCustomSource } from './components/add_source/add_custom_source';
|
||||
import { ExternalConnectorConfig } from './components/add_source/add_external_connector';
|
||||
import { ConfigurationChoice } from './components/add_source/configuration_choice';
|
||||
import { AddSourceChoice } from './components/add_source/add_source_choice';
|
||||
import { AddSourceIntro } from './components/add_source/add_source_intro';
|
||||
import { OrganizationSources } from './organization_sources';
|
||||
import { PrivateSources } from './private_sources';
|
||||
import { staticCustomSourceData, staticSourceData as sources } from './source_data';
|
||||
import { SourceRouter } from './source_router';
|
||||
import { SourcesLogic } from './sources_logic';
|
||||
|
||||
|
@ -42,7 +39,6 @@ import './sources.scss';
|
|||
|
||||
export const SourcesRouter: React.FC = () => {
|
||||
const { pathname } = useLocation() as Location;
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { resetSourcesState } = useActions(SourcesLogic);
|
||||
const {
|
||||
account: { canCreatePrivateSources },
|
||||
|
@ -82,119 +78,51 @@ export const SourcesRouter: React.FC = () => {
|
|||
<Route exact path={getAddPath(GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE)}>
|
||||
<GitHubViaApp isGithubEnterpriseServer />
|
||||
</Route>
|
||||
{sources.map((sourceData, i) => {
|
||||
const { serviceType, externalConnectorAvailable, internalConnectorAvailable } = sourceData;
|
||||
const path = `${getSourcesPath(getAddPath(serviceType), isOrganization)}`;
|
||||
const defaultOption = internalConnectorAvailable
|
||||
? 'internal'
|
||||
: externalConnectorAvailable
|
||||
? 'external'
|
||||
: 'custom';
|
||||
const showChoice = defaultOption !== 'internal' && hasMultipleConnectorOptions(sourceData);
|
||||
return (
|
||||
<Route key={i} exact path={path}>
|
||||
{showChoice ? (
|
||||
<ConfigurationChoice sourceData={sourceData} />
|
||||
) : (
|
||||
<Redirect exact from={path} to={`${path}/${defaultOption}`} />
|
||||
)}
|
||||
</Route>
|
||||
);
|
||||
})}
|
||||
<Route exact path={getSourcesPath(ADD_CUSTOM_PATH, isOrganization)}>
|
||||
<AddCustomSource sourceData={staticCustomSourceData} />
|
||||
<Route
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(':serviceType'), isOrganization)}/intro`}
|
||||
data-test-subj="ConnectorIntroRoute"
|
||||
>
|
||||
<AddSourceIntro />
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(':serviceType'), isOrganization)}/choice`}
|
||||
data-test-subj="ConnectorChoiceRoute"
|
||||
>
|
||||
<AddSourceChoice />
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={`${getSourcesPath(
|
||||
getAddPath('external', ':baseServiceType'),
|
||||
isOrganization
|
||||
)}/connector_registration`}
|
||||
data-test-subj="ExternalConnectorConfigRoute"
|
||||
>
|
||||
<ExternalConnectorConfig />
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath('custom'), isOrganization)}/`}
|
||||
data-test-subj="AddCustomSourceRoute"
|
||||
>
|
||||
<AddCustomSource />
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath('custom', ':baseServiceType'), isOrganization)}/`}
|
||||
data-test-subj="AddCustomSourceRoute"
|
||||
>
|
||||
<AddCustomSource />
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(':serviceType'), isOrganization)}/:initialStep?`}
|
||||
data-test-subj="AddSourceRoute"
|
||||
>
|
||||
<AddSource />
|
||||
</Route>
|
||||
{sources
|
||||
.filter((sourceData) => sourceData.internalConnectorAvailable)
|
||||
.map((sourceData, i) => {
|
||||
const { serviceType, accountContextOnly } = sourceData;
|
||||
return (
|
||||
<Route
|
||||
key={i}
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(serviceType), isOrganization)}/internal`}
|
||||
>
|
||||
{!hasPlatinumLicense && accountContextOnly ? (
|
||||
<Redirect exact from={ADD_SOURCE_PATH} to={SOURCES_PATH} />
|
||||
) : (
|
||||
<AddSource sourceData={sourceData} />
|
||||
)}
|
||||
</Route>
|
||||
);
|
||||
})}
|
||||
{sources
|
||||
.filter((sourceData) => sourceData.externalConnectorAvailable)
|
||||
.map((sourceData, i) => {
|
||||
const { serviceType, accountContextOnly } = sourceData;
|
||||
|
||||
return (
|
||||
<Route
|
||||
key={i}
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(serviceType), isOrganization)}/external`}
|
||||
>
|
||||
{!hasPlatinumLicense && accountContextOnly ? (
|
||||
<Redirect exact from={ADD_SOURCE_PATH} to={SOURCES_PATH} />
|
||||
) : (
|
||||
<ExternalConnectorConfig sourceData={sourceData} />
|
||||
)}
|
||||
</Route>
|
||||
);
|
||||
})}
|
||||
{sources
|
||||
.filter((sourceData) => sourceData.customConnectorAvailable)
|
||||
.map((sourceData, i) => {
|
||||
const { serviceType, accountContextOnly } = sourceData;
|
||||
return (
|
||||
<Route
|
||||
key={i}
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(serviceType), isOrganization)}/custom`}
|
||||
>
|
||||
{!hasPlatinumLicense && accountContextOnly ? (
|
||||
<Redirect exact from={ADD_SOURCE_PATH} to={SOURCES_PATH} />
|
||||
) : (
|
||||
<AddCustomSource sourceData={sourceData} initialValue={sourceData.name} />
|
||||
)}
|
||||
</Route>
|
||||
);
|
||||
})}
|
||||
{sources.map((sourceData, i) => (
|
||||
<Route
|
||||
key={i}
|
||||
exact
|
||||
path={`${getSourcesPath(getAddPath(sourceData.serviceType), isOrganization)}/connect`}
|
||||
>
|
||||
<AddSource connect sourceData={sourceData} />
|
||||
</Route>
|
||||
))}
|
||||
{sources.map((sourceData, i) => (
|
||||
<Route
|
||||
key={i}
|
||||
exact
|
||||
path={`${getSourcesPath(
|
||||
getAddPath(sourceData.serviceType),
|
||||
isOrganization
|
||||
)}/reauthenticate`}
|
||||
>
|
||||
<AddSource reAuthenticate sourceData={sourceData} />
|
||||
</Route>
|
||||
))}
|
||||
{sources.map((sourceData, i) => {
|
||||
if (sourceData.configuration.needsConfiguration)
|
||||
return (
|
||||
<Route
|
||||
key={i}
|
||||
exact
|
||||
path={`${getSourcesPath(
|
||||
getAddPath(sourceData.serviceType),
|
||||
isOrganization
|
||||
)}/configure`}
|
||||
>
|
||||
<AddSource configure sourceData={sourceData} />
|
||||
</Route>
|
||||
);
|
||||
})}
|
||||
{canCreatePrivateSources ? (
|
||||
<Route exact path={getSourcesPath(ADD_SOURCE_PATH, false)}>
|
||||
<AddSourceList />
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import '../../../../__mocks__/shallow_useeffect.mock';
|
||||
|
||||
import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../__mocks__/react_router';
|
||||
import { sourceConfigData } from '../../../__mocks__/content_sources.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
@ -18,8 +19,6 @@ import { EuiCallOut, EuiConfirmModal } from '@elastic/eui';
|
|||
|
||||
import { SaveConfig } from '../../content_sources/components/add_source/save_config';
|
||||
|
||||
import { staticSourceData } from '../../content_sources/source_data';
|
||||
|
||||
import { SourceConfig } from './source_config';
|
||||
|
||||
describe('SourceConfig', () => {
|
||||
|
@ -30,10 +29,11 @@ describe('SourceConfig', () => {
|
|||
beforeEach(() => {
|
||||
setMockValues({ sourceConfigData, dataLoading: false });
|
||||
setMockActions({ deleteSourceConfig, getSourceConfigData, saveSourceConfig });
|
||||
mockUseParams.mockReturnValue({ serviceType: 'share_point' });
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<SourceConfig sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
const saveConfig = wrapper.find(SaveConfig);
|
||||
|
||||
// Trigger modal visibility
|
||||
|
@ -43,15 +43,23 @@ describe('SourceConfig', () => {
|
|||
expect(wrapper.find(EuiCallOut)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('returns null if there is no matching source data for the service type', () => {
|
||||
mockUseParams.mockReturnValue({ serviceType: 'doesnt_exist' });
|
||||
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a breadcrumb fallback while data is loading', () => {
|
||||
setMockValues({ dataLoading: true, sourceConfigData: {} });
|
||||
const wrapper = shallow(<SourceConfig sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
|
||||
expect(wrapper.prop('pageChrome')).toEqual(['Settings', 'Content source connectors', '...']);
|
||||
});
|
||||
|
||||
it('handles delete click', () => {
|
||||
const wrapper = shallow(<SourceConfig sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
const saveConfig = wrapper.find(SaveConfig);
|
||||
|
||||
// Trigger modal visibility
|
||||
|
@ -63,7 +71,7 @@ describe('SourceConfig', () => {
|
|||
});
|
||||
|
||||
it('saves source config', () => {
|
||||
const wrapper = shallow(<SourceConfig sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
const saveConfig = wrapper.find(SaveConfig);
|
||||
|
||||
// Trigger modal visibility
|
||||
|
@ -75,7 +83,7 @@ describe('SourceConfig', () => {
|
|||
});
|
||||
|
||||
it('cancels and closes modal', () => {
|
||||
const wrapper = shallow(<SourceConfig sourceData={staticSourceData[1]} />);
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
const saveConfig = wrapper.find(SaveConfig);
|
||||
|
||||
// Trigger modal visibility
|
||||
|
@ -87,9 +95,8 @@ describe('SourceConfig', () => {
|
|||
});
|
||||
|
||||
it('shows feedback link for external sources', () => {
|
||||
const wrapper = shallow(
|
||||
<SourceConfig sourceData={{ ...staticSourceData[1], serviceType: 'external' }} />
|
||||
);
|
||||
mockUseParams.mockReturnValue({ serviceType: 'external' });
|
||||
const wrapper = shallow(<SourceConfig />);
|
||||
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
|
@ -21,29 +23,34 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { WorkplaceSearchPageTemplate } from '../../../components/layout';
|
||||
import { NAV, REMOVE_BUTTON, CANCEL_BUTTON } from '../../../constants';
|
||||
import { SourceDataItem } from '../../../types';
|
||||
import { AddSourceHeader } from '../../content_sources/components/add_source/add_source_header';
|
||||
import { AddSourceLogic } from '../../content_sources/components/add_source/add_source_logic';
|
||||
import { SaveConfig } from '../../content_sources/components/add_source/save_config';
|
||||
import { getSourceData } from '../../content_sources/source_data';
|
||||
import { SettingsLogic } from '../settings_logic';
|
||||
|
||||
interface SourceConfigProps {
|
||||
sourceData: SourceDataItem;
|
||||
}
|
||||
|
||||
export const SourceConfig: React.FC<SourceConfigProps> = ({ sourceData }) => {
|
||||
export const SourceConfig: React.FC = () => {
|
||||
const { serviceType } = useParams<{ serviceType: string }>();
|
||||
const [confirmModalVisible, setConfirmModalVisibility] = useState(false);
|
||||
const { configuration, serviceType } = sourceData;
|
||||
const addSourceLogic = AddSourceLogic({ serviceType });
|
||||
const { deleteSourceConfig } = useActions(SettingsLogic);
|
||||
const { saveSourceConfig, getSourceConfigData } = useActions(AddSourceLogic);
|
||||
const { saveSourceConfig, getSourceConfigData, resetSourceState } = useActions(addSourceLogic);
|
||||
const {
|
||||
sourceConfigData: { name, categories },
|
||||
dataLoading,
|
||||
} = useValues(AddSourceLogic);
|
||||
} = useValues(addSourceLogic);
|
||||
const sourceData = getSourceData(serviceType);
|
||||
|
||||
useEffect(() => {
|
||||
getSourceConfigData(serviceType);
|
||||
}, []);
|
||||
getSourceConfigData();
|
||||
return resetSourceState;
|
||||
}, [serviceType]);
|
||||
|
||||
if (!sourceData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { configuration } = sourceData;
|
||||
|
||||
const hideConfirmModal = () => setConfirmModalVisibility(false);
|
||||
const showConfirmModal = () => setConfirmModalVisibility(true);
|
||||
|
|
|
@ -10,12 +10,10 @@ import '../../../__mocks__/shallow_useeffect.mock';
|
|||
import { setMockActions } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
import { Route, Redirect, Switch } from 'react-router-dom';
|
||||
import { Redirect, Switch } from 'react-router-dom';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { staticSourceData } from '../content_sources/source_data';
|
||||
|
||||
import { Connectors } from './components/connectors';
|
||||
import { Customize } from './components/customize';
|
||||
import { OauthApplication } from './components/oauth_application';
|
||||
|
@ -24,9 +22,6 @@ import { SettingsRouter } from './settings_router';
|
|||
|
||||
describe('SettingsRouter', () => {
|
||||
const initializeSettings = jest.fn();
|
||||
const NUM_SOURCES = staticSourceData.length;
|
||||
// Should be 4 routes other than the sources listed: Connectors, Customize, & OauthApplication, & a redirect
|
||||
const NUM_ROUTES = NUM_SOURCES + 4;
|
||||
|
||||
beforeEach(() => {
|
||||
setMockActions({ initializeSettings });
|
||||
|
@ -36,11 +31,10 @@ describe('SettingsRouter', () => {
|
|||
const wrapper = shallow(<SettingsRouter />);
|
||||
|
||||
expect(wrapper.find(Switch)).toHaveLength(1);
|
||||
expect(wrapper.find(Route)).toHaveLength(NUM_ROUTES);
|
||||
expect(wrapper.find(Redirect)).toHaveLength(1);
|
||||
expect(wrapper.find(Connectors)).toHaveLength(1);
|
||||
expect(wrapper.find(Customize)).toHaveLength(1);
|
||||
expect(wrapper.find(OauthApplication)).toHaveLength(1);
|
||||
expect(wrapper.find(SourceConfig)).toHaveLength(NUM_SOURCES);
|
||||
expect(wrapper.find(SourceConfig)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
ORG_SETTINGS_OAUTH_APPLICATION_PATH,
|
||||
getEditPath,
|
||||
} from '../../routes';
|
||||
import { staticSourceData } from '../content_sources/source_data';
|
||||
|
||||
import { Connectors } from './components/connectors';
|
||||
import { Customize } from './components/customize';
|
||||
|
@ -42,11 +41,9 @@ export const SettingsRouter: React.FC = () => {
|
|||
<Route exact path={ORG_SETTINGS_OAUTH_APPLICATION_PATH}>
|
||||
<OauthApplication />
|
||||
</Route>
|
||||
{staticSourceData.map((sourceData, i) => (
|
||||
<Route key={i} exact path={getEditPath(sourceData.serviceType)}>
|
||||
<SourceConfig sourceData={sourceData} />
|
||||
</Route>
|
||||
))}
|
||||
<Route exact path={getEditPath(':serviceType')}>
|
||||
<SourceConfig />
|
||||
</Route>
|
||||
<Route>
|
||||
<Redirect to={ORG_SETTINGS_CUSTOMIZE_PATH} />
|
||||
</Route>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue