[105264] Fix error not surfacing bug for Jira (#114800)

* [105264] Fix error not surfacing bug for Jira

* [105264] Fix ServiceNow errors not surfacing to UI

* Fix tests with updated functon

* Fix PR errors

Co-authored-by: Kristof-Pierre Cummings <kristofpierre.cummings@elastic.co>
This commit is contained in:
Kristof C 2021-10-29 13:27:41 -05:00 committed by GitHub
parent 91af33b69f
commit 26ca3ffe05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 342 additions and 200 deletions

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './connectors_api';

View file

@ -74,13 +74,33 @@ const fieldsResponse = {
},
};
const issueResponse = {
const issue = {
id: '10267',
key: 'RJ-107',
fields: { summary: 'Test title' },
title: 'test title',
};
const issuesResponse = [issueResponse];
const issueResponse = {
status: 'ok' as const,
connector_id: '1',
data: issue,
};
const issuesResponse = {
...issueResponse,
data: [issue],
};
const camelCasedIssueResponse = {
status: 'ok' as const,
actionId: '1',
data: issue,
};
const camelCasedIssuesResponse = {
...camelCasedIssueResponse,
data: [issue],
};
describe('Jira API', () => {
const http = httpServiceMock.createStartContract();
@ -131,7 +151,7 @@ describe('Jira API', () => {
title: 'test issue',
});
expect(res).toEqual(issuesResponse);
expect(res).toEqual(camelCasedIssuesResponse);
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', {
body: '{"params":{"subAction":"issues","subActionParams":{"title":"test issue"}}}',
signal: abortCtrl.signal,
@ -142,7 +162,7 @@ describe('Jira API', () => {
describe('getIssue', () => {
test('should call get fields API', async () => {
const abortCtrl = new AbortController();
http.post.mockResolvedValueOnce(issuesResponse);
http.post.mockResolvedValueOnce(issueResponse);
const res = await getIssue({
http,
signal: abortCtrl.signal,
@ -150,7 +170,7 @@ describe('Jira API', () => {
id: 'RJ-107',
});
expect(res).toEqual(issuesResponse);
expect(res).toEqual(camelCasedIssueResponse);
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', {
body: '{"params":{"subAction":"issue","subActionParams":{"id":"RJ-107"}}}',
signal: abortCtrl.signal,

View file

@ -7,7 +7,11 @@
import { HttpSetup } from 'kibana/public';
import { ActionTypeExecutorResult } from '../../../../../actions/common';
import { getExecuteConnectorUrl } from '../../../../common/utils/connectors_api';
import { getExecuteConnectorUrl } from '../../../../common/utils';
import {
ConnectorExecutorResult,
rewriteResponseToCamelCase,
} from '../rewrite_response_to_camel_case';
import { IssueTypes, Fields, Issues, Issue } from './types';
export interface GetIssueTypesProps {
@ -17,12 +21,17 @@ export interface GetIssueTypesProps {
}
export async function getIssueTypes({ http, signal, connectorId }: GetIssueTypesProps) {
return http.post<ActionTypeExecutorResult<IssueTypes>>(getExecuteConnectorUrl(connectorId), {
body: JSON.stringify({
params: { subAction: 'issueTypes', subActionParams: {} },
}),
signal,
});
const res = await http.post<ConnectorExecutorResult<IssueTypes>>(
getExecuteConnectorUrl(connectorId),
{
body: JSON.stringify({
params: { subAction: 'issueTypes', subActionParams: {} },
}),
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export interface GetFieldsByIssueTypeProps {
@ -38,12 +47,16 @@ export async function getFieldsByIssueType({
connectorId,
id,
}: GetFieldsByIssueTypeProps): Promise<ActionTypeExecutorResult<Fields>> {
return http.post(getExecuteConnectorUrl(connectorId), {
body: JSON.stringify({
params: { subAction: 'fieldsByIssueType', subActionParams: { id } },
}),
signal,
});
const res = await http.post<ConnectorExecutorResult<Fields>>(
getExecuteConnectorUrl(connectorId),
{
body: JSON.stringify({
params: { subAction: 'fieldsByIssueType', subActionParams: { id } },
}),
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export interface GetIssuesTypeProps {
@ -59,12 +72,16 @@ export async function getIssues({
connectorId,
title,
}: GetIssuesTypeProps): Promise<ActionTypeExecutorResult<Issues>> {
return http.post(getExecuteConnectorUrl(connectorId), {
body: JSON.stringify({
params: { subAction: 'issues', subActionParams: { title } },
}),
signal,
});
const res = await http.post<ConnectorExecutorResult<Issues>>(
getExecuteConnectorUrl(connectorId),
{
body: JSON.stringify({
params: { subAction: 'issues', subActionParams: { title } },
}),
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export interface GetIssueTypeProps {
@ -80,10 +97,11 @@ export async function getIssue({
connectorId,
id,
}: GetIssueTypeProps): Promise<ActionTypeExecutorResult<Issue>> {
return http.post(getExecuteConnectorUrl(connectorId), {
const res = await http.post<ConnectorExecutorResult<Issue>>(getExecuteConnectorUrl(connectorId), {
body: JSON.stringify({
params: { subAction: 'issue', subActionParams: { id } },
}),
signal,
});
return rewriteResponseToCamelCase(res);
}

View file

@ -6,7 +6,7 @@
*/
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../common';
import { getFieldsByIssueType } from './api';
import { Fields } from './types';
@ -14,10 +14,7 @@ import * as i18n from './translations';
interface Props {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
issueType: string | null;
connector?: ActionConnector;
}

View file

@ -6,7 +6,7 @@
*/
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../common';
import { getIssueTypes } from './api';
import { IssueTypes } from './types';
@ -14,10 +14,7 @@ import * as i18n from './translations';
interface Props {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
connector?: ActionConnector;
handleIssueType: (options: Array<{ value: string; text: string }>) => void;
}

View file

@ -6,8 +6,11 @@
*/
import { HttpSetup } from 'kibana/public';
import { ActionTypeExecutorResult } from '../../../../../actions/common';
import { getExecuteConnectorUrl } from '../../../../common/utils/connectors_api';
import {
ConnectorExecutorResult,
rewriteResponseToCamelCase,
} from '../rewrite_response_to_camel_case';
import { ResilientIncidentTypes, ResilientSeverity } from './types';
export const BASE_ACTION_API_PATH = '/api/actions';
@ -19,7 +22,7 @@ export interface Props {
}
export async function getIncidentTypes({ http, signal, connectorId }: Props) {
return http.post<ActionTypeExecutorResult<ResilientIncidentTypes>>(
const res = await http.post<ConnectorExecutorResult<ResilientIncidentTypes>>(
getExecuteConnectorUrl(connectorId),
{
body: JSON.stringify({
@ -28,10 +31,12 @@ export async function getIncidentTypes({ http, signal, connectorId }: Props) {
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export async function getSeverity({ http, signal, connectorId }: Props) {
return http.post<ActionTypeExecutorResult<ResilientSeverity>>(
const res = await http.post<ConnectorExecutorResult<ResilientSeverity>>(
getExecuteConnectorUrl(connectorId),
{
body: JSON.stringify({
@ -40,4 +45,6 @@ export async function getSeverity({ http, signal, connectorId }: Props) {
signal,
}
);
return rewriteResponseToCamelCase(res);
}

View file

@ -0,0 +1,31 @@
/*
* 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 {
ConnectorExecutorResult,
rewriteResponseToCamelCase,
} from './rewrite_response_to_camel_case';
const responseWithSnakeCasedFields: ConnectorExecutorResult<{}> = {
service_message: 'oh noooooo',
connector_id: '1213',
data: {},
status: 'ok',
};
describe('rewriteResponseToCamelCase works correctly', () => {
it('correctly transforms snake case to camel case for ActionTypeExecuteResults', () => {
const camelCasedData = rewriteResponseToCamelCase(responseWithSnakeCasedFields);
expect(camelCasedData).toEqual({
serviceMessage: 'oh noooooo',
actionId: '1213',
data: {},
status: 'ok',
});
});
});

View file

@ -0,0 +1,22 @@
/*
* 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 { ActionTypeExecutorResult, RewriteResponseCase } from '../../../../actions/common';
export type ConnectorExecutorResult<T> = ReturnType<
RewriteResponseCase<ActionTypeExecutorResult<T>>
>;
export const rewriteResponseToCamelCase = <T>({
connector_id: actionId,
service_message: serviceMessage,
...data
}: ConnectorExecutorResult<T>): ActionTypeExecutorResult<T> => ({
...data,
actionId,
...(serviceMessage && { serviceMessage }),
});

View file

@ -6,8 +6,11 @@
*/
import { HttpSetup } from 'kibana/public';
import { ActionTypeExecutorResult } from '../../../../../actions/common';
import { getExecuteConnectorUrl } from '../../../../common/utils/connectors_api';
import {
ConnectorExecutorResult,
rewriteResponseToCamelCase,
} from '../rewrite_response_to_camel_case';
import { Choice } from './types';
export const BASE_ACTION_API_PATH = '/api/actions';
@ -20,10 +23,14 @@ export interface GetChoicesProps {
}
export async function getChoices({ http, signal, connectorId, fields }: GetChoicesProps) {
return http.post<ActionTypeExecutorResult<Choice[]>>(getExecuteConnectorUrl(connectorId), {
body: JSON.stringify({
params: { subAction: 'getChoices', subActionParams: { fields } },
}),
signal,
});
const res = await http.post<ConnectorExecutorResult<Choice[]>>(
getExecuteConnectorUrl(connectorId),
{
body: JSON.stringify({
params: { subAction: 'getChoices', subActionParams: { fields } },
}),
signal,
}
);
return rewriteResponseToCamelCase(res);
}

View file

@ -6,7 +6,7 @@
*/
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../common';
import { getChoices } from './api';
import { Choice } from './types';
@ -14,10 +14,7 @@ import * as i18n from './translations';
export interface UseGetChoicesProps {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
connector?: ActionConnector;
fields: string[];
onSuccess?: (choices: Choice[]) => void;

View file

@ -8,87 +8,96 @@
import { httpServiceMock } from '../../../../../../../../src/core/public/mocks';
import { getIssueTypes, getFieldsByIssueType, getIssues, getIssue } from './api';
const issueTypesResponse = {
status: 'ok',
data: {
projects: [
{
issuetypes: [
{
id: '10006',
name: 'Task',
},
{
id: '10007',
name: 'Bug',
},
],
},
],
},
actionId: 'test',
const issueTypesData = {
projects: [
{
issuetypes: [
{
id: '10006',
name: 'Task',
},
{
id: '10007',
name: 'Bug',
},
],
},
],
};
const fieldsResponse = {
status: 'ok',
data: {
projects: [
{
issuetypes: [
{
id: '10006',
name: 'Task',
fields: {
summary: { fieldId: 'summary' },
priority: {
fieldId: 'priority',
allowedValues: [
{
name: 'Highest',
id: '1',
},
{
name: 'High',
id: '2',
},
{
name: 'Medium',
id: '3',
},
{
name: 'Low',
id: '4',
},
{
name: 'Lowest',
id: '5',
},
],
defaultValue: {
const fieldData = {
projects: [
{
issuetypes: [
{
id: '10006',
name: 'Task',
fields: {
summary: { fieldId: 'summary' },
priority: {
fieldId: 'priority',
allowedValues: [
{
name: 'Highest',
id: '1',
},
{
name: 'High',
id: '2',
},
{
name: 'Medium',
id: '3',
},
{
name: 'Low',
id: '4',
},
{
name: 'Lowest',
id: '5',
},
],
defaultValue: {
name: 'Medium',
id: '3',
},
},
},
],
},
],
actionId: 'test',
},
},
],
},
],
};
const issueTypesResponse = {
status: 'ok' as const,
connector_id: 'test',
data: issueTypesData,
};
const fieldsResponse = {
status: 'ok' as const,
data: fieldData,
connector_id: 'test',
};
const singleIssue = {
id: '10267',
key: 'RJ-107',
title: 'some title',
};
const issueResponse = {
status: 'ok',
data: {
id: '10267',
key: 'RJ-107',
fields: { summary: 'Test title' },
},
actionId: 'test',
status: 'ok' as const,
data: singleIssue,
connector_id: 'test',
};
const issuesResponse = [issueResponse];
const issuesResponse = {
...issueResponse,
data: [singleIssue],
};
describe('Jira API', () => {
const http = httpServiceMock.createStartContract();
@ -100,8 +109,11 @@ describe('Jira API', () => {
const abortCtrl = new AbortController();
http.post.mockResolvedValueOnce(issueTypesResponse);
const res = await getIssueTypes({ http, signal: abortCtrl.signal, connectorId: 'te/st' });
expect(res).toEqual(issueTypesResponse);
expect(res).toEqual({
status: 'ok' as const,
actionId: 'test',
data: issueTypesData,
});
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', {
body: '{"params":{"subAction":"issueTypes","subActionParams":{}}}',
signal: abortCtrl.signal,
@ -120,7 +132,7 @@ describe('Jira API', () => {
id: '10006',
});
expect(res).toEqual(fieldsResponse);
expect(res).toEqual({ status: 'ok', data: fieldData, actionId: 'test' });
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', {
body: '{"params":{"subAction":"fieldsByIssueType","subActionParams":{"id":"10006"}}}',
signal: abortCtrl.signal,
@ -139,7 +151,11 @@ describe('Jira API', () => {
title: 'test issue',
});
expect(res).toEqual(issuesResponse);
expect(res).toEqual({
status: 'ok',
data: [singleIssue],
actionId: 'test',
});
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', {
body: '{"params":{"subAction":"issues","subActionParams":{"title":"test issue"}}}',
signal: abortCtrl.signal,
@ -150,7 +166,7 @@ describe('Jira API', () => {
describe('getIssue', () => {
test('should call get fields API', async () => {
const abortCtrl = new AbortController();
http.post.mockResolvedValueOnce(issuesResponse);
http.post.mockResolvedValueOnce(issueResponse);
const res = await getIssue({
http,
signal: abortCtrl.signal,
@ -158,7 +174,11 @@ describe('Jira API', () => {
id: 'RJ-107',
});
expect(res).toEqual(issuesResponse);
expect(res).toEqual({
status: 'ok',
data: singleIssue,
actionId: 'test',
});
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', {
body: '{"params":{"subAction":"issue","subActionParams":{"id":"RJ-107"}}}',
signal: abortCtrl.signal,

View file

@ -6,7 +6,10 @@
*/
import { HttpSetup } from 'kibana/public';
import { ActionTypeExecutorResult } from '../../../../../../actions/common';
import { BASE_ACTION_API_PATH } from '../../../constants';
import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body';
import { Fields, Issue, IssueTypes } from './types';
export async function getIssueTypes({
http,
@ -16,8 +19,8 @@ export async function getIssueTypes({
http: HttpSetup;
signal: AbortSignal;
connectorId: string;
}): Promise<Record<string, any>> {
return await http.post(
}): Promise<ActionTypeExecutorResult<IssueTypes>> {
const res = await http.post<ConnectorExecutorResult<IssueTypes>>(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -26,6 +29,7 @@ export async function getIssueTypes({
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export async function getFieldsByIssueType({
@ -38,8 +42,8 @@ export async function getFieldsByIssueType({
signal: AbortSignal;
connectorId: string;
id: string;
}): Promise<Record<string, any>> {
return await http.post(
}): Promise<ActionTypeExecutorResult<Fields>> {
const res = await http.post<ConnectorExecutorResult<Fields>>(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -48,6 +52,7 @@ export async function getFieldsByIssueType({
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export async function getIssues({
@ -60,8 +65,8 @@ export async function getIssues({
signal: AbortSignal;
connectorId: string;
title: string;
}): Promise<Record<string, any>> {
return await http.post(
}): Promise<ActionTypeExecutorResult<Issue[]>> {
const res = await http.post<ConnectorExecutorResult<Issue[]>>(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -70,6 +75,7 @@ export async function getIssues({
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export async function getIssue({
@ -82,8 +88,8 @@ export async function getIssue({
signal: AbortSignal;
connectorId: string;
id: string;
}): Promise<Record<string, any>> {
return await http.post(
}): Promise<ActionTypeExecutorResult<Issue>> {
const res = await http.post<ConnectorExecutorResult<Issue>>(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -92,4 +98,5 @@ export async function getIssue({
signal,
}
);
return rewriteResponseToCamelCase(res);
}

View file

@ -8,7 +8,7 @@
import React, { useMemo, useEffect, useCallback, useState, memo } from 'react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { useGetIssues } from './use_get_issues';
import { useGetSingleIssue } from './use_get_single_issue';
@ -17,10 +17,7 @@ import * as i18n from './translations';
interface Props {
selectedValue?: string | null;
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
actionConnector?: ActionConnector;
onChange: (parentIssueKey: string) => void;
}

View file

@ -24,3 +24,18 @@ export interface JiraSecrets {
email: string;
apiToken: string;
}
export type IssueTypes = Array<{ id: string; name: string }>;
export interface Issue {
id: string;
key: string;
title: string;
}
export interface Fields {
[key: string]: {
allowedValues: Array<{ name: string; id: string }> | [];
defaultValue: { name: string; id: string } | {};
};
}

View file

@ -6,24 +6,15 @@
*/
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { Fields } from './types';
import { getFieldsByIssueType } from './api';
import * as i18n from './translations';
interface Fields {
[key: string]: {
allowedValues: Array<{ name: string; id: string }> | [];
defaultValue: { name: string; id: string } | {};
};
}
interface Props {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
issueType: string | undefined;
actionConnector?: ActionConnector;
}

View file

@ -6,19 +6,16 @@
*/
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { IssueTypes } from './types';
import { getIssueTypes } from './api';
import * as i18n from './translations';
type IssueTypes = Array<{ id: string; name: string }>;
interface Props {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
actionConnector?: ActionConnector;
}

View file

@ -7,25 +7,21 @@
import { isEmpty, debounce } from 'lodash/fp';
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { Issue } from './types';
import { getIssues } from './api';
import * as i18n from './translations';
type Issues = Array<{ id: string; key: string; title: string }>;
interface Props {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
actionConnector?: ActionConnector;
query: string | null;
}
export interface UseGetIssues {
issues: Issues;
issues: Issue[];
isLoading: boolean;
}
@ -36,7 +32,7 @@ export const useGetIssues = ({
query,
}: Props): UseGetIssues => {
const [isLoading, setIsLoading] = useState(false);
const [issues, setIssues] = useState<Issues>([]);
const [issues, setIssues] = useState<Issue[]>([]);
const abortCtrl = useRef(new AbortController());
useEffect(() => {

View file

@ -6,23 +6,15 @@
*/
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { Issue } from './types';
import { getIssue } from './api';
import * as i18n from './translations';
interface Issue {
id: string;
key: string;
title: string;
}
interface Props {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
id?: string | null;
actionConnector?: ActionConnector;
}

View file

@ -32,7 +32,6 @@ const incidentTypesResponse = {
{ id: 16, name: 'TBD / Unknown' },
{ id: 15, name: 'Vendor / 3rd party error' },
],
actionId: 'te/st',
};
const severityResponse = {
@ -42,7 +41,6 @@ const severityResponse = {
{ id: 5, name: 'Medium' },
{ id: 6, name: 'High' },
],
actionId: 'te/st',
};
describe('Resilient API', () => {
@ -53,14 +51,14 @@ describe('Resilient API', () => {
describe('getIncidentTypes', () => {
test('should call get choices API', async () => {
const abortCtrl = new AbortController();
http.post.mockResolvedValueOnce(incidentTypesResponse);
http.post.mockResolvedValueOnce({ ...incidentTypesResponse, connector_id: 'te/st' });
const res = await getIncidentTypes({
http,
signal: abortCtrl.signal,
connectorId: 'te/st',
});
expect(res).toEqual(incidentTypesResponse);
expect(res).toEqual({ ...incidentTypesResponse, actionId: 'te/st' });
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', {
body: '{"params":{"subAction":"incidentTypes","subActionParams":{}}}',
signal: abortCtrl.signal,
@ -71,14 +69,15 @@ describe('Resilient API', () => {
describe('getSeverity', () => {
test('should call get choices API', async () => {
const abortCtrl = new AbortController();
http.post.mockResolvedValueOnce(severityResponse);
http.post.mockResolvedValueOnce({ ...severityResponse, connector_id: 'te/st' });
const res = await getSeverity({
http,
signal: abortCtrl.signal,
connectorId: 'te/st',
});
expect(res).toEqual(severityResponse);
expect(res).toEqual({ ...severityResponse, actionId: 'te/st' });
expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', {
body: '{"params":{"subAction":"severity","subActionParams":{}}}',
signal: abortCtrl.signal,

View file

@ -7,6 +7,7 @@
import { HttpSetup } from 'kibana/public';
import { BASE_ACTION_API_PATH } from '../../../constants';
import { rewriteResponseToCamelCase } from '../rewrite_response_body';
export async function getIncidentTypes({
http,
@ -17,7 +18,7 @@ export async function getIncidentTypes({
signal: AbortSignal;
connectorId: string;
}): Promise<Record<string, any>> {
return await http.post(
const res = await http.post(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -26,6 +27,7 @@ export async function getIncidentTypes({
signal,
}
);
return rewriteResponseToCamelCase(res);
}
export async function getSeverity({
@ -37,7 +39,7 @@ export async function getSeverity({
signal: AbortSignal;
connectorId: string;
}): Promise<Record<string, any>> {
return await http.post(
const res = await http.post(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -46,4 +48,5 @@ export async function getSeverity({
signal,
}
);
return rewriteResponseToCamelCase(res);
}

View file

@ -0,0 +1,22 @@
/*
* 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 { ActionTypeExecutorResult, RewriteResponseCase } from '../../../../../actions/common';
export type ConnectorExecutorResult<T> = ReturnType<
RewriteResponseCase<ActionTypeExecutorResult<T>>
>;
export const rewriteResponseToCamelCase = <T>({
connector_id: actionId,
service_message: serviceMessage,
...data
}: ConnectorExecutorResult<T>): ActionTypeExecutorResult<T> => ({
...data,
actionId,
...(serviceMessage && { serviceMessage }),
});

View file

@ -6,11 +6,15 @@
*/
import { HttpSetup } from 'kibana/public';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { snExternalServiceConfig } from '../../../../../../actions/server/builtin_action_types/servicenow/config';
import { BASE_ACTION_API_PATH } from '../../../constants';
import { API_INFO_ERROR } from './translations';
import { AppInfo, RESTApiError } from './types';
import { ConnectorExecutorResult, rewriteResponseToCamelCase } from '../rewrite_response_body';
import { ActionTypeExecutorResult } from '../../../../../../actions/common';
import { Choice } from './types';
export async function getChoices({
http,
@ -22,8 +26,8 @@ export async function getChoices({
signal: AbortSignal;
connectorId: string;
fields: string[];
}): Promise<Record<string, any>> {
return await http.post(
}): Promise<ActionTypeExecutorResult<Choice[]>> {
const res = await http.post<ConnectorExecutorResult<Choice[]>>(
`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`,
{
body: JSON.stringify({
@ -32,6 +36,7 @@ export async function getChoices({
signal,
}
);
return rewriteResponseToCamelCase(res);
}
/**

View file

@ -122,7 +122,7 @@ describe('UseChoices', () => {
it('it displays an error when service fails', async () => {
getChoicesMock.mockResolvedValue({
status: 'error',
service_message: 'An error occurred',
serviceMessage: 'An error occurred',
});
const { waitForNextUpdate } = renderHook<UseChoicesProps, UseChoices>(() =>

View file

@ -6,7 +6,7 @@
*/
import { useCallback, useMemo, useState } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { Choice, Fields } from './types';
@ -14,10 +14,7 @@ import { useGetChoices } from './use_get_choices';
export interface UseChoicesProps {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
actionConnector?: ActionConnector;
fields: string[];
}

View file

@ -121,7 +121,7 @@ describe('useGetChoices', () => {
it('it displays an error when service fails', async () => {
getChoicesMock.mockResolvedValue({
status: 'error',
service_message: 'An error occurred',
serviceMessage: 'An error occurred',
});
const { waitForNextUpdate } = renderHook<UseGetChoicesProps, UseGetChoices>(() =>

View file

@ -6,7 +6,7 @@
*/
import { useState, useEffect, useRef, useCallback } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
import { HttpSetup, IToasts } from 'kibana/public';
import { ActionConnector } from '../../../../types';
import { getChoices } from './api';
import { Choice } from './types';
@ -14,10 +14,7 @@ import * as i18n from './translations';
export interface UseGetChoicesProps {
http: HttpSetup;
toastNotifications: Pick<
ToastsApi,
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
toastNotifications: IToasts;
actionConnector?: ActionConnector;
fields: string[];
onSuccess?: (choices: Choice[]) => void;
@ -66,7 +63,7 @@ export const useGetChoices = ({
if (res.status && res.status === 'error') {
toastNotifications.addDanger({
title: i18n.CHOICES_API_ERROR,
text: `${res.service_message ?? res.message}`,
text: `${res.serviceMessage ?? res.message}`,
});
} else if (onSuccess) {
onSuccess(data);