diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts
index 846013674986e..4aa6725159043 100644
--- a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts
+++ b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts
@@ -27,7 +27,7 @@ export const getActions = (): FindActionResult[] => [
referencedByCount: 0,
},
{
- id: 'd611af27-3532-4da9-8034-271fee81d634',
+ id: '123',
actionTypeId: '.servicenow',
name: 'ServiceNow',
config: {
diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.ts b/x-pack/plugins/case/server/routes/api/cases/push_case.ts
index 5be36d34549b7..871e78495c5dd 100644
--- a/x-pack/plugins/case/server/routes/api/cases/push_case.ts
+++ b/x-pack/plugins/case/server/routes/api/cases/push_case.ts
@@ -37,15 +37,23 @@ export function initPushCaseUserActionApi({
async (context, request, response) => {
try {
const client = context.core.savedObjects.client;
+ const actionsClient = await context.actions?.getActionsClient();
+
const caseId = request.params.case_id;
const query = pipe(
CaseExternalServiceRequestRt.decode(request.body),
fold(throwErrors(Boom.badRequest), identity)
);
+
+ if (actionsClient == null) {
+ throw Boom.notFound('Action client have not been found');
+ }
+
const { username, full_name, email } = await caseService.getUser({ request, response });
+
const pushedDate = new Date().toISOString();
- const [myCase, myCaseConfigure, totalCommentsFindByCases] = await Promise.all([
+ const [myCase, myCaseConfigure, totalCommentsFindByCases, connectors] = await Promise.all([
caseService.getCase({
client,
caseId: request.params.case_id,
@@ -60,6 +68,7 @@ export function initPushCaseUserActionApi({
perPage: 1,
},
}),
+ actionsClient.getAll(),
]);
if (myCase.attributes.status === 'closed') {
@@ -85,9 +94,15 @@ export function initPushCaseUserActionApi({
};
const caseConfigureConnectorId = getConnectorId(myCaseConfigure);
+
// old case may not have new attribute connector_id, so we default to the configured system
- const updateConnectorId =
- myCase.attributes.connector_id == null ? { connector_id: caseConfigureConnectorId } : {};
+ const updateConnectorId = {
+ connector_id: myCase.attributes.connector_id ?? caseConfigureConnectorId,
+ };
+
+ if (!connectors.some(connector => connector.id === updateConnectorId.connector_id)) {
+ throw Boom.notFound('Connector not found or set to none');
+ }
const [updatedCase, updatedComments] = await Promise.all([
caseService.patchCase({
diff --git a/x-pack/plugins/siem/cypress/integration/cases.spec.ts b/x-pack/plugins/siem/cypress/integration/cases.spec.ts
index f541555d56440..e11d76d8f608a 100644
--- a/x-pack/plugins/siem/cypress/integration/cases.spec.ts
+++ b/x-pack/plugins/siem/cypress/integration/cases.spec.ts
@@ -27,7 +27,7 @@ import {
ACTION,
CASE_DETAILS_DESCRIPTION,
CASE_DETAILS_PAGE_TITLE,
- CASE_DETAILS_PUSH_AS_SERVICE_NOW_BTN,
+ CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN,
CASE_DETAILS_STATUS,
CASE_DETAILS_TAGS,
CASE_DETAILS_TIMELINE_MARKDOWN,
@@ -102,7 +102,7 @@ describe('Cases', () => {
.eq(PARTICIPANTS)
.should('have.text', case1.reporter);
cy.get(CASE_DETAILS_TAGS).should('have.text', expectedTags);
- cy.get(CASE_DETAILS_PUSH_AS_SERVICE_NOW_BTN).should('have.attr', 'disabled');
+ cy.get(CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN).should('have.attr', 'disabled');
cy.get(CASE_DETAILS_TIMELINE_MARKDOWN).then($element => {
const timelineLink = $element.prop('href').match(/http(s?):\/\/\w*:\w*(\S*)/)[0];
openCaseTimeline(timelineLink);
diff --git a/x-pack/plugins/siem/cypress/screens/case_details.ts b/x-pack/plugins/siem/cypress/screens/case_details.ts
index dadc1bdff1933..32bb64e93b05f 100644
--- a/x-pack/plugins/siem/cypress/screens/case_details.ts
+++ b/x-pack/plugins/siem/cypress/screens/case_details.ts
@@ -10,7 +10,8 @@ export const CASE_DETAILS_DESCRIPTION = '[data-test-subj="markdown-root"]';
export const CASE_DETAILS_PAGE_TITLE = '[data-test-subj="header-page-title"]';
-export const CASE_DETAILS_PUSH_AS_SERVICE_NOW_BTN = '[data-test-subj="push-to-external-service"]';
+export const CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN =
+ '[data-test-subj="push-to-external-service"]';
export const CASE_DETAILS_STATUS = '[data-test-subj="case-view-status"]';
diff --git a/x-pack/plugins/siem/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/siem/public/cases/components/case_view/index.test.tsx
index 70d2dc97f3f45..881e607808314 100644
--- a/x-pack/plugins/siem/public/cases/components/case_view/index.test.tsx
+++ b/x-pack/plugins/siem/public/cases/components/case_view/index.test.tsx
@@ -15,19 +15,27 @@ import { useUpdateCase } from '../../containers/use_update_case';
import { useGetCase } from '../../containers/use_get_case';
import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions';
import { wait } from '../../../common/lib/helpers';
-import { usePushToService } from '../use_push_to_service';
+
+import { useConnectors } from '../../containers/configure/use_connectors';
+import { connectorsMock } from '../../containers/configure/mock';
+
+import { usePostPushToService } from '../../containers/use_post_push_to_service';
+
jest.mock('../../containers/use_update_case');
jest.mock('../../containers/use_get_case_user_actions');
jest.mock('../../containers/use_get_case');
-jest.mock('../use_push_to_service');
+jest.mock('../../containers/configure/use_connectors');
+jest.mock('../../containers/use_post_push_to_service');
+
const useUpdateCaseMock = useUpdateCase as jest.Mock;
const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock;
-const usePushToServiceMock = usePushToService as jest.Mock;
+const useConnectorsMock = useConnectors as jest.Mock;
+const usePostPushToServiceMock = usePostPushToService as jest.Mock;
export const caseProps: CaseProps = {
caseId: basicCase.id,
userCanCrud: true,
- caseData: basicCase,
+ caseData: { ...basicCase, connectorId: 'servicenow-2' },
fetchCase: jest.fn(),
updateCase: jest.fn(),
};
@@ -42,6 +50,8 @@ describe('CaseView ', () => {
const fetchCaseUserActions = jest.fn();
const fetchCase = jest.fn();
const updateCase = jest.fn();
+ const postPushToService = jest.fn();
+
const data = caseProps.caseData;
const defaultGetCase = {
isLoading: false,
@@ -85,18 +95,8 @@ describe('CaseView ', () => {
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions);
- usePushToServiceMock.mockImplementation(({ updateCase: updateCaseMockCall }) => ({
- pushButton: (
-
- ),
- pushCallouts: null,
- }));
+ usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService }));
+ useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false }));
});
it('should render CaseComponent', async () => {
@@ -328,6 +328,7 @@ describe('CaseView ', () => {
...defaultUseGetCaseUserActions,
hasDataToPush: true,
}));
+
const wrapper = mount(
@@ -335,20 +336,24 @@ describe('CaseView ', () => {
);
+
+ await wait();
+
expect(
wrapper
.find('[data-test-subj="has-data-to-push-button"]')
.first()
.exists()
).toBeTruthy();
+
wrapper
- .find('[data-test-subj="mock-button"]')
+ .find('[data-test-subj="push-to-external-service"]')
.first()
.simulate('click');
+
wrapper.update();
- await wait();
- expect(updateCase).toBeCalledWith(caseProps.caseData);
- expect(fetchCaseUserActions).toBeCalledWith(caseProps.caseData.id);
+
+ expect(postPushToService).toHaveBeenCalled();
});
it('should return null if error', () => {
@@ -429,4 +434,32 @@ describe('CaseView ', () => {
expect(fetchCaseUserActions).toBeCalledWith(caseProps.caseData.id);
expect(fetchCase).toBeCalled();
});
+
+ it('should disable the push button when connector is invalid', () => {
+ useGetCaseUserActionsMock.mockImplementation(() => ({
+ ...defaultUseGetCaseUserActions,
+ hasDataToPush: true,
+ }));
+
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ expect(
+ wrapper
+ .find('button[data-test-subj="push-to-external-service"]')
+ .first()
+ .prop('disabled')
+ ).toBeTruthy();
+ });
});
diff --git a/x-pack/plugins/siem/public/cases/components/case_view/index.tsx b/x-pack/plugins/siem/public/cases/components/case_view/index.tsx
index f80075fbe260d..163272f5087d7 100644
--- a/x-pack/plugins/siem/public/cases/components/case_view/index.tsx
+++ b/x-pack/plugins/siem/public/cases/components/case_view/index.tsx
@@ -163,10 +163,11 @@ export const CaseComponent = React.memo(
);
const { loading: isLoadingConnectors, connectors } = useConnectors();
- const caseConnectorName = useMemo(
- () => connectors.find(c => c.id === caseData.connectorId)?.name ?? 'none',
- [connectors, caseData.connectorId]
- );
+
+ const [caseConnectorName, isValidConnector] = useMemo(() => {
+ const connector = connectors.find(c => c.id === caseData.connectorId);
+ return [connector?.name ?? 'none', !!connector];
+ }, [connectors, caseData.connectorId]);
const currentExternalIncident = useMemo(
() =>
@@ -185,6 +186,7 @@ export const CaseComponent = React.memo(
connectors,
updateCase: handleUpdateCase,
userCanCrud,
+ isValidConnector,
});
const onSubmitConnector = useCallback(
diff --git a/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.test.tsx
index cb00201942312..e3e627e3a136e 100644
--- a/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.test.tsx
+++ b/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.test.tsx
@@ -46,7 +46,9 @@ describe('usePushToService', () => {
connectors: connectorsMock,
updateCase,
userCanCrud: true,
+ isValidConnector: true,
};
+
beforeEach(() => {
jest.resetAllMocks();
(usePostPushToService as jest.Mock).mockImplementation(() => mockPostPush);
@@ -55,6 +57,7 @@ describe('usePushToService', () => {
actionLicense,
}));
});
+
it('push case button posts the push with correct args', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(
@@ -75,6 +78,7 @@ describe('usePushToService', () => {
expect(result.current.pushCallouts).toBeNull();
});
});
+
it('Displays message when user does not have premium license', async () => {
(useGetActionLicense as jest.Mock).mockImplementation(() => ({
isLoading: false,
@@ -96,6 +100,7 @@ describe('usePushToService', () => {
expect(errorsMsg[0].title).toEqual(getLicenseError().title);
});
});
+
it('Displays message when user does not have case enabled in config', async () => {
(useGetActionLicense as jest.Mock).mockImplementation(() => ({
isLoading: false,
@@ -117,6 +122,7 @@ describe('usePushToService', () => {
expect(errorsMsg[0].title).toEqual(getKibanaConfigError().title);
});
});
+
it('Displays message when user does not have a connector configured', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(
@@ -135,6 +141,27 @@ describe('usePushToService', () => {
expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE);
});
});
+
+ it('Displays message when connector is deleted', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(
+ () =>
+ usePushToService({
+ ...defaultArgs,
+ caseConnectorId: 'not-exist',
+ isValidConnector: false,
+ }),
+ {
+ wrapper: ({ children }) => {children},
+ }
+ );
+ await waitForNextUpdate();
+ const errorsMsg = result.current.pushCallouts?.props.messages;
+ expect(errorsMsg).toHaveLength(1);
+ expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE);
+ });
+ });
+
it('Displays message when case is closed', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(
diff --git a/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.tsx b/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.tsx
index 157639f011fef..ae8a67b75d36c 100644
--- a/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.tsx
+++ b/x-pack/plugins/siem/public/cases/components/use_push_to_service/index.tsx
@@ -29,6 +29,7 @@ export interface UsePushToService {
connectors: Connector[];
updateCase: (newCase: Case) => void;
userCanCrud: boolean;
+ isValidConnector: boolean;
}
export interface ReturnUsePushToService {
@@ -45,6 +46,7 @@ export const usePushToService = ({
connectors,
updateCase,
userCanCrud,
+ isValidConnector,
}: UsePushToService): ReturnUsePushToService => {
const urlSearch = useGetUrlSearch(navTabs.case);
@@ -77,7 +79,7 @@ export const usePushToService = ({
description: (
@@ -97,7 +99,20 @@ export const usePushToService = ({
description: (
+ ),
+ },
+ ];
+ } else if (!isValidConnector && !loadingLicense) {
+ errors = [
+ ...errors,
+ {
+ title: i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE,
+ description: (
+
),
},
@@ -130,7 +145,9 @@ export const usePushToService = ({
fill
iconType="importAction"
onClick={handlePushToService}
- disabled={isLoading || loadingLicense || errorsMsg.length > 0 || !userCanCrud}
+ disabled={
+ isLoading || loadingLicense || errorsMsg.length > 0 || !userCanCrud || !isValidConnector
+ }
isLoading={isLoading}
>
{caseServices[caseConnectorId]
@@ -147,6 +164,7 @@ export const usePushToService = ({
isLoading,
loadingLicense,
userCanCrud,
+ isValidConnector,
]);
const objToReturn = useMemo(() => {
diff --git a/x-pack/plugins/siem/public/cases/components/use_push_to_service/translations.ts b/x-pack/plugins/siem/public/cases/components/use_push_to_service/translations.ts
index bdd6ae98a5d01..4b55aa83ef726 100644
--- a/x-pack/plugins/siem/public/cases/components/use_push_to_service/translations.ts
+++ b/x-pack/plugins/siem/public/cases/components/use_push_to_service/translations.ts
@@ -15,9 +15,10 @@ export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate(
export const PUSH_THIRD = (thirdParty: string) => {
if (thirdParty === 'none') {
return i18n.translate('xpack.siem.case.caseView.pushThirdPartyIncident', {
- defaultMessage: 'Push as third party incident',
+ defaultMessage: 'Push as external incident',
});
}
+
return i18n.translate('xpack.siem.case.caseView.pushNamedIncident', {
values: { thirdParty },
defaultMessage: 'Push as { thirdParty } incident',
@@ -27,9 +28,10 @@ export const PUSH_THIRD = (thirdParty: string) => {
export const UPDATE_THIRD = (thirdParty: string) => {
if (thirdParty === 'none') {
return i18n.translate('xpack.siem.case.caseView.updateThirdPartyIncident', {
- defaultMessage: 'Update third party incident',
+ defaultMessage: 'Update external incident',
});
}
+
return i18n.translate('xpack.siem.case.caseView.updateNamedIncident', {
values: { thirdParty },
defaultMessage: 'Update { thirdParty } incident',
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index abc54183ca41a..e49cfbbc67d61 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -13131,7 +13131,6 @@
"xpack.siem.case.caseView.pushToServiceDisableByConfigTitle": "Kibana の構成ファイルで ServiceNow を有効にする",
"xpack.siem.case.caseView.pushToServiceDisableByLicenseDescription": "外部システムでケースを開くには、ライセンスをプラチナに更新するか、30 日間の無料トライアルを開始するか、AWS、GCP、または Azure で {link} にサインアップする必要があります。",
"xpack.siem.case.caseView.pushToServiceDisableByLicenseTitle": "E lastic Platinum へのアップグレード",
- "xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigDescription": "外部システムでケースを開いて更新するには、{link} を設定する必要があります。",
"xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigTitle": "外部コネクターを構成",
"xpack.siem.case.caseView.reopenCase": "ケースを再開",
"xpack.siem.case.caseView.reopenedCase": "ケースを再開する",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index b86d137f0692d..1d3eb0e3a0da8 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -13138,7 +13138,6 @@
"xpack.siem.case.caseView.pushToServiceDisableByConfigTitle": "在 Kibana 配置文件中启用 ServiceNow",
"xpack.siem.case.caseView.pushToServiceDisableByLicenseDescription": "要在外部系统中打开案例,必须将许可证更新到白金级,开始为期 30 天的免费试用,或在 AWS、GCP 或 Azure 上快速部署 {link}。",
"xpack.siem.case.caseView.pushToServiceDisableByLicenseTitle": "升级到 Elastic 白金级",
- "xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigDescription": "要在外部系统上打开和更新案例,必须配置 {link}。",
"xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigTitle": "配置外部连接器",
"xpack.siem.case.caseView.reopenCase": "重新打开案例",
"xpack.siem.case.caseView.reopenedCase": "重新打开的案例",
diff --git a/x-pack/test/case_api_integration/basic/config.ts b/x-pack/test/case_api_integration/basic/config.ts
index f9c248ec3d56f..e711560e11097 100644
--- a/x-pack/test/case_api_integration/basic/config.ts
+++ b/x-pack/test/case_api_integration/basic/config.ts
@@ -9,6 +9,6 @@ import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('basic', {
disabledPlugins: [],
- license: 'basic',
+ license: 'trial',
ssl: true,
});
diff --git a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts
index 848b980dee769..2c1c4369e3ccd 100644
--- a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts
+++ b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts
@@ -6,6 +6,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
+import { ObjectRemover as ActionsRemover } from '../../../../alerting_api_integration/common/lib';
import { CASE_CONFIGURE_URL, CASES_URL } from '../../../../../plugins/case/common/constants';
import { postCaseReq, defaultUser, postCommentReq } from '../../../common/lib/mock';
@@ -15,6 +16,7 @@ import {
deleteComments,
deleteConfiguration,
getConfiguration,
+ getConnector,
} from '../../../common/lib/utils';
// eslint-disable-next-line import/no-default-export
@@ -23,19 +25,31 @@ export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('push_case', () => {
+ const actionsRemover = new ActionsRemover(supertest);
+
afterEach(async () => {
await deleteCases(es);
await deleteComments(es);
await deleteConfiguration(es);
await deleteCasesUserActions(es);
+ await actionsRemover.removeAll();
});
it('should push a case', async () => {
+ const { body: connector } = await supertest
+ .post('/api/action')
+ .set('kbn-xsrf', 'true')
+ .send(getConnector())
+ .expect(200);
+
+ actionsRemover.add('default', connector.id, 'action');
+
const { body: configure } = await supertest
.post(CASE_CONFIGURE_URL)
.set('kbn-xsrf', 'true')
- .send(getConfiguration())
+ .send(getConfiguration(connector.id))
.expect(200);
+
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
@@ -58,11 +72,20 @@ export default ({ getService }: FtrProviderContext): void => {
});
it('pushes a comment appropriately', async () => {
+ const { body: connector } = await supertest
+ .post('/api/action')
+ .set('kbn-xsrf', 'true')
+ .send(getConnector())
+ .expect(200);
+
+ actionsRemover.add('default', connector.id, 'action');
+
const { body: configure } = await supertest
.post(CASE_CONFIGURE_URL)
.set('kbn-xsrf', 'true')
- .send(getConfiguration())
+ .send(getConfiguration(connector.id))
.expect(200);
+
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
@@ -99,6 +122,7 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
expect(body.comments[0].pushed_by).to.eql(defaultUser);
});
+
it('unhappy path - 404s when case does not exist', async () => {
await supertest
.post(`${CASES_URL}/fake-id/_push`)
diff --git a/x-pack/test/case_api_integration/common/config.ts b/x-pack/test/case_api_integration/common/config.ts
index 862705ab9610b..8df4ff66c2a2a 100644
--- a/x-pack/test/case_api_integration/common/config.ts
+++ b/x-pack/test/case_api_integration/common/config.ts
@@ -24,6 +24,7 @@ const enabledActionTypes = [
'.pagerduty',
'.server-log',
'.servicenow',
+ '.jira',
'.slack',
'.webhook',
'test.authorization',
diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts
index 4b1dc6ffa5891..5861db2eb8e5b 100644
--- a/x-pack/test/case_api_integration/common/lib/utils.ts
+++ b/x-pack/test/case_api_integration/common/lib/utils.ts
@@ -23,6 +23,37 @@ export const getConfigurationOutput = (update = false): Partial ({
+ name: 'ServiceNow Connector',
+ actionTypeId: '.servicenow',
+ secrets: {
+ username: 'admin',
+ password: 'password',
+ },
+ config: {
+ apiUrl: 'http://some.non.existent.com',
+ casesConfiguration: {
+ mapping: [
+ {
+ source: 'title',
+ target: 'short_description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'description',
+ target: 'description',
+ actionType: 'append',
+ },
+ {
+ source: 'comments',
+ target: 'comments',
+ actionType: 'append',
+ },
+ ],
+ },
+ },
+});
+
export const removeServerGeneratedPropertiesFromConfigure = (
config: Partial
): Partial => {