mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Defend workflows] Fix action status on action list (#160758)
This commit is contained in:
parent
065ded6ff7
commit
a43ee939b5
6 changed files with 161 additions and 39 deletions
|
@ -30,7 +30,7 @@ export class FleetActionGenerator extends BaseDataGenerator {
|
|||
expiration: this.randomFutureDate(timeStamp),
|
||||
type: 'INPUT_ACTION',
|
||||
input_type: 'endpoint',
|
||||
agents: [this.seededUUIDv4()],
|
||||
agents: overrides.agents ? overrides.agents : [this.seededUUIDv4()],
|
||||
user_id: 'elastic',
|
||||
data: {
|
||||
command: this.randomResponseActionCommand(),
|
||||
|
|
|
@ -212,11 +212,12 @@ const getResponseActionListTableColumns = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: TABLE_COLUMN_NAMES.status,
|
||||
width: !showHostNames ? '15%' : '10%',
|
||||
render: (action: ActionListApiResponse['data'][number]) => {
|
||||
const _status = action.errors?.length ? 'failed' : action.status;
|
||||
render: (_status: ActionListApiResponse['data'][number]['status']) => {
|
||||
const status = getActionStatus(_status);
|
||||
|
||||
return (
|
||||
<EuiToolTip content={status} anchorClassName="eui-textTruncate">
|
||||
<StatusBadge
|
||||
|
|
|
@ -118,7 +118,7 @@ export const getActionDetailsById = async (
|
|||
});
|
||||
|
||||
const { isCompleted, completedAt, wasSuccessful, errors, outputs, agentState } =
|
||||
getActionCompletionInfo(normalizedActionRequest.agents, actionResponses);
|
||||
getActionCompletionInfo(normalizedActionRequest, actionResponses);
|
||||
|
||||
const { isExpired, status } = getActionStatus({
|
||||
expirationDate: normalizedActionRequest.expiration,
|
||||
|
|
|
@ -276,7 +276,7 @@ const getActionDetailsList = async ({
|
|||
|
||||
// find the specific response's details using that set of matching responses
|
||||
const { isCompleted, completedAt, wasSuccessful, errors, agentState, outputs } =
|
||||
getActionCompletionInfo(action.agents, matchedResponses);
|
||||
getActionCompletionInfo(action, matchedResponses);
|
||||
|
||||
const { isExpired, status } = getActionStatus({
|
||||
expirationDate: action.expiration,
|
||||
|
|
|
@ -123,17 +123,42 @@ describe('When using Actions service utilities', () => {
|
|||
});
|
||||
|
||||
it('should show complete `false` if no action ids', () => {
|
||||
expect(getActionCompletionInfo([], [])).toEqual({ ...NOT_COMPLETED_OUTPUT, agentState: {} });
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: [],
|
||||
})
|
||||
),
|
||||
[]
|
||||
)
|
||||
).toEqual({
|
||||
...NOT_COMPLETED_OUTPUT,
|
||||
agentState: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should show complete as `false` if no responses', () => {
|
||||
expect(getActionCompletionInfo(['123'], [])).toEqual(NOT_COMPLETED_OUTPUT);
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[]
|
||||
)
|
||||
).toEqual(NOT_COMPLETED_OUTPUT);
|
||||
});
|
||||
|
||||
it('should show complete as `false` if no Endpoint response', () => {
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
['123'],
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[
|
||||
fleetActionGenerator.generateActivityLogActionResponse({
|
||||
item: { data: { action_id: '123' } },
|
||||
|
@ -156,7 +181,16 @@ describe('When using Actions service utilities', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
expect(getActionCompletionInfo(['123'], [endpointResponse])).toEqual({
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[endpointResponse]
|
||||
)
|
||||
).toEqual({
|
||||
isCompleted: true,
|
||||
completedAt: COMPLETED_AT,
|
||||
errors: undefined,
|
||||
|
@ -201,7 +235,16 @@ describe('When using Actions service utilities', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
expect(getActionCompletionInfo(['123'], [endpointResponse])).toEqual({
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[endpointResponse]
|
||||
)
|
||||
).toEqual({
|
||||
isCompleted: true,
|
||||
completedAt: COMPLETED_AT,
|
||||
errors: undefined,
|
||||
|
@ -255,7 +298,16 @@ describe('When using Actions service utilities', () => {
|
|||
});
|
||||
|
||||
it('should show `wasSuccessful` as `false` if endpoint action response has error', () => {
|
||||
expect(getActionCompletionInfo(['123'], [endpointResponseAtError])).toEqual({
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[endpointResponseAtError]
|
||||
)
|
||||
).toEqual({
|
||||
completedAt: endpointResponseAtError.item.data['@timestamp'],
|
||||
errors: ['Endpoint action response error: endpoint failed to apply'],
|
||||
isCompleted: true,
|
||||
|
@ -273,7 +325,16 @@ describe('When using Actions service utilities', () => {
|
|||
});
|
||||
|
||||
it('should show `wasSuccessful` as `false` if fleet action response has error (no endpoint response)', () => {
|
||||
expect(getActionCompletionInfo(['123'], [fleetResponseAtError])).toEqual({
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[fleetResponseAtError]
|
||||
)
|
||||
).toEqual({
|
||||
completedAt: fleetResponseAtError.item.data.completed_at,
|
||||
errors: ['Fleet action response error: agent failed to deliver'],
|
||||
isCompleted: true,
|
||||
|
@ -292,7 +353,14 @@ describe('When using Actions service utilities', () => {
|
|||
|
||||
it('should include both fleet and endpoint errors if both responses returned failure', () => {
|
||||
expect(
|
||||
getActionCompletionInfo(['123'], [fleetResponseAtError, endpointResponseAtError])
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: ['123'],
|
||||
})
|
||||
),
|
||||
[fleetResponseAtError, endpointResponseAtError]
|
||||
)
|
||||
).toEqual({
|
||||
completedAt: endpointResponseAtError.item.data['@timestamp'],
|
||||
errors: [
|
||||
|
@ -374,7 +442,16 @@ describe('When using Actions service utilities', () => {
|
|||
});
|
||||
|
||||
it('should show complete as `false` if no responses', () => {
|
||||
expect(getActionCompletionInfo(agentIds, [])).toEqual({
|
||||
expect(
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: agentIds,
|
||||
})
|
||||
),
|
||||
[]
|
||||
)
|
||||
).toEqual({
|
||||
...NOT_COMPLETED_OUTPUT,
|
||||
agentState: {
|
||||
...NOT_COMPLETED_OUTPUT.agentState,
|
||||
|
@ -396,14 +473,21 @@ describe('When using Actions service utilities', () => {
|
|||
|
||||
it('should complete as `false` if at least one agent id has not received a response', () => {
|
||||
expect(
|
||||
getActionCompletionInfo(agentIds, [
|
||||
...action123Responses,
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: agentIds,
|
||||
})
|
||||
),
|
||||
[
|
||||
...action123Responses,
|
||||
|
||||
// Action id: 456 === Not complete (only fleet response)
|
||||
action456Responses[0],
|
||||
// Action id: 456 === Not complete (only fleet response)
|
||||
action456Responses[0],
|
||||
|
||||
...action789Responses,
|
||||
])
|
||||
...action789Responses,
|
||||
]
|
||||
)
|
||||
).toEqual({
|
||||
...NOT_COMPLETED_OUTPUT,
|
||||
outputs: expect.any(Object),
|
||||
|
@ -432,11 +516,14 @@ describe('When using Actions service utilities', () => {
|
|||
|
||||
it('should show complete as `true` if all agent response were received', () => {
|
||||
expect(
|
||||
getActionCompletionInfo(agentIds, [
|
||||
...action123Responses,
|
||||
...action456Responses,
|
||||
...action789Responses,
|
||||
])
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: agentIds,
|
||||
})
|
||||
),
|
||||
[...action123Responses, ...action456Responses, ...action789Responses]
|
||||
)
|
||||
).toEqual({
|
||||
isCompleted: true,
|
||||
completedAt: COMPLETED_AT,
|
||||
|
@ -471,14 +558,21 @@ describe('When using Actions service utilities', () => {
|
|||
action456Responses[0].item.data['@timestamp'] = '2022-05-06T12:50:19.747Z';
|
||||
|
||||
expect(
|
||||
getActionCompletionInfo(agentIds, [
|
||||
...action123Responses,
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: agentIds,
|
||||
})
|
||||
),
|
||||
[
|
||||
...action123Responses,
|
||||
|
||||
// Action id: 456 === is complete with only a fleet response that has `error`
|
||||
action456Responses[0],
|
||||
// Action id: 456 === is complete with only a fleet response that has `error`
|
||||
action456Responses[0],
|
||||
|
||||
...action789Responses,
|
||||
])
|
||||
...action789Responses,
|
||||
]
|
||||
)
|
||||
).toEqual({
|
||||
completedAt: '2022-05-06T12:50:19.747Z',
|
||||
errors: ['Fleet action response error: something is no good'],
|
||||
|
@ -528,14 +622,21 @@ describe('When using Actions service utilities', () => {
|
|||
};
|
||||
|
||||
expect(
|
||||
getActionCompletionInfo(agentIds, [
|
||||
...action123Responses,
|
||||
getActionCompletionInfo(
|
||||
mapToNormalizedActionRequest(
|
||||
fleetActionGenerator.generate({
|
||||
agents: agentIds,
|
||||
})
|
||||
),
|
||||
[
|
||||
...action123Responses,
|
||||
|
||||
// Action id: 456 === Not complete (only fleet response)
|
||||
action456Responses[0],
|
||||
// Action id: 456 === Not complete (only fleet response)
|
||||
action456Responses[0],
|
||||
|
||||
...action789Responses,
|
||||
])
|
||||
...action789Responses,
|
||||
]
|
||||
)
|
||||
).toEqual({
|
||||
...NOT_COMPLETED_OUTPUT,
|
||||
agentState: {
|
||||
|
|
|
@ -115,11 +115,12 @@ type ActionCompletionInfo = Pick<
|
|||
>;
|
||||
|
||||
export const getActionCompletionInfo = (
|
||||
/** List of agents that the action was sent to */
|
||||
agentIds: string[],
|
||||
/** The normalized action request */
|
||||
action: NormalizedActionRequest,
|
||||
/** List of action Log responses received for the action */
|
||||
actionResponses: Array<ActivityLogActionResponse | EndpointActivityLogActionResponse>
|
||||
): ActionCompletionInfo => {
|
||||
const agentIds = action.agents;
|
||||
const completedInfo: ActionCompletionInfo = {
|
||||
completedAt: undefined,
|
||||
errors: undefined,
|
||||
|
@ -191,6 +192,25 @@ export const getActionCompletionInfo = (
|
|||
}
|
||||
}
|
||||
|
||||
// If the action request has an Error, then we'll never get actual response from all of the agents
|
||||
// to which this action sent. In this case, we adjust the completion information to all be "complete"
|
||||
// and un-successful
|
||||
if (action.error?.message) {
|
||||
const errorMessage = action.error.message;
|
||||
|
||||
completedInfo.completedAt = action.createdAt;
|
||||
completedInfo.isCompleted = true;
|
||||
completedInfo.wasSuccessful = false;
|
||||
completedInfo.errors = [errorMessage];
|
||||
|
||||
Object.values(completedInfo.agentState).forEach((agentState) => {
|
||||
agentState.completedAt = action.createdAt;
|
||||
agentState.isCompleted = true;
|
||||
agentState.wasSuccessful = false;
|
||||
agentState.errors = [errorMessage];
|
||||
});
|
||||
}
|
||||
|
||||
return completedInfo;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue