mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Actions][Jira] Set parent issue for Sub-task issue type (#78772)
This commit is contained in:
parent
ec9d220b3c
commit
3d9ea52803
19 changed files with 733 additions and 24 deletions
|
@ -69,6 +69,8 @@ Priority:: The priority of the incident.
|
|||
Labels:: The labels of the incident.
|
||||
Title:: A title for the issue, used for searching the contents of the knowledge base.
|
||||
Description:: The details about the incident.
|
||||
Parent:: The parent issue id or key. Only for `Sub-task` issue types.
|
||||
Priority:: The priority of the incident.
|
||||
Additional comments:: Additional information for the client, such as how to troubleshoot the issue.
|
||||
|
||||
[[configuring-jira]]
|
||||
|
|
|
@ -274,12 +274,12 @@ Running the action by scheduling a task means that we will no longer have a user
|
|||
|
||||
The following table describes the properties of the `options` object.
|
||||
|
||||
| Property | Description | Type |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------ | ------ |
|
||||
| id | The id of the action you want to execute. | string |
|
||||
| params | The `params` value to give the action type executor. | object |
|
||||
| spaceId | The space id the action is within. | string |
|
||||
| apiKey | The Elasticsearch API key to use for context. (Note: only required and used when security is enabled). | string |
|
||||
| Property | Description | Type |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------ | ---------------- |
|
||||
| id | The id of the action you want to execute. | string |
|
||||
| params | The `params` value to give the action type executor. | object |
|
||||
| spaceId | The space id the action is within. | string |
|
||||
| apiKey | The Elasticsearch API key to use for context. (Note: only required and used when security is enabled). | string |
|
||||
| source | The source of the execution, either an HTTP request or a reference to a Saved Object. | object, optional |
|
||||
|
||||
## Example
|
||||
|
@ -308,11 +308,11 @@ This api runs the action and asynchronously returns the result of running the ac
|
|||
|
||||
The following table describes the properties of the `options` object.
|
||||
|
||||
| Property | Description | Type |
|
||||
| -------- | ------------------------------------------------------------------------------------ | ------ |
|
||||
| id | The id of the action you want to execute. | string |
|
||||
| params | The `params` value to give the action type executor. | object |
|
||||
| source | The source of the execution, either an HTTP request or a reference to a Saved Object.| object, optional |
|
||||
| Property | Description | Type |
|
||||
| -------- | ------------------------------------------------------------------------------------- | ---------------- |
|
||||
| id | The id of the action you want to execute. | string |
|
||||
| params | The `params` value to give the action type executor. | object |
|
||||
| source | The source of the execution, either an HTTP request or a reference to a Saved Object. | object, optional |
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -330,7 +330,7 @@ const result = await actionsClient.execute({
|
|||
},
|
||||
source: asSavedObjectExecutionSource({
|
||||
id: '573891ae-8c48-49cb-a197-0cd5ec34a88b',
|
||||
type: 'alert'
|
||||
type: 'alert',
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
@ -620,6 +620,7 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla
|
|||
| issueType | The id of the issue type in Jira. | string _(optional)_ |
|
||||
| priority | The name of the priority in Jira. Example: `Medium`. | string _(optional)_ |
|
||||
| labels | An array of labels. | string[] _(optional)_ |
|
||||
| parent | The parent issue id or key. Only for `Sub-task` issue types. | string _(optional)_ |
|
||||
| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }` | object[] _(optional)_ |
|
||||
|
||||
#### `subActionParams (issueTypes)`
|
||||
|
|
|
@ -93,6 +93,7 @@ describe('api', () => {
|
|||
issueType: '10006',
|
||||
labels: ['kibana', 'elastic'],
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
expect(externalService.updateIncident).not.toHaveBeenCalled();
|
||||
|
@ -252,6 +253,7 @@ describe('api', () => {
|
|||
issueType: '10006',
|
||||
labels: ['kibana', 'elastic'],
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
expect(externalService.createIncident).not.toHaveBeenCalled();
|
||||
|
@ -380,6 +382,36 @@ describe('api', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getIssues', () => {
|
||||
test('it returns the issues correctly', async () => {
|
||||
const res = await api.issues({
|
||||
externalService,
|
||||
params: { title: 'Title test' },
|
||||
});
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
title: 'Test title',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIssue', () => {
|
||||
test('it returns the issue correctly', async () => {
|
||||
const res = await api.issue({
|
||||
externalService,
|
||||
params: { id: 'RJ-107' },
|
||||
});
|
||||
expect(res).toEqual({
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
title: 'Test title',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapping variations', () => {
|
||||
test('overwrite & append', async () => {
|
||||
mapping.set('title', {
|
||||
|
|
|
@ -13,8 +13,10 @@ import {
|
|||
Incident,
|
||||
GetFieldsByIssueTypeHandlerArgs,
|
||||
GetIssueTypesHandlerArgs,
|
||||
GetIssuesHandlerArgs,
|
||||
PushToServiceApiParams,
|
||||
PushToServiceResponse,
|
||||
GetIssueHandlerArgs,
|
||||
} from './types';
|
||||
|
||||
// TODO: to remove, need to support Case
|
||||
|
@ -46,6 +48,18 @@ const getFieldsByIssueTypeHandler = async ({
|
|||
return res;
|
||||
};
|
||||
|
||||
const getIssuesHandler = async ({ externalService, params }: GetIssuesHandlerArgs) => {
|
||||
const { title } = params;
|
||||
const res = await externalService.getIssues(title);
|
||||
return res;
|
||||
};
|
||||
|
||||
const getIssueHandler = async ({ externalService, params }: GetIssueHandlerArgs) => {
|
||||
const { id } = params;
|
||||
const res = await externalService.getIssue(id);
|
||||
return res;
|
||||
};
|
||||
|
||||
const pushToServiceHandler = async ({
|
||||
externalService,
|
||||
mapping,
|
||||
|
@ -83,8 +97,8 @@ const pushToServiceHandler = async ({
|
|||
currentIncident,
|
||||
});
|
||||
} else {
|
||||
const { title, description, priority, labels, issueType } = params;
|
||||
incident = { summary: title, description, priority, labels, issueType };
|
||||
const { title, description, priority, labels, issueType, parent } = params;
|
||||
incident = { summary: title, description, priority, labels, issueType, parent };
|
||||
}
|
||||
|
||||
if (externalId != null) {
|
||||
|
@ -134,4 +148,6 @@ export const api: ExternalServiceApi = {
|
|||
getIncident: getIncidentHandler,
|
||||
issueTypes: getIssueTypesHandler,
|
||||
fieldsByIssueType: getFieldsByIssueTypeHandler,
|
||||
issues: getIssuesHandler,
|
||||
issue: getIssueHandler,
|
||||
};
|
||||
|
|
|
@ -25,6 +25,8 @@ import {
|
|||
JiraExecutorResultData,
|
||||
ExecutorSubActionGetFieldsByIssueTypeParams,
|
||||
ExecutorSubActionGetIssueTypesParams,
|
||||
ExecutorSubActionGetIssuesParams,
|
||||
ExecutorSubActionGetIssueParams,
|
||||
} from './types';
|
||||
import * as i18n from './translations';
|
||||
import { Logger } from '../../../../../../src/core/server';
|
||||
|
@ -37,7 +39,13 @@ interface GetActionTypeParams {
|
|||
configurationUtilities: ActionsConfigurationUtilities;
|
||||
}
|
||||
|
||||
const supportedSubActions: string[] = ['pushToService', 'issueTypes', 'fieldsByIssueType'];
|
||||
const supportedSubActions: string[] = [
|
||||
'pushToService',
|
||||
'issueTypes',
|
||||
'fieldsByIssueType',
|
||||
'issues',
|
||||
'issue',
|
||||
];
|
||||
|
||||
// action type definition
|
||||
export function getActionType(
|
||||
|
@ -137,5 +145,21 @@ async function executor(
|
|||
});
|
||||
}
|
||||
|
||||
if (subAction === 'issues') {
|
||||
const getIssuesParams = subActionParams as ExecutorSubActionGetIssuesParams;
|
||||
data = await api.issues({
|
||||
externalService,
|
||||
params: getIssuesParams,
|
||||
});
|
||||
}
|
||||
|
||||
if (subAction === 'issue') {
|
||||
const getIssueParams = subActionParams as ExecutorSubActionGetIssueParams;
|
||||
data = await api.issue({
|
||||
externalService,
|
||||
params: getIssueParams,
|
||||
});
|
||||
}
|
||||
|
||||
return { status: 'ok', data: data ?? {}, actionId };
|
||||
}
|
||||
|
|
|
@ -61,6 +61,18 @@ const createMock = (): jest.Mocked<ExternalService> => {
|
|||
defaultValue: { name: 'Medium', id: '3' },
|
||||
},
|
||||
})),
|
||||
getIssues: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
title: 'Test title',
|
||||
},
|
||||
]),
|
||||
getIssue: jest.fn().mockImplementation(() => ({
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
title: 'Test title',
|
||||
})),
|
||||
};
|
||||
|
||||
service.createComment.mockImplementationOnce(() =>
|
||||
|
@ -120,6 +132,7 @@ const executorParams: ExecutorSubActionPushParams = {
|
|||
labels: ['kibana', 'elastic'],
|
||||
priority: 'High',
|
||||
issueType: '10006',
|
||||
parent: null,
|
||||
comments: [
|
||||
{
|
||||
commentId: 'case-comment-1',
|
||||
|
|
|
@ -44,6 +44,7 @@ export const ExecutorSubActionPushParamsSchema = schema.object({
|
|||
issueType: schema.nullable(schema.string()),
|
||||
priority: schema.nullable(schema.string()),
|
||||
labels: schema.nullable(schema.arrayOf(schema.string())),
|
||||
parent: schema.nullable(schema.string()),
|
||||
// TODO: modify later to string[] - need for support Case schema
|
||||
comments: schema.nullable(schema.arrayOf(CommentSchema)),
|
||||
...EntityInformation,
|
||||
|
@ -60,6 +61,8 @@ export const ExecutorSubActionGetIssueTypesParamsSchema = schema.object({});
|
|||
export const ExecutorSubActionGetFieldsByIssueTypeParamsSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
export const ExecutorSubActionGetIssuesParamsSchema = schema.object({ title: schema.string() });
|
||||
export const ExecutorSubActionGetIssueParamsSchema = schema.object({ id: schema.string() });
|
||||
|
||||
export const ExecutorParamsSchema = schema.oneOf([
|
||||
schema.object({
|
||||
|
@ -82,4 +85,12 @@ export const ExecutorParamsSchema = schema.oneOf([
|
|||
subAction: schema.literal('fieldsByIssueType'),
|
||||
subActionParams: ExecutorSubActionGetFieldsByIssueTypeParamsSchema,
|
||||
}),
|
||||
schema.object({
|
||||
subAction: schema.literal('issues'),
|
||||
subActionParams: ExecutorSubActionGetIssuesParamsSchema,
|
||||
}),
|
||||
schema.object({
|
||||
subAction: schema.literal('issue'),
|
||||
subActionParams: ExecutorSubActionGetIssueParamsSchema,
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -95,6 +95,14 @@ const fieldsResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
const issueResponse = {
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
fields: { summary: 'Test title' },
|
||||
};
|
||||
|
||||
const issuesResponse = [issueResponse];
|
||||
|
||||
describe('Jira service', () => {
|
||||
let service: ExternalService;
|
||||
|
||||
|
@ -219,6 +227,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -264,6 +273,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
priority: 'High',
|
||||
issueType: null,
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -308,6 +318,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: 'RJ-107',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -324,6 +335,7 @@ describe('Jira service', () => {
|
|||
issuetype: { id: '10006' },
|
||||
labels: [],
|
||||
priority: { name: 'High' },
|
||||
parent: { key: 'RJ-107' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -344,6 +356,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(
|
||||
|
@ -370,6 +383,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -398,6 +412,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: 'RJ-107',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -414,6 +429,7 @@ describe('Jira service', () => {
|
|||
priority: { name: 'High' },
|
||||
issuetype: { id: '10006' },
|
||||
project: { key: 'CK' },
|
||||
parent: { key: 'RJ-107' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -435,6 +451,7 @@ describe('Jira service', () => {
|
|||
labels: [],
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(
|
||||
|
@ -916,4 +933,96 @@ describe('Jira service', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIssues', () => {
|
||||
test('it should return the issues', async () => {
|
||||
requestMock.mockImplementation(() => ({
|
||||
data: {
|
||||
issues: issuesResponse,
|
||||
},
|
||||
}));
|
||||
|
||||
const res = await service.getIssues('Test title');
|
||||
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
title: 'Test title',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('it should call request with correct arguments', async () => {
|
||||
requestMock.mockImplementation(() => ({
|
||||
data: {
|
||||
issues: issuesResponse,
|
||||
},
|
||||
}));
|
||||
|
||||
await service.getIssues('Test title');
|
||||
expect(requestMock).toHaveBeenLastCalledWith({
|
||||
axios,
|
||||
logger,
|
||||
method: 'get',
|
||||
url: `https://siem-kibana.atlassian.net/rest/api/2/search?jql=project=CK and summary ~"Test title"`,
|
||||
});
|
||||
});
|
||||
|
||||
test('it should throw an error', async () => {
|
||||
requestMock.mockImplementation(() => {
|
||||
const error: ResponseError = new Error('An error has occurred');
|
||||
error.response = { data: { errors: { issuetypes: 'Could not get issue types' } } };
|
||||
throw error;
|
||||
});
|
||||
|
||||
expect(service.getIssues('Test title')).rejects.toThrow(
|
||||
'[Action][Jira]: Unable to get issues. Error: An error has occurred. Reason: Could not get issue types'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIssue', () => {
|
||||
test('it should return a single issue', async () => {
|
||||
requestMock.mockImplementation(() => ({
|
||||
data: issueResponse,
|
||||
}));
|
||||
|
||||
const res = await service.getIssue('RJ-107');
|
||||
|
||||
expect(res).toEqual({
|
||||
id: '10267',
|
||||
key: 'RJ-107',
|
||||
title: 'Test title',
|
||||
});
|
||||
});
|
||||
|
||||
test('it should call request with correct arguments', async () => {
|
||||
requestMock.mockImplementation(() => ({
|
||||
data: {
|
||||
issues: issuesResponse,
|
||||
},
|
||||
}));
|
||||
|
||||
await service.getIssue('RJ-107');
|
||||
expect(requestMock).toHaveBeenLastCalledWith({
|
||||
axios,
|
||||
logger,
|
||||
method: 'get',
|
||||
url: `https://siem-kibana.atlassian.net/rest/api/2/issue/RJ-107`,
|
||||
});
|
||||
});
|
||||
|
||||
test('it should throw an error', async () => {
|
||||
requestMock.mockImplementation(() => {
|
||||
const error: ResponseError = new Error('An error has occurred');
|
||||
error.response = { data: { errors: { issuetypes: 'Could not get issue types' } } };
|
||||
throw error;
|
||||
});
|
||||
|
||||
expect(service.getIssue('RJ-107')).rejects.toThrow(
|
||||
'[Action][Jira]: Unable to get issue with id RJ-107. Error: An error has occurred. Reason: Could not get issue types'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,6 +53,8 @@ export const createExternalService = (
|
|||
const getIssueTypeFieldsOldAPIURL = `${url}/${BASE_URL}/issue/createmeta?projectKeys=${projectKey}&issuetypeIds={issueTypeId}&expand=projects.issuetypes.fields`;
|
||||
const getIssueTypesUrl = `${url}/${BASE_URL}/issue/createmeta/${projectKey}/issuetypes`;
|
||||
const getIssueTypeFieldsUrl = `${url}/${BASE_URL}/issue/createmeta/${projectKey}/issuetypes/{issueTypeId}`;
|
||||
const searchUrl = `${url}/${BASE_URL}/search`;
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
auth: { username: email, password: apiToken },
|
||||
});
|
||||
|
@ -90,6 +92,10 @@ export const createExternalService = (
|
|||
fields = { ...fields, priority: { name: incident.priority } };
|
||||
}
|
||||
|
||||
if (incident.parent) {
|
||||
fields = { ...fields, parent: { key: incident.parent } };
|
||||
}
|
||||
|
||||
return fields;
|
||||
};
|
||||
|
||||
|
@ -119,6 +125,17 @@ export const createExternalService = (
|
|||
};
|
||||
}, {});
|
||||
|
||||
const normalizeSearchResults = (
|
||||
issues: Array<{ id: string; key: string; fields: { summary: string } }>
|
||||
) =>
|
||||
issues.map((issue) => ({ id: issue.id, key: issue.key, title: issue.fields?.summary ?? null }));
|
||||
|
||||
const normalizeIssue = (issue: { id: string; key: string; fields: { summary: string } }) => ({
|
||||
id: issue.id,
|
||||
key: issue.key,
|
||||
title: issue.fields?.summary ?? null,
|
||||
});
|
||||
|
||||
const getIncident = async (id: string) => {
|
||||
try {
|
||||
const res = await request({
|
||||
|
@ -378,6 +395,54 @@ export const createExternalService = (
|
|||
}
|
||||
};
|
||||
|
||||
const getIssues = async (title: string) => {
|
||||
const query = `${searchUrl}?jql=project=${projectKey} and summary ~"${title}"`;
|
||||
try {
|
||||
const res = await request({
|
||||
axios: axiosInstance,
|
||||
method: 'get',
|
||||
url: query,
|
||||
logger,
|
||||
proxySettings,
|
||||
});
|
||||
|
||||
return normalizeSearchResults(res.data?.issues ?? []);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
getErrorMessage(
|
||||
i18n.NAME,
|
||||
`Unable to get issues. Error: ${error.message}. Reason: ${createErrorMessage(
|
||||
error.response?.data?.errors ?? {}
|
||||
)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getIssue = async (id: string) => {
|
||||
const getIssueUrl = `${incidentUrl}/${id}`;
|
||||
try {
|
||||
const res = await request({
|
||||
axios: axiosInstance,
|
||||
method: 'get',
|
||||
url: getIssueUrl,
|
||||
logger,
|
||||
proxySettings,
|
||||
});
|
||||
|
||||
return normalizeIssue(res.data ?? {});
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
getErrorMessage(
|
||||
i18n.NAME,
|
||||
`Unable to get issue with id ${id}. Error: ${error.message}. Reason: ${createErrorMessage(
|
||||
error.response?.data?.errors ?? {}
|
||||
)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getIncident,
|
||||
createIncident,
|
||||
|
@ -386,5 +451,7 @@ export const createExternalService = (
|
|||
getCapabilities,
|
||||
getIssueTypes,
|
||||
getFieldsByIssueType,
|
||||
getIssues,
|
||||
getIssue,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
ExecutorSubActionGetCapabilitiesParamsSchema,
|
||||
ExecutorSubActionGetIssueTypesParamsSchema,
|
||||
ExecutorSubActionGetFieldsByIssueTypeParamsSchema,
|
||||
ExecutorSubActionGetIssuesParamsSchema,
|
||||
ExecutorSubActionGetIssueParamsSchema,
|
||||
} from './schema';
|
||||
import { ActionsConfigurationUtilities } from '../../actions_config';
|
||||
import { IncidentConfigurationSchema } from '../case/schema';
|
||||
|
@ -60,7 +62,7 @@ export type ExternalServiceParams = Record<string, unknown>;
|
|||
|
||||
export type Incident = Pick<
|
||||
ExecutorSubActionPushParams,
|
||||
'description' | 'priority' | 'labels' | 'issueType'
|
||||
'description' | 'priority' | 'labels' | 'issueType' | 'parent'
|
||||
> & { summary: string };
|
||||
|
||||
export interface CreateIncidentParams {
|
||||
|
@ -83,6 +85,13 @@ export type GetFieldsByIssueTypeResponse = Record<
|
|||
{ allowedValues: Array<{}>; defaultValue: {} }
|
||||
>;
|
||||
|
||||
export type GetIssuesResponse = Array<{ id: string; key: string; title: string }>;
|
||||
export interface GetIssueResponse {
|
||||
id: string;
|
||||
key: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ExternalService {
|
||||
getIncident: (id: string) => Promise<ExternalServiceParams | undefined>;
|
||||
createIncident: (params: CreateIncidentParams) => Promise<ExternalServiceIncidentResponse>;
|
||||
|
@ -91,6 +100,8 @@ export interface ExternalService {
|
|||
getCapabilities: () => Promise<ExternalServiceParams>;
|
||||
getIssueTypes: () => Promise<GetIssueTypesResponse>;
|
||||
getFieldsByIssueType: (issueTypeId: string) => Promise<GetFieldsByIssueTypeResponse>;
|
||||
getIssues: (title: string) => Promise<GetIssuesResponse>;
|
||||
getIssue: (id: string) => Promise<GetIssueResponse>;
|
||||
}
|
||||
|
||||
export interface PushToServiceApiParams extends ExecutorSubActionPushParams {
|
||||
|
@ -117,6 +128,12 @@ export type ExecutorSubActionGetFieldsByIssueTypeParams = TypeOf<
|
|||
typeof ExecutorSubActionGetFieldsByIssueTypeParamsSchema
|
||||
>;
|
||||
|
||||
export type ExecutorSubActionGetIssuesParams = TypeOf<
|
||||
typeof ExecutorSubActionGetIssuesParamsSchema
|
||||
>;
|
||||
|
||||
export type ExecutorSubActionGetIssueParams = TypeOf<typeof ExecutorSubActionGetIssueParamsSchema>;
|
||||
|
||||
export interface ExternalServiceApiHandlerArgs {
|
||||
externalService: ExternalService;
|
||||
mapping: Map<string, any> | null;
|
||||
|
@ -149,6 +166,16 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse {
|
|||
comments?: ExternalServiceCommentResponse[];
|
||||
}
|
||||
|
||||
export interface GetIssuesHandlerArgs {
|
||||
externalService: ExternalService;
|
||||
params: ExecutorSubActionGetIssuesParams;
|
||||
}
|
||||
|
||||
export interface GetIssueHandlerArgs {
|
||||
externalService: ExternalService;
|
||||
params: ExecutorSubActionGetIssueParams;
|
||||
}
|
||||
|
||||
export interface ExternalServiceApi {
|
||||
handshake: (args: HandshakeApiHandlerArgs) => Promise<void>;
|
||||
pushToService: (args: PushToServiceApiHandlerArgs) => Promise<PushToServiceResponse>;
|
||||
|
@ -157,12 +184,16 @@ export interface ExternalServiceApi {
|
|||
fieldsByIssueType: (
|
||||
args: GetFieldsByIssueTypeHandlerArgs
|
||||
) => Promise<GetFieldsByIssueTypeResponse>;
|
||||
issues: (args: GetIssuesHandlerArgs) => Promise<GetIssuesResponse>;
|
||||
issue: (args: GetIssueHandlerArgs) => Promise<GetIssueResponse>;
|
||||
}
|
||||
|
||||
export type JiraExecutorResultData =
|
||||
| PushToServiceResponse
|
||||
| GetIssueTypesResponse
|
||||
| GetFieldsByIssueTypeResponse;
|
||||
| GetFieldsByIssueTypeResponse
|
||||
| GetIssuesResponse
|
||||
| GetIssueResponse;
|
||||
|
||||
export interface Fields {
|
||||
[key: string]: string | string[] | { name: string } | { key: string } | { id: string };
|
||||
|
|
|
@ -42,3 +42,41 @@ export async function getFieldsByIssueType({
|
|||
signal,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getIssues({
|
||||
http,
|
||||
signal,
|
||||
connectorId,
|
||||
title,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
signal: AbortSignal;
|
||||
connectorId: string;
|
||||
title: string;
|
||||
}): Promise<Record<string, any>> {
|
||||
return await http.post(`${BASE_ACTION_API_PATH}/action/${connectorId}/_execute`, {
|
||||
body: JSON.stringify({
|
||||
params: { subAction: 'issues', subActionParams: { title } },
|
||||
}),
|
||||
signal,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getIssue({
|
||||
http,
|
||||
signal,
|
||||
connectorId,
|
||||
id,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
signal: AbortSignal;
|
||||
connectorId: string;
|
||||
id: string;
|
||||
}): Promise<Record<string, any>> {
|
||||
return await http.post(`${BASE_ACTION_API_PATH}/action/${connectorId}/_execute`, {
|
||||
body: JSON.stringify({
|
||||
params: { subAction: 'getIncident', subActionParams: { id } },
|
||||
}),
|
||||
signal,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,8 +31,10 @@ const actionParams = {
|
|||
priority: 'High',
|
||||
savedObjectId: '123',
|
||||
externalId: null,
|
||||
parent: null,
|
||||
},
|
||||
};
|
||||
|
||||
const connector = {
|
||||
secrets: {},
|
||||
config: {},
|
||||
|
@ -237,5 +239,6 @@ describe('JiraParamsFields renders', () => {
|
|||
expect(wrapper.find('[data-test-subj="prioritySelect"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="descriptionTextArea"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="labelsComboBox"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="search-parent-issues"]').exists()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ import { TextFieldWithMessageVariables } from '../../text_field_with_message_var
|
|||
import { JiraActionParams } from './types';
|
||||
import { useGetIssueTypes } from './use_get_issue_types';
|
||||
import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type';
|
||||
import { SearchIssues } from './search_issues';
|
||||
|
||||
const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionParams>> = ({
|
||||
actionParams,
|
||||
|
@ -30,7 +31,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
http,
|
||||
toastNotifications,
|
||||
}) => {
|
||||
const { title, description, comments, issueType, priority, labels, savedObjectId } =
|
||||
const { title, description, comments, issueType, priority, labels, parent, savedObjectId } =
|
||||
actionParams.subActionParams || {};
|
||||
|
||||
const [issueTypesSelectOptions, setIssueTypesSelectOptions] = useState<EuiSelectOption[]>([]);
|
||||
|
@ -62,6 +63,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
const hasPriority = useMemo(() => Object.prototype.hasOwnProperty.call(fields, 'priority'), [
|
||||
fields,
|
||||
]);
|
||||
const hasParent = useMemo(() => Object.prototype.hasOwnProperty.call(fields, 'parent'), [fields]);
|
||||
|
||||
useEffect(() => {
|
||||
const options = issueTypes.map((type) => ({
|
||||
|
@ -179,6 +181,34 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
/>
|
||||
</EuiFormRow>
|
||||
<EuiHorizontalRule />
|
||||
{hasParent && (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.parentIssueSearchLabel',
|
||||
{
|
||||
defaultMessage: 'Parent issue',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<SearchIssues
|
||||
selectedValue={parent}
|
||||
http={http}
|
||||
toastNotifications={toastNotifications}
|
||||
actionConnector={actionConnector}
|
||||
onChange={(parentIssueKey) => {
|
||||
editSubActionProperty('parent', parentIssueKey);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
{hasPriority && (
|
||||
<>
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useEffect, useCallback, useState, memo } from 'react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
import { HttpSetup, ToastsApi } from 'kibana/public';
|
||||
import { ActionConnector } from '../../../../types';
|
||||
import { useGetIssues } from './use_get_issues';
|
||||
import { useGetSingleIssue } from './use_get_single_issue';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
selectedValue: string | null;
|
||||
http: HttpSetup;
|
||||
toastNotifications: Pick<
|
||||
ToastsApi,
|
||||
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
|
||||
>;
|
||||
actionConnector?: ActionConnector;
|
||||
onChange: (parentIssueKey: string) => void;
|
||||
}
|
||||
|
||||
const SearchIssuesComponent: React.FC<Props> = ({
|
||||
selectedValue,
|
||||
http,
|
||||
toastNotifications,
|
||||
actionConnector,
|
||||
onChange,
|
||||
}) => {
|
||||
const [query, setQuery] = useState<string | null>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>(
|
||||
[]
|
||||
);
|
||||
const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<string>>>([]);
|
||||
|
||||
const { isLoading: isLoadingIssues, issues } = useGetIssues({
|
||||
http,
|
||||
toastNotifications,
|
||||
actionConnector,
|
||||
query,
|
||||
});
|
||||
|
||||
const { isLoading: isLoadingSingleIssue, issue: singleIssue } = useGetSingleIssue({
|
||||
http,
|
||||
toastNotifications,
|
||||
actionConnector,
|
||||
id: selectedValue,
|
||||
});
|
||||
|
||||
useEffect(() => setOptions(issues.map((issue) => ({ label: issue.title, value: issue.key }))), [
|
||||
issues,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoadingSingleIssue || singleIssue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const singleIssueAsOptions = [{ label: singleIssue.title, value: singleIssue.key }];
|
||||
setOptions(singleIssueAsOptions);
|
||||
setSelectedOptions(singleIssueAsOptions);
|
||||
}, [singleIssue, isLoadingSingleIssue]);
|
||||
|
||||
const onSearchChange = useCallback((searchVal: string) => {
|
||||
setQuery(searchVal);
|
||||
}, []);
|
||||
|
||||
const onChangeComboBox = useCallback(
|
||||
(changedOptions) => {
|
||||
setSelectedOptions(changedOptions);
|
||||
onChange(changedOptions[0].value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const inputPlaceholder = useMemo(
|
||||
(): string =>
|
||||
isLoadingIssues || isLoadingSingleIssue
|
||||
? i18n.SEARCH_ISSUES_LOADING
|
||||
: i18n.SEARCH_ISSUES_PLACEHOLDER,
|
||||
[isLoadingIssues, isLoadingSingleIssue]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiComboBox
|
||||
singleSelection
|
||||
fullWidth
|
||||
placeholder={inputPlaceholder}
|
||||
data-test-sub={'search-parent-issues'}
|
||||
aria-label={i18n.SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL}
|
||||
options={options}
|
||||
isLoading={isLoadingIssues || isLoadingSingleIssue}
|
||||
onSearchChange={onSearchChange}
|
||||
selectedOptions={selectedOptions}
|
||||
onChange={onChangeComboBox}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchIssues = memo(SearchIssuesComponent);
|
|
@ -131,3 +131,40 @@ export const FIELDS_API_ERROR = i18n.translate(
|
|||
defaultMessage: 'Unable to get fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const ISSUES_API_ERROR = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssuesMessage',
|
||||
{
|
||||
defaultMessage: 'Unable to get issues',
|
||||
}
|
||||
);
|
||||
|
||||
export const GET_ISSUE_API_ERROR = (id: string) =>
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.unableToGetIssueMessage',
|
||||
{
|
||||
defaultMessage: 'Unable to get issue with id {id}',
|
||||
values: { id },
|
||||
}
|
||||
);
|
||||
|
||||
export const SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Select parent issue',
|
||||
}
|
||||
);
|
||||
|
||||
export const SEARCH_ISSUES_PLACEHOLDER = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Select parent issue',
|
||||
}
|
||||
);
|
||||
|
||||
export const SEARCH_ISSUES_LOADING = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesLoading',
|
||||
{
|
||||
defaultMessage: 'Loading...',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface JiraActionParams {
|
|||
issueType: string;
|
||||
priority: string;
|
||||
labels: string[];
|
||||
parent: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty, debounce } from 'lodash/fp';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { HttpSetup, ToastsApi } from 'kibana/public';
|
||||
import { ActionConnector } 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'
|
||||
>;
|
||||
actionConnector?: ActionConnector;
|
||||
query: string | null;
|
||||
}
|
||||
|
||||
export interface UseGetIssues {
|
||||
issues: Issues;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const useGetIssues = ({
|
||||
http,
|
||||
actionConnector,
|
||||
toastNotifications,
|
||||
query,
|
||||
}: Props): UseGetIssues => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [issues, setIssues] = useState<Issues>([]);
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
const fetchData = debounce(500, async () => {
|
||||
if (!actionConnector || isEmpty(query)) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
abortCtrl.current = new AbortController();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const res = await getIssues({
|
||||
http,
|
||||
signal: abortCtrl.current.signal,
|
||||
connectorId: actionConnector.id,
|
||||
title: query ?? '',
|
||||
});
|
||||
|
||||
if (!didCancel) {
|
||||
setIsLoading(false);
|
||||
setIssues(res.data ?? []);
|
||||
if (res.status && res.status === 'error') {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.ISSUES_API_ERROR,
|
||||
text: `${res.serviceMessage ?? res.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!didCancel) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.ISSUES_API_ERROR,
|
||||
text: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
abortCtrl.current.abort();
|
||||
fetchData();
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
setIsLoading(false);
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
}, [http, actionConnector, toastNotifications, query]);
|
||||
|
||||
return {
|
||||
issues,
|
||||
isLoading,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { HttpSetup, ToastsApi } from 'kibana/public';
|
||||
import { ActionConnector } 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'
|
||||
>;
|
||||
id: string | null;
|
||||
actionConnector?: ActionConnector;
|
||||
}
|
||||
|
||||
export interface UseGetSingleIssue {
|
||||
issue: Issue | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const useGetSingleIssue = ({
|
||||
http,
|
||||
toastNotifications,
|
||||
actionConnector,
|
||||
id,
|
||||
}: Props): UseGetSingleIssue => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [issue, setIssue] = useState<Issue | null>(null);
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
const fetchData = async () => {
|
||||
if (!actionConnector || !id) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
abortCtrl.current = new AbortController();
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await getIssue({
|
||||
http,
|
||||
signal: abortCtrl.current.signal,
|
||||
connectorId: actionConnector.id,
|
||||
id,
|
||||
});
|
||||
|
||||
if (!didCancel) {
|
||||
setIsLoading(false);
|
||||
setIssue(res.data ?? {});
|
||||
if (res.status && res.status === 'error') {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.GET_ISSUE_API_ERROR(id),
|
||||
text: `${res.serviceMessage ?? res.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!didCancel) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.GET_ISSUE_API_ERROR(id),
|
||||
text: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
abortCtrl.current.abort();
|
||||
fetchData();
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
setIsLoading(false);
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
}, [http, actionConnector, id, toastNotifications]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
issue,
|
||||
};
|
||||
};
|
|
@ -333,7 +333,7 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
status: 'error',
|
||||
retry: false,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]',
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -351,7 +351,7 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
status: 'error',
|
||||
retry: false,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.savedObjectId]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]',
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.savedObjectId]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -369,7 +369,7 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
status: 'error',
|
||||
retry: false,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.savedObjectId]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]',
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.savedObjectId]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -392,7 +392,7 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
status: 'error',
|
||||
retry: false,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]',
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -420,7 +420,7 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
status: 'error',
|
||||
retry: false,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]',
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -448,7 +448,7 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
status: 'error',
|
||||
retry: false,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]',
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue