[8.8] [Synthetics] http monitors - account for custom Content-Type headers (#159737) (#159918)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[Synthetics] http monitors - account for custom Content-Type headers
(#159737)](https://github.com/elastic/kibana/pull/159737)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Dominique
Clarke","email":"dominique.clarke@elastic.co"},"sourceCommit":{"committedDate":"2023-06-19T11:49:55Z","message":"[Synthetics]
http monitors - account for custom Content-Type headers
(#159737)","sha":"fc32999c91760ed399b872d507fd84d308f5829e","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","Team:uptime","v8.9.0","v8.8.2"],"number":159737,"url":"https://github.com/elastic/kibana/pull/159737","mergeCommit":{"message":"[Synthetics]
http monitors - account for custom Content-Type headers
(#159737)","sha":"fc32999c91760ed399b872d507fd84d308f5829e"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/159737","number":159737,"mergeCommit":{"message":"[Synthetics]
http monitors - account for custom Content-Type headers
(#159737)","sha":"fc32999c91760ed399b872d507fd84d308f5829e"}},{"branch":"8.8","label":"v8.8.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Dominique Clarke <dominique.clarke@elastic.co>
This commit is contained in:
Kibana Machine 2023-06-19 09:29:02 -04:00 committed by GitHub
parent d2683cba7b
commit a7a6425553
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 6 deletions

View file

@ -140,3 +140,7 @@ export const ResponseCheckJSONCodec = t.interface({
expression: t.string,
});
export type ResponseCheckJSON = t.TypeOf<typeof ResponseCheckJSONCodec>;
export const RequestBodyCheckCodec = t.interface({ value: t.string, type: CodeEditorModeCodec });
export type RequestBodyCheck = t.TypeOf<typeof RequestBodyCheckCodec>;

View file

@ -11,7 +11,6 @@ import { secretKeys } from '../../constants/monitor_management';
import { ConfigKey } from './config_key';
import { MonitorServiceLocationCodec, ServiceLocationErrors } from './locations';
import {
CodeEditorModeCodec,
DataStream,
DataStreamCodec,
FormMonitorTypeCodec,
@ -22,6 +21,7 @@ import {
SourceTypeCodec,
TLSVersionCodec,
VerificationModeCodec,
RequestBodyCheckCodec,
} from './monitor_configs';
import { MetadataCodec } from './monitor_meta_data';
import { PrivateLocationCodec } from './synthetics_private_locations';
@ -195,7 +195,7 @@ export const HTTPSensitiveAdvancedFieldsCodec = t.intersection([
[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: t.array(t.string),
[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: t.array(t.string),
[ConfigKey.RESPONSE_HEADERS_CHECK]: t.record(t.string, t.string),
[ConfigKey.REQUEST_BODY_CHECK]: t.interface({ value: t.string, type: CodeEditorModeCodec }),
[ConfigKey.REQUEST_BODY_CHECK]: RequestBodyCheckCodec,
[ConfigKey.REQUEST_HEADERS_CHECK]: t.record(t.string, t.string),
[ConfigKey.USERNAME]: t.string,
}),

View file

@ -106,4 +106,35 @@ describe('<HeaderField />', () => {
});
});
});
it('shows custom Content-Type', async () => {
const contentMode: CodeEditorMode = CodeEditorMode.PLAINTEXT;
const { getByTestId } = render(
<HeaderField
defaultValue={{ ...defaultValue, 'Content-Type': 'custom' }}
onChange={onChange}
contentMode={contentMode}
/>
);
const key = getByTestId('keyValuePairsKey0') as HTMLInputElement;
const value = getByTestId('keyValuePairsValue0') as HTMLInputElement;
expect(key.value).toBe('Content-Type');
expect(value.value).toBe('custom');
});
it('hides default Content-Type', async () => {
const contentMode: CodeEditorMode = CodeEditorMode.PLAINTEXT;
const { queryByTestId } = render(
<HeaderField
defaultValue={{ ...defaultValue, 'Content-Type': 'text/plain' }}
onChange={onChange}
contentMode={contentMode}
/>
);
expect(queryByTestId('keyValuePairsKey0')).not.toBeInTheDocument();
expect(queryByTestId('keyValuePairsValue0')).not.toBeInTheDocument();
});
});

View file

@ -28,7 +28,9 @@ export const HeaderField = ({
'data-test-subj': dataTestSubj,
readOnly,
}: HeaderFieldProps) => {
const defaultValueKeys = Object.keys(defaultValue).filter((key) => key !== 'Content-Type'); // Content-Type is a secret header we hide from the user
const defaultValueKeys = Object.keys(defaultValue).filter(
filterContentType(defaultValue, contentTypes, contentMode)
);
const formattedDefaultValues: Pair[] = [
...defaultValueKeys.map<Pair>((key) => {
return [key || '', defaultValue[key] || '']; // key, value
@ -72,6 +74,22 @@ export const HeaderField = ({
);
};
// We apply default `Content-Type` headers automatically depending on the request body mime type
// We hide the default Content-Type headers from the user as an implementation detail
// However, If the user applies a custom `Content-Type` header, it should be shown
export const filterContentType =
(
defaultValue: Record<string, string>,
contentTypeMap: Record<CodeEditorMode, ContentType>,
contentMode?: CodeEditorMode
) =>
(key: string) => {
return (
key !== 'Content-Type' ||
(key === 'Content-Type' && contentMode && defaultValue[key] !== contentTypeMap[contentMode])
);
};
export const contentTypes: Record<CodeEditorMode, ContentType> = {
[CodeEditorMode.JSON]: ContentType.JSON,
[CodeEditorMode.PLAINTEXT]: ContentType.TEXT,

View file

@ -76,6 +76,7 @@ import {
ResponseBodyIndexPolicy,
ResponseCheckJSON,
ThrottlingConfig,
RequestBodyCheck,
} from '../types';
import { AlertConfigKey, ALLOWED_SCHEDULES_IN_MINUTES } from '../constants';
import { getDefaultFormFields } from './defaults';
@ -718,12 +719,20 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
validation: () => ({
validate: (headers) => !validateHeaders(headers),
}),
dependencies: [ConfigKey.REQUEST_BODY_CHECK],
error: i18n.translate('xpack.synthetics.monitorConfig.requestHeaders.error', {
defaultMessage: 'Header key must be a valid HTTP token.',
}),
props: (): HeaderFieldProps => ({
readOnly,
}),
// contentMode is optional for other implementations, but required for this implemention of this field
props: ({
dependencies,
}): HeaderFieldProps & { contentMode: HeaderFieldProps['contentMode'] } => {
const [requestBody] = dependencies;
return {
readOnly,
contentMode: (requestBody as RequestBodyCheck).type,
};
},
},
[ConfigKey.REQUEST_BODY_CHECK]: {
fieldKey: ConfigKey.REQUEST_BODY_CHECK,

View file

@ -18,6 +18,7 @@ import {
FormMonitorType,
MonitorFields,
ResponseCheckJSON,
RequestBodyCheck,
} from '../../../../../common/runtime_types/monitor_management';
import { AlertConfigKey } from './constants';
@ -38,6 +39,7 @@ export interface FormLocation {
isServiceManaged: boolean;
label: string;
}
export type FormConfig = MonitorFields & {
isTLSEnabled: boolean;
['schedule.number']: string;
@ -57,6 +59,9 @@ export type FormConfig = MonitorFields & {
supported_protocols: MonitorFields[ConfigKey.TLS_VERSION];
};
check: {
request: {
body: RequestBodyCheck;
};
response: {
json: ResponseCheckJSON[];
};