Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Connectors][ServiceNow] Update store links #117374

Merged
merged 5 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('config', () => {
table: 'incident',
useImportAPI: true,
commentFieldKey: 'work_notes',
appId: '7148dbc91bf1f450ced060a7234bcb88',
});
});

Expand All @@ -35,6 +36,7 @@ describe('config', () => {
table: 'sn_si_incident',
useImportAPI: true,
commentFieldKey: 'work_notes',
appId: '2f0746801baeb01019ae54e4604bcb0f',
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,25 @@ export const ServiceNowITSMActionTypeId = '.servicenow';
export const ServiceNowSIRActionTypeId = '.servicenow-sir';
export const ServiceNowITOMActionTypeId = '.servicenow-itom';

const SN_ITSM_APP_ID = '7148dbc91bf1f450ced060a7234bcb88';
const SN_SIR_APP_ID = '2f0746801baeb01019ae54e4604bcb0f';

export const snExternalServiceConfig: SNProductsConfig = {
'.servicenow': {
importSetTable: 'x_elas2_inc_int_elastic_incident',
appScope: 'x_elas2_inc_int',
table: 'incident',
useImportAPI: ENABLE_NEW_SN_ITSM_CONNECTOR,
commentFieldKey: 'work_notes',
appId: SN_ITSM_APP_ID,
},
'.servicenow-sir': {
importSetTable: 'x_elas2_sir_int_elastic_si_incident',
appScope: 'x_elas2_sir_int',
table: 'sn_si_incident',
useImportAPI: ENABLE_NEW_SN_SIR_CONNECTOR,
commentFieldKey: 'work_notes',
appId: SN_SIR_APP_ID,
},
'.servicenow-itom': {
importSetTable: 'x_elas2_inc_int_elastic_incident',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export interface SNProductsConfigValue {
useImportAPI: boolean;
importSetTable: string;
commentFieldKey: string;
appId?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder why this is optional...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I make it optional because ITOM does not have an application.

}

export type SNProductsConfig = Record<string, SNProductsConfigValue>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,32 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import { ApplicationRequiredCallout } from './application_required_callout';

const appId = 'test';

describe('ApplicationRequiredCallout', () => {
test('it renders the callout', () => {
render(<ApplicationRequiredCallout />);
render(<ApplicationRequiredCallout appId={appId} />);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this ids is something which will never change, then I think the better approach to avoid passing this as props and just use a constants

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I pass the id as a pros is that the component is used by both the ITSM & SecOps so it is different for each connector. Do you want me to do something different?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation. It seems to be right.

expect(screen.getByText('Elastic ServiceNow App not installed')).toBeInTheDocument();
expect(
screen.getByText('Please go to the ServiceNow app store and install the application')
).toBeInTheDocument();
});

test('it renders the ServiceNow store button', () => {
render(<ApplicationRequiredCallout />);
render(<ApplicationRequiredCallout appId={appId} />);
expect(screen.getByText('Visit ServiceNow app store')).toBeInTheDocument();
});

it('should render with correct href for the ServiceNow store button', () => {
render(<ApplicationRequiredCallout appId={appId} />);
expect(screen.getByRole('link')).toHaveAttribute(
'href',
'https://store.servicenow.com/sn_appstore_store.do#!/store/application/test'
);
});

test('it renders an error message if provided', () => {
render(<ApplicationRequiredCallout message="Denied" />);
render(<ApplicationRequiredCallout message="Denied" appId={appId} />);
expect(screen.getByText('Error message: Denied')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ const ERROR_MESSAGE = i18n.translate(
);

interface Props {
appId: string;
message?: string | null;
}

const ApplicationRequiredCalloutComponent: React.FC<Props> = ({ message }) => {
const ApplicationRequiredCalloutComponent: React.FC<Props> = ({ appId, message }) => {
return (
<>
<EuiSpacer size="s" />
Expand All @@ -50,7 +51,7 @@ const ApplicationRequiredCalloutComponent: React.FC<Props> = ({ message }) => {
{ERROR_MESSAGE}: {message}
</p>
)}
<SNStoreButton color="danger" />
<SNStoreButton color="danger" appId={appId} />
</EuiCallOut>
<EuiSpacer size="m" />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { render, screen } from '@testing-library/react';

import { InstallationCallout } from './installation_callout';

const appId = 'test';

describe('DeprecatedCallout', () => {
test('it renders correctly', () => {
render(<InstallationCallout />);
render(<InstallationCallout appId={appId} />);
expect(
screen.getByText(
'To use this connector, first install the Elastic app from the ServiceNow app store.'
Expand All @@ -21,7 +23,15 @@ describe('DeprecatedCallout', () => {
});

test('it renders the button', () => {
render(<InstallationCallout />);
render(<InstallationCallout appId={appId} />);
expect(screen.getByRole('link')).toBeInTheDocument();
});

it('should render with correct href for the ServiceNow store button', () => {
render(<InstallationCallout appId={appId} />);
expect(screen.getByRole('link')).toHaveAttribute(
'href',
'https://store.servicenow.com/sn_appstore_store.do#!/store/application/test'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import { EuiSpacer, EuiCallOut } from '@elastic/eui';
import * as i18n from './translations';
import { SNStoreButton } from './sn_store_button';

const InstallationCalloutComponent: React.FC = () => {
interface Props {
appId: string;
}

const InstallationCalloutComponent: React.FC<Props> = ({ appId }) => {
return (
<>
<EuiSpacer size="s" />
Expand All @@ -22,7 +26,7 @@ const InstallationCalloutComponent: React.FC = () => {
data-test-subj="snInstallationCallout"
title={i18n.INSTALLATION_CALLOUT_TITLE}
>
<SNStoreButton color="warning" />
<SNStoreButton color="warning" appId={appId} />
</EuiCallOut>
<EuiSpacer size="m" />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { InstallationCallout } from './installation_callout';
import { UpdateConnector } from './update_connector';
import { updateActionConnector } from '../../../lib/action_connector_api';
import { Credentials } from './credentials';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { snExternalServiceConfig } from '../../../../../../actions/server/builtin_action_types/servicenow/config';

const ServiceNowConnectorFields: React.FC<ActionConnectorFieldsProps<ServiceNowActionConnector>> =
({
Expand Down Expand Up @@ -151,7 +153,9 @@ const ServiceNowConnectorFields: React.FC<ActionConnectorFieldsProps<ServiceNowA
onCancel={onModalCancel}
/>
)}
{requiresNewApplication && <InstallationCallout />}
{requiresNewApplication && (
<InstallationCallout appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''} />
)}
{!requiresNewApplication && <DeprecatedCallout onMigrate={onMigrateClick} />}
<Credentials
action={action}
Expand All @@ -162,7 +166,10 @@ const ServiceNowConnectorFields: React.FC<ActionConnectorFieldsProps<ServiceNowA
editActionConfig={editActionConfig}
/>
{showApplicationRequiredCallout && requiresNewApplication && (
<ApplicationRequiredCallout message={applicationInfoErrorMsg} />
<ApplicationRequiredCallout
message={applicationInfoErrorMsg}
appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''}
/>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,49 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import { SNStoreButton, SNStoreLink } from './sn_store_button';

const appId = 'test';

describe('SNStoreButton', () => {
it('should render the button', () => {
render(<SNStoreButton color="warning" />);
render(<SNStoreButton color="warning" appId={appId} />);
expect(screen.getByText('Visit ServiceNow app store')).toBeInTheDocument();
});

it('should render a danger button', () => {
render(<SNStoreButton color="danger" />);
render(<SNStoreButton color="danger" appId={appId} />);
expect(screen.getByRole('link')).toHaveClass('euiButton--danger');
});

it('should render with correct href', () => {
render(<SNStoreButton color="warning" />);
expect(screen.getByRole('link')).toHaveAttribute('href', 'https://store.servicenow.com/');
render(<SNStoreButton color="warning" appId={appId} />);
expect(screen.getByRole('link')).toHaveAttribute(
'href',
'https://store.servicenow.com/sn_appstore_store.do#!/store/application/test'
);
});

it('should render with target blank', () => {
render(<SNStoreButton color="warning" />);
render(<SNStoreButton color="warning" appId={appId} />);
expect(screen.getByRole('link')).toHaveAttribute('target', '_blank');
});
});

describe('SNStoreLink', () => {
it('should render the link', () => {
render(<SNStoreLink />);
render(<SNStoreLink appId={appId} />);
expect(screen.getByText('Visit ServiceNow app store')).toBeInTheDocument();
});

it('should render with correct href', () => {
render(<SNStoreLink />);
expect(screen.getByRole('link')).toHaveAttribute('href', 'https://store.servicenow.com/');
render(<SNStoreLink appId={appId} />);
expect(screen.getByRole('link')).toHaveAttribute(
'href',
'https://store.servicenow.com/sn_appstore_store.do#!/store/application/test'
);
});

it('should render with target blank', () => {
render(<SNStoreLink />);
render(<SNStoreLink appId={appId} />);
expect(screen.getByRole('link')).toHaveAttribute('target', '_blank');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,32 @@ import { EuiButtonProps, EuiButton, EuiLink } from '@elastic/eui';

import * as i18n from './translations';

const STORE_URL = 'https://store.servicenow.com/';
const getStoreURL = (appId: string): string =>
`https://store.servicenow.com/sn_appstore_store.do#!/store/application/${appId}`;

interface Props {
appId: string;
color: EuiButtonProps['color'];
}

const SNStoreButtonComponent: React.FC<Props> = ({ color }) => {
const SNStoreButtonComponent: React.FC<Props> = ({ color, appId }) => {
return (
<EuiButton href={STORE_URL} color={color} iconSide="right" iconType="popout" target="_blank">
<EuiButton
href={getStoreURL(appId)}
color={color}
iconSide="right"
iconType="popout"
target="_blank"
>
{i18n.VISIT_SN_STORE}
</EuiButton>
);
};

export const SNStoreButton = memo(SNStoreButtonComponent);

const SNStoreLinkComponent: React.FC = () => (
<EuiLink href={STORE_URL} target="_blank">
const SNStoreLinkComponent: React.FC<Pick<Props, 'appId'>> = ({ appId }) => (
<EuiLink href={getStoreURL(appId)} target="_blank">
{i18n.VISIT_SN_STORE}
</EuiLink>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { isFieldInvalid } from './helpers';
import { ApplicationRequiredCallout } from './application_required_callout';
import { SNStoreLink } from './sn_store_button';
import { CredentialsAuth } from './credentials_auth';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { snExternalServiceConfig } from '../../../../../../actions/server/builtin_action_types/servicenow/config';

const title = i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.updateFormTitle',
Expand Down Expand Up @@ -140,7 +142,11 @@ const UpdateConnectorComponent: React.FC<Props> = ({
id="xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.serviceNowAppRunning"
defaultMessage="The Elastic App from the ServiceNow app store must be installed prior to running the update. {visitLink} to install the app"
values={{
visitLink: <SNStoreLink />,
visitLink: (
<SNStoreLink
appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''}
/>
),
}}
/>
),
Expand Down Expand Up @@ -175,7 +181,10 @@ const UpdateConnectorComponent: React.FC<Props> = ({
<EuiFlexGroup>
<EuiFlexItem>
{applicationInfoErrorMsg && (
<ApplicationRequiredCallout message={applicationInfoErrorMsg} />
<ApplicationRequiredCallout
message={applicationInfoErrorMsg}
appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''}
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
Expand Down