Skip to content

Commit

Permalink
Merge branch 'main' into unified-search-remove-saved-from-query
Browse files Browse the repository at this point in the history
  • Loading branch information
stratoula committed Apr 10, 2023
2 parents c3dfd25 + f63fffc commit bc0bc5b
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 78 deletions.
27 changes: 10 additions & 17 deletions src/dev/build/tasks/fetch_agent_versions_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,22 @@ const getAvailableVersions = async (log: ToolingLog) => {
};
// Endpoint maintained by the web-team and hosted on the elastic website
// See https://github.com/elastic/website-development/issues/9331
const url = 'https://www.elastic.co/content/product_versions';
log.info('Fetching Elastic Agent versions list');
const results = await fetch(url, options);
const rawBody = await results.text();

const url = 'https://www.elastic.co/api/product_versions';
try {
const jsonBody = JSON.parse(rawBody);
log.info('Fetching Elastic Agent versions list');
const results = await fetch(url, options);

const jsonBody = await results.json();

const versions: string[] = (jsonBody.length ? jsonBody[0] : [])
.filter((item: any) => item?.title?.includes('Elastic Agent'))
.map((item: any) => item?.version_number);

log.info(`Retrieved available Elastic Agent versions`);
log.info(`Retrieved available versions`);
return versions;
} catch (error) {
log.warning(`Failed to fetch Elastic Agent versions list`);
log.info(`Status: ${results.status}`);
log.info(rawBody);
if (process.env.BUILDKITE_PULL_REQUEST === 'true') {
log.warning(error);
} else {
throw new Error(error);
}
log.warning(`Failed to fetch versions list`);
log.warning(error);
}
return [];
};
Expand All @@ -54,8 +47,8 @@ export const FetchAgentVersionsList: Task = {
const versionsList = await getAvailableVersions(log);
const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_list.json';

if (versionsList.length !== 0) {
log.info(`Writing Elastic Agent versions list to ${AGENT_VERSION_BUILD_FILE}`);
if (versionsList !== []) {
log.info(`Writing versions list to ${AGENT_VERSION_BUILD_FILE}`);
await write(
build.resolvePath(AGENT_VERSION_BUILD_FILE),
JSON.stringify(versionsList, null, ' ')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export class EndpointActionGenerator extends BaseDataGenerator {
stderr_truncated: true,
shell_code: 0,
shell: 'bash',
cwd: '/some/path',
cwd: this.randomChoice(['/some/path', '/a-very/long/path'.repeat(30)]),
output_file_id: 'some-output-file-id',
output_file_stdout_truncated: this.randomChoice([true, false]),
output_file_stderr_truncated: this.randomChoice([true, false]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,42 @@ describe('When using the `ExecuteActionHostResponse` component', () => {
};
});

const outputSuffix = 'output';

it('should show shell info and shell code', async () => {
render();
const { queryByTestId } = renderResult;
expect(queryByTestId(`test-executeResponseOutput-context`)).toBeInTheDocument();
expect(queryByTestId(`test-executeResponseOutput-shell`)).toBeInTheDocument();
expect(queryByTestId(`test-executeResponseOutput-cwd`)).toBeInTheDocument();
});

it('should show execute context accordion as `closed`', async () => {
render();
expect(renderResult.getByTestId('test-executeResponseOutput-context').className).toEqual(
'euiAccordion'
);
});

it('should show current working directory', async () => {
render();
const { queryByTestId } = renderResult;
expect(queryByTestId(`test-executeResponseOutput-context`)).toBeInTheDocument();
expect(queryByTestId(`test-executeResponseOutput-cwd`)).toBeInTheDocument();
});

it('should show execute output and execute errors', async () => {
render();
expect(renderResult.getByTestId('test-executeResponseOutput')).toBeTruthy();
const { queryByTestId } = renderResult;
expect(queryByTestId(`test-executeResponseOutput-${outputSuffix}`)).toBeInTheDocument();
expect(queryByTestId(`test-executeResponseOutput-error`)).toBeInTheDocument();
});

it('should show execute output accordion as `open`', async () => {
render();
const accordionOutputButton = Array.from(
renderResult.getByTestId('test-executeResponseOutput').querySelectorAll('.euiAccordion')
)[0];
expect(accordionOutputButton.className).toContain('isOpen');
expect(
renderResult.getByTestId(`test-executeResponseOutput-${outputSuffix}`).className
).toContain('isOpen');
});

it('should show `-` in output accordion when no output content', async () => {
Expand All @@ -66,13 +91,11 @@ describe('When using the `ExecuteActionHostResponse` component', () => {
},
},
};

render();
const accordionOutputButton = Array.from(
renderResult.getByTestId('test-executeResponseOutput').querySelectorAll('.euiAccordion')
)[0];
expect(accordionOutputButton.textContent).toContain(
`Execution output (truncated)${getEmptyValue()}`
);
expect(
renderResult.getByTestId(`test-executeResponseOutput-${outputSuffix}`).textContent
).toContain(`Execution output (truncated)${getEmptyValue()}`);
});

it('should show `-` in error accordion when no error content', async () => {
Expand All @@ -85,18 +108,19 @@ describe('When using the `ExecuteActionHostResponse` component', () => {
},
},
};

render();
const accordionErrorButton = Array.from(
renderResult.getByTestId('test-executeResponseOutput').querySelectorAll('.euiAccordion')
)[1];
expect(accordionErrorButton.textContent).toContain(
expect(renderResult.getByTestId('test-executeResponseOutput-error').textContent).toContain(
`Execution error (truncated)${getEmptyValue()}`
);
});

it('should not show execute output accordions when no output in action details', () => {
(renderProps.action as ActionDetails).outputs = undefined;

render();
expect(renderResult.queryByTestId('test-executeResponseOutput')).toBeNull();
const { queryByTestId } = renderResult;
expect(queryByTestId(`test-executeResponseOutput-context`)).not.toBeInTheDocument();
expect(queryByTestId(`test-executeResponseOutput-${outputSuffix}`)).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@
* 2.0.
*/

import React, { memo } from 'react';
import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiText, useGeneratedHtmlId } from '@elastic/eui';
import React, { memo, useMemo } from 'react';
import {
EuiAccordion,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
useGeneratedHtmlId,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type { ResponseActionExecuteOutputContent } from '../../../../common/endpoint/types';
import { getEmptyValue } from '../../../common/components/empty_value';

const emptyValue = getEmptyValue();

const ACCORDION_BUTTON_TEXT = Object.freeze({
context: i18n.translate(
'xpack.securitySolution.responseActionExecuteAccordion.executionContext',
{
defaultMessage: 'Execution context',
}
),
output: {
regular: i18n.translate(
'xpack.securitySolution.responseActionExecuteAccordion.outputButtonTextRegular',
Expand Down Expand Up @@ -44,36 +58,95 @@ const ACCORDION_BUTTON_TEXT = Object.freeze({
),
},
});

const SHELL_INFO = Object.freeze({
shell: i18n.translate('xpack.securitySolution.responseActionExecuteAccordion.shellInformation', {
defaultMessage: 'Shell',
}),

returnCode: i18n.translate(
'xpack.securitySolution.responseActionExecuteAccordion.shellReturnCode',
{
defaultMessage: 'Return code',
}
),
currentDir: i18n.translate(
'xpack.securitySolution.responseActionExecuteAccordion.currentWorkingDirectory',
{
defaultMessage: 'Current working directory',
}
),
});

const StyledEuiText = euiStyled(EuiText)`
white-space: pre-wrap;
line-break: anywhere;
`;

interface ShellInfoContentProps {
content: string | number;
textSize?: 's' | 'xs';
title: string;
}
const ShellInfoContent = memo<ShellInfoContentProps>(({ content, textSize, title }) => (
<StyledEuiText size={textSize}>
<strong>
{title}
{': '}
</strong>
{content}
</StyledEuiText>
));

ShellInfoContent.displayName = 'ShellInfoContent';

interface ExecuteActionOutputProps {
content?: string;
content?: string | React.ReactNode;
initialIsOpen?: boolean;
isTruncated?: boolean;
textSize?: 's' | 'xs';
type: 'error' | 'output';
type: 'error' | 'output' | 'context';
'data-test-subj'?: string;
}

const ExecutionActionOutputAccordion = memo<ExecuteActionOutputProps>(
({ content = emptyValue, initialIsOpen = false, isTruncated = false, textSize, type }) => {
({
content = emptyValue,
initialIsOpen = false,
isTruncated = false,
textSize,
type,
'data-test-subj': dataTestSubj,
}) => {
const id = useGeneratedHtmlId({
prefix: 'executeActionOutputAccordions',
suffix: type,
});

const accordionButtonContent = useMemo(
() => (
<EuiText size={textSize}>
{type !== 'context'
? isTruncated
? ACCORDION_BUTTON_TEXT[type].truncated
: ACCORDION_BUTTON_TEXT[type].regular
: ACCORDION_BUTTON_TEXT[type]}
</EuiText>
),
[isTruncated, textSize, type]
);

return (
<EuiAccordion
id={id}
initialIsOpen={initialIsOpen}
buttonContent={ACCORDION_BUTTON_TEXT[type][isTruncated ? 'truncated' : 'regular']}
buttonContent={accordionButtonContent}
paddingSize="s"
data-test-subj={dataTestSubj}
>
<EuiText
size={textSize}
style={{
whiteSpace: 'pre-wrap',
lineBreak: 'anywhere',
}}
>
<p>{content}</p>
</EuiText>
<StyledEuiText size={textSize}>
{typeof content === 'string' ? <p>{content}</p> : content}
</StyledEuiText>
</EuiAccordion>
);
}
Expand All @@ -87,24 +160,70 @@ export interface ExecuteActionHostResponseOutputProps {
}

export const ExecuteActionHostResponseOutput = memo<ExecuteActionHostResponseOutputProps>(
({ outputContent, 'data-test-subj': dataTestSubj, textSize = 'xs' }) => (
<EuiFlexItem data-test-subj={dataTestSubj}>
<EuiSpacer size="m" />
<ExecutionActionOutputAccordion
content={outputContent.stdout.length ? outputContent.stdout : undefined}
isTruncated={outputContent.stdout_truncated}
initialIsOpen
textSize={textSize}
type="output"
/>
<EuiSpacer size="m" />
<ExecutionActionOutputAccordion
content={outputContent.stderr.length ? outputContent.stderr : undefined}
isTruncated={outputContent.stderr_truncated}
textSize={textSize}
type="error"
/>
</EuiFlexItem>
)
({ outputContent, 'data-test-subj': dataTestSubj, textSize = 'xs' }) => {
const contextContent = useMemo(
() => (
<>
<EuiFlexGroup gutterSize="m" data-test-subj={`${dataTestSubj}-shell`}>
<EuiFlexItem grow={false}>
<ShellInfoContent
title={SHELL_INFO.shell}
content={outputContent.shell}
textSize={textSize}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ShellInfoContent
title={SHELL_INFO.returnCode}
content={outputContent.shell_code}
textSize={textSize}
/>
</EuiFlexItem>
</EuiFlexGroup>
<div data-test-subj={`${dataTestSubj}-cwd`}>
<EuiSpacer size="m" />
<ShellInfoContent
title={SHELL_INFO.currentDir}
content={outputContent.cwd}
textSize={textSize}
/>
</div>
</>
),
[dataTestSubj, outputContent.cwd, outputContent.shell, outputContent.shell_code, textSize]
);
return (
<>
<EuiFlexItem>
<EuiSpacer size="m" />
<ExecutionActionOutputAccordion
content={contextContent}
data-test-subj={`${dataTestSubj}-context`}
textSize={textSize}
type="context"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiSpacer size="m" />
<ExecutionActionOutputAccordion
content={outputContent.stdout.length ? outputContent.stdout : undefined}
data-test-subj={`${dataTestSubj}-output`}
isTruncated={outputContent.stdout_truncated}
initialIsOpen
textSize={textSize}
type="output"
/>
<EuiSpacer size="m" />
<ExecutionActionOutputAccordion
content={outputContent.stderr.length ? outputContent.stderr : undefined}
data-test-subj={`${dataTestSubj}-error`}
isTruncated={outputContent.stderr_truncated}
textSize={textSize}
type="error"
/>
</EuiFlexItem>
</>
);
}
);
ExecuteActionHostResponseOutput.displayName = 'ExecuteActionHostResponseOutput';
Loading

0 comments on commit bc0bc5b

Please sign in to comment.