From c6c006b9325c101173d29ac91c57807dc57e6d11 Mon Sep 17 00:00:00 2001 From: Mat Schaffer Date: Thu, 17 Mar 2022 21:27:59 +0900 Subject: [PATCH 01/10] Add a link to our internal doc location (#127697) --- x-pack/plugins/monitoring/readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/monitoring/readme.md b/x-pack/plugins/monitoring/readme.md index f9b7cbfda791cc..e33f3183d3e28e 100644 --- a/x-pack/plugins/monitoring/readme.md +++ b/x-pack/plugins/monitoring/readme.md @@ -19,4 +19,8 @@ This plugin provides the Stack Monitoring kibana application. ## Troubleshooting - [Diagnostic queries](dev_docs/runbook/diagnostic_queries.md) -- [CPU metrics](dev_docs/runbook/cpu_metrics.md) \ No newline at end of file +- [CPU metrics](dev_docs/runbook/cpu_metrics.md) + +## Additional reference + +- [Internal documentation for engineers working on Stack Monitoring](https://github.com/elastic/observability-dev/tree/main/docs/monitoring) \ No newline at end of file From eaf1d5e82c908ce1da42afdfebfba75dc8943f70 Mon Sep 17 00:00:00 2001 From: Mat Schaffer Date: Thu, 17 Mar 2022 21:42:35 +0900 Subject: [PATCH 02/10] [Stack Monitoring] Porting version compatibility docs (#127699) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../dev_docs/how_to/version_compatibility.md | 40 +++++++++++++++++++ x-pack/plugins/monitoring/readme.md | 1 + 2 files changed, 41 insertions(+) create mode 100644 x-pack/plugins/monitoring/dev_docs/how_to/version_compatibility.md diff --git a/x-pack/plugins/monitoring/dev_docs/how_to/version_compatibility.md b/x-pack/plugins/monitoring/dev_docs/how_to/version_compatibility.md new file mode 100644 index 00000000000000..119727699c02d2 --- /dev/null +++ b/x-pack/plugins/monitoring/dev_docs/how_to/version_compatibility.md @@ -0,0 +1,40 @@ +## Compatability Testing + +For each release, the Product team maintains a release of compatability between stack components. + +From the perspective of Stack Monitoring, there are two vectors which must be monitored between monitored components and the +Stack Monitoring application: + +1) _Beats version X -> Stack Monitoring Cluster Y_ +2) _Logstash version X -> Stack Monitoring Cluster Y_ + +For each of the above, compatability must be verified. + +## How to Test Compatability + +For each release, the team will get tagged by the Product folks, generally via a comment on a spreadsheet. The spreadsheet will +contain rows which specify a range of versions of Stack Monitoring for which a given component must be tested. For example, +a cell in a row might indicate that tests need to be performed for Logstash 7.2.x against Stack Monitoring versions 7.0 - 7.3. + +The following instructions will guide you through performing these tests. It is not necessary to follow the steps +as written. They are simply instructive. So long as functionality itself can be tested between the defined versions, +how you set up your environment to achieve that is up to you. + +To begin, determine which versions need to be tested against. Typically, this is going to be a pair of Elasticsearch and Kibana +both of the same version and then a service, such as Logstash which is of another version. Download all needed versions +from elastic.co. As of this writing, artifacts are stored here: https://www.elastic.co/downloads/past-releases + +Start up the Kibana and Elasticsearch pair using your downloaded archives. Navigate to the Kibana interface and then to the +Stack Monitoring application. + +Enable monitoring and wait a few moments for monitoring data to start flowing in. + +If it is not the first time testing a given service, it may already appear in the monitoring overview. If this is the case, +the corresponding index needs to be deleted. + +Then, enable monitoring in the service that is being tested. Point it to the Elasticsearch cluster you just configured. + +Verify that monitoring data flows in, that an index has been created and that graphs are populating themselves with data. + +After this is done, tear down the cluster and stop the service, record the result, and move on to the next combination until +all have been verified. \ No newline at end of file diff --git a/x-pack/plugins/monitoring/readme.md b/x-pack/plugins/monitoring/readme.md index e33f3183d3e28e..85e31614257d22 100644 --- a/x-pack/plugins/monitoring/readme.md +++ b/x-pack/plugins/monitoring/readme.md @@ -6,6 +6,7 @@ This plugin provides the Stack Monitoring kibana application. - [Local setup](dev_docs/how_to/local_setup.md) - [Cloud setup](dev_docs/how_to/cloud_setup.md) - [Testing](dev_docs/how_to/testing.md) +- [Version Compatibility](dev_docs/how_to/version_compatibility.md) ## Concepts - [Architectural Overview](dev_docs/reference/architectural_overview.md) From 06088649ee43fb78456d8b03f0eee2346f24aec2 Mon Sep 17 00:00:00 2001 From: Mat Schaffer Date: Thu, 17 Mar 2022 21:44:10 +0900 Subject: [PATCH 03/10] [Stack Monitoring] APM tracing dev docs (#127569) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../monitoring/dev_docs/how_to/apm_tracing.md | 44 +++++++++++++++++++ x-pack/plugins/monitoring/readme.md | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/monitoring/dev_docs/how_to/apm_tracing.md b/x-pack/plugins/monitoring/dev_docs/how_to/apm_tracing.md index e69de29bb2d1d6..4356a440f73f9c 100644 --- a/x-pack/plugins/monitoring/dev_docs/how_to/apm_tracing.md +++ b/x-pack/plugins/monitoring/dev_docs/how_to/apm_tracing.md @@ -0,0 +1,44 @@ +To get deeper information about the behavior of the stack monitoring application you can configure APM to send trace data an APM server. + +The kibana source references a central APM server for use by anyone working at Elastic, or you can send trace data to an APM server you manage yourself. + +## For local development + +Elasticians are able to send data to a central APM server and kibana instance by exporting the following environment variables: + +```shell +export ELASTIC_APM_ACTIVE=true +export ELASTIC_APM_ENVIRONMENT="dev-${USER}" +``` + +All data will be available on the shared APM cluster. See (internal) https://docs.elastic.dev/kibana-team/data-and-dashboards#kibana-cloud-apm for access information. + +## For ESS + +Add the settings into the `user_settings_override_yaml` deployment configuration within `kibana.plan.kibana` section as escaped YAML inside JSON. + +```json +{ + "user_settings_override_yaml": "elastic.apm.active: true\nelastic.apm.serverUrl: https://\nelastic.apm.secretToken: \nelastic.apm.globalLabels.deploymentId: \nelastic.apm.centralConfig: false\nelastic.apm.breakdownMetrics: false\nelastic.apm.transactionSampleRate: 0.1\nelastic.apm.metricsInterval: 120s\nelastic.apm.captureSpanStackTraces: false" +} +``` + +This is an administrative-only API currently, so you'll need admin access or open a support case to have it configured. + +## For ECE or other deployments + +For on-premise deployments or developers outside Elastic, you can configure an APM endpoint via `kibana.yml`. Note that at the time of writing this document you can't use `kibana.dev.yml` for these settings due to library load order. + +```yaml +elastic.apm.active: true +elastic.apm.serverUrl: https:// +elastic.apm.secretToken: +elastic.apm.globalLabels.deploymentId: +elastic.apm.centralConfig: false +elastic.apm.breakdownMetrics: false +elastic.apm.transactionSampleRate: 0.1 +elastic.apm.metricsInterval: 120s +elastic.apm.captureSpanStackTraces: false +``` + +When running in ECE you can update `kibana.yml` settings via the ECE web UI under "Edit user setting" for the kibana nodes in the deployment. \ No newline at end of file diff --git a/x-pack/plugins/monitoring/readme.md b/x-pack/plugins/monitoring/readme.md index 85e31614257d22..d0d01e6f08b4ba 100644 --- a/x-pack/plugins/monitoring/readme.md +++ b/x-pack/plugins/monitoring/readme.md @@ -16,7 +16,7 @@ This plugin provides the Stack Monitoring kibana application. ## Tooling - [Debugging logging](dev_docs/how_to/debug_logging.md) (WIP) -- [APM tracing](dev_docs/how_to/apm_tracing.md) (WIP) +- [APM tracing](dev_docs/how_to/apm_tracing.md) ## Troubleshooting - [Diagnostic queries](dev_docs/runbook/diagnostic_queries.md) From 067f4c2edeccdd4a917a86d0e0f6ac8c39c9e786 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 17 Mar 2022 14:16:39 +0100 Subject: [PATCH 04/10] [Lens] Normalize Axis title input within popover with compact design (#126943) * :lipstick: revisit Axis title fields popover * :fire: Remove unused translations * :lipstick: Remove justify end * :lipstick: Move switches to columnCompressed * :recycle: Reimplemented dual range without slider * review comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Reuter --- .../toolbar_component.scss | 3 + .../toolbar_component.tsx | 2 + .../axis_title_settings.test.tsx | 39 +- .../shared_components/axis_title_settings.tsx | 90 ++-- .../public/shared_components/vis_label.tsx | 8 +- .../axis_settings_popover.scss | 3 + .../axis_settings_popover.test.tsx | 7 +- .../xy_config_panel/axis_settings_popover.tsx | 415 +++++++++--------- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 11 files changed, 306 insertions(+), 271 deletions(-) create mode 100644 x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.scss create mode 100644 x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.scss diff --git a/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.scss b/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.scss new file mode 100644 index 00000000000000..360a76274416d9 --- /dev/null +++ b/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.scss @@ -0,0 +1,3 @@ +.lnsVisToolbarAxis__popover { + width: 500px; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx b/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx index 66d1cfcb1e5df0..ae8f9c32d43586 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx @@ -21,6 +21,7 @@ import { EuiIconAxisLeft } from '../assets/axis_left'; import { EuiIconAxisBottom } from '../assets/axis_bottom'; import type { HeatmapVisualizationState } from './types'; import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values'; +import './toolbar_component.scss'; const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [ { @@ -139,6 +140,7 @@ export const HeatmapToolbar = memo( groupPosition="left" isDisabled={!Boolean(state?.yAccessor)} buttonDataTestSubj="lnsHeatmapVerticalAxisButton" + panelClassName="lnsVisToolbarAxis__popover" > { let props: AxisTitleSettingsProps; @@ -21,14 +23,41 @@ describe('Axes Title settings', () => { }; }); it('should show the axes title on the corresponding input text', () => { - const component = shallow(); - expect(component.find('[data-test-subj="lnsxAxisTitle"]').prop('value')).toBe( + const component = mount(); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').last().prop('value')).toBe( 'My custom X axis title' ); }); + it('should set the mode to Auto if no title is passed over', () => { + const component = mount(); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').last().prop('value')).toBe(''); + }); + + it('should set the mode to Auto if empty title is passed over', () => { + const component = mount(); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').last().prop('value')).toBe(''); + }); + it('should disable the input text if the switch is off', () => { - const component = shallow(); - expect(component.find('[data-test-subj="lnsxAxisTitle"]').prop('disabled')).toBe(true); + const component = mount(); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').last().prop('disabled')).toBe(true); + }); + + it('should allow custom mode on user input even with empty string', () => { + let component = mount(); + + // switch mode + act(() => { + component.find(VisLabel).prop('handleChange')!({ + label: '', + mode: 'custom', + } as Label); + }); + component = component.update(); + expect(component.find('[data-test-subj="lnsxAxisTitle-select"]').last().prop('value')).toBe( + 'custom' + ); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').last().prop('value')).toBe(''); }); }); diff --git a/x-pack/plugins/lens/public/shared_components/axis_title_settings.tsx b/x-pack/plugins/lens/public/shared_components/axis_title_settings.tsx index f54b07905b94cf..00cba7db4d17a1 100644 --- a/x-pack/plugins/lens/public/shared_components/axis_title_settings.tsx +++ b/x-pack/plugins/lens/public/shared_components/axis_title_settings.tsx @@ -5,18 +5,11 @@ * 2.0. */ -import React from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiSwitch, - EuiSpacer, - EuiFieldText, -} from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import { EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AxesSettingsConfig } from '../../common/expressions'; -import { useDebouncedValue } from './'; +import { LabelMode, useDebouncedValue, VisLabel } from './'; type AxesSettingsConfigKeys = keyof AxesSettingsConfig; export interface AxisTitleSettingsProps { @@ -56,45 +49,50 @@ export const AxisTitleSettings: React.FunctionComponent }, { allowFalsyValue: true } ); + const [titleMode, setTitleMode] = useState( + !title ? 'auto' : isAxisTitleVisible ? 'custom' : 'none' + ); + + const updateVisibility = useCallback( + (mode: LabelMode) => { + const visible = mode !== 'none'; + if (visible !== isAxisTitleVisible) { + toggleAxisTitleVisibility(axis, visible); + } + setTitleMode(mode); + }, + [axis, isAxisTitleVisible, toggleAxisTitleVisibility] + ); + return ( <> - - - -

- {i18n.translate('xpack.lens.shared.axisNameLabel', { - defaultMessage: 'Axis title', - })} -

-
-
- - toggleAxisTitleVisibility(axis, target.checked)} - checked={isAxisTitleVisible} - /> - -
- - onTitleChange(target.value)} - aria-label={i18n.translate('xpack.lens.shared.overwriteAxisTitle', { - defaultMessage: 'Overwrite axis title', + - + fullWidth + > + { + if (title !== label) { + onTitleChange(label); + } + updateVisibility(mode); + }} + /> + + ); }; diff --git a/x-pack/plugins/lens/public/shared_components/vis_label.tsx b/x-pack/plugins/lens/public/shared_components/vis_label.tsx index 2fec7f56561a99..79bab27bbcb6f4 100644 --- a/x-pack/plugins/lens/public/shared_components/vis_label.tsx +++ b/x-pack/plugins/lens/public/shared_components/vis_label.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -type LabelMode = 'auto' | 'custom' | 'none'; +export type LabelMode = 'auto' | 'custom' | 'none'; -interface Label { +export interface Label { mode: LabelMode; label: string; } @@ -68,7 +68,7 @@ export function VisLabel({ dataTestSubj, }: VisLabelProps) { return ( - + - + { setExtent={setSpy} /> ); - const lower = component.find('[data-test-subj="lnsXY_axisExtent_lowerBound"]'); - const upper = component.find('[data-test-subj="lnsXY_axisExtent_upperBound"]'); + const rangeInput = component + .find('[data-test-subj="lnsXY_axisExtent_customBounds"]') + .shallow(); + const lower = rangeInput.find('[data-test-subj="lnsXY_axisExtent_lowerBound"]'); + const upper = rangeInput.find('[data-test-subj="lnsXY_axisExtent_upperBound"]'); expect(lower.prop('value')).toEqual(123); expect(upper.prop('value')).toEqual(456); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx index 3766e1f022c882..ef692b59a7eb7d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx @@ -7,15 +7,13 @@ import React, { useEffect, useState } from 'react'; import { - EuiFlexGroup, - EuiFlexItem, EuiSwitch, - EuiSpacer, IconType, EuiFormRow, EuiButtonGroup, htmlIdGenerator, EuiFieldNumber, + EuiFormControlLayoutDelimited, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEqual } from 'lodash'; @@ -33,6 +31,7 @@ import { EuiIconAxisRight } from '../../assets/axis_right'; import { EuiIconAxisTop } from '../../assets/axis_top'; import { ToolbarButtonProps } from '../../../../../../src/plugins/kibana_react/public'; import { validateExtent } from '../axes_configuration'; +import './axis_settings_popover.scss'; type AxesSettingsConfigKeys = keyof AxesSettingsConfig; @@ -262,6 +261,7 @@ export const AxisSettingsPopover: React.FunctionComponent - toggleGridlinesVisibility(axis)} - checked={areGridlinesVisible} - /> - - + toggleGridlinesVisibility(axis)} + checked={areGridlinesVisible} + showLabel={false} + /> + + + toggleTickLabelsVisibility(axis)} - checked={areTickLabelsVisible} - /> - - + toggleTickLabelsVisibility(axis)} + checked={areTickLabelsVisible} + showLabel={false} + /> + + - - - + + {setEndzoneVisibility && ( - <> - + setEndzoneVisibility(!Boolean(endzonesVisible))} checked={Boolean(endzonesVisible)} + showLabel={false} /> - + )} {localExtent && setExtent && ( - <> - - + { + const newMode = id.replace(idPrefix, '') as AxisExtentConfig['mode']; + setLocalExtent({ + ...localExtent, + mode: newMode, + lowerBound: + newMode === 'custom' && dataBounds ? Math.min(0, dataBounds.min) : undefined, + upperBound: newMode === 'custom' && dataBounds ? dataBounds.max : undefined, + }); + }} + /> + + )} + {localExtent?.mode === 'custom' && !hasPercentageAxis && ( + + { + const val = Number(e.target.value); + if (e.target.value === '' || Number.isNaN(Number(val))) { + setLocalExtent({ + ...localExtent, + lowerBound: undefined, + }); + } else { + setLocalExtent({ + ...localExtent, + lowerBound: val, + }); + } + }} + onBlur={() => { + if (localExtent.lowerBound === undefined && dataBounds) { + setLocalExtent({ + ...localExtent, + lowerBound: Math.min(0, dataBounds.min), + }); + } + }} + step="any" + controlOnly + /> } - > - { - const newMode = id.replace(idPrefix, '') as AxisExtentConfig['mode']; - setLocalExtent({ - ...localExtent, - mode: newMode, - lowerBound: - newMode === 'custom' && dataBounds ? Math.min(0, dataBounds.min) : undefined, - upperBound: newMode === 'custom' && dataBounds ? dataBounds.max : undefined, - }); - }} - /> - - {localExtent.mode === 'custom' && !hasPercentageAxis && ( - <> - - - - - { - const val = Number(e.target.value); - if (e.target.value === '' || Number.isNaN(Number(val))) { - setLocalExtent({ - ...localExtent, - lowerBound: undefined, - }); - } else { - setLocalExtent({ - ...localExtent, - lowerBound: val, - }); - } - }} - onBlur={() => { - if (localExtent.lowerBound === undefined && dataBounds) { - setLocalExtent({ - ...localExtent, - lowerBound: Math.min(0, dataBounds.min), - }); - } - }} - step="any" - /> - - - - - { - const val = Number(e.target.value); - if (e.target.value === '' || Number.isNaN(Number(val))) { - setLocalExtent({ - ...localExtent, - upperBound: undefined, - }); - } else { - setLocalExtent({ - ...localExtent, - upperBound: val, - }); - } - }} - onBlur={() => { - if (localExtent.upperBound === undefined && dataBounds) { - setLocalExtent({ - ...localExtent, - upperBound: dataBounds.max, - }); - } - }} - step="any" - /> - - - - - )} - + endControl={ + { + const val = Number(e.target.value); + if (e.target.value === '' || Number.isNaN(Number(val))) { + setLocalExtent({ + ...localExtent, + upperBound: undefined, + }); + } else { + setLocalExtent({ + ...localExtent, + upperBound: val, + }); + } + }} + onBlur={() => { + if (localExtent.upperBound === undefined && dataBounds) { + setLocalExtent({ + ...localExtent, + upperBound: dataBounds.max, + }); + } + }} + step="any" + controlOnly + /> + } + compressed + /> + )} ); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 8aa5e9d0bca326..db8cbd956e8fd7 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -762,7 +762,6 @@ "xpack.lens.xyChart.legendVisibility.auto": "Auto", "xpack.lens.xyChart.legendVisibility.hide": "Masquer", "xpack.lens.xyChart.legendVisibility.show": "Afficher", - "xpack.lens.xyChart.lowerBoundLabel": "Limite inférieure", "xpack.lens.xyChart.maxLines.help": "Spécifie le nombre de lignes par élément de légende.", "xpack.lens.xyChart.missingValuesLabel": "Valeurs manquantes", "xpack.lens.xyChart.missingValuesLabelHelpText": "Par défaut, Lens masque les blancs dans les données. Pour remplir le blanc, effectuez une sélection.", @@ -782,7 +781,6 @@ "xpack.lens.xyChart.title.help": "Titre de l'axe", "xpack.lens.xyChart.topAxisDisabledHelpText": "Ce paramètre s'applique uniquement lorsque l'axe du haut est activé.", "xpack.lens.xyChart.topAxisLabel": "Axe du haut", - "xpack.lens.xyChart.upperBoundLabel": "Limite supérieure", "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "Ce paramètre ne peut pas être modifié dans les histogrammes.", "xpack.lens.xyChart.valuesInLegend.help": "Afficher les valeurs dans la légende", "xpack.lens.xyChart.valuesPercentageDisabledHelpText": "Ce paramètre ne peut pas être modifié dans les graphiques en aires à pourcentages.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a1492a6dc2117e..6a232901a14af8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -797,7 +797,6 @@ "xpack.lens.shared.maxLinesLabel": "最大行", "xpack.lens.shared.nestedLegendLabel": "ネスト済み", "xpack.lens.shared.overwriteAxisTitle": "軸タイトルを上書き", - "xpack.lens.shared.ShowAxisTitleLabel": "表示", "xpack.lens.shared.ticksPositionOptions": "帯の目盛", "xpack.lens.shared.ticksPositionOptionsTooltip": "目盛を均等に分布するのではなく、各帯の境界に目盛を表示します", "xpack.lens.shared.truncateLegend": "テキストを切り捨て", @@ -859,7 +858,6 @@ "xpack.lens.xyChart.addReferenceLineLayerLabel": "基準レイヤーの追加", "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "一部のデータを追加して、基準レイヤーを有効にする", "xpack.lens.xyChart.axisExtent.custom": "カスタム", - "xpack.lens.xyChart.axisExtent.dataBounds": "データ境界", "xpack.lens.xyChart.axisExtent.disabledDataBoundsMessage": "折れ線グラフのみをデータ境界に合わせることができます", "xpack.lens.xyChart.axisExtent.full": "完全", "xpack.lens.xyChart.axisExtent.label": "境界", @@ -907,7 +905,6 @@ "xpack.lens.xyChart.legendVisibility.auto": "自動", "xpack.lens.xyChart.legendVisibility.hide": "非表示", "xpack.lens.xyChart.legendVisibility.show": "表示", - "xpack.lens.xyChart.lowerBoundLabel": "下界", "xpack.lens.xyChart.markerPosition.above": "トップ", "xpack.lens.xyChart.markerPosition.below": "一番下", "xpack.lens.xyChart.markerPosition.left": "左", @@ -931,7 +928,6 @@ "xpack.lens.xyChart.title.help": "軸のタイトル", "xpack.lens.xyChart.topAxisDisabledHelpText": "この設定は、上の軸が有効であるときにのみ適用されます。", "xpack.lens.xyChart.topAxisLabel": "上の軸", - "xpack.lens.xyChart.upperBoundLabel": "上界", "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "この設定はヒストグラムで変更できません。", "xpack.lens.xyChart.valuesInLegend.help": "凡例に値を表示", "xpack.lens.xyChart.valuesPercentageDisabledHelpText": "この設定は割合エリアグラフで変更できません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3385b9ef92a4f7..2947c7d7dd233c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -803,7 +803,6 @@ "xpack.lens.shared.maxLinesLabel": "最大行数", "xpack.lens.shared.nestedLegendLabel": "嵌套", "xpack.lens.shared.overwriteAxisTitle": "覆盖轴标题", - "xpack.lens.shared.ShowAxisTitleLabel": "显示", "xpack.lens.shared.ticksPositionOptions": "波段上的刻度", "xpack.lens.shared.ticksPositionOptionsTooltip": "将刻度放在每个波段边框上,而不是平均分布", "xpack.lens.shared.truncateLegend": "截断文本", @@ -865,7 +864,6 @@ "xpack.lens.xyChart.addReferenceLineLayerLabel": "添加参考图层", "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "添加一些数据以启用参考图层", "xpack.lens.xyChart.axisExtent.custom": "定制", - "xpack.lens.xyChart.axisExtent.dataBounds": "数据边界", "xpack.lens.xyChart.axisExtent.disabledDataBoundsMessage": "仅折线图可适应数据边界", "xpack.lens.xyChart.axisExtent.full": "实线", "xpack.lens.xyChart.axisExtent.label": "边界", @@ -913,7 +911,6 @@ "xpack.lens.xyChart.legendVisibility.auto": "自动", "xpack.lens.xyChart.legendVisibility.hide": "隐藏", "xpack.lens.xyChart.legendVisibility.show": "显示", - "xpack.lens.xyChart.lowerBoundLabel": "下边界", "xpack.lens.xyChart.markerPosition.above": "顶部", "xpack.lens.xyChart.markerPosition.below": "底部", "xpack.lens.xyChart.markerPosition.left": "左", @@ -937,7 +934,6 @@ "xpack.lens.xyChart.title.help": "轴标题", "xpack.lens.xyChart.topAxisDisabledHelpText": "此设置仅在启用顶轴时应用。", "xpack.lens.xyChart.topAxisLabel": "顶轴", - "xpack.lens.xyChart.upperBoundLabel": "上边界", "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "不能在直方图上更改此设置。", "xpack.lens.xyChart.valuesInLegend.help": "在图例中显示值", "xpack.lens.xyChart.valuesPercentageDisabledHelpText": "不能在百分比面积图上更改此设置。", From 26a47d069f77a3c0f976eaf32f13839be22042f5 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Thu, 17 Mar 2022 09:21:06 -0400 Subject: [PATCH 05/10] [SecuritySolution] Add alert prevalence column to highlighted fields table (#127599) Co-authored-by: Jan Monschke Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../event_details/alert_summary_view.tsx | 50 +----- .../enrichment_accordion_group.tsx | 29 +++- .../event_details/cti_details/helpers.tsx | 8 + .../cti_details/threat_summary_table.tsx | 19 +++ .../cti_details/threat_summary_title.tsx | 21 +++ .../event_details/event_details.tsx | 2 +- .../event_details/get_alert_summary_rows.tsx | 4 +- .../components/event_details/helpers.tsx | 47 ------ .../__snapshots__/index.test.tsx.snap | 36 ++++- .../event_details/summary_view.test.tsx | 95 +++++++++++ .../components/event_details/summary_view.tsx | 97 ++++++------ .../event_details/table/action_cell.tsx | 3 + .../table/add_to_timeline_cell.test.tsx | 82 ++++++++++ .../table/add_to_timeline_cell.tsx | 51 ++++++ .../event_details/table/field_value_cell.tsx | 4 + .../table/prevalence_cell.test.tsx | 110 +++++++++++++ .../event_details/table/prevalence_cell.tsx | 37 +++++ .../event_details/table/summary_table.tsx | 31 ++++ .../table/summary_value_cell.test.tsx | 79 ++++++++++ .../table/summary_value_cell.tsx | 51 ++++++ .../table/use_action_cell_data_provider.ts | 12 +- .../components/event_details/translations.ts | 31 +++- .../common/components/hover_actions/index.tsx | 3 + .../use_hover_action_items.test.tsx | 18 +++ .../hover_actions/use_hover_action_items.tsx | 5 +- .../containers/alerts/use_alert_prevalence.ts | 147 ++++++++++++++++++ .../public/mock/mock_hover_actions.tsx | 14 +- 27 files changed, 919 insertions(+), 167 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/summary_table.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.tsx create mode 100644 x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 052ced8bfdf7ed..3750753114c361 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -5,56 +5,15 @@ * 2.0. */ -import { EuiBasicTableColumn } from '@elastic/eui'; import React, { useMemo } from 'react'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; import { SummaryView } from './summary_view'; -import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers'; -import { ActionCell } from './table/action_cell'; -import { FieldValueCell } from './table/field_value_cell'; -import { TimelineId } from '../../../../common/types'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { getSummaryRows } from './get_alert_summary_rows'; -const getDescription = ({ - data, - eventId, - fieldFromBrowserField, - isDraggable, - linkValue, - timelineId, - values, -}: AlertSummaryRow['description']) => ( - <> - - {timelineId !== TimelineId.active && ( - - )} - -); - -const summaryColumns: Array> = getSummaryColumns(getDescription); - const AlertSummaryViewComponent: React.FC<{ browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; @@ -69,14 +28,7 @@ const AlertSummaryViewComponent: React.FC<{ [browserFields, data, eventId, isDraggable, timelineId] ); - return ( - - ); + return ; }; export const AlertSummaryView = React.memo(AlertSummaryViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx index 6531d9ae2823f6..a727c6e5ce72d4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx @@ -17,14 +17,19 @@ import { } from '@elastic/eui'; import { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti'; -import { getEnrichmentIdentifiers, isInvestigationTimeEnrichment, getFirstSeen } from './helpers'; +import { + getEnrichmentIdentifiers, + isInvestigationTimeEnrichment, + getFirstSeen, + ThreatDetailsRow, +} from './helpers'; import { EnrichmentButtonContent } from './enrichment_button_content'; +import { ThreatSummaryTitle } from './threat_summary_title'; import { InspectButton } from '../../inspect'; import { QUERY_ID } from '../../../containers/cti/event_enrichment'; import * as i18n from './translations'; -import { StyledEuiInMemoryTable } from '../summary_view'; +import { ThreatSummaryTable } from './threat_summary_table'; import { REFERENCE } from '../../../../../common/cti/constants'; -import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from '../helpers'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { getFirstElement } from '../../../../../common/utils/data_retrieval'; @@ -65,7 +70,21 @@ const ThreatDetailsDescription: React.FC = ({ ); }; -const columns: Array> = getSummaryColumns(ThreatDetailsDescription); +const columns: Array> = [ + { + field: 'title', + truncateText: false, + render: ThreatSummaryTitle, + width: '220px', + name: '', + }, + { + field: 'description', + truncateText: false, + render: ThreatDetailsDescription, + name: '', + }, +]; const buildThreatDetailsItems = (enrichment: CtiEnrichment) => Object.keys(enrichment) @@ -107,7 +126,7 @@ const EnrichmentAccordion: React.FC<{ ) } > - { const firstSeenDate = Date.parse(firstSeenValue ?? 'no date'); return Number.isInteger(firstSeenDate) ? firstSeenDate : new Date(-1).valueOf(); }; + +export interface ThreatDetailsRow { + title: string; + description: { + fieldName: string; + value: string; + }; +} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx new file mode 100644 index 00000000000000..64c547842cd114 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx @@ -0,0 +1,19 @@ +/* + * 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 styled, { AnyStyledComponent } from 'styled-components'; +import { EuiInMemoryTable } from '@elastic/eui'; + +export const ThreatSummaryTable = styled(EuiInMemoryTable as unknown as AnyStyledComponent)` + .euiTableHeaderCell, + .euiTableRowCell { + border: none; + } + .euiTableHeaderCell .euiTableCellContent { + padding: 0; + } +`; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx new file mode 100644 index 00000000000000..71541c9d9ac1ed --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx @@ -0,0 +1,21 @@ +/* + * 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 styled from 'styled-components'; +import React from 'react'; +import { EuiTitle } from '@elastic/eui'; + +const StyledH5 = styled.h5` + line-height: 1.7rem; +`; + +export const ThreatSummaryTitle = (title: string) => ( + + {title} + +); +ThreatSummaryTitle.displayName = 'ThreatSummaryTitle'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 3eb7bd935a9f80..5291f5a3f15a31 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -178,7 +178,7 @@ const EventDetailsComponent: React.FC = ({ browserFields, isDraggable, timelineId, - title: i18n.HIGHLIGHTES_FIELDS, + title: i18n.HIGHLIGHTED_FIELDS, }} goToTable={goToTableTab} /> diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx index 8550cd84351242..0527acfef1f9aa 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx @@ -18,7 +18,7 @@ import { } from '../../../detections/components/alerts_table/translations'; import { ALERT_THRESHOLD_RESULT } from '../../../../common/field_maps/field_names'; import { AGENT_STATUS_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants'; -import { getEnrichedFieldInfo, SummaryRow } from './helpers'; +import { getEnrichedFieldInfo, AlertSummaryRow } from './helpers'; import { EventSummaryField, EnrichedFieldInfo } from './types'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; @@ -253,7 +253,7 @@ export const getSummaryRows = ({ }); return data != null - ? tableFields.reduce((acc, field) => { + ? tableFields.reduce((acc, field) => { const item = data.find( (d) => d.field === field.id || (field.legacyId && d.field === field.legacyId) ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index dcca42f2a1df75..a6c9d71e3371cf 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -7,9 +7,6 @@ import { get, getOr, isEmpty, uniqBy } from 'lodash/fp'; -import styled from 'styled-components'; -import React from 'react'; -import { EuiBasicTableColumn, EuiTitle } from '@elastic/eui'; import { elementOrChildrenHasFocus, getFocusedDataColindexCell, @@ -62,16 +59,6 @@ export interface AlertSummaryRow { }; } -export interface ThreatDetailsRow { - title: string; - description: { - fieldName: string; - value: string; - }; -} - -export type SummaryRow = AlertSummaryRow | ThreatDetailsRow; - export const getColumnHeaderFromBrowserField = ({ browserField, width = DEFAULT_COLUMN_MIN_WIDTH, @@ -194,40 +181,6 @@ export const onEventDetailsTabKeyPressed = ({ } }; -const StyledH5 = styled.h5` - line-height: 1.7rem; -`; - -const getTitle = (title: string) => ( - - {title} - -); -getTitle.displayName = 'getTitle'; - -export const getSummaryColumns = ( - DescriptionComponent: - | React.FC - | React.FC -): Array> => { - return [ - { - field: 'title', - truncateText: false, - render: getTitle, - width: '220px', - name: '', - }, - { - className: 'flyoutOverviewDescription', - field: 'description', - truncateText: false, - render: DescriptionComponent, - name: '', - }, - ]; -}; - export function getEnrichedFieldInfo({ browserFields, contextId, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap index 16c9136d55e694..112ebb252f7d72 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap @@ -153,12 +153,20 @@ exports[`Event Details Overview Cards renders rows and spacers correctly 1`] = `
- Filter button + + Filter button +
- Filter out button + + Filter out button +
- Filter button + + Filter button +
- Filter out button + + Filter out button +
- Filter button + + Filter button +
- Filter out button + + Filter out button +
({ + useAlertPrevalence: () => ({ + loading: false, + count: 1, + error: false, + }), +})); + +describe('Summary View', () => { + describe('when no data is provided', () => { + test('should show an empty table', () => { + render( + + + + ); + expect(screen.getByText('No items found')).toBeInTheDocument(); + }); + }); + + describe('when data is provided', () => { + test('should show the data', () => { + const sampleRows: AlertSummaryRow[] = [ + { + title: hostIpData.field, + description: enrichedHostIpData, + }, + ]; + + render( + + + + ); + expect(screen.getByText(hostIpData.field)).toBeInTheDocument(); + hostIpValues.forEach((ipValue) => { + expect(screen.getByText(ipValue)).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index c07c4043fc9633..451ffd64584a78 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -6,7 +6,6 @@ */ import { - EuiInMemoryTable, EuiBasicTableColumn, EuiLink, EuiTitle, @@ -14,51 +13,60 @@ import { EuiFlexItem, EuiSpacer, EuiText, + EuiIconTip, } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; -import { SummaryRow } from './helpers'; +import type { AlertSummaryRow } from './helpers'; +import * as i18n from './translations'; import { VIEW_ALL_FIELDS } from './translations'; +import { SummaryTable } from './table/summary_table'; +import { SummaryValueCell } from './table/summary_value_cell'; +import { PrevalenceCellRenderer } from './table/prevalence_cell'; -export const Indent = styled.div` - padding: 0 12px; -`; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` - .euiTableHeaderCell, - .euiTableRowCell { - border: none; - } - .euiTableHeaderCell .euiTableCellContent { - padding: 0; - } - - .flyoutOverviewDescription { - .hoverActions-active { - .timelines__hoverActionButton, - .securitySolution__hoverActionButton { - opacity: 1; - } - } +const summaryColumns: Array> = [ + { + field: 'title', + truncateText: false, + name: i18n.HIGHLIGHTED_FIELDS_FIELD, + textOnly: true, + }, + { + field: 'description', + truncateText: false, + render: SummaryValueCell, + name: i18n.HIGHLIGHTED_FIELDS_VALUE, + }, + { + field: 'description', + truncateText: true, + render: PrevalenceCellRenderer, + name: ( + <> + {i18n.HIGHLIGHTED_FIELDS_ALERT_PREVALENCE}{' '} + {i18n.HIGHLIGHTED_FIELDS_ALERT_PREVALENCE_TOOLTIP}} + /> + + ), + align: 'right', + width: '130px', + }, +]; - &:hover { - .timelines__hoverActionButton, - .securitySolution__hoverActionButton { - opacity: 1; - } - } - } -`; +const rowProps = { + // Class name for each row. On hover of a row, all actions for that row will be shown. + className: 'flyoutTableHoverActions', +}; -export const SummaryViewComponent: React.FC<{ +const SummaryViewComponent: React.FC<{ goToTable: () => void; title: string; - summaryColumns: Array>; - summaryRows: SummaryRow[]; - dataTestSubj?: string; -}> = ({ goToTable, summaryColumns, summaryRows, dataTestSubj = 'summary-view', title }) => { + rows: AlertSummaryRow[]; +}> = ({ goToTable, rows, title }) => { return (
@@ -74,14 +82,13 @@ export const SummaryViewComponent: React.FC<{ - - - +
); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx index b49aafea922455..523b56e9ecf765 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx @@ -19,6 +19,7 @@ interface Props extends EnrichedFieldInfo { getLinkValue?: (field: string) => string | null; onFilterAdded?: () => void; toggleColumn?: (column: ColumnHeaderOptions) => void; + hideAddToTimeline?: boolean; } export const ActionCell: React.FC = React.memo( @@ -34,6 +35,7 @@ export const ActionCell: React.FC = React.memo( timelineId, toggleColumn, values, + hideAddToTimeline, }) => { const actionCellConfig = useActionCellDataProvider({ contextId, @@ -69,6 +71,7 @@ export const ActionCell: React.FC = React.memo( dataProvider={actionCellConfig?.dataProvider} enableOverflowButton={true} field={data.field} + hideAddToTimeline={hideAddToTimeline} isObjectArray={data.isObjectArray} onFilterAdded={onFilterAdded} ownFocus={hoverActionsOwnFocus} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx new file mode 100644 index 00000000000000..4749a0349e94d9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx @@ -0,0 +1,82 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { BrowserField } from '../../../containers/source'; +import { AddToTimelineCellRenderer } from './add_to_timeline_cell'; +import { TestProviders } from '../../../mock'; +import { EventFieldsData } from '../types'; +import { TimelineId } from '../../../../../common/types'; + +jest.mock('../../../lib/kibana'); + +const eventId = 'TUWyf3wBFCFU0qRJTauW'; + +const hostIpFieldFromBrowserField: BrowserField = { + aggregatable: true, + category: 'host', + description: 'Host ip addresses.', + example: '127.0.0.1', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + name: 'host.ip', + readFromDocValues: false, + searchable: true, + type: 'ip', +}; + +const hostIpData: EventFieldsData = { + ...hostIpFieldFromBrowserField, + ariaRowindex: 35, + field: 'host.ip', + fields: {}, + format: '', + isObjectArray: false, + originalValue: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], + values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], +}; + +describe('AddToTimelineCellRenderer', () => { + describe('When all props are provided', () => { + test('it should display the add to timeline button', () => { + render( + + + + ); + expect(screen.getByTestId('test-add-to-timeline')).toBeInTheDocument(); + }); + }); + + describe('When browser field data necessary for timeline is unavailable', () => { + test('it should not render', () => { + render( + + + + ); + expect(screen.queryByTestId('test-add-to-timeline')).toBeNull(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx new file mode 100644 index 00000000000000..fbf33e450ded38 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx @@ -0,0 +1,51 @@ +/* + * 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 React from 'react'; +import { isEmpty } from 'lodash'; + +import { useKibana } from '../../../lib/kibana'; +import { AlertSummaryRow } from '../helpers'; +import { useActionCellDataProvider } from './use_action_cell_data_provider'; + +const AddToTimelineCell = React.memo( + ({ data, eventId, fieldFromBrowserField, linkValue, timelineId, values }) => { + const kibana = useKibana(); + const { timelines } = kibana.services; + const { getAddToTimelineButton } = timelines.getHoverActions(); + + const actionCellConfig = useActionCellDataProvider({ + contextId: timelineId, + eventId, + field: data.field, + fieldFormat: data.format, + fieldFromBrowserField, + fieldType: data.type, + isObjectArray: data.isObjectArray, + linkValue, + values, + }); + + const showButton = values != null && !isEmpty(actionCellConfig?.dataProvider); + + if (showButton) { + return getAddToTimelineButton({ + dataProvider: actionCellConfig?.dataProvider, + field: data.field, + ownFocus: true, + }); + } else { + return null; + } + } +); + +AddToTimelineCell.displayName = 'AddToTimelineCell'; + +export const AddToTimelineCellRenderer = (props: AlertSummaryRow['description']) => ( + +); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx index 62aad517852061..cab7a58243cca3 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { CSSObject } from 'styled-components'; import { BrowserField } from '../../../containers/source'; import { OverflowField } from '../../tables/helpers'; import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; @@ -21,6 +22,7 @@ export interface FieldValueCellProps { getLinkValue?: (field: string) => string | null; isDraggable?: boolean; linkValue?: string | null | undefined; + style?: CSSObject | undefined; values: string[] | null | undefined; } @@ -33,6 +35,7 @@ export const FieldValueCell = React.memo( getLinkValue, isDraggable = false, linkValue, + style, values, }: FieldValueCellProps) => { return ( @@ -41,6 +44,7 @@ export const FieldValueCell = React.memo( data-test-subj={`event-field-${data.field}`} direction="column" gutterSize="none" + style={style} > {values != null && values.map((value, i) => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx new file mode 100644 index 00000000000000..83b4c63484dd3d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx @@ -0,0 +1,110 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { BrowserField } from '../../../containers/source'; +import { PrevalenceCellRenderer } from './prevalence_cell'; +import { TestProviders } from '../../../mock'; +import { EventFieldsData } from '../types'; +import { TimelineId } from '../../../../../common/types'; +import { AlertSummaryRow } from '../helpers'; +import { useAlertPrevalence } from '../../../containers/alerts/use_alert_prevalence'; + +jest.mock('../../../lib/kibana'); +jest.mock('../../../containers/alerts/use_alert_prevalence', () => ({ + useAlertPrevalence: jest.fn(), +})); +const mockUseAlertPrevalence = useAlertPrevalence as jest.Mock; + +const eventId = 'TUWyf3wBFCFU0qRJTauW'; +const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::']; +const hostIpFieldFromBrowserField: BrowserField = { + aggregatable: true, + category: 'host', + description: 'Host ip addresses.', + example: '127.0.0.1', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + name: 'host.ip', + readFromDocValues: false, + searchable: true, + type: 'ip', +}; +const hostIpData: EventFieldsData = { + ...hostIpFieldFromBrowserField, + ariaRowindex: 35, + field: 'host.ip', + fields: {}, + format: '', + isObjectArray: false, + originalValue: [...hostIpValues], + values: [...hostIpValues], +}; + +const enrichedHostIpData: AlertSummaryRow['description'] = { + data: { ...hostIpData }, + eventId, + fieldFromBrowserField: { ...hostIpFieldFromBrowserField }, + isDraggable: false, + timelineId: TimelineId.test, + values: [...hostIpValues], +}; + +describe('PrevalenceCellRenderer', () => { + describe('When data is loading', () => { + test('it should show the loading spinner', async () => { + mockUseAlertPrevalence.mockImplementation(() => ({ + loading: true, + count: 123, + error: true, + })); + const { container } = render( + + + + ); + expect(container.getElementsByClassName('euiLoadingSpinner')).toHaveLength(1); + }); + }); + + describe('When an error was returned', () => { + test('it should return null', async () => { + mockUseAlertPrevalence.mockImplementation(() => ({ + loading: false, + count: 123, + error: true, + })); + const { container } = render( + + + + ); + expect(container.getElementsByClassName('euiLoadingSpinner')).toHaveLength(0); + expect(screen.queryByText('123')).toBeNull(); + }); + }); + + describe('When an actual count is returned', () => { + test('it should show the count', async () => { + mockUseAlertPrevalence.mockImplementation(() => ({ + loading: false, + count: 123, + error: false, + })); + const { container } = render( + + + + ); + expect(container.getElementsByClassName('euiLoadingSpinner')).toHaveLength(0); + expect(screen.queryByText('123')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx new file mode 100644 index 00000000000000..ed8b610b39d1f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx @@ -0,0 +1,37 @@ +/* + * 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 React from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; + +import { AlertSummaryRow } from '../helpers'; +import { useAlertPrevalence } from '../../../containers/alerts/use_alert_prevalence'; + +const PrevalenceCell = React.memo( + ({ data, values, timelineId }) => { + const { loading, count, error } = useAlertPrevalence({ + field: data.field, + timelineId, + value: values, + signalIndexName: null, + }); + + if (loading) { + return ; + } else if (error) { + return null; + } + + return <>{count}; + } +); + +PrevalenceCell.displayName = 'PrevalenceCell'; + +export const PrevalenceCellRenderer = (data: AlertSummaryRow['description']) => { + return ; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_table.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_table.tsx new file mode 100644 index 00000000000000..089d9607c261d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_table.tsx @@ -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 styled, { AnyStyledComponent } from 'styled-components'; +import { EuiInMemoryTable } from '@elastic/eui'; + +export const SummaryTable = styled(EuiInMemoryTable as unknown as AnyStyledComponent)` + .timelines__hoverActionButton { + opacity: 0; + } + + .flyoutTableHoverActions { + .hoverActions-active { + .timelines__hoverActionButton, + .securitySolution__hoverActionButton { + opacity: 1; + } + } + + &:hover { + .timelines__hoverActionButton, + .securitySolution__hoverActionButton { + opacity: 1; + } + } + } +`; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.test.tsx new file mode 100644 index 00000000000000..b51aeb3253eee6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.test.tsx @@ -0,0 +1,79 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { BrowserField } from '../../../containers/source'; +import { SummaryValueCell } from './summary_value_cell'; +import { TestProviders } from '../../../mock'; +import { EventFieldsData } from '../types'; +import { AlertSummaryRow } from '../helpers'; +import { TimelineId } from '../../../../../common/types'; + +jest.mock('../../../lib/kibana'); + +const eventId = 'TUWyf3wBFCFU0qRJTauW'; +const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::']; +const hostIpFieldFromBrowserField: BrowserField = { + aggregatable: true, + category: 'host', + description: 'Host ip addresses.', + example: '127.0.0.1', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + name: 'host.ip', + readFromDocValues: false, + searchable: true, + type: 'ip', +}; +const hostIpData: EventFieldsData = { + ...hostIpFieldFromBrowserField, + ariaRowindex: 35, + field: 'host.ip', + fields: {}, + format: '', + isObjectArray: false, + originalValue: [...hostIpValues], + values: [...hostIpValues], +}; + +const enrichedHostIpData: AlertSummaryRow['description'] = { + data: { ...hostIpData }, + eventId, + fieldFromBrowserField: { ...hostIpFieldFromBrowserField }, + isDraggable: false, + timelineId: TimelineId.test, + values: [...hostIpValues], +}; + +describe('SummaryValueCell', () => { + test('it should render', async () => { + render( + + + + ); + hostIpValues.forEach((ipValue) => expect(screen.getByText(ipValue)).toBeInTheDocument()); + expect(screen.getAllByTestId('test-filter-for')).toHaveLength(1); + expect(screen.getAllByTestId('test-filter-out')).toHaveLength(1); + }); + + describe('When in the timeline flyout with timelineId active', () => { + test('it should not render the default hover actions', async () => { + render( + + + + ); + hostIpValues.forEach((ipValue) => expect(screen.getByText(ipValue)).toBeInTheDocument()); + expect(screen.queryByTestId('test-filter-for')).toBeNull(); + expect(screen.queryByTestId('test-filter-out')).toBeNull(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.tsx new file mode 100644 index 00000000000000..4289e0e73327d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.tsx @@ -0,0 +1,51 @@ +/* + * 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 React from 'react'; + +import { ActionCell } from './action_cell'; +import { FieldValueCell } from './field_value_cell'; +import { AlertSummaryRow } from '../helpers'; +import { TimelineId } from '../../../../../common/types'; + +export const SummaryValueCell: React.FC = ({ + data, + eventId, + fieldFromBrowserField, + isDraggable, + linkValue, + timelineId, + values, +}) => ( + <> + + {timelineId !== TimelineId.active && ( + + )} + +); + +SummaryValueCell.displayName = 'SummaryValueCell'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts index 23acd90183af4a..15a117817b6273 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts @@ -92,15 +92,17 @@ export const useActionCellDataProvider = ({ } else if (fieldType === IP_FIELD_TYPE) { id = `formatted-ip-data-provider-${contextId}-${field}-${value}-${eventId}`; if (isString(value) && !isEmpty(value)) { + let addresses = value; try { - const addresses = JSON.parse(value); - if (isArray(addresses)) { - valueAsString = addresses.join(','); - addresses.forEach((ip) => memo.dataProvider.push(getDataProvider(field, id, ip))); - } + addresses = JSON.parse(value); } catch (_) { // Default to keeping the existing string value } + if (isArray(addresses)) { + valueAsString = addresses.join(','); + addresses.forEach((ip) => memo.dataProvider.push(getDataProvider(field, id, ip))); + } + memo.dataProvider.push(getDataProvider(field, id, addresses)); memo.stringValues.push(valueAsString); return memo; } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index 8fefec910a3ceb..52f73e9de481a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -22,13 +22,42 @@ export const OVERVIEW = i18n.translate('xpack.securitySolution.alertDetails.over defaultMessage: 'Overview', }); -export const HIGHLIGHTES_FIELDS = i18n.translate( +export const HIGHLIGHTED_FIELDS = i18n.translate( 'xpack.securitySolution.alertDetails.overview.highlightedFields', { defaultMessage: 'Highlighted fields', } ); +export const HIGHLIGHTED_FIELDS_FIELD = i18n.translate( + 'xpack.securitySolution.alertDetails.overview.highlightedFields.field', + { + defaultMessage: 'Field', + } +); + +export const HIGHLIGHTED_FIELDS_VALUE = i18n.translate( + 'xpack.securitySolution.alertDetails.overview.highlightedFields.value', + { + defaultMessage: 'Value', + } +); + +export const HIGHLIGHTED_FIELDS_ALERT_PREVALENCE = i18n.translate( + 'xpack.securitySolution.alertDetails.overview.highlightedFields.alertPrevalence', + { + defaultMessage: 'Alert Prevalence', + } +); + +export const HIGHLIGHTED_FIELDS_ALERT_PREVALENCE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.alertDetails.overview.highlightedFields.alertPrevalenceTooltip', + { + defaultMessage: + 'The total count of alerts with the same value within the currently selected timerange. This value is not affected by additional filters.', + } +); + export const TABLE = i18n.translate('xpack.securitySolution.eventDetails.table', { defaultMessage: 'Table', }); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx index 2617a1a4068a4f..02a59b41f2ee9e 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx @@ -97,6 +97,7 @@ interface Props { enableOverflowButton?: boolean; field: string; goGetTimelineId?: (args: boolean) => void; + hideAddToTimeline?: boolean; hideTopN?: boolean; isObjectArray: boolean; onFilterAdded?: () => void; @@ -137,6 +138,7 @@ export const HoverActions: React.FC = React.memo( field, goGetTimelineId, isObjectArray, + hideAddToTimeline = false, hideTopN = false, onFilterAdded, ownFocus, @@ -218,6 +220,7 @@ export const HoverActions: React.FC = React.memo( enableOverflowButton: enableOverflowButton && !isCaseView, field, handleHoverActionClicked, + hideAddToTimeline, hideTopN, isCaseView, isObjectArray, diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx index 1f6436f59602c5..7bf63479b65ead 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx @@ -22,6 +22,7 @@ describe('useHoverActionItems', () => { defaultFocusedButtonRef: null, field: 'kibana.alert.rule.name', handleHoverActionClicked: jest.fn(), + hideAddToTimeline: false, hideTopN: false, isCaseView: false, isObjectArray: false, @@ -274,4 +275,21 @@ describe('useHoverActionItems', () => { ); }); }); + + test('when timeline button is disabled, it should not show', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => { + const testProps = { + ...defaultProps, + hideAddToTimeline: true, + }; + return useHoverActionItems(testProps); + }); + await waitForNextUpdate(); + + result.current.allActionItems.forEach((actionItem) => { + expect(actionItem.props['data-test-subj']).not.toEqual('hover-actions-add-timeline'); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx index 44321d68467695..528519e913f1ef 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx @@ -30,6 +30,7 @@ export interface UseHoverActionItemsProps { enableOverflowButton?: boolean; field: string; handleHoverActionClicked: () => void; + hideAddToTimeline: boolean; hideTopN: boolean; isCaseView: boolean; isObjectArray: boolean; @@ -60,6 +61,7 @@ export const useHoverActionItems = ({ field, handleHoverActionClicked, hideTopN, + hideAddToTimeline, isCaseView, isObjectArray, isOverflowPopoverOpen, @@ -204,7 +206,7 @@ export const useHoverActionItems = ({ })}
) : null, - values != null && (draggableId != null || !isEmpty(dataProvider)) ? ( + values != null && (draggableId != null || !isEmpty(dataProvider)) && !hideAddToTimeline ? (
{getAddToTimelineButton({ Component: enableOverflowButton ? EuiContextMenuItem : undefined, @@ -258,6 +260,7 @@ export const useHoverActionItems = ({ getFilterForValueButton, getFilterOutValueButton, handleHoverActionClicked, + hideAddToTimeline, hideTopN, isObjectArray, onFilterAdded, diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts new file mode 100644 index 00000000000000..478eef0ebfbe16 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts @@ -0,0 +1,147 @@ +/* + * 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 { useEffect, useState } from 'react'; + +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../common/constants'; +import { useGlobalTime } from '../use_global_time'; +import { GenericBuckets } from '../../../../common/search_strategy'; +import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; +import { TimelineId } from '../../../../common/types'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { inputsSelectors } from '../../store'; + +const ALERT_PREVALENCE_AGG = 'countOfAlertsWithSameFieldAndValue'; +export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; + +interface UseAlertPrevalenceOptions { + field: string; + value: string | string[] | undefined | null; + timelineId: string; + signalIndexName: string | null; +} + +interface UserAlertPrevalenceResult { + loading: boolean; + count: undefined | number; + error: boolean; +} + +export const useAlertPrevalence = ({ + field, + value, + timelineId, + signalIndexName, +}: UseAlertPrevalenceOptions): UserAlertPrevalenceResult => { + const timelineTime = useDeepEqualSelector((state) => + inputsSelectors.timelineTimeRangeSelector(state) + ); + const globalTime = useGlobalTime(); + + const { to, from } = timelineId === TimelineId.active ? timelineTime : globalTime; + const [initialQuery] = useState(() => generateAlertPrevalenceQuery(field, value, from, to)); + + const { loading, data, setQuery } = useQueryAlerts<{}, AlertPrevalenceAggregation>({ + query: initialQuery, + indexName: signalIndexName, + }); + + useEffect(() => { + setQuery(generateAlertPrevalenceQuery(field, value, from, to)); + }, [setQuery, field, value, from, to]); + + let count: undefined | number; + if (data) { + const buckets = data.aggregations?.[ALERT_PREVALENCE_AGG]?.buckets; + if (buckets && buckets.length > 0) { + /** + * Currently for array fields like `process.args` or potentially any `ip` fields + * We show the combined count of all occurences of the value, even though those values + * could be shared across multiple documents. To make this clearer, we should separate + * these values into separate table rows + */ + count = buckets?.reduce((sum, bucket) => sum + (bucket?.doc_count ?? 0), 0); + } + } + + const error = !loading && count === undefined; + + return { + loading, + count, + error, + }; +}; + +const generateAlertPrevalenceQuery = ( + field: string, + value: string | string[] | undefined | null, + from: string, + to: string +) => { + const actualValue = Array.isArray(value) && value.length === 1 ? value[0] : value; + let query; + query = { + bool: { + must: { + match: { + [field]: actualValue, + }, + }, + filter: [ + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, + }, + ], + }, + }; + + if (Array.isArray(value) && value.length > 1) { + const shouldValues = value.map((val) => ({ match: { [field]: val } })); + query = { + bool: { + minimum_should_match: 1, + must: [ + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, + }, + ], + should: shouldValues, + }, + }; + } + + return { + size: 0, + aggs: { + [ALERT_PREVALENCE_AGG]: { + terms: { + field, + size: DEFAULT_MAX_TABLE_QUERY_SIZE, + }, + }, + }, + query, + runtime_mappings: {}, + }; +}; + +export interface AlertPrevalenceAggregation { + [ALERT_PREVALENCE_AGG]: { + buckets: GenericBuckets[]; + }; +} diff --git a/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx b/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx index 31df992f5ef0b3..22ab99c35cf920 100644 --- a/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx +++ b/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx @@ -7,11 +7,15 @@ import React from 'react'; export const mockHoverActions = { - getAddToTimelineButton: () => <>{'Add To Timeline'}, - getColumnToggleButton: () => <>{'Column Toggle'}, - getCopyButton: () => <>{'Copy button'}, - getFilterForValueButton: () => <>{'Filter button'}, - getFilterOutValueButton: () => <>{'Filter out button'}, + getAddToTimelineButton: () => ( + {'Add To Timeline'} + ), + getColumnToggleButton: () => {'Column Toggle'}, + getCopyButton: () => {'Copy button'}, + getFilterForValueButton: () => {'Filter button'}, + getFilterOutValueButton: () => ( + {'Filter out button'} + ), getOverflowButton: (props: { field: string }) => (
{'Overflow button'} From 92255e85427cf746467759dff647e3c5298c20de Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Thu, 17 Mar 2022 09:35:30 -0400 Subject: [PATCH 06/10] [APM] Enables Java runtime attacher UI in Fleet integration agents tab (#127414) (#127790) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apm_agents/agent_instructions_mappings.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts index 8a0f4fa8016875..93260f09d7f6b6 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts @@ -20,7 +20,7 @@ import { } from '../../../../common/tutorial/instructions/apm_agent_instructions'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; // TODO: Uncomment once https://github.com/elastic/beats/issues/29631 has been closed -// import { JavaRuntimeAttachment } from './runtime_attachment/supported_agents/java_runtime_attachment'; +import { JavaRuntimeAttachment } from './runtime_attachment/supported_agents/java_runtime_attachment'; import { NewPackagePolicy, PackagePolicy, @@ -56,8 +56,7 @@ export const ApmAgentInstructionsMappings: Array<{ title: 'Java', variantId: 'java', createAgentInstructions: createJavaAgentInstructions, - // TODO: Uncomment once https://github.com/elastic/beats/issues/29631 has been closed - // AgentRuntimeAttachment: JavaRuntimeAttachment, + AgentRuntimeAttachment: JavaRuntimeAttachment, }, { agentName: 'rum-js', From 8bc1e55ba2e89926e44b189d17ed51fd242c3ed9 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:47:48 +0500 Subject: [PATCH 07/10] [Console] encode + sign in ISO8601 time range in query (#126660) * Encode + sign in ISO8601 time range in query * Added tests * Fix lint * Moved added code to toURL function * Updated tests * Updated tests * Remove unused export Co-authored-by: Muhammad Ibragimov --- .../api/console/proxy/create_handler.ts | 10 ++++++++- .../api/console/proxy/query_string.test.ts | 21 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts index bdf9426a821075..815c507b763f78 100644 --- a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts +++ b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts @@ -31,6 +31,14 @@ import { RouteDependencies } from '../../../'; import { Body, Query } from './validation_config'; function toURL(base: string, path: string) { + const [p, query = ''] = path.split('?'); + + // if there is a '+' sign in query e.g. ?q=create_date:[2020-05-10T08:00:00.000+08:00 TO *] + // node url encodes it as a whitespace which results in a faulty request + // we need to replace '+' with '%2b' to encode it correctly + if (/\+/g.test(query)) { + path = `${p}?${query.replace(/\+/g, '%2b')}`; + } const urlResult = new url.URL(`${trimEnd(base, '/')}/${trimStart(path, '/')}`); // Appending pretty here to have Elasticsearch do the JSON formatting, as doing // in JS can lead to data loss (7.0 will get munged into 7, thus losing indication of @@ -116,7 +124,7 @@ export const createHandler = }: RouteDependencies): RequestHandler => async (ctx, request, response) => { const { body, query } = request; - const { path, method, withProductOrigin } = query; + const { method, path, withProductOrigin } = query; if (kibanaVersion.major < 8) { // The "console.proxyFilter" setting in kibana.yaml has been deprecated in 8.x diff --git a/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts b/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts index be4f1dbab942f4..3f7ba81c33c03b 100644 --- a/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts +++ b/src/plugins/console/server/routes/api/console/proxy/query_string.test.ts @@ -16,6 +16,8 @@ import { createHandler } from './create_handler'; describe('Console Proxy Route', () => { let request: (method: string, path: string) => Promise | IKibanaResponse; + const proxyRequestMock = requestModule.proxyRequest as jest.Mock; + beforeEach(() => { (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo')); @@ -39,7 +41,7 @@ describe('Console Proxy Route', () => { describe('contains full url', () => { it('treats the url as a path', async () => { await request('GET', 'http://evil.com/test'); - expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + expect(proxyRequestMock.mock.calls.length).toBe(1); const [[args]] = (requestModule.proxyRequest as jest.Mock).mock.calls; expect(args.uri.href).toBe('http://localhost:9200/http://evil.com/test?pretty=true'); }); @@ -47,7 +49,7 @@ describe('Console Proxy Route', () => { describe('starts with a slash', () => { it('combines well with the base url', async () => { await request('GET', '/index/id'); - expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + expect(proxyRequestMock.mock.calls.length).toBe(1); const [[args]] = (requestModule.proxyRequest as jest.Mock).mock.calls; expect(args.uri.href).toBe('http://localhost:9200/index/id?pretty=true'); }); @@ -55,11 +57,24 @@ describe('Console Proxy Route', () => { describe(`doesn't start with a slash`, () => { it('combines well with the base url', async () => { await request('GET', 'index/id'); - expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + expect(proxyRequestMock.mock.calls.length).toBe(1); const [[args]] = (requestModule.proxyRequest as jest.Mock).mock.calls; expect(args.uri.href).toBe('http://localhost:9200/index/id?pretty=true'); }); }); + describe('contains special characters', () => { + it('correctly encodes plus sign', async () => { + const path = '/_search?q=create_date:[2022-03-10T08:00:00.000+08:00 TO *]'; + + const { status } = await request('GET', path); + expect(status).toBe(200); + expect(proxyRequestMock.mock.calls.length).toBe(1); + const [[args]] = proxyRequestMock.mock.calls; + expect(args.uri.search).toEqual( + '?q=create_date%3A%5B2022-03-10T08%3A00%3A00.000%2B08%3A00+TO+*%5D&pretty=true' + ); + }); + }); }); }); }); From 0e7be441400979bf170cf8f74d77c12433da19f3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 17 Mar 2022 14:56:27 +0100 Subject: [PATCH 08/10] [Screenshotting] Move PDF generation code to screenshotting (#127463) * wip: just move pdf (reporting plugin) -> pdf_maker (screenshotting plugin) * refactor paths for the jest integration tests * move assets folder * updated assets file * use the screenshotting error in screenshotting * fix worker imports * first iteration of updated screenshotting options * whole lotta wip, but got pdf maker set up in screenshot observable * looks like we were not passing through renderErrors? * some more minor refactors * cleaned up APM tracker for screenshotting pngsToPdf * big restructure of initial approach: rather place formats outside of the screenshotting functionality * remove unused imports * updated reporting core to work with new screenshotting functions * fixed some more types issues in core * done printable pdf v1 * refactored pdf v2 * removed layout from screenshot result * we need to check that only supported layout ids are passed to screenshotting from reporting for a given format * fix types and an import issue * fix tsconfig mistakenly changed * remove unused import * added jest.integration.config.js * update snapshots * update snapshots part ii * update jest test * safe handling of layout objects on jobs * update screenshotting mocks and make access layout safer * updated docs for new exported types, refactored to single getScreenshots per conversation with Dokolin * update report API usage and fix tests after refactor * added new type file for formats * make format optional and default to PNG at type level too * make format required again, update docs and use const type * update casing check to point to new file location * fix type error * update i18n IDs to match plugin namespace * remove * export * appease the linting gods * just use the getScreenshots method * update jest.integration.test.js path * move pageCount to metrics Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/dev/precommit_hook/casing_check_config.js | 12 +- .../common/errors/map_to_reporting_error.ts | 3 + .../plugins/reporting/common/types/index.ts | 4 +- x-pack/plugins/reporting/server/core.ts | 21 +++- .../export_types/common/generate_png.ts | 6 +- .../server/export_types/common/pdf_tracker.ts | 47 ++++++++ .../common/v2/get_full_redirect_app_url.ts | 2 +- .../export_types/png/execute_job/index.ts | 9 +- .../server/export_types/png_v2/execute_job.ts | 9 +- .../printable_pdf/execute_job/index.test.ts | 5 +- .../printable_pdf/execute_job/index.ts | 24 ++-- .../printable_pdf/lib/generate_pdf.ts | 81 ++------------ .../export_types/printable_pdf/lib/tracker.ts | 79 ------------- .../printable_pdf_v2/execute_job.test.ts | 5 +- .../printable_pdf_v2/execute_job.ts | 23 ++-- .../printable_pdf_v2/lib/generate_pdf.ts | 82 ++------------ .../printable_pdf_v2/lib/tracker.ts | 79 ------------- x-pack/plugins/reporting/server/types.ts | 9 +- .../plugins/screenshotting/common/errors.ts | 1 + x-pack/plugins/screenshotting/common/index.ts | 1 - .../plugins/screenshotting/common/layout.ts | 10 +- .../jest.integration.config.js} | 6 +- .../screenshotting/server/formats/index.ts | 12 ++ .../server/formats/pdf/index.ts | 104 ++++++++++++++++++ .../server/formats/pdf/pdf_maker}/README.md | 0 .../assets/fonts/noto/LICENSE_OFL.txt | 0 .../fonts/noto/NotoSansCJKtc-Medium.ttf | Bin .../fonts/noto/NotoSansCJKtc-Regular.ttf | Bin .../pdf/pdf_maker}/assets/fonts/noto/index.js | 0 .../assets/fonts/roboto/LICENSE.txt | 0 .../assets/fonts/roboto/Roboto-Italic.ttf | Bin .../assets/fonts/roboto/Roboto-Medium.ttf | Bin .../assets/fonts/roboto/Roboto-Regular.ttf | Bin .../pdf/pdf_maker}/assets/img/logo-grey.png | Bin .../formats/pdf/pdf_maker}/constants.ts | 2 +- .../formats/pdf/pdf_maker}/get_doc_options.ts | 0 .../formats/pdf/pdf_maker}/get_font.test.ts | 0 .../server/formats/pdf/pdf_maker}/get_font.ts | 0 .../formats/pdf/pdf_maker}/get_template.ts | 20 ++-- .../server/formats/pdf/pdf_maker/index.ts | 61 ++++++++++ .../integration_tests/buggy_worker.js | 0 .../integration_tests/memory_leak_worker.js | 0 .../integration_tests/pdfmaker.test.ts | 17 +-- .../server/formats/pdf/pdf_maker}/pdfmaker.ts | 56 +++++----- .../server/formats/pdf/pdf_maker/tracker.ts | 52 +++++++++ .../server/formats/pdf/pdf_maker}/types.ts | 0 .../server/formats/pdf/pdf_maker}/worker.js | 0 .../server/formats/pdf/pdf_maker}/worker.ts | 0 .../pdf/pdf_maker}/worker_dependencies.ts | 0 .../screenshotting/server/formats/png.ts | 38 +++++++ .../screenshotting/server/formats/types.ts | 17 +++ x-pack/plugins/screenshotting/server/index.ts | 9 +- x-pack/plugins/screenshotting/server/mock.ts | 13 ++- .../plugins/screenshotting/server/plugin.ts | 28 +++-- .../server/screenshots/index.test.ts | 8 +- .../server/screenshots/index.ts | 16 ++- .../server/screenshots/observable.ts | 5 +- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 60 files changed, 563 insertions(+), 419 deletions(-) create mode 100644 x-pack/plugins/reporting/server/export_types/common/pdf_tracker.ts delete mode 100644 x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts delete mode 100644 x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts rename x-pack/plugins/{reporting/server/export_types/common/pdf/index.ts => screenshotting/jest.integration.config.js} (64%) create mode 100644 x-pack/plugins/screenshotting/server/formats/index.ts create mode 100644 x-pack/plugins/screenshotting/server/formats/pdf/index.ts rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/README.md (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/noto/LICENSE_OFL.txt (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/noto/NotoSansCJKtc-Medium.ttf (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/noto/NotoSansCJKtc-Regular.ttf (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/noto/index.js (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/roboto/LICENSE.txt (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/roboto/Roboto-Italic.ttf (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/roboto/Roboto-Medium.ttf (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/fonts/roboto/Roboto-Regular.ttf (100%) rename x-pack/plugins/{reporting/server/export_types/common => screenshotting/server/formats/pdf/pdf_maker}/assets/img/logo-grey.png (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/constants.ts (91%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/get_doc_options.ts (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/get_font.test.ts (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/get_font.ts (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/get_template.ts (84%) create mode 100644 x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/index.ts rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/integration_tests/buggy_worker.js (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/integration_tests/memory_leak_worker.js (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/integration_tests/pdfmaker.test.ts (87%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/pdfmaker.ts (81%) create mode 100644 x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/tracker.ts rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/types.ts (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/worker.js (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/worker.ts (100%) rename x-pack/plugins/{reporting/server/export_types/common/pdf => screenshotting/server/formats/pdf/pdf_maker}/worker_dependencies.ts (100%) create mode 100644 x-pack/plugins/screenshotting/server/formats/png.ts create mode 100644 x-pack/plugins/screenshotting/server/formats/types.ts diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index eb65e0b752174d..8aa2d6f1cfe551 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -146,10 +146,10 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'x-pack/plugins/monitoring/public/icons/health-green.svg', 'x-pack/plugins/monitoring/public/icons/health-red.svg', 'x-pack/plugins/monitoring/public/icons/health-yellow.svg', - 'x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/NotoSansCJKtc-Medium.ttf', - 'x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/NotoSansCJKtc-Regular.ttf', - 'x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Italic.ttf', - 'x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Medium.ttf', - 'x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Regular.ttf', - 'x-pack/plugins/reporting/server/export_types/common/assets/img/logo-grey.png', + 'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Medium.ttf', + 'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Regular.ttf', + 'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Italic.ttf', + 'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Medium.ttf', + 'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Regular.ttf', + 'x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/img/logo-grey.png', ]; diff --git a/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts b/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts index ff24e684901fe6..0e38890817140d 100644 --- a/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts +++ b/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts @@ -12,6 +12,7 @@ import { BrowserCouldNotLaunchError, BrowserUnexpectedlyClosedError, BrowserScreenshotError, + PdfWorkerOutOfMemoryError, } from '.'; export function mapToReportingError(error: unknown): ReportingError { @@ -25,6 +26,8 @@ export function mapToReportingError(error: unknown): ReportingError { return new BrowserScreenshotError((error as Error).message); case error instanceof errors.FailedToSpawnBrowserError: return new BrowserCouldNotLaunchError(); + case error instanceof errors.PdfWorkerOutOfMemoryError: + return new PdfWorkerOutOfMemoryError(); } return new UnknownError(); } diff --git a/x-pack/plugins/reporting/common/types/index.ts b/x-pack/plugins/reporting/common/types/index.ts index b9ffe09b5fe5cd..082d66234965f3 100644 --- a/x-pack/plugins/reporting/common/types/index.ts +++ b/x-pack/plugins/reporting/common/types/index.ts @@ -6,7 +6,7 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { ScreenshotResult } from '../../../screenshotting/server'; +import type { FormattedScreenshotResult } from '../../../screenshotting/server'; import type { BaseParams, BaseParamsV2, BasePayload, BasePayloadV2, JobId } from './base'; export type { JobParamsPNGDeprecated } from './export_types/png'; @@ -35,7 +35,7 @@ export interface ReportOutput extends TaskRunResult { size: number; } -type ScreenshotMetrics = Required['metrics']; +type ScreenshotMetrics = Required['metrics']; export interface CsvMetrics { rows: number; diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index ee50b99e5b3b7e..702270383276e7 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -24,7 +24,13 @@ import type { FieldFormatsStart } from 'src/plugins/field_formats/server'; import { KibanaRequest, ServiceStatusLevels } from '../../../../src/core/server'; import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import type { LicensingPluginStart } from '../../licensing/server'; -import type { ScreenshotResult, ScreenshottingStart } from '../../screenshotting/server'; +import type { + ScreenshottingStart, + PngScreenshotOptions as BasePngScreenshotOptions, + PngScreenshotResult, + PdfScreenshotResult, + UrlOrUrlWithContext, +} from '../../screenshotting/server'; import type { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; import { DEFAULT_SPACE_ID } from '../../spaces/common/constants'; import type { SpacesPluginSetup } from '../../spaces/server'; @@ -37,7 +43,7 @@ import { checkLicense, getExportTypesRegistry } from './lib'; import { reportingEventLoggerFactory } from './lib/event_logger/logger'; import type { IReport, ReportingStore } from './lib/store'; import { ExecuteReportTask, MonitorReportsTask, ReportTaskParams } from './lib/tasks'; -import type { ReportingPluginRouter, ScreenshotOptions } from './types'; +import type { ReportingPluginRouter, PngScreenshotOptions, PdfScreenshotOptions } from './types'; export interface ReportingInternalSetup { basePath: Pick; @@ -357,13 +363,16 @@ export class ReportingCore { return startDeps.esClient; } - public getScreenshots(options: ScreenshotOptions): Rx.Observable { + public getScreenshots(options: PdfScreenshotOptions): Rx.Observable; + public getScreenshots(options: PngScreenshotOptions): Rx.Observable; + public getScreenshots( + options: PngScreenshotOptions | PdfScreenshotOptions + ): Rx.Observable { return Rx.defer(() => this.getPluginStartDeps()).pipe( switchMap(({ screenshotting }) => { const config = this.getConfig(); return screenshotting.getScreenshots({ ...options, - timeouts: { loadDelay: durationToNumber(config.get('capture', 'loadDelay')), openUrl: durationToNumber(config.get('capture', 'timeouts', 'openUrl')), @@ -380,8 +389,8 @@ export class ReportingCore { typeof url === 'string' ? url : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }] - ), - }); + ) as UrlOrUrlWithContext[], + } as BasePngScreenshotOptions); }) ); } diff --git a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts index 272d1c287178ad..dea75594addbc6 100644 --- a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts +++ b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts @@ -13,7 +13,7 @@ import type { ReportingCore } from '../../'; import { LayoutTypes } from '../../../../screenshotting/common'; import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; import type { PngMetrics } from '../../../common/types'; -import type { ScreenshotOptions } from '../../types'; +import type { PngScreenshotOptions } from '../../types'; interface PngResult { buffer: Buffer; @@ -24,7 +24,7 @@ interface PngResult { export function generatePngObservable( reporting: ReportingCore, logger: Logger, - options: ScreenshotOptions + options: Omit ): Rx.Observable { const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE); const apmLayout = apmTrans?.startSpan('create-layout', 'setup'); @@ -41,7 +41,7 @@ export function generatePngObservable( const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); let apmBuffer: typeof apm.currentSpan; - return reporting.getScreenshots({ ...options, layout }).pipe( + return reporting.getScreenshots({ ...options, layout, format: 'png' }).pipe( tap(({ metrics }) => { if (metrics) { apmTrans?.setLabel('cpu', metrics.cpu, false); diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf_tracker.ts b/x-pack/plugins/reporting/server/export_types/common/pdf_tracker.ts new file mode 100644 index 00000000000000..4daba9d40a32d5 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/pdf_tracker.ts @@ -0,0 +1,47 @@ +/* + * 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 apm from 'elastic-apm-node'; + +interface PdfTracker { + setCpuUsage: (cpu: number) => void; + setMemoryUsage: (memory: number) => void; + startScreenshots: () => void; + endScreenshots: () => void; + end: () => void; +} + +const TRANSACTION_TYPE = 'reporting'; +const SPANTYPE_SETUP = 'setup'; + +interface ApmSpan { + end: () => void; +} + +export function getTracker(): PdfTracker { + const apmTrans = apm.startTransaction('generate-pdf', TRANSACTION_TYPE); + + let apmScreenshots: ApmSpan | null = null; + + return { + startScreenshots() { + apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null; + }, + endScreenshots() { + if (apmScreenshots) apmScreenshots.end(); + }, + setCpuUsage(cpu: number) { + apmTrans?.setLabel('cpu', cpu, false); + }, + setMemoryUsage(memory: number) { + apmTrans?.setLabel('memory', memory, false); + }, + end() { + if (apmTrans) apmTrans.end(); + }, + }; +} diff --git a/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts b/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts index 9c329db64fa1a7..aaffd6254cceb8 100644 --- a/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts +++ b/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts @@ -6,7 +6,7 @@ */ import { format } from 'url'; -import { ReportingConfig } from '../../..'; +import { ReportingConfig } from '../../../'; import { getRedirectAppPath } from '../../../../common/constants'; import { buildKibanaPath } from '../../../../common/build_kibana_path'; diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 52023e53b80b56..4e5d6d519c10ec 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -10,7 +10,7 @@ import * as Rx from 'rxjs'; import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; import { TaskRunResult } from '../../../lib/tasks'; -import { RunTaskFn, RunTaskFnFactory } from '../../../types'; +import { PngScreenshotOptions, RunTaskFn, RunTaskFnFactory } from '../../../types'; import { decryptJobHeaders, getFullUrls, generatePngObservable } from '../../common'; import { TaskPayloadPNG } from '../types'; @@ -37,7 +37,12 @@ export const runTaskFnFactory: RunTaskFnFactory> = headers, urls: [url], browserTimezone: job.browserTimezone, - layout: job.layout, + layout: { + ...job.layout, + // TODO: We do not do a runtime check for supported layout id types for now. But technically + // we should. + id: job.layout?.id as PngScreenshotOptions['layout']['id'], + }, }); }), tap(({ buffer }) => stream.write(buffer)), diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts index 5df7a497adf6c7..a2e220b9e55ddc 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts @@ -10,7 +10,7 @@ import * as Rx from 'rxjs'; import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; import { TaskRunResult } from '../../lib/tasks'; -import { RunTaskFn, RunTaskFnFactory } from '../../types'; +import { PngScreenshotOptions, RunTaskFn, RunTaskFnFactory } from '../../types'; import { decryptJobHeaders, generatePngObservable } from '../common'; import { getFullRedirectAppUrl } from '../common/v2/get_full_redirect_app_url'; import { TaskPayloadPNGV2 } from './types'; @@ -38,7 +38,12 @@ export const runTaskFnFactory: RunTaskFnFactory> = return generatePngObservable(reporting, jobLogger, { headers, browserTimezone: job.browserTimezone, - layout: job.layout, + layout: { + ...job.layout, + // TODO: We do not do a runtime check for supported layout id types for now. But technically + // we should. + id: job.layout?.id as PngScreenshotOptions['layout']['id'], + }, urls: [[url, locatorParams]], }); }), diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts index 7faa13486b5a18..6741b6928e33a6 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts @@ -74,10 +74,7 @@ test(`passes browserTimezone to generatePdf`, async () => { expect(generatePdfObservable).toHaveBeenCalledWith( expect.anything(), - expect.anything(), - expect.anything(), - expect.objectContaining({ browserTimezone: 'UTC' }), - undefined + expect.objectContaining({ browserTimezone: 'UTC' }) ); }); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index 9b4db48ed66970..c14b2f864ada99 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -10,7 +10,7 @@ import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; import { TaskRunResult } from '../../../lib/tasks'; -import { RunTaskFn, RunTaskFnFactory } from '../../../types'; +import { PdfScreenshotOptions, RunTaskFn, RunTaskFnFactory } from '../../../types'; import { decryptJobHeaders, getFullUrls, getCustomLogo } from '../../common'; import { generatePdfObservable } from '../lib/generate_pdf'; import { TaskPayloadPDF } from '../types'; @@ -36,18 +36,20 @@ export const runTaskFnFactory: RunTaskFnFactory> = apmGetAssets?.end(); apmGeneratePdf = apmTrans?.startSpan('generate-pdf-pipeline', 'execute'); - return generatePdfObservable( - reporting, - jobLogger, + return generatePdfObservable(reporting, { + format: 'pdf', title, - { - urls, - browserTimezone, - headers, - layout, + logo, + urls, + browserTimezone, + headers, + layout: { + ...layout, + // TODO: We do not do a runtime check for supported layout id types for now. But technically + // we should. + id: layout?.id as PdfScreenshotOptions['layout']['id'], }, - logo - ); + }); }), tap(({ buffer }) => { apmGeneratePdf?.end(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts index ff0ef2cf39af4b..604a39ac2e228f 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts @@ -5,26 +5,12 @@ * 2.0. */ -import type { Logger } from 'kibana/server'; -import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap, tap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; -import { ScreenshotResult } from '../../../../../screenshotting/server'; +import { PdfScreenshotOptions } from '../../../types'; import type { PdfMetrics } from '../../../../common/types'; -import { ScreenshotOptions } from '../../../types'; -import { PdfMaker } from '../../common/pdf'; -import { getTracker } from './tracker'; - -const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { - const grouped = groupBy(urlScreenshots.map(({ timeRange }) => timeRange)); - const values = Object.values(grouped); - if (values.length === 1) { - return values[0][0]; - } - - return null; -}; +import { getTracker } from '../../common/pdf_tracker'; interface PdfResult { buffer: Uint8Array | null; @@ -34,10 +20,7 @@ interface PdfResult { export function generatePdfObservable( reporting: ReportingCore, - logger: Logger, - title: string, - options: ScreenshotOptions, - logo?: string + options: PdfScreenshotOptions ): Rx.Observable { const tracker = getTracker(); tracker.startScreenshots(); @@ -48,65 +31,25 @@ export function generatePdfObservable( tracker.setCpuUsage(metrics.cpu); tracker.setMemoryUsage(metrics.memory); } - tracker.endScreenshots(); - tracker.startSetup(); }), - mergeMap(async ({ layout, metrics, results }) => { - const pdfOutput = new PdfMaker(layout, logo); - if (title) { - const timeRange = getTimeRange(results); - title += timeRange ? ` - ${timeRange}` : ''; - pdfOutput.setTitle(title); + mergeMap(async ({ metrics, result }) => { + tracker.endScreenshots(); + const warnings: string[] = []; + if (result.errors) { + warnings.push(...result.errors.map((error) => error.message)); } - tracker.endSetup(); - - results.forEach((r) => { - r.screenshots.forEach((screenshot) => { - logger.debug(`Adding image to PDF. Image size: ${screenshot.data.byteLength}`); // prettier-ignore - tracker.startAddImage(); - tracker.endAddImage(); - pdfOutput.addImage(screenshot.data, { - title: screenshot.title ?? undefined, - description: screenshot.description ?? undefined, - }); - }); - }); - - const warnings = results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, []); - - let buffer: Uint8Array | null = null; - try { - tracker.startCompile(); - logger.info(`Compiling PDF using "${layout.id}" layout...`); - buffer = await pdfOutput.generate(); - tracker.endCompile(); - - logger.debug(`Generating PDF Buffer...`); - - const byteLength = buffer?.byteLength ?? 0; - logger.debug(`PDF buffer byte length: ${byteLength}`); - tracker.setByteLength(byteLength); - } catch (err) { - logger.error(`Could not generate the PDF buffer!`); - throw err; + if (result.renderErrors) { + warnings.push(...result.renderErrors); } tracker.end(); return { - buffer, + buffer: result.data, warnings, metrics: { ...metrics, - pages: pdfOutput.getPageCount(), + pages: metrics.pageCount, }, }; }) diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts deleted file mode 100644 index 62094be45ef351..00000000000000 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 apm from 'elastic-apm-node'; -import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; - -interface PdfTracker { - setByteLength: (byteLength: number) => void; - setCpuUsage: (cpu: number) => void; - setMemoryUsage: (memory: number) => void; - startScreenshots: () => void; - endScreenshots: () => void; - startSetup: () => void; - endSetup: () => void; - startAddImage: () => void; - endAddImage: () => void; - startCompile: () => void; - endCompile: () => void; - end: () => void; -} - -const SPANTYPE_SETUP = 'setup'; -const SPANTYPE_OUTPUT = 'output'; - -interface ApmSpan { - end: () => void; -} - -export function getTracker(): PdfTracker { - const apmTrans = apm.startTransaction('generate-pdf', REPORTING_TRANSACTION_TYPE); - - let apmScreenshots: ApmSpan | null = null; - let apmSetup: ApmSpan | null = null; - let apmAddImage: ApmSpan | null = null; - let apmCompilePdf: ApmSpan | null = null; - - return { - startScreenshots() { - apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null; - }, - endScreenshots() { - if (apmScreenshots) apmScreenshots.end(); - }, - startSetup() { - apmSetup = apmTrans?.startSpan('setup-pdf', SPANTYPE_SETUP) || null; - }, - endSetup() { - if (apmSetup) apmSetup.end(); - }, - startAddImage() { - apmAddImage = apmTrans?.startSpan('add-pdf-image', SPANTYPE_OUTPUT) || null; - }, - endAddImage() { - if (apmAddImage) apmAddImage.end(); - }, - startCompile() { - apmCompilePdf = apmTrans?.startSpan('compile-pdf', SPANTYPE_OUTPUT) || null; - }, - endCompile() { - if (apmCompilePdf) apmCompilePdf.end(); - }, - setByteLength(byteLength: number) { - apmTrans?.setLabel('byte-length', byteLength, false); - }, - setCpuUsage(cpu: number) { - apmTrans?.setLabel('cpu', cpu, false); - }, - setMemoryUsage(memory: number) { - apmTrans?.setLabel('memory', memory, false); - }, - end() { - if (apmTrans) apmTrans.end(); - }, - }; -} diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts index efad71a64a81d6..205c55ab4e9960 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts @@ -82,10 +82,7 @@ test(`passes browserTimezone to generatePdf`, async () => { expect.anything(), expect.anything(), expect.anything(), - expect.anything(), - expect.anything(), - expect.objectContaining({ browserTimezone: 'UTC' }), - undefined + expect.objectContaining({ browserTimezone: 'UTC' }) ); }); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts index 7f887707829cb1..acdde21d257165 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts @@ -9,6 +9,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; +import type { PdfScreenshotOptions } from '../../types'; import { TaskRunResult } from '../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../types'; import { decryptJobHeaders, getCustomLogo } from '../common'; @@ -34,19 +35,19 @@ export const runTaskFnFactory: RunTaskFnFactory> = apmGetAssets?.end(); apmGeneratePdf = apmTrans?.startSpan('generate-pdf-pipeline', 'execute'); - return generatePdfObservable( - reporting, - jobLogger, - job, + return generatePdfObservable(reporting, job, locatorParams, { + format: 'pdf', title, - locatorParams, - { - browserTimezone, - headers, - layout, + logo, + browserTimezone, + headers, + layout: { + ...layout, + // TODO: We do not do a runtime check for supported layout id types for now. But technically + // we should. + id: layout?.id as PdfScreenshotOptions['layout']['id'], }, - logo - ); + }); }), tap(({ buffer }) => { apmGeneratePdf?.end(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts index 8bec3cac28f430..ddc215b5d96f20 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts @@ -5,28 +5,14 @@ * 2.0. */ -import type { Logger } from 'kibana/server'; -import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap, tap } from 'rxjs/operators'; import type { ReportingCore } from '../../../'; -import type { ScreenshotResult } from '../../../../../screenshotting/server'; import type { LocatorParams, PdfMetrics, UrlOrUrlLocatorTuple } from '../../../../common/types'; -import type { ScreenshotOptions } from '../../../types'; -import { PdfMaker } from '../../common/pdf'; +import type { PdfScreenshotOptions } from '../../../types'; import { getFullRedirectAppUrl } from '../../common/v2/get_full_redirect_app_url'; +import { getTracker } from '../../common/pdf_tracker'; import type { TaskPayloadPDFV2 } from '../types'; -import { getTracker } from './tracker'; - -const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { - const grouped = groupBy(urlScreenshots.map((u) => u.timeRange)); - const values = Object.values(grouped); - if (values.length === 1) { - return values[0][0]; - } - - return null; -}; interface PdfResult { buffer: Uint8Array | null; @@ -36,12 +22,9 @@ interface PdfResult { export function generatePdfObservable( reporting: ReportingCore, - logger: Logger, job: TaskPayloadPDFV2, - title: string, locatorParams: LocatorParams[], - options: Omit, - logo?: string + options: Omit ): Rx.Observable { const tracker = getTracker(); tracker.startScreenshots(); @@ -53,70 +36,29 @@ export function generatePdfObservable( getFullRedirectAppUrl(reporting.getConfig(), job.spaceId, job.forceNow), locator, ]) as UrlOrUrlLocatorTuple[]; - const screenshots$ = reporting.getScreenshots({ ...options, urls }).pipe( tap(({ metrics }) => { if (metrics) { tracker.setCpuUsage(metrics.cpu); tracker.setMemoryUsage(metrics.memory); } - tracker.endScreenshots(); - tracker.startSetup(); }), - mergeMap(async ({ layout, metrics, results }) => { - const pdfOutput = new PdfMaker(layout, logo); - if (title) { - const timeRange = getTimeRange(results); - title += timeRange ? ` - ${timeRange}` : ''; - pdfOutput.setTitle(title); + mergeMap(async ({ metrics, result }) => { + tracker.endScreenshots(); + const warnings: string[] = []; + if (result.errors) { + warnings.push(...result.errors.map((error) => error.message)); } - tracker.endSetup(); - - results.forEach((r) => { - r.screenshots.forEach((screenshot) => { - logger.debug(`Adding image to PDF. Image base64 size: ${screenshot.data.byteLength}`); // prettier-ignore - tracker.startAddImage(); - tracker.endAddImage(); - pdfOutput.addImage(screenshot.data, { - title: screenshot.title ?? undefined, - description: screenshot.description ?? undefined, - }); - }); - }); - - const warnings = results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, []); - - let buffer: Uint8Array | null = null; - try { - tracker.startCompile(); - logger.info(`Compiling PDF using "${layout.id}" layout...`); - buffer = await pdfOutput.generate(); - tracker.endCompile(); - - const byteLength = buffer?.byteLength ?? 0; - logger.debug(`PDF buffer byte length: ${byteLength}`); - tracker.setByteLength(byteLength); - - tracker.end(); - } catch (err) { - logger.error(`Could not generate the PDF buffer!`); - throw err; + if (result.renderErrors) { + warnings.push(...result.renderErrors); } return { - buffer, + buffer: result.data, warnings, metrics: { ...metrics, - pages: pdfOutput.getPageCount(), + pages: metrics.pageCount, }, }; }) diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts deleted file mode 100644 index 62094be45ef351..00000000000000 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 apm from 'elastic-apm-node'; -import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; - -interface PdfTracker { - setByteLength: (byteLength: number) => void; - setCpuUsage: (cpu: number) => void; - setMemoryUsage: (memory: number) => void; - startScreenshots: () => void; - endScreenshots: () => void; - startSetup: () => void; - endSetup: () => void; - startAddImage: () => void; - endAddImage: () => void; - startCompile: () => void; - endCompile: () => void; - end: () => void; -} - -const SPANTYPE_SETUP = 'setup'; -const SPANTYPE_OUTPUT = 'output'; - -interface ApmSpan { - end: () => void; -} - -export function getTracker(): PdfTracker { - const apmTrans = apm.startTransaction('generate-pdf', REPORTING_TRANSACTION_TYPE); - - let apmScreenshots: ApmSpan | null = null; - let apmSetup: ApmSpan | null = null; - let apmAddImage: ApmSpan | null = null; - let apmCompilePdf: ApmSpan | null = null; - - return { - startScreenshots() { - apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null; - }, - endScreenshots() { - if (apmScreenshots) apmScreenshots.end(); - }, - startSetup() { - apmSetup = apmTrans?.startSpan('setup-pdf', SPANTYPE_SETUP) || null; - }, - endSetup() { - if (apmSetup) apmSetup.end(); - }, - startAddImage() { - apmAddImage = apmTrans?.startSpan('add-pdf-image', SPANTYPE_OUTPUT) || null; - }, - endAddImage() { - if (apmAddImage) apmAddImage.end(); - }, - startCompile() { - apmCompilePdf = apmTrans?.startSpan('compile-pdf', SPANTYPE_OUTPUT) || null; - }, - endCompile() { - if (apmCompilePdf) apmCompilePdf.end(); - }, - setByteLength(byteLength: number) { - apmTrans?.setLabel('byte-length', byteLength, false); - }, - setCpuUsage(cpu: number) { - apmTrans?.setLabel('cpu', cpu, false); - }, - setMemoryUsage(memory: number) { - apmTrans?.setLabel('memory', memory, false); - }, - end() { - if (apmTrans) apmTrans.end(); - }, - }; -} diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index b3c9261bfd9244..5ff13a72ea83db 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -15,7 +15,8 @@ import type { Writable } from 'stream'; import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import type { LicensingPluginStart } from '../../licensing/server'; import type { - ScreenshotOptions as BaseScreenshotOptions, + PngScreenshotOptions as BasePngScreenshotOptions, + PdfScreenshotOptions as BasePdfScreenshotOptions, ScreenshottingStart, } from '../../screenshotting/server'; import type { @@ -117,7 +118,11 @@ export interface ReportingRequestHandlerContext { export type ReportingPluginRouter = IRouter; -export interface ScreenshotOptions extends Omit { +export interface PdfScreenshotOptions extends Omit { + urls: UrlOrUrlLocatorTuple[]; +} + +export interface PngScreenshotOptions extends Omit { urls: UrlOrUrlLocatorTuple[]; } diff --git a/x-pack/plugins/screenshotting/common/errors.ts b/x-pack/plugins/screenshotting/common/errors.ts index 35082c7cc40716..50effb03ee1dad 100644 --- a/x-pack/plugins/screenshotting/common/errors.ts +++ b/x-pack/plugins/screenshotting/common/errors.ts @@ -6,6 +6,7 @@ */ /* eslint-disable max-classes-per-file */ +export class PdfWorkerOutOfMemoryError extends Error {} export class FailedToSpawnBrowserError extends Error {} diff --git a/x-pack/plugins/screenshotting/common/index.ts b/x-pack/plugins/screenshotting/common/index.ts index d514eb507bb705..2218832000c2dc 100644 --- a/x-pack/plugins/screenshotting/common/index.ts +++ b/x-pack/plugins/screenshotting/common/index.ts @@ -7,6 +7,5 @@ export type { LayoutParams } from './layout'; export { LayoutTypes } from './layout'; - import * as errors from './errors'; export { errors }; diff --git a/x-pack/plugins/screenshotting/common/layout.ts b/x-pack/plugins/screenshotting/common/layout.ts index aade05eeea04ed..b53f155a5dbdd4 100644 --- a/x-pack/plugins/screenshotting/common/layout.ts +++ b/x-pack/plugins/screenshotting/common/layout.ts @@ -40,12 +40,12 @@ export interface LayoutSelectorDictionary { /** * Screenshot layout parameters. */ -export type LayoutParams = Ensure< +export type LayoutParams = Ensure< { /** * Unique layout name. */ - id?: string; + id?: ID; /** * Layout sizing. @@ -69,7 +69,7 @@ export type LayoutParams = Ensure< * Supported layout types. */ export const LayoutTypes = { - PRESERVE_LAYOUT: 'preserve_layout', - PRINT: 'print', - CANVAS: 'canvas', // no margins or branding in the layout + PRESERVE_LAYOUT: 'preserve_layout' as const, + PRINT: 'print' as const, + CANVAS: 'canvas' as const, // no margins or branding in the layout }; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts b/x-pack/plugins/screenshotting/jest.integration.config.js similarity index 64% rename from x-pack/plugins/reporting/server/export_types/common/pdf/index.ts rename to x-pack/plugins/screenshotting/jest.integration.config.js index e4a1680a958dd2..45a65c93c6af37 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts +++ b/x-pack/plugins/screenshotting/jest.integration.config.js @@ -5,4 +5,8 @@ * 2.0. */ -export { PdfMaker } from './pdfmaker'; +module.exports = { + preset: '@kbn/test/jest_integration', + rootDir: '../../..', + roots: ['/x-pack/plugins/screenshotting'], +}; diff --git a/x-pack/plugins/screenshotting/server/formats/index.ts b/x-pack/plugins/screenshotting/server/formats/index.ts new file mode 100644 index 00000000000000..baa4087be550ea --- /dev/null +++ b/x-pack/plugins/screenshotting/server/formats/index.ts @@ -0,0 +1,12 @@ +/* + * 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 type { PdfLayoutParams, PdfScreenshotOptions, PdfScreenshotResult } from './pdf'; +export { toPdf } from './pdf'; +export type { PngLayoutParams, PngScreenshotOptions, PngScreenshotResult } from './png'; +export { toPng } from './png'; +export type { FormattedScreenshotResult } from './types'; diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/index.ts b/x-pack/plugins/screenshotting/server/formats/pdf/index.ts new file mode 100644 index 00000000000000..32df9d9b66b5ba --- /dev/null +++ b/x-pack/plugins/screenshotting/server/formats/pdf/index.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { groupBy } from 'lodash'; +import type { Logger } from 'src/core/server'; +import { LayoutParams, LayoutTypes } from '../../../common'; +import { ScreenshotResult, ScreenshotOptions } from '../../screenshots'; +import { FormattedScreenshotResult } from '../types'; +import { pngsToPdf } from './pdf_maker'; + +const supportedLayouts = [LayoutTypes.PRESERVE_LAYOUT, LayoutTypes.CANVAS, LayoutTypes.PRINT]; + +/** + * PDFs can be a single, long page or they can be multiple pages. For example: + * + * => When creating a PDF intended for print multiple PNGs will be spread out across pages + * => When creating a PDF from a Canvas workpad, each page in the workpad will be placed on a separate page + */ +export type PdfLayoutParams = LayoutParams; + +/** + * Options that should be provided to a PDF screenshot request. + */ +export interface PdfScreenshotOptions extends ScreenshotOptions<'pdf'> { + title?: string; + logo?: string; + /** + * We default to the "print" layout if no ID is specified for the layout + */ + layout: PdfLayoutParams; +} + +/** + * Final, formatted PDF result + */ +export interface PdfScreenshotResult extends Omit { + metrics: FormattedScreenshotResult['metrics'] & { pageCount: number }; + result: { + data: Buffer; + /** + * Any errors that were encountered while create the PDF and navigating between pages + */ + errors: Error[]; + /** + * Any render errors that could mean some visualizations are missing from the end result. + */ + renderErrors: string[]; + }; +} + +interface ScreenshotsResultsToPdfArgs { + logger: Logger; + title?: string; + logo?: string; +} + +const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { + const grouped = groupBy(urlScreenshots.map((u) => u.timeRange)); + const values = Object.values(grouped); + if (values.length === 1) { + return values[0][0]; + } + + return null; +}; + +export function toPdf({ title, logo, logger }: ScreenshotsResultsToPdfArgs) { + return async (screenshotResult: ScreenshotResult): Promise => { + const timeRange = getTimeRange(screenshotResult.results); + try { + const { buffer, pageCount } = await pngsToPdf({ + results: screenshotResult.results, + title: title ? title + (timeRange ? ` - ${timeRange}` : '') : undefined, + logo, + layout: screenshotResult.layout, + logger, + }); + + return { + ...screenshotResult, + metrics: { + pageCount, + ...screenshotResult.metrics!, + }, + result: { + data: buffer, + errors: screenshotResult.results.flatMap((result) => + result.error ? [result.error] : [] + ), + renderErrors: screenshotResult.results.flatMap((result) => + result.renderErrors ? [...result.renderErrors] : [] + ), + }, + }; + } catch (e) { + logger.error(`Could not generate the PDF buffer!`); + throw e; + } + }; +} diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/README.md b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/README.md similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/README.md rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/README.md diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/LICENSE_OFL.txt b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/LICENSE_OFL.txt similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/LICENSE_OFL.txt rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/LICENSE_OFL.txt diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/NotoSansCJKtc-Medium.ttf b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Medium.ttf similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/NotoSansCJKtc-Medium.ttf rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Medium.ttf diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/NotoSansCJKtc-Regular.ttf b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Regular.ttf similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/NotoSansCJKtc-Regular.ttf rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/NotoSansCJKtc-Regular.ttf diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/index.js b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/index.js similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/noto/index.js rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/noto/index.js diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/LICENSE.txt b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/LICENSE.txt similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/LICENSE.txt rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/LICENSE.txt diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Italic.ttf b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Italic.ttf similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Italic.ttf rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Italic.ttf diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Medium.ttf b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Medium.ttf similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Medium.ttf rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Medium.ttf diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Regular.ttf b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Regular.ttf similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/fonts/roboto/Roboto-Regular.ttf rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/fonts/roboto/Roboto-Regular.ttf diff --git a/x-pack/plugins/reporting/server/export_types/common/assets/img/logo-grey.png b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/img/logo-grey.png similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/assets/img/logo-grey.png rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/assets/img/logo-grey.png diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/constants.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/constants.ts similarity index 91% rename from x-pack/plugins/reporting/server/export_types/common/pdf/constants.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/constants.ts index 7e20d1d8b45d5f..3e44a53a7f3c09 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/constants.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/constants.ts @@ -7,7 +7,7 @@ import path from 'path'; -export const assetPath = path.resolve(__dirname, '..', '..', 'common', 'assets'); +export const assetPath = path.resolve(__dirname, 'assets'); export const tableBorderWidth = 1; export const pageMarginTop = 40; export const pageMarginBottom = 80; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_doc_options.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_doc_options.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/get_doc_options.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_doc_options.ts diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_font.test.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_font.test.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/get_font.test.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_font.test.ts diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_font.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_font.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/get_font.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_font.ts diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_template.ts similarity index 84% rename from x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_template.ts index 80c5238411379e..8be888c8aef905 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/get_template.ts @@ -77,10 +77,13 @@ export function getTemplate( }, { alignment: 'center', - text: i18n.translate('xpack.reporting.exportTypes.printablePdf.pagingDescription', { - defaultMessage: 'Page {currentPage} of {pageCount}', - values: { currentPage: currentPage.toString(), pageCount }, - }), + text: i18n.translate( + 'xpack.screenshotting.exportTypes.printablePdf.pagingDescription', + { + defaultMessage: 'Page {currentPage} of {pageCount}', + values: { currentPage: currentPage.toString(), pageCount }, + } + ), style: { color: '#aaa', }, @@ -90,9 +93,12 @@ export function getTemplate( [ logo ? { - text: i18n.translate('xpack.reporting.exportTypes.printablePdf.logoDescription', { - defaultMessage: 'Powered by Elastic', - }), + text: i18n.translate( + 'xpack.screenshotting.exportTypes.printablePdf.logoDescription', + { + defaultMessage: 'Powered by Elastic', + } + ), fontSize: 10, style: { color: '#aaa', diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/index.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/index.ts new file mode 100644 index 00000000000000..79b0a8c5504b86 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/index.ts @@ -0,0 +1,61 @@ +/* + * 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 type { Logger } from 'src/core/server'; +import { PdfMaker } from './pdfmaker'; +import type { Layout } from '../../../layouts'; +import { getTracker } from './tracker'; +import { ScreenshotResult } from '../../../screenshots'; + +interface PngsToPdfArgs { + results: ScreenshotResult['results']; + layout: Layout; + logger: Logger; + logo?: string; + title?: string; +} + +export async function pngsToPdf({ + results, + layout, + logo, + title, + logger, +}: PngsToPdfArgs): Promise<{ buffer: Buffer; pageCount: number }> { + const pdfMaker = new PdfMaker(layout, logo, logger); + const tracker = getTracker(); + if (title) { + pdfMaker.setTitle(title); + } + results.forEach((result) => { + result.screenshots.forEach((png) => { + tracker.startAddImage(); + pdfMaker.addImage(png.data, { + title: png.title ?? undefined, + description: png.description ?? undefined, + }); + tracker.endAddImage(); + }); + }); + + let buffer: Uint8Array | null = null; + try { + tracker.startCompile(); + buffer = await pdfMaker.generate(); + tracker.endCompile(); + + const byteLength = buffer?.byteLength ?? 0; + logger.debug(`PDF buffer byte length: ${byteLength}`); + tracker.setByteLength(byteLength); + } catch (err) { + throw err; + } finally { + tracker.end(); + } + + return { buffer: Buffer.from(buffer.buffer), pageCount: pdfMaker.getPageCount() }; +} diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/buggy_worker.js b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/buggy_worker.js similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/buggy_worker.js rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/buggy_worker.js diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/memory_leak_worker.js b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/memory_leak_worker.js similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/memory_leak_worker.js rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/memory_leak_worker.js diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts similarity index 87% rename from x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts index 5302ba07f60020..0e9ed014d6ea63 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/integration_tests/pdfmaker.test.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts @@ -8,10 +8,11 @@ /* eslint-disable max-classes-per-file */ import path from 'path'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { isUint8Array } from 'util/types'; -import { createMockLayout } from '../../../../../../screenshotting/server/layouts/mock'; -import { PdfWorkerOutOfMemoryError } from '../../../../../common/errors'; -import { PdfMaker } from '../'; +import { createMockLayout } from '../../../../layouts/mock'; +import { errors } from '../../../../../common'; +import { PdfMaker } from '../pdfmaker'; const imageBase64 = Buffer.from( `iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAGFBMVEXy8vJpaWn7+/vY2Nj39/cAAACcnJzx8fFvt0oZAAAAi0lEQVR4nO3SSQoDIBBFwR7U3P/GQXKEIIJULXr9H3TMrHhX5Yysvj3jjM8+XRnVa9wec8QuHKv3h74Z+PNyGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xu3Bxy026rXu4ljdUVW395xUFfGzLo946DK+QW+bgCTFcecSAAAAABJRU5ErkJggg==`, @@ -21,10 +22,12 @@ const imageBase64 = Buffer.from( describe.skip('PdfMaker', () => { let layout: ReturnType; let pdf: PdfMaker; + let logger: ReturnType; beforeEach(() => { layout = createMockLayout(); - pdf = new PdfMaker(layout, undefined); + logger = loggingSystemMock.createLogger(); + pdf = new PdfMaker(layout, undefined, logger); }); describe('generate', () => { @@ -53,14 +56,14 @@ describe.skip('PdfMaker', () => { protected workerMaxOldHeapSizeMb = 2; protected workerMaxYoungHeapSizeMb = 2; protected workerModulePath = path.resolve(__dirname, './memory_leak_worker.js'); - })(layout, undefined); - await expect(leakyMaker.generate()).rejects.toBeInstanceOf(PdfWorkerOutOfMemoryError); + })(layout, undefined, logger); + await expect(leakyMaker.generate()).rejects.toBeInstanceOf(errors.PdfWorkerOutOfMemoryError); }); it.skip('restarts the PDF worker if it crashes', async () => { const buggyMaker = new (class BuggyPdfMaker extends PdfMaker { protected workerModulePath = path.resolve(__dirname, './buggy_worker.js'); - })(layout, undefined); + })(layout, undefined, logger); await expect(buggyMaker.generate()).rejects.toEqual(new Error('This is a bug')); await expect(buggyMaker.generate()).rejects.toEqual(new Error('This is a bug')); diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts similarity index 81% rename from x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts index 6685cf692ef59a..82f6977ba7c85a 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/pdfmaker.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts @@ -5,12 +5,13 @@ * 2.0. */ +import type { Logger } from 'src/core/server'; import { SerializableRecord } from '@kbn/utility-types'; import path from 'path'; import { Content, ContentImage, ContentText } from 'pdfmake/interfaces'; import { MessageChannel, MessagePort, Worker } from 'worker_threads'; -import type { Layout } from '../../../../../screenshotting/server'; -import { PdfWorkerOutOfMemoryError } from '../../../../common/errors'; +import type { Layout } from '../../../layouts'; +import { errors } from '../../../../common'; import { headingHeight, pageMarginBottom, @@ -27,10 +28,8 @@ import type { GeneratePdfRequest, GeneratePdfResponse, WorkerData } from './work import './worker_dependencies'; export class PdfMaker { - _layout: Layout; - private _logo: string | undefined; - private _title: string; - private _content: Content[]; + private title: string; + private content: Content[]; private worker?: Worker; private pageCount: number = 0; @@ -63,18 +62,20 @@ export class PdfMaker { */ protected workerMaxYoungHeapSizeMb: number | undefined = undefined; - constructor(layout: Layout, logo: string | undefined) { - this._layout = layout; - this._logo = logo; - this._title = ''; - this._content = []; + constructor( + private readonly layout: Layout, + private readonly logo: string | undefined, + private readonly logger: Logger + ) { + this.title = ''; + this.content = []; } _addContents(contents: Content[]) { - const groupCount = this._content.length; + const groupCount = this.content.length; // inject a page break for every 2 groups on the page - if (groupCount > 0 && groupCount % this._layout.groupCount === 0) { + if (groupCount > 0 && groupCount % this.layout.groupCount === 0) { contents = [ { text: '', @@ -82,7 +83,7 @@ export class PdfMaker { } as ContentText as Content, ].concat(contents); } - this._content.push(contents); + this.content.push(contents); } addBrandedImage(img: ContentImage, { title = '', description = '' }) { @@ -122,7 +123,8 @@ export class PdfMaker { image: Buffer, opts: { title?: string; description?: string } = { title: '', description: '' } ) { - const size = this._layout.getPdfImageSize(); + this.logger.debug(`Adding image to PDF. Image size: ${image.byteLength}`); // prettier-ignore + const size = this.layout.getPdfImageSize(); const img = { image: `data:image/png;base64,${image.toString('base64')}`, alignment: 'center' as 'center', @@ -130,7 +132,7 @@ export class PdfMaker { width: size.width, }; - if (this._layout.useReportingBranding) { + if (this.layout.useReportingBranding) { return this.addBrandedImage(img, opts); } @@ -138,17 +140,17 @@ export class PdfMaker { } setTitle(title: string) { - this._title = title; + this.title = title; } private getGeneratePdfRequestData(): GeneratePdfRequest['data'] { return { layout: { - hasFooter: this._layout.hasFooter, - hasHeader: this._layout.hasHeader, - orientation: this._layout.getPdfPageOrientation(), - useReportingBranding: this._layout.useReportingBranding, - pageSize: this._layout.getPdfPageSize({ + hasHeader: this.layout.hasHeader, + hasFooter: this.layout.hasFooter, + orientation: this.layout.getPdfPageOrientation(), + useReportingBranding: this.layout.useReportingBranding, + pageSize: this.layout.getPdfPageSize({ pageMarginTop, pageMarginBottom, pageMarginWidth, @@ -157,9 +159,9 @@ export class PdfMaker { subheadingHeight, }), }, - title: this._title, - logo: this._logo, - content: this._content as unknown as SerializableRecord[], + title: this.title, + logo: this.logo, + content: this.content as unknown as SerializableRecord[], }; } @@ -187,13 +189,15 @@ export class PdfMaker { public async generate(): Promise { if (this.worker) throw new Error('PDF generation already in progress!'); + this.logger.info(`Compiling PDF using "${this.layout.id}" layout...`); + try { return await new Promise((resolve, reject) => { const { port1: myPort, port2: theirPort } = new MessageChannel(); this.worker = this.createWorker(theirPort); this.worker.on('error', (workerError: NodeJS.ErrnoException) => { if (workerError.code === 'ERR_WORKER_OUT_OF_MEMORY') { - reject(new PdfWorkerOutOfMemoryError(workerError.message)); + reject(new errors.PdfWorkerOutOfMemoryError(workerError.message)); } else { reject(workerError); } diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/tracker.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/tracker.ts new file mode 100644 index 00000000000000..49576a03d18a39 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/tracker.ts @@ -0,0 +1,52 @@ +/* + * 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 apm from 'elastic-apm-node'; + +interface PdfTracker { + setByteLength: (byteLength: number) => void; + startAddImage: () => void; + endAddImage: () => void; + startCompile: () => void; + endCompile: () => void; + end: () => void; +} + +const TRANSACTION_TYPE = 'reporting'; // TODO: Find out whether we can rename to "screenshotting"; +const SPANTYPE_OUTPUT = 'output'; + +interface ApmSpan { + end: () => void; +} + +export function getTracker(): PdfTracker { + const apmTrans = apm.startTransaction('generate-pdf', TRANSACTION_TYPE); + + let apmAddImage: ApmSpan | null = null; + let apmCompilePdf: ApmSpan | null = null; + + return { + startAddImage() { + apmAddImage = apmTrans?.startSpan('add-pdf-image', SPANTYPE_OUTPUT) || null; + }, + endAddImage() { + apmAddImage?.end(); + }, + startCompile() { + apmCompilePdf = apmTrans?.startSpan('compile-pdf', SPANTYPE_OUTPUT) || null; + }, + endCompile() { + apmCompilePdf?.end(); + }, + setByteLength(byteLength: number) { + apmTrans?.setLabel('byte-length', byteLength, false); + }, + end() { + apmTrans?.end(); + }, + }; +} diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/types.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/types.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/types.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/types.ts diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/worker.js b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.js similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/worker.js rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.js diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/worker.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/worker.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.ts diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/worker_dependencies.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker_dependencies.ts similarity index 100% rename from x-pack/plugins/reporting/server/export_types/common/pdf/worker_dependencies.ts rename to x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker_dependencies.ts diff --git a/x-pack/plugins/screenshotting/server/formats/png.ts b/x-pack/plugins/screenshotting/server/formats/png.ts new file mode 100644 index 00000000000000..6546faa234cd67 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/formats/png.ts @@ -0,0 +1,38 @@ +/* + * 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 { ScreenshotResult, ScreenshotOptions } from '../screenshots'; +import { LayoutParams, LayoutTypes } from '../../common'; +import { FormattedScreenshotResult } from './types'; + +const supportedLayouts = [LayoutTypes.PRESERVE_LAYOUT]; + +/** + * The layout parameters that are accepted by PNG screenshots + */ +export type PngLayoutParams = LayoutParams; + +/** + * Options that should be provided to a screenshot PNG request + */ +export interface PngScreenshotOptions extends ScreenshotOptions<'png'> { + layout: PngLayoutParams; +} + +/** + * The final output of a PNG screenshot + */ +export interface PngScreenshotResult extends FormattedScreenshotResult { + metadata: undefined; +} + +export async function toPng({ + layout: _, + ...rest +}: ScreenshotResult): Promise { + return { ...rest, metadata: undefined }; +} diff --git a/x-pack/plugins/screenshotting/server/formats/types.ts b/x-pack/plugins/screenshotting/server/formats/types.ts new file mode 100644 index 00000000000000..ec5c1a6ce727c0 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/formats/types.ts @@ -0,0 +1,17 @@ +/* + * 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 type { ScreenshotResult } from '../screenshots'; + +/** + * A general, overridable type of screenshot result + * + * PDF or PNG screenshots should extend this and convert the output to a type + * that best suits their use cases. + * + * This type documents what might appear on any given output type + */ +export type FormattedScreenshotResult = Omit; diff --git a/x-pack/plugins/screenshotting/server/index.ts b/x-pack/plugins/screenshotting/server/index.ts index 340a6688e79ebf..fcb8102ccbae7c 100755 --- a/x-pack/plugins/screenshotting/server/index.ts +++ b/x-pack/plugins/screenshotting/server/index.ts @@ -17,4 +17,11 @@ export function plugin(...args: ConstructorParameters { const driver = createMockBrowserDriverFactory(); @@ -16,6 +20,13 @@ export function createMockScreenshottingStart(): jest.Mocked getScreenshots(options)), + getScreenshots: jest.fn( + (options): ReturnType => + getScreenshots(options).pipe( + mergeMap>( + options.format === 'pdf' ? toPdf({ logger: loggingSystemMock.createLogger() }) : toPng + ) + ) + ), }; } diff --git a/x-pack/plugins/screenshotting/server/plugin.ts b/x-pack/plugins/screenshotting/server/plugin.ts index e0c5d45f67d754..5aa2422a5a96f4 100755 --- a/x-pack/plugins/screenshotting/server/plugin.ts +++ b/x-pack/plugins/screenshotting/server/plugin.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { from } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { from, Observable } from 'rxjs'; +import { switchMap, mergeMap } from 'rxjs/operators'; import type { CoreSetup, CoreStart, @@ -17,8 +17,16 @@ import type { import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; import { ChromiumArchivePaths, HeadlessChromiumDriverFactory, install } from './browsers'; import { ConfigType, createConfig } from './config'; -import { Screenshots } from './screenshots'; +import { ScreenshotResult, Screenshots } from './screenshots'; import { getChromiumPackage } from './utils'; +import { + PngScreenshotOptions, + PdfScreenshotOptions, + PdfScreenshotResult, + PngScreenshotResult, + toPng, + toPdf, +} from './formats'; interface SetupDeps { screenshotMode: ScreenshotModePluginSetup; @@ -33,13 +41,14 @@ export interface ScreenshottingStart { * @returns Observable with output messages. */ diagnose: HeadlessChromiumDriverFactory['diagnose']; - /** * Takes screenshots of multiple pages. * @param options Screenshots session options. * @returns Observable with screenshotting results. */ - getScreenshots: Screenshots['getScreenshots']; + getScreenshots: ( + options: O + ) => Observable; } export class ScreenshottingPlugin implements Plugin { @@ -94,9 +103,14 @@ export class ScreenshottingPlugin implements Plugin from(this.browserDriverFactory).pipe(switchMap((factory) => factory.diagnose())), - getScreenshots: (options) => + getScreenshots: (options): Observable> => from(this.screenshots).pipe( - switchMap((screenshots) => screenshots.getScreenshots(options)) + switchMap((screenshots) => screenshots.getScreenshots(options)), + mergeMap>( + options.format === 'pdf' + ? toPdf({ logger: this.logger, logo: options.logo, title: options.title }) + : toPng + ) ), }; } diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index b129d875f018d7..6ff15d0c6fa0c2 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -86,6 +86,7 @@ describe('Screenshot Observable Pipeline', () => { }, ], "error": undefined, + "renderErrors": undefined, "screenshots": Array [ Object { "data": Object { @@ -147,6 +148,7 @@ describe('Screenshot Observable Pipeline', () => { }, ], "error": undefined, + "renderErrors": undefined, "screenshots": Array [ Object { "data": Object { @@ -198,6 +200,7 @@ describe('Screenshot Observable Pipeline', () => { }, ], "error": undefined, + "renderErrors": undefined, "screenshots": Array [ Object { "data": Object { @@ -228,7 +231,7 @@ describe('Screenshot Observable Pipeline', () => { "timeRange": "Default GetTimeRange Result", }, ] - `); + `); expect(driver.open).toHaveBeenCalledTimes(2); expect(driver.open).nthCalledWith( @@ -282,6 +285,7 @@ describe('Screenshot Observable Pipeline', () => { }, ], "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Mock error!], + "renderErrors": undefined, "screenshots": Array [ Object { "data": Object { @@ -324,6 +328,7 @@ describe('Screenshot Observable Pipeline', () => { }, ], "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Mock error!], + "renderErrors": undefined, "screenshots": Array [ Object { "data": Object { @@ -395,6 +400,7 @@ describe('Screenshot Observable Pipeline', () => { }, ], "error": undefined, + "renderErrors": undefined, "screenshots": Array [ Object { "data": Object { diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.ts b/x-pack/plugins/screenshotting/server/screenshots/index.ts index b056e9a5450ed5..235e034fb777be 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.ts @@ -28,7 +28,17 @@ import { ScreenshotObservableHandler } from './observable'; import type { ScreenshotObservableOptions, ScreenshotObservableResult } from './observable'; import { Semaphore } from './semaphore'; -export interface ScreenshotOptions extends ScreenshotObservableOptions { +export type { UrlOrUrlWithContext } from './observable'; +export type { ScreenshotObservableResult } from './observable'; + +export interface ScreenshotOptions + extends ScreenshotObservableOptions { + /** + * Whether to format the output as a PDF or PNG. Defaults to requiring 'png' + * unless specified otherwise. + */ + format: F; + layout: LayoutParams; /** @@ -70,7 +80,9 @@ export class Screenshots { this.semaphore = new Semaphore(poolSize); } - getScreenshots(options: ScreenshotOptions): Observable { + getScreenshots( + options: ScreenshotOptions + ): Observable { const apmTrans = apm.startTransaction('screenshot-pipeline', 'screenshotting'); const apmCreateLayout = apmTrans?.startSpan('create-layout', 'setup'); const layout = createLayout(options.layout); diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.ts index 1c78d397a74157..fbc147102e0afd 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/observable.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/observable.ts @@ -108,6 +108,7 @@ interface PageSetupResults { elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null; timeRange: string | null; error?: Error; + renderErrors?: string[]; } const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => { @@ -241,7 +242,6 @@ export class ScreenshotObservableHandler { withRenderComplete.pipe( mergeMap(async (data: PageSetupResults): Promise => { this.checkPageIsOpen(); // fail the report job if the browser has closed - const elements = data.elementsPositionAndAttributes ?? getDefaultElementPosition(this.layout.getViewport(1)); @@ -251,12 +251,13 @@ export class ScreenshotObservableHandler { } catch (e) { throw new errors.FailedToCaptureScreenshot(e.message); } - const { timeRange, error: setupError } = data; + const { timeRange, error: setupError, renderErrors } = data; return { timeRange, screenshots, error: setupError, + renderErrors, elementsPositionAndAttributes: elements, }; }) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index db8cbd956e8fd7..2839091d1577c4 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18648,8 +18648,6 @@ "xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "Impossible de déchiffrer les données de la tâche de reporting. Veuillez vous assurer que {encryptionKey} est défini et générez à nouveau ce rapport. {err}", "xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "Les en-têtes de tâche sont manquants", "xpack.reporting.exportTypes.csv.generateCsv.escapedFormulaValues": "Le CSV peut contenir des formules dont les valeurs sont précédées d'un caractère d'échappement", - "xpack.reporting.exportTypes.printablePdf.logoDescription": "Propulsé par Elastic", - "xpack.reporting.exportTypes.printablePdf.pagingDescription": "Page {currentPage} sur {pageCount}", "xpack.reporting.jobsQuery.deleteError": "Impossible de supprimer le rapport : {error}", "xpack.reporting.jobStatusDetail.attemptXofY": "Tentative {attempts} sur {max_attempts}.", "xpack.reporting.jobStatusDetail.deprecatedText": "Il s'agit d'un type d'exportation déclassé. L'automatisation de ce rapport devra être à nouveau créée pour une question de compatibilité avec les futures versions de Kibana.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6a232901a14af8..1a3a5153409a46 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -21484,8 +21484,6 @@ "xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "レポートジョブデータの解読に失敗しました。{encryptionKey}が設定されていることを確認してこのレポートを再生成してください。{err}", "xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "ジョブヘッダーがありません", "xpack.reporting.exportTypes.csv.generateCsv.escapedFormulaValues": "CSVには、値がエスケープされた式が含まれる場合があります", - "xpack.reporting.exportTypes.printablePdf.logoDescription": "Elastic 提供", - "xpack.reporting.exportTypes.printablePdf.pagingDescription": "{pageCount} ページ中 {currentPage} ページ目", "xpack.reporting.jobCreatedBy.unknownUserPlaceholderText": "不明", "xpack.reporting.jobsQuery.deleteError": "レポートを削除できません:{error}", "xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage": "{jobType}情報を表示する権限がありません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2947c7d7dd233c..a72a0a4c3ebc03 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -21513,8 +21513,6 @@ "xpack.reporting.exportTypes.common.failedToDecryptReportJobDataErrorMessage": "无法解密报告作业数据。请确保已设置 {encryptionKey},然后重新生成此报告。{err}", "xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage": "作业标头缺失", "xpack.reporting.exportTypes.csv.generateCsv.escapedFormulaValues": "CSV 可能包含值已转义的公式", - "xpack.reporting.exportTypes.printablePdf.logoDescription": "由 Elastic 提供支持", - "xpack.reporting.exportTypes.printablePdf.pagingDescription": "第 {currentPage} 页,共 {pageCount} 页", "xpack.reporting.jobCreatedBy.unknownUserPlaceholderText": "未知", "xpack.reporting.jobsQuery.deleteError": "无法删除报告:{error}", "xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage": "抱歉,您无权查看 {jobType} 信息", From f4c4fd6778412aca01401452d8c13892aa3b255f Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 17 Mar 2022 15:45:05 +0100 Subject: [PATCH 09/10] [ML] Functional tests - refactor response code checks (#127875) This PR refactors the way how we assert response codes from API requests in our functional tests. --- .../apis/ml/annotations/create_annotations.ts | 18 +- .../apis/ml/annotations/delete_annotations.ts | 18 +- .../apis/ml/annotations/get_annotations.ts | 25 +- .../apis/ml/annotations/update_annotations.ts | 24 +- .../ml/anomaly_detectors/close_with_spaces.ts | 7 +- .../apis/ml/anomaly_detectors/create.ts | 6 +- .../anomaly_detectors/create_with_spaces.ts | 6 +- .../anomaly_detectors/delete_with_spaces.ts | 7 +- .../anomaly_detectors/forecast_with_spaces.ts | 6 +- .../apis/ml/anomaly_detectors/get.ts | 60 ++--- .../get_stats_with_spaces.ts | 6 +- .../ml/anomaly_detectors/get_with_spaces.ts | 6 +- .../ml/anomaly_detectors/open_with_spaces.ts | 7 +- .../apis/ml/calendars/create_calendars.ts | 18 +- .../apis/ml/calendars/delete_calendars.ts | 24 +- .../apis/ml/calendars/get_calendars.ts | 44 ++-- .../apis/ml/calendars/update_calendars.ts | 24 +- .../ml/data_frame_analytics/create_job.ts | 18 +- .../apis/ml/data_frame_analytics/delete.ts | 42 ++-- .../ml/data_frame_analytics/delete_spaces.ts | 6 +- .../apis/ml/data_frame_analytics/evaluate.ts | 18 +- .../apis/ml/data_frame_analytics/explain.ts | 18 +- .../apis/ml/data_frame_analytics/get.ts | 89 ++++---- .../ml/data_frame_analytics/get_spaces.ts | 12 +- .../data_frame_analytics/jobs_exist_spaces.ts | 6 +- .../ml/data_frame_analytics/new_job_caps.ts | 6 +- .../apis/ml/data_frame_analytics/start.ts | 24 +- .../ml/data_frame_analytics/start_spaces.ts | 6 +- .../apis/ml/data_frame_analytics/stop.ts | 24 +- .../ml/data_frame_analytics/stop_spaces.ts | 5 +- .../apis/ml/data_frame_analytics/update.ts | 48 ++-- .../ml/data_frame_analytics/update_spaces.ts | 6 +- .../apis/ml/data_frame_analytics/validate.ts | 18 +- .../ml/datafeeds/get_stats_with_spaces.ts | 6 +- .../apis/ml/datafeeds/get_with_spaces.ts | 6 +- .../apis/ml/datafeeds/update.ts | 6 +- .../ml/fields_service/field_cardinality.ts | 8 +- .../ml/fields_service/time_field_range.ts | 6 +- .../apis/ml/filters/create_filters.ts | 7 +- .../apis/ml/filters/delete_filters.ts | 25 +- .../apis/ml/filters/get_filters.ts | 33 +-- .../apis/ml/filters/update_filters.ts | 24 +- .../apis/ml/indices/field_caps.ts | 6 +- .../ml/job_audit_messages/clear_messages.ts | 44 ++-- .../get_job_audit_messages.ts | 24 +- .../job_validation/bucket_span_estimator.ts | 42 ++-- .../calculate_model_memory_limit.ts | 6 +- .../apis/ml/job_validation/cardinality.ts | 24 +- .../datafeed_preview_validation.ts | 30 +-- .../apis/ml/job_validation/validate.ts | 30 +-- .../ml/jobs/categorization_field_examples.ts | 6 +- .../apis/ml/jobs/close_jobs.ts | 6 +- .../apis/ml/jobs/close_jobs_spaces.ts | 6 +- .../apis/ml/jobs/datafeed_preview.ts | 36 +-- .../apis/ml/jobs/delete_jobs.ts | 6 +- .../apis/ml/jobs/delete_jobs_spaces.ts | 6 +- .../apis/ml/jobs/force_start_datafeeds.ts | 6 +- .../ml/jobs/force_start_datafeeds_spaces.ts | 6 +- .../apis/ml/jobs/jobs_exist.ts | 6 +- .../apis/ml/jobs/jobs_exist_spaces.ts | 6 +- .../apis/ml/jobs/jobs_summary.ts | 6 +- .../apis/ml/jobs/jobs_summary_spaces.ts | 6 +- .../apis/ml/jobs/stop_datafeeds.ts | 6 +- .../apis/ml/jobs/stop_datafeeds_spaces.ts | 6 +- .../apis/ml/modules/get_module.ts | 6 +- .../apis/ml/modules/recognize_module.ts | 6 +- .../apis/ml/modules/setup_module.ts | 6 +- .../ml/results/get_anomalies_table_data.ts | 18 +- .../apis/ml/results/get_categorizer_stats.ts | 37 +-- .../ml/results/get_category_definition.ts | 6 +- .../apis/ml/results/get_category_examples.ts | 6 +- .../apis/ml/results/get_stopped_partitions.ts | 39 ++-- .../apis/ml/saved_objects/can_delete_job.ts | 7 +- .../apis/ml/saved_objects/initialize.ts | 6 +- .../apis/ml/saved_objects/jobs_spaces.ts | 6 +- .../apis/ml/saved_objects/status.ts | 6 +- .../apis/ml/saved_objects/sync.ts | 12 +- .../ml/saved_objects/update_jobs_spaces.ts | 6 +- .../apis/ml/system/capabilities.ts | 6 +- .../apis/ml/system/space_capabilities.ts | 6 +- .../apis/ml/trained_models/delete_model.ts | 30 +-- .../ml/trained_models/get_model_pipelines.ts | 12 +- .../apis/ml/trained_models/get_model_stats.ts | 18 +- .../apis/ml/trained_models/get_models.ts | 33 +-- .../apis/transform/delete_transforms.ts | 50 ++-- .../apis/transform/reset_transforms.ts | 30 +-- .../apis/transform/start_transforms.ts | 30 +-- .../apis/transform/stop_transforms.ts | 30 +-- .../apis/transform/transforms.ts | 30 +-- .../apis/transform/transforms_create.ts | 12 +- .../apis/transform/transforms_nodes.ts | 18 +- .../apis/transform/transforms_preview.ts | 18 +- .../apis/transform/transforms_stats.ts | 12 +- .../apis/transform/transforms_update.ts | 41 ++-- x-pack/test/api_integration/services/ml.ts | 2 +- .../api_integration/services/transform.ts | 4 +- .../test/functional/services/ml/alerting.ts | 13 +- x-pack/test/functional/services/ml/api.ts | 213 ++++++++++-------- x-pack/test/functional/services/ml/index.ts | 4 +- .../functional/services/ml/test_resources.ts | 89 ++++---- .../test/functional/services/transform/api.ts | 71 ++++-- .../functional/services/transform/index.ts | 4 +- 102 files changed, 1071 insertions(+), 965 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts index c2a204c1d1881c..07575891ba0830 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts @@ -34,12 +34,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should successfully create annotations for anomaly job', async () => { - const { body } = await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(annotationRequestBody) - .expect(200); + .send(annotationRequestBody); + ml.api.assertResponseStatusCode(200, status, body); const annotationId = body._id; const fetchedAnnotation = await ml.api.getAnnotationById(annotationId); @@ -56,12 +56,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should successfully create annotation for user with ML read permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(annotationRequestBody) - .expect(200); + .send(annotationRequestBody); + ml.api.assertResponseStatusCode(200, status, body); const annotationId = body._id; const fetchedAnnotation = await ml.api.getAnnotationById(annotationId); @@ -76,12 +76,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to create annotation for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(annotationRequestBody) - .expect(403); + .send(annotationRequestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts index 77b6103a6e22a8..11cc38da82a02c 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts @@ -41,11 +41,11 @@ export default ({ getService }: FtrProviderContext) => { const annotationIdToDelete = annotationsForJob[0]._id; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/annotations/delete/${annotationIdToDelete}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body._id).to.eql(annotationIdToDelete); expect(body.result).to.eql('deleted'); @@ -59,11 +59,11 @@ export default ({ getService }: FtrProviderContext) => { const annotationIdToDelete = annotationsForJob[0]._id; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/annotations/delete/${annotationIdToDelete}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body._id).to.eql(annotationIdToDelete); expect(body.result).to.eql('deleted'); @@ -77,11 +77,11 @@ export default ({ getService }: FtrProviderContext) => { const annotationIdToDelete = annotationsForJob[0]._id; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/annotations/delete/${annotationIdToDelete}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts index bff9f3157c69d3..9e18722373b8d4 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts @@ -43,12 +43,12 @@ export default ({ getService }: FtrProviderContext) => { latestMs: Date.now(), maxAnnotations: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/annotations') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body.success).to.eql(true); expect(body.annotations).not.to.be(undefined); @@ -68,12 +68,12 @@ export default ({ getService }: FtrProviderContext) => { latestMs: Date.now(), maxAnnotations: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/annotations') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body.success).to.eql(true); expect(body.annotations).not.to.be(undefined); @@ -93,12 +93,13 @@ export default ({ getService }: FtrProviderContext) => { latestMs: Date.now(), maxAnnotations: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/annotations') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.success).to.eql(true); expect(body.annotations).not.to.be(undefined); jobIds.forEach((jobId, idx) => { @@ -117,12 +118,12 @@ export default ({ getService }: FtrProviderContext) => { latestMs: Date.now(), maxAnnotations: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/annotations') .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts index 0beb5e7fde6c5e..eb1318a2763e97 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts @@ -59,12 +59,12 @@ export default ({ getService }: FtrProviderContext) => { _id: originalAnnotation._id, }; - const { body } = await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(annotationUpdateRequestBody) - .expect(200); + .send(annotationUpdateRequestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body._id).to.eql(originalAnnotation._id); expect(body.result).to.eql('updated'); @@ -90,12 +90,12 @@ export default ({ getService }: FtrProviderContext) => { _id: originalAnnotation._id, }; - const { body } = await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(annotationUpdateRequestBody) - .expect(200); + .send(annotationUpdateRequestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body._id).to.eql(originalAnnotation._id); expect(body.result).to.eql('updated'); @@ -121,12 +121,12 @@ export default ({ getService }: FtrProviderContext) => { _id: originalAnnotation._id, }; - const { body } = await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(annotationUpdateRequestBody) - .expect(403); + .send(annotationUpdateRequestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -150,12 +150,12 @@ export default ({ getService }: FtrProviderContext) => { detector_index: 2, _id: originalAnnotation._id, }; - await supertest + const { body, status } = await supertest .put('/api/ml/annotations/index') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(annotationUpdateRequestBodyWithMissingFields) - .expect(200); + .send(annotationUpdateRequestBodyWithMissingFields); + ml.api.assertResponseStatusCode(200, status, body); const updatedAnnotation = await ml.api.getAnnotationById(originalAnnotation._id); if (updatedAnnotation) { diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/close_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/close_with_spaces.ts index 54425cf007ece7..7d8fd156812125 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/close_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/close_with_spaces.ts @@ -21,14 +21,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(jobId: string, expectedStatusCode: number, space?: string) { - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_close`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); + return body; } diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts index c04a368a63e9b7..26d15e2626ae53 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts @@ -110,12 +110,12 @@ export default ({ getService }: FtrProviderContext) => { for (const testData of testDataList) { it(`${testData.testTitle}`, async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/anomaly_detectors/${testData.jobId}`) .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); if (body.error === undefined) { // Validate the important parts of the response. diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/create_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/create_with_spaces.ts index 3320f7762ef7bd..e6bd7d2bb4792a 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/create_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/create_with_spaces.ts @@ -37,15 +37,15 @@ export default ({ getService }: FtrProviderContext) => { const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1); await ml.testExecution.logTestStep('should create job'); - await supertest + const { body, status } = await supertest .put(`/s/${idSpace1}/api/ml/anomaly_detectors/${jobIdSpace1}`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send(jobConfig) - .expect(200); + .send(jobConfig); + ml.api.assertResponseStatusCode(200, status, body); await ml.testExecution.logTestStep(`job should be in space '${idSpace1}' only`); await ml.api.assertJobSpaces(jobIdSpace1, 'anomaly-detector', [idSpace1]); diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/delete_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/delete_with_spaces.ts index 969e15a1500866..518374c152cb7d 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/delete_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/delete_with_spaces.ts @@ -19,14 +19,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(jobId: string, expectedStatusCode: number, space?: string) { - const { body } = await supertest + const { body, status } = await supertest .delete(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); + return body; } diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts index 19bb3c7a95c554..54550c547521f3 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts @@ -28,12 +28,12 @@ export default ({ getService }: FtrProviderContext) => { user: USER, expectedStatusCode: number ) { - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_forecast`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send({ duration }) - .expect(expectedStatusCode); + .send({ duration }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts index 3acefac817d480..3ac0bacf8b4298 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts @@ -71,11 +71,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetAnomalyDetectors', () => { it('should fetch all anomaly detector jobs', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(2); expect(body.jobs.length).to.eql(2); @@ -84,11 +84,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve jobs for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -97,11 +97,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetAnomalyDetectorsById', () => { it('should fetch single anomaly detector job by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/${jobId}_1`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(1); expect(body.jobs.length).to.eql(1); @@ -109,11 +109,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch anomaly detector jobs based on provided ids', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/${jobId}_1,${jobId}_2`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(2); expect(body.jobs.length).to.eql(2); @@ -122,11 +122,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve a job for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/${jobId}_1`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -135,11 +135,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetAnomalyDetectorsStats', () => { it('should fetch jobs stats', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(2); expect(body.jobs.length).to.eql(2); @@ -155,11 +155,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve jobs stats for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/_stats`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -168,11 +168,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetAnomalyDetectorsStatsById', () => { it('should fetch single job stats', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/${jobId}_1/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(1); expect(body.jobs.length).to.eql(1); @@ -187,11 +187,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch multiple jobs stats based on provided ids', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/${jobId}_1,${jobId}_2/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(2); expect(body.jobs.length).to.eql(2); @@ -207,11 +207,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve a job stats for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/anomaly_detectors/${jobId}_1/_stats`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_stats_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_stats_with_spaces.ts index d15b1ab60f9ffc..12530e34d51b61 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_stats_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_stats_with_spaces.ts @@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .get( `${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors${ jobOrGroup ? `/${jobOrGroup}` : '' @@ -37,8 +37,8 @@ export default ({ getService }: FtrProviderContext) => { USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_with_spaces.ts index 2bebea5e63ea16..95d234791b5240 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get_with_spaces.ts @@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .get( `${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors${ jobOrGroup ? `/${jobOrGroup}` : '' @@ -37,8 +37,8 @@ export default ({ getService }: FtrProviderContext) => { USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/open_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/open_with_spaces.ts index 113bbe4ca595f3..09e98d14304919 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/open_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/open_with_spaces.ts @@ -21,14 +21,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(jobId: string, expectedStatusCode: number, space?: string) { - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_open`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); + return body; } diff --git a/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts index fac62237aa74e5..79c1d8c49f6049 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts @@ -38,12 +38,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should successfully create calendar by id', async () => { - await supertest + const { body, status } = await supertest .put(`/api/ml/calendars`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); const results = await ml.api.getCalendar(requestBody.calendarId); const createdCalendar = results.body.calendars[0]; @@ -56,12 +56,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not create new calendar for user without required permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/calendars`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -69,12 +69,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not create new calendar for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/calendars`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts index a2e1709731aa74..1f2f2455c97a68 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts @@ -42,44 +42,44 @@ export default ({ getService }: FtrProviderContext) => { }); it('should delete calendar by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.acknowledged).to.eql(true); await ml.api.waitForCalendarNotToExist(calendarId); }); it('should not delete calendar for user without required permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); await ml.api.waitForCalendarToExist(calendarId); }); it('should not delete calendar for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); await ml.api.waitForCalendarToExist(calendarId); }); it('should return 404 if invalid calendarId', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/calendars/calendar_id_dne`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); expect(body.error).to.eql('Not Found'); }); diff --git a/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts index 243a40abe97a4a..cca564e33cb911 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts @@ -46,11 +46,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch all calendars', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.have.length(testCalendars.length); expect(body[0].events).to.have.length(testEvents.length); @@ -58,11 +58,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch all calendars for user with view permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.have.length(testCalendars.length); expect(body[0].events).to.have.length(testEvents.length); @@ -70,11 +70,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not fetch calendars for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); + expect(body.error).to.eql('Forbidden'); }); }); @@ -97,11 +98,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch calendar & associated events by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.job_ids).to.eql(testCalendar.job_ids); expect(body.description).to.eql(testCalendar.description); @@ -110,11 +111,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch calendar & associated events by id for user with view permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.job_ids).to.eql(testCalendar.job_ids); expect(body.description).to.eql(testCalendar.description); @@ -123,22 +124,23 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not fetch calendars for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); }); }); it('should return 404 if invalid calendar id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/calendars/calendar_id_dne`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); + expect(body.error).to.eql('Not Found'); }); }); diff --git a/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts index 1ca9a663199436..0ccf60fd88dce2 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts @@ -50,12 +50,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update calendar by id with new settings', async () => { - await supertest + const { body, status } = await supertest .put(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(updateCalendarRequestBody) - .expect(200); + .send(updateCalendarRequestBody); + ml.api.assertResponseStatusCode(200, status, body); await ml.api.waitForCalendarToExist(calendarId); @@ -75,30 +75,30 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to update calendar for user without required permission', async () => { - await supertest + const { body, status } = await supertest .put(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(updateCalendarRequestBody) - .expect(403); + .send(updateCalendarRequestBody); + ml.api.assertResponseStatusCode(403, status, body); }); it('should not allow to update calendar for unauthorized user', async () => { - await supertest + const { body, status } = await supertest .put(`/api/ml/calendars/${calendarId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(updateCalendarRequestBody) - .expect(403); + .send(updateCalendarRequestBody); + ml.api.assertResponseStatusCode(403, status, body); }); it('should return error if invalid calendarId', async () => { - await supertest + const { body, status } = await supertest .put(`/api/ml/calendars/calendar_id_dne`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(updateCalendarRequestBody) - .expect(404); + .send(updateCalendarRequestBody); + ml.api.assertResponseStatusCode(404, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/create_job.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/create_job.ts index e1e391add48978..44c1b0c6fe2bbe 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/create_job.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/create_job.ts @@ -91,12 +91,12 @@ export default ({ getService }: FtrProviderContext) => { const analyticsId = `${testConfig.jobId}`; const requestBody = testConfig.config; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/data_frame/analytics/${analyticsId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); @@ -113,12 +113,12 @@ export default ({ getService }: FtrProviderContext) => { const analyticsId = `${testJobConfigs[0].jobId}`; const requestBody = testJobConfigs[0].config; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/data_frame/analytics/${analyticsId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -128,12 +128,12 @@ export default ({ getService }: FtrProviderContext) => { const analyticsId = `${testJobConfigs[0].jobId}`; const requestBody = testJobConfigs[0].config; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/data_frame/analytics/${analyticsId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts index e7ea71863352e8..b2d306b923f1d4 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts @@ -77,11 +77,11 @@ export default ({ getService }: FtrProviderContext) => { describe('DeleteDataFrameAnalytics', () => { it('should delete analytics jobs by id', async () => { const analyticsId = `${jobId}_1`; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${analyticsId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.analyticsJobDeleted.success).to.eql(true); await ml.api.waitForDataFrameAnalyticsJobNotToExist(analyticsId); @@ -89,11 +89,11 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow to retrieve analytics jobs for unauthorized user', async () => { const analyticsId = `${jobId}_2`; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${analyticsId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -102,11 +102,11 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow to retrieve analytics jobs for the user with only view permission', async () => { const analyticsId = `${jobId}_2`; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${analyticsId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -115,11 +115,11 @@ export default ({ getService }: FtrProviderContext) => { it('should show 404 error if job does not exist or has already been deleted', async () => { const id = `${jobId}_invalid`; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${id}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); expect(body.error).to.eql('Not Found'); expect(body.message).to.eql(`No known job with id '${id}'`); @@ -139,12 +139,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should delete job and destination index by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${analyticsId}`) .query({ deleteDestIndex: true }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.analyticsJobDeleted.success).to.eql(true); expect(body.destIndexDeleted.success).to.eql(true); @@ -168,12 +168,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should delete job and index pattern by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${analyticsId}`) .query({ deleteDestIndexPattern: true }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.analyticsJobDeleted.success).to.eql(true); expect(body.destIndexDeleted.success).to.eql(false); @@ -200,12 +200,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should delete job, target index, and index pattern by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/data_frame/analytics/${analyticsId}`) .query({ deleteDestIndex: true, deleteDestIndexPattern: true }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.analyticsJobDeleted.success).to.eql(true); expect(body.destIndexDeleted.success).to.eql(true); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete_spaces.ts index 9492358d090006..48933e837baeaf 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete_spaces.ts @@ -22,14 +22,14 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(jobId: string, space: string, expectedStatusCode: number) { - const { body } = await supertest + const { body, status } = await supertest .delete(`/s/${space}/api/ml/data_frame/analytics/${jobId}`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts index e2a9d123aafc98..8b03c42caa7877 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts @@ -126,12 +126,12 @@ export default ({ getService }: FtrProviderContext) => { testJobConfigs.forEach((testConfig) => { describe(`EvaluateDataFrameAnalytics ${testConfig.jobType}`, async () => { it(`should evaluate ${testConfig.jobType} analytics job`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/_evaluate`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(testConfig.eval) - .expect(200); + .send(testConfig.eval); + ml.api.assertResponseStatusCode(200, status, body); if (testConfig.jobType === 'classification') { const { classification } = body; @@ -149,12 +149,12 @@ export default ({ getService }: FtrProviderContext) => { }); it(`should evaluate ${testConfig.jobType} job for the user with only view permission`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/_evaluate`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(testConfig.eval) - .expect(200); + .send(testConfig.eval); + ml.api.assertResponseStatusCode(200, status, body); if (testConfig.jobType === 'classification') { const { classification } = body; @@ -172,12 +172,12 @@ export default ({ getService }: FtrProviderContext) => { }); it(`should not allow unauthorized user to evaluate ${testConfig.jobType} job`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/_evaluate`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(testConfig.eval) - .expect(403); + .send(testConfig.eval); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/explain.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/explain.ts index b3219c60c1aab8..31db3b645093b2 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/explain.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/explain.ts @@ -90,12 +90,12 @@ export default ({ getService }: FtrProviderContext) => { testJobConfigs.forEach((testConfig) => { describe(`ExplainDataFrameAnalytics ${testConfig.jobType}`, async () => { it(`should explain ${testConfig.jobType} analytics job`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/_explain`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(testConfig.config) - .expect(200); + .send(testConfig.config); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.have.property('field_selection'); // eslint-disable-next-line @@ -111,24 +111,24 @@ export default ({ getService }: FtrProviderContext) => { }); it(`should not allow user with only view permission to use explain endpoint for ${testConfig.jobType} job `, async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/_explain`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(testConfig.config) - .expect(403); + .send(testConfig.config); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); }); it(`should not allow unauthorized user to use explain endpoint for ${testConfig.jobType} job`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/_explain`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(testConfig.config) - .expect(403); + .send(testConfig.config); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts index 69f40e46bc7e82..9faaa32441a306 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts @@ -95,11 +95,12 @@ export default ({ getService }: FtrProviderContext) => { describe('GetDataFrameAnalytics', () => { it('should fetch all analytics jobs', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.count).to.eql(2); expect(body.data_frame_analytics.length).to.eql(2); expect(body.data_frame_analytics[0].id).to.eql(`${jobId}_1`); @@ -107,11 +108,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve analytics jobs for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -120,11 +121,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetDataFrameAnalyticsById', () => { it('should fetch single analytics job by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(1); expect(body.data_frame_analytics.length).to.eql(1); @@ -132,11 +133,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch analytics jobs based on provided ids', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1,${jobId}_2`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(2); expect(body.data_frame_analytics.length).to.eql(2); @@ -145,11 +146,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve a job for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -158,11 +159,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetDataFrameAnalyticsStats', () => { it('should fetch analytics jobs stats', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(2); expect(body.data_frame_analytics.length).to.eql(2); @@ -178,11 +179,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve jobs stats for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/_stats`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -191,11 +192,12 @@ export default ({ getService }: FtrProviderContext) => { describe('GetDataFrameAnalyticsStatsById', () => { it('should fetch single analytics job stats by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.count).to.eql(1); expect(body.data_frame_analytics.length).to.eql(1); expect(body.data_frame_analytics[0].id).to.eql(`${jobId}_1`); @@ -209,11 +211,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch multiple analytics jobs stats based on provided ids', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1,${jobId}_2/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.count).to.eql(2); expect(body.data_frame_analytics.length).to.eql(2); expect(body.data_frame_analytics[0].id).to.eql(`${jobId}_1`); @@ -228,11 +231,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve a job stats for the user without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1/_stats`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); + expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); }); @@ -240,11 +244,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetDataFrameAnalyticsIdMap', () => { it('should return a map of objects leading up to analytics job id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/map/${jobId}_1`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.have.keys('elements', 'details', 'error'); // Index node, 2 job nodes (with same source index), and 2 edge nodes to connect them @@ -261,11 +265,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return empty results and an error message if the job does not exist', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/map/${jobId}_fake`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.elements.length).to.eql(0); expect(body.details).to.eql({}); @@ -278,11 +282,11 @@ export default ({ getService }: FtrProviderContext) => { describe('GetDataFrameAnalyticsMessages', () => { it('should fetch single analytics job messages by id', async () => { await retry.tryForTime(5000, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1/messages`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.length).to.eql(1); expect(body[0].job_id).to.eql(`${jobId}_1`); @@ -298,11 +302,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to retrieve job messages without required permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/data_frame/analytics/${jobId}_1/messages`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); + expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); }); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts index b6509438831964..2b853ef92ff181 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts @@ -29,7 +29,7 @@ export default ({ getService }: FtrProviderContext) => { requestStats: boolean, jobId?: string ) { - const { body } = await supertest + const { body, status } = await supertest .get( `/s/${space}/api/ml/data_frame/analytics${jobId ? `/${jobId}` : ''}${ requestStats ? '/_stats' : '' @@ -39,21 +39,21 @@ export default ({ getService }: FtrProviderContext) => { USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } async function runMapRequest(space: string, expectedStatusCode: number, jobId: string) { - const { body } = await supertest + const { body, status } = await supertest .get(`/s/${space}/api/ml/data_frame/analytics/map/${jobId}`) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/jobs_exist_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/jobs_exist_spaces.ts index 4934af379ae660..bf81e4df2a5a4d 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/jobs_exist_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/jobs_exist_spaces.ts @@ -30,15 +30,15 @@ export default ({ getService }: FtrProviderContext) => { analyticsIds?: string[], allSpaces?: boolean ) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/data_frame/analytics/jobs_exist`) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send(allSpaces ? { analyticsIds, allSpaces } : { analyticsIds }) - .expect(expectedStatusCode); + .send(allSpaces ? { analyticsIds, allSpaces } : { analyticsIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/new_job_caps.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/new_job_caps.ts index 72ac632a8b8dd2..cd2349cc6fba53 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/new_job_caps.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/new_job_caps.ts @@ -22,14 +22,14 @@ export default ({ getService }: FtrProviderContext) => { if (rollup !== undefined) { url += `?rollup=${rollup}`; } - const { body } = await supertest + const { body, status } = await supertest .get(url) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/start.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/start.ts index d19569407dfcdd..87933763746931 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/start.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/start.ts @@ -78,11 +78,11 @@ export default ({ getService }: FtrProviderContext) => { it('should start analytics job for specified id if job exists', async () => { const analyticsId = `${jobId}_0`; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_start`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); expect(body.acknowledged).to.be(true); @@ -96,11 +96,11 @@ export default ({ getService }: FtrProviderContext) => { const id = `${jobId}_invalid`; const message = `No known job with id '${id}'`; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${id}/_start`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); expect(body.error).to.eql('Not Found'); expect(body.message).to.eql(message); @@ -109,11 +109,11 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow to start analytics job for unauthorized user', async () => { const analyticsId = `${jobId}_0`; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_start`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -122,11 +122,11 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow to start analytics job for user with view only permission', async () => { const analyticsId = `${jobId}_0`; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_start`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/start_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/start_spaces.ts index 636097a81c4f64..638e81cf88947c 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/start_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/start_spaces.ts @@ -26,14 +26,14 @@ export default ({ getService }: FtrProviderContext) => { const initialModelMemoryLimit = '17mb'; async function runStartRequest(jobId: string, space: string, expectedStatusCode: number) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/data_frame/analytics/${jobId}/_start`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop.ts index 09663947f61c0a..c9c54260a93c1f 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop.ts @@ -40,11 +40,11 @@ export default ({ getService }: FtrProviderContext) => { describe('StopsDataFrameAnalyticsJob', () => { it('should stop analytics job for specified id when job exists', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_stop`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); expect(body.stopped).to.be(true); @@ -55,33 +55,33 @@ export default ({ getService }: FtrProviderContext) => { const id = `${jobId}_invalid`; const message = `No known job with id '${id}'`; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${id}/_stop`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); expect(body.error).to.eql('Not Found'); expect(body.message).to.eql(message); }); it('should not allow to stop analytics job for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_stop`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); }); it('should not allow to stop analytics job for user with view only permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_stop`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop_spaces.ts index 71eff1f00aa005..c1a2ea32e942a5 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/stop_spaces.ts @@ -29,16 +29,15 @@ export default ({ getService }: FtrProviderContext) => { action: string, expectedStatusCode: number ) { - const resp = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/data_frame/analytics/${jobId}/${action}`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS); - const { body, status } = resp; + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); - expect(status).to.be(expectedStatusCode); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts index c39cd72ac80e6b..3cc7a1f1944091 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts @@ -99,12 +99,12 @@ export default ({ getService }: FtrProviderContext) => { max_num_threads: 2, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); @@ -123,12 +123,12 @@ export default ({ getService }: FtrProviderContext) => { description: 'Edited description for job 1', }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); @@ -147,12 +147,12 @@ export default ({ getService }: FtrProviderContext) => { allow_lazy_start: true, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); @@ -171,12 +171,12 @@ export default ({ getService }: FtrProviderContext) => { model_memory_limit: '61mb', }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); @@ -195,12 +195,12 @@ export default ({ getService }: FtrProviderContext) => { max_num_threads: 2, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); @@ -218,12 +218,12 @@ export default ({ getService }: FtrProviderContext) => { description: 'Unauthorized', }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -239,12 +239,12 @@ export default ({ getService }: FtrProviderContext) => { description: 'View only', }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${analyticsId}/_update`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -261,12 +261,12 @@ export default ({ getService }: FtrProviderContext) => { const id = `${jobId}_invalid`; const message = `No known job with id '${id}'`; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/data_frame/analytics/${id}/_update`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(404); + .send(requestBody); + ml.api.assertResponseStatusCode(404, status, body); expect(body.error).to.eql('Not Found'); expect(body.message).to.eql(message); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update_spaces.ts index 22286628beb353..ff3235a00592b6 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update_spaces.ts @@ -31,15 +31,15 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, requestBody: unknown ) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/data_frame/analytics/${jobId}/_update`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedStatusCode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/validate.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/validate.ts index 24cc23c21cff34..530661559dd2b2 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/validate.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/validate.ts @@ -90,12 +90,12 @@ export default ({ getService }: FtrProviderContext) => { it(`should validate ${testConfig.jobType} job for given config`, async () => { const requestBody = testConfig.config; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/data_frame/analytics/validate') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).not.to.be(undefined); expect(body.length).to.eql(testConfig.jobType === 'outlier_detection' ? 1 : 3); @@ -106,12 +106,12 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow analytics job validation for unauthorized user', async () => { const requestBody = testJobConfigs[0].config; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/data_frame/analytics/validate') .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -120,12 +120,12 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow analytics job validation for the user with only view permission', async () => { const requestBody = testJobConfigs[0].config; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/data_frame/analytics/validate') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/datafeeds/get_stats_with_spaces.ts b/x-pack/test/api_integration/apis/ml/datafeeds/get_stats_with_spaces.ts index aaa60aabdfbd04..6fef5c8ed8172d 100644 --- a/x-pack/test/api_integration/apis/ml/datafeeds/get_stats_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/datafeeds/get_stats_with_spaces.ts @@ -26,7 +26,7 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .get( `${space ? `/s/${space}` : ''}/api/ml/datafeeds${datafeedId ? `/${datafeedId}` : ''}/_stats` ) @@ -34,8 +34,8 @@ export default ({ getService }: FtrProviderContext) => { USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/datafeeds/get_with_spaces.ts b/x-pack/test/api_integration/apis/ml/datafeeds/get_with_spaces.ts index a1b045832789ec..086f555baa72c0 100644 --- a/x-pack/test/api_integration/apis/ml/datafeeds/get_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/datafeeds/get_with_spaces.ts @@ -26,14 +26,14 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .get(`${space ? `/s/${space}` : ''}/api/ml/datafeeds${datafeedId ? `/${datafeedId}` : ''}`) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/datafeeds/update.ts b/x-pack/test/api_integration/apis/ml/datafeeds/update.ts index c4a2ec465cee5f..89262e2cbb8feb 100644 --- a/x-pack/test/api_integration/apis/ml/datafeeds/update.ts +++ b/x-pack/test/api_integration/apis/ml/datafeeds/update.ts @@ -30,12 +30,12 @@ export default ({ getService }: FtrProviderContext) => { space?: string ) { const datafeedId = datafeedConfig.datafeed_id; - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/datafeeds/${datafeedId}/_update`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(datafeedConfig.body) - .expect(expectedStatusCode); + .send(datafeedConfig.body); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts b/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts index 9e233b7d0f7a21..5bfbada12200ad 100644 --- a/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts +++ b/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts @@ -27,6 +27,7 @@ export default ({ getService }: FtrProviderContext) => { timeFieldName: 'order_date', }, expected: { + statusCode: 200, responseBody: { 'customer_first_name.keyword': 46, 'customer_last_name.keyword': 183, @@ -45,6 +46,7 @@ export default ({ getService }: FtrProviderContext) => { latestMs: 1560643199000, // June 15, 2019 11:59:59 PM GMT }, expected: { + statusCode: 200, responseBody: { 'geoip.city_name': 10, 'geoip.continent_name': 5, @@ -64,6 +66,7 @@ export default ({ getService }: FtrProviderContext) => { latestMs: 1560643199000, // June 15, 2019 11:59:59 PM GMT }, expected: { + statusCode: 200, responseBody: {}, }, }, @@ -76,8 +79,8 @@ export default ({ getService }: FtrProviderContext) => { timeFieldName: 'order_date', }, expected: { + statusCode: 404, responseBody: { - statusCode: 404, error: 'Not Found', message: 'index_not_found_exception', }, @@ -93,11 +96,12 @@ export default ({ getService }: FtrProviderContext) => { for (const testData of testDataList) { it(`${testData.testTitle}`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/fields_service/field_cardinality') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.statusCode, status, body); if (body.error === undefined) { expect(body).to.eql(testData.expected.responseBody); diff --git a/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts b/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts index 7cc285f0cece53..2a2c732a09bb81 100644 --- a/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts +++ b/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts @@ -96,12 +96,12 @@ export default ({ getService }: FtrProviderContext) => { for (const testData of testDataList) { it(`${testData.testTitle}`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/fields_service/time_field_range') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); if (body.error === undefined) { expect(body).to.eql(testData.expected.responseBody); diff --git a/x-pack/test/api_integration/apis/ml/filters/create_filters.ts b/x-pack/test/api_integration/apis/ml/filters/create_filters.ts index e15277926e9777..86b607337dc4a1 100644 --- a/x-pack/test/api_integration/apis/ml/filters/create_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/create_filters.ts @@ -108,12 +108,13 @@ export default ({ getService }: FtrProviderContext) => { for (const testData of testDataList) { const { testTitle, user, requestBody, expected } = testData; it(`${testTitle}`, async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/filters`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expected.responseCode); + .send(requestBody); + ml.api.assertResponseStatusCode(expected.responseCode, status, body); + if (body.error === undefined) { // Validate the important parts of the response. const expectedResponse = testData.expected.responseBody; diff --git a/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts b/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts index 8bdd3c78a44e53..0cd06f64ba7711 100644 --- a/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts @@ -49,11 +49,11 @@ export default ({ getService }: FtrProviderContext) => { it(`should delete filter by id`, async () => { const { filterId } = validFilters[0]; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/filters/${filterId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.acknowledged).to.eql(true); await ml.api.waitForFilterToNotExist(filterId); @@ -61,11 +61,11 @@ export default ({ getService }: FtrProviderContext) => { it(`should not delete filter for user without required permission`, async () => { const { filterId } = validFilters[1]; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/filters/${filterId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); await ml.api.waitForFilterToExist(filterId); @@ -73,22 +73,23 @@ export default ({ getService }: FtrProviderContext) => { it(`should not delete filter for unauthorized user`, async () => { const { filterId } = validFilters[2]; - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/filters/${filterId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); await ml.api.waitForFilterToExist(filterId); }); it(`should not allow user to delete filter if invalid filterId`, async () => { - const { body } = await supertest + const { body, status } = await supertest .delete(`/api/ml/filters/filter_id_dne`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); + expect(body.error).to.eql('Not Found'); }); }); diff --git a/x-pack/test/api_integration/apis/ml/filters/get_filters.ts b/x-pack/test/api_integration/apis/ml/filters/get_filters.ts index 049b6936f1893a..b111a97fdbba9c 100644 --- a/x-pack/test/api_integration/apis/ml/filters/get_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/get_filters.ts @@ -42,32 +42,34 @@ export default ({ getService }: FtrProviderContext) => { await ml.api.deleteFilter(filterId); } }); + it(`should fetch all filters`, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/filters`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.have.length(validFilters.length); }); it(`should not allow to retrieve filters for user without required permission`, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/filters`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); + expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); }); it(`should not allow to retrieve filters for unauthorized user`, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/filters`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); @@ -75,11 +77,11 @@ export default ({ getService }: FtrProviderContext) => { it(`should fetch single filter by id`, async () => { const { filterId, requestBody } = validFilters[0]; - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/filters/${filterId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.filter_id).to.eql(filterId); expect(body.description).to.eql(requestBody.description); @@ -87,11 +89,12 @@ export default ({ getService }: FtrProviderContext) => { }); it(`should return 400 if filterId does not exist`, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/filters/filter_id_dne`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(400); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(400, status, body); + expect(body.error).to.eql('Bad Request'); expect(body.message).to.contain('resource_not_found_exception'); }); diff --git a/x-pack/test/api_integration/apis/ml/filters/update_filters.ts b/x-pack/test/api_integration/apis/ml/filters/update_filters.ts index d08e9d50c64fe6..737e2c21cf0f66 100644 --- a/x-pack/test/api_integration/apis/ml/filters/update_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/update_filters.ts @@ -54,12 +54,12 @@ export default ({ getService }: FtrProviderContext) => { it(`should update filter by id`, async () => { const { filterId } = validFilters[0]; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/filters/${filterId}`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(updateFilterRequestBody) - .expect(200); + .send(updateFilterRequestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body.filter_id).to.eql(filterId); expect(body.description).to.eql(updateFilterRequestBody.description); @@ -68,12 +68,12 @@ export default ({ getService }: FtrProviderContext) => { it(`should not allow to update filter for user without required permission`, async () => { const { filterId, requestBody: oldFilterRequest } = validFilters[1]; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/filters/${filterId}`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(updateFilterRequestBody) - .expect(403); + .send(updateFilterRequestBody); + ml.api.assertResponseStatusCode(403, status, body); // response should return not found expect(body.error).to.eql('Forbidden'); @@ -88,12 +88,12 @@ export default ({ getService }: FtrProviderContext) => { it(`should not allow to update filter for unauthorized user`, async () => { const { filterId, requestBody: oldFilterRequest } = validFilters[2]; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/filters/${filterId}`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(updateFilterRequestBody) - .expect(403); + .send(updateFilterRequestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); @@ -105,12 +105,12 @@ export default ({ getService }: FtrProviderContext) => { }); it(`should return appropriate error if invalid filterId`, async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/filters/filter_id_dne`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(updateFilterRequestBody) - .expect(400); + .send(updateFilterRequestBody); + ml.api.assertResponseStatusCode(400, status, body); expect(body.message).to.contain('resource_not_found_exception'); }); diff --git a/x-pack/test/api_integration/apis/ml/indices/field_caps.ts b/x-pack/test/api_integration/apis/ml/indices/field_caps.ts index a99d100ad73a84..829430024599cb 100644 --- a/x-pack/test/api_integration/apis/ml/indices/field_caps.ts +++ b/x-pack/test/api_integration/apis/ml/indices/field_caps.ts @@ -20,12 +20,12 @@ export default ({ getService }: FtrProviderContext) => { index: string, fields?: string[] ): Promise<{ indices: string[]; fields: any }> { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/indices/field_caps`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ index, fields }) - .expect(200); + .send({ index, fields }); + ml.api.assertResponseStatusCode(200, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/job_audit_messages/clear_messages.ts b/x-pack/test/api_integration/apis/ml/job_audit_messages/clear_messages.ts index d2a2f9a4d02ddf..8335bd73c1f3ff 100644 --- a/x-pack/test/api_integration/apis/ml/job_audit_messages/clear_messages.ts +++ b/x-pack/test/api_integration/apis/ml/job_audit_messages/clear_messages.ts @@ -29,11 +29,11 @@ export default ({ getService }: FtrProviderContext) => { await ml.api.createAnomalyDetectionJob(jobConfig); } - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/job_audit_messages/messages`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); notificationIndices = body.notificationIndices; }); @@ -45,25 +45,25 @@ export default ({ getService }: FtrProviderContext) => { it('should mark audit messages as cleared for provided job', async () => { const timestamp = Date.now(); - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/job_audit_messages/clear_messages`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) .send({ jobId: 'test_get_job_audit_messages_1', notificationIndices, - }) - .expect(200); + }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.success).to.eql(true); expect(body.last_cleared).to.be.above(timestamp); await retry.tryForTime(5000, async () => { - const { body: getBody } = await supertest + const { body: getBody, status: getStatus } = await supertest .get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, getStatus, getBody); expect(getBody.messages.length).to.eql( 1, @@ -81,45 +81,47 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not mark audit messages as cleared for the user with ML read permissions', async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/job_audit_messages/clear_messages`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) .send({ jobId: 'test_get_job_audit_messages_2', notificationIndices, - }) - .expect(403); + }); + ml.api.assertResponseStatusCode(403, status, body); + expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); - const { body: getBody } = await supertest + const { body: getBody, status: getStatus } = await supertest .get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_2`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, getStatus, getBody); expect(getBody.messages[0].cleared).to.not.eql(true); }); it('should not mark audit messages as cleared for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .put(`/api/ml/job_audit_messages/clear_messages`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) .send({ jobId: 'test_get_job_audit_messages_2', notificationIndices, - }) - .expect(403); + }); + ml.api.assertResponseStatusCode(403, status, body); + expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); - const { body: getBody } = await supertest + const { body: getBody, status: getStatus } = await supertest .get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_2`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, getStatus, getBody); expect(getBody.messages[0].cleared).to.not.eql(true); }); diff --git a/x-pack/test/api_integration/apis/ml/job_audit_messages/get_job_audit_messages.ts b/x-pack/test/api_integration/apis/ml/job_audit_messages/get_job_audit_messages.ts index a288a7b491bb6b..2c79f02c75030b 100644 --- a/x-pack/test/api_integration/apis/ml/job_audit_messages/get_job_audit_messages.ts +++ b/x-pack/test/api_integration/apis/ml/job_audit_messages/get_job_audit_messages.ts @@ -34,11 +34,11 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch all audit messages', async () => { await retry.tryForTime(5000, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/job_audit_messages/messages`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.messages.length).to.eql( 2, @@ -68,11 +68,11 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch audit messages for specified job', async () => { await retry.tryForTime(5000, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.messages.length).to.eql( 1, @@ -90,11 +90,11 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch audit messages for user with ML read permissions', async () => { await retry.tryForTime(5000, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.messages.length).to.eql( 1, @@ -111,11 +111,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not allow to fetch audit messages for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts b/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts index c55e6e2628c18b..8baffcd9ae0ea5 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts @@ -95,12 +95,12 @@ export default ({ getService }: FtrProviderContext) => { describe('with default settings', function () { for (const testData of testDataList) { it(`estimates the bucket span ${testData.testTitleSuffix}`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/estimate_bucket_span') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); expect(body).to.eql(testData.expected.responseBody); }); @@ -109,28 +109,28 @@ export default ({ getService }: FtrProviderContext) => { describe('with transient search.max_buckets setting', function () { before(async () => { - await esSupertest + const { body, status } = await esSupertest .put('/_cluster/settings') - .send({ transient: { 'search.max_buckets': 9000 } }) - .expect(200); + .send({ transient: { 'search.max_buckets': 9000 } }); + ml.api.assertResponseStatusCode(200, status, body); }); after(async () => { - await esSupertest + const { body, status } = await esSupertest .put('/_cluster/settings') - .send({ transient: { 'search.max_buckets': null } }) - .expect(200); + .send({ transient: { 'search.max_buckets': null } }); + ml.api.assertResponseStatusCode(200, status, body); }); const testData = testDataList[0]; it(`estimates the bucket span`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/estimate_bucket_span') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); expect(body).to.eql(testData.expected.responseBody); }); @@ -138,28 +138,28 @@ export default ({ getService }: FtrProviderContext) => { describe('with persistent search.max_buckets setting', function () { before(async () => { - await esSupertest + const { body, status } = await esSupertest .put('/_cluster/settings') - .send({ persistent: { 'search.max_buckets': 9000 } }) - .expect(200); + .send({ persistent: { 'search.max_buckets': 9000 } }); + ml.api.assertResponseStatusCode(200, status, body); }); after(async () => { - await esSupertest + const { body, status } = await esSupertest .put('/_cluster/settings') - .send({ persistent: { 'search.max_buckets': null } }) - .expect(200); + .send({ persistent: { 'search.max_buckets': null } }); + ml.api.assertResponseStatusCode(200, status, body); }); const testData = testDataList[0]; it(`estimates the bucket span`, async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/estimate_bucket_span') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); expect(body).to.eql(testData.expected.responseBody); }); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts b/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts index c2cc9480fee3b3..67570039b915f4 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts @@ -151,12 +151,12 @@ export default ({ getService }: FtrProviderContext) => { for (const testData of testDataList) { it(`calculates the model memory limit ${testData.testTitleSuffix}`, async () => { - await supertest + const { body, status } = await supertest .post('/api/ml/validate/calculate_model_memory_limit') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); // More backend changes to the model memory calculation are planned. // This value check will be re-enabled when the final batch of updates is in. diff --git a/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts b/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts index e287537b0ca34f..7d199ab28101a5 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts @@ -54,12 +54,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/cardinality') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.eql([{ id: 'success_cardinality' }]); }); @@ -91,12 +91,12 @@ export default ({ getService }: FtrProviderContext) => { query: { bool: { must: [{ match_all: {} }], filter: [], must_not: [] } }, }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/cardinality') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); const expectedResponse = [ { @@ -143,12 +143,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/cardinality') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(400); + .send(requestBody); + ml.api.assertResponseStatusCode(400, status, body); expect(body.error).to.eql('Bad Request'); expect(body.message).to.eql( @@ -183,12 +183,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/cardinality') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/datafeed_preview_validation.ts b/x-pack/test/api_integration/apis/ml/job_validation/datafeed_preview_validation.ts index b449fb903958f0..f311384ba11276 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/datafeed_preview_validation.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/datafeed_preview_validation.ts @@ -100,12 +100,12 @@ export default ({ getService }: FtrProviderContext) => { it(`should validate a job with documents`, async () => { const job = getBaseJobConfig(); - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job }) - .expect(200); + .send({ job }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.valid).to.eql(true, `valid should be true, but got ${body.valid}`); expect(body.documentsFound).to.eql( @@ -118,12 +118,12 @@ export default ({ getService }: FtrProviderContext) => { const job = getBaseJobConfig(); job.analysis_config.detectors[0].field_name = 'no_such_field'; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job }) - .expect(200); + .send({ job }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.valid).to.eql(false, `valid should be false, but got ${body.valid}`); expect(body.documentsFound).to.eql( @@ -136,12 +136,12 @@ export default ({ getService }: FtrProviderContext) => { const job = getBaseJobConfig(); job.datafeed_config.indices = ['farequote_empty']; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job }) - .expect(200); + .send({ job }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.valid).to.eql(true, `valid should be true, but got ${body.valid}`); expect(body.documentsFound).to.eql( @@ -153,23 +153,23 @@ export default ({ getService }: FtrProviderContext) => { it(`should fail for viewer user`, async () => { const job = getBaseJobConfig(); - await supertest + const { body, status } = await supertest .post('/api/ml/validate/datafeed_preview') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send({ job }) - .expect(403); + .send({ job }); + ml.api.assertResponseStatusCode(403, status, body); }); it(`should fail for unauthorized user`, async () => { const job = getBaseJobConfig(); - await supertest + const { body, status } = await supertest .post('/api/ml/validate/datafeed_preview') .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send({ job }) - .expect(403); + .send({ job }); + ml.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts index 293b0e94351d0c..4e335cdbd48d13 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts @@ -70,12 +70,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/job') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.eql(basicValidJobMessages); }); @@ -114,12 +114,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/job') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body).to.eql(basicInvalidJobMessages); }); @@ -164,12 +164,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/job') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); // The existance and value of maxModelMemoryLimit depends on ES settings // and may vary between test environments, e.g. cloud vs non-cloud, @@ -231,12 +231,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/job') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(400); + .send(requestBody); + ml.api.assertResponseStatusCode(400, status, body); expect(body.error).to.eql('Bad Request'); expect(body.message).to.eql( @@ -277,12 +277,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/validate/job') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts b/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts index 9d6009bbb3ea66..480a6350e7ca50 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts @@ -292,12 +292,12 @@ export default ({ getService }: FtrProviderContext) => { for (const testData of testDataList) { it(testData.title, async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/categorization_field_examples') .auth(testData.user, ml.securityCommon.getPasswordForUser(testData.user)) .set(COMMON_REQUEST_HEADERS) - .send(testData.requestBody) - .expect(testData.expected.responseCode); + .send(testData.requestBody); + ml.api.assertResponseStatusCode(testData.expected.responseCode, status, body); expect(body.overallValidStatus).to.eql(testData.expected.overallValidStatus); expect(body.sampleSize).to.eql(testData.expected.sampleSize); diff --git a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts index 7efb64db923f42..e29ef5efd51923 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts @@ -25,12 +25,12 @@ export default ({ getService }: FtrProviderContext) => { requestBody: object, expectedResponsecode: number ): Promise { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/close_jobs') .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedResponsecode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedResponsecode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/close_jobs_spaces.ts b/x-pack/test/api_integration/apis/ml/jobs/close_jobs_spaces.ts index 5b8e5e67f03a81..a3a6ad309cb31c 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/close_jobs_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/close_jobs_spaces.ts @@ -23,15 +23,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(space: string, expectedStatusCode: number, jobIds?: string[]) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/jobs/close_jobs`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send({ jobIds }) - .expect(expectedStatusCode); + .send({ jobIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts b/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts index 4a0545049a76e4..33817adb8b27c4 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/datafeed_preview.ts @@ -65,12 +65,12 @@ export default ({ getService }: FtrProviderContext) => { runtime_mappings: {}, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job, datafeed }) - .expect(200); + .send({ job, datafeed }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.length).to.eql(1000, 'Response body total hits should be 1000'); expect(typeof body[0]?.airline).to.eql('string', 'Response body airlines should be a string'); @@ -102,12 +102,12 @@ export default ({ getService }: FtrProviderContext) => { runtime_mappings: {}, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job, datafeed }) - .expect(200); + .send({ job, datafeed }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.length).to.eql(1000, 'Response body total hits should be 1000'); expect(typeof body[0]?.airline).to.eql('string', 'Response body airlines should be a string'); @@ -144,12 +144,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job, datafeed }) - .expect(200); + .send({ job, datafeed }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.length).to.eql(1000, 'Response body total hits should be 1000'); expect(typeof body[0]?.airline).to.eql('string', 'Response body airlines should be a string'); @@ -189,12 +189,12 @@ export default ({ getService }: FtrProviderContext) => { }, }; - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/datafeed_preview') .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(COMMON_REQUEST_HEADERS) - .send({ job, datafeed }) - .expect(200); + .send({ job, datafeed }); + ml.api.assertResponseStatusCode(200, status, body); expect(body.length).to.eql(1000, 'Response body total hits should be 1000'); expect(typeof body[0]?.airline).to.eql('string', 'Response body airlines should be a string'); @@ -224,12 +224,12 @@ export default ({ getService }: FtrProviderContext) => { runtime_mappings: {}, }; - await supertest + const { body, status } = await supertest .post('/api/ml/jobs/datafeed_preview') .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send({ job, datafeed }) - .expect(403); + .send({ job, datafeed }); + ml.api.assertResponseStatusCode(403, status, body); }); it(`should return not a datafeed preview for unauthorized user`, async () => { @@ -249,12 +249,12 @@ export default ({ getService }: FtrProviderContext) => { runtime_mappings: {}, }; - await supertest + const { body, status } = await supertest .post('/api/ml/jobs/datafeed_preview') .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send({ job, datafeed }) - .expect(403); + .send({ job, datafeed }); + ml.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts index f785a35feafac9..7ef6b49f563a7e 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts @@ -68,12 +68,12 @@ export default ({ getService }: FtrProviderContext) => { requestBody: object, expectedResponsecode: number ): Promise { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/delete_jobs') .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedResponsecode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedResponsecode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/delete_jobs_spaces.ts b/x-pack/test/api_integration/apis/ml/jobs/delete_jobs_spaces.ts index 547d71ddc28428..58fd588db11f85 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/delete_jobs_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/delete_jobs_spaces.ts @@ -22,15 +22,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(space: string, expectedStatusCode: number, jobIds?: string[]) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/jobs/delete_jobs`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send({ jobIds }) - .expect(expectedStatusCode); + .send({ jobIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds.ts b/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds.ts index 04ab308a0d7b29..fae4c706660482 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds.ts @@ -24,12 +24,12 @@ export default ({ getService }: FtrProviderContext) => { requestBody: object, expectedResponsecode: number ): Promise> { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/force_start_datafeeds') .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedResponsecode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedResponsecode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds_spaces.ts b/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds_spaces.ts index 1ebc6c5b784242..9e282a4ef9a11a 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/force_start_datafeeds_spaces.ts @@ -34,15 +34,15 @@ export default ({ getService }: FtrProviderContext) => { start: number, end: number ): Promise> { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/jobs/force_start_datafeeds`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send({ datafeedIds, start, end }) - .expect(expectedStatusCode); + .send({ datafeedIds, start, end }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/jobs_exist.ts b/x-pack/test/api_integration/apis/ml/jobs/jobs_exist.ts index 81c19c07acbcb9..a3ba73338bee93 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/jobs_exist.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/jobs_exist.ts @@ -70,12 +70,12 @@ export default ({ getService }: FtrProviderContext) => { requestBody: object, expectedResponsecode: number ): Promise { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/jobs_exist') .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedResponsecode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedResponsecode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/jobs_exist_spaces.ts b/x-pack/test/api_integration/apis/ml/jobs/jobs_exist_spaces.ts index caf574596f31a1..8a7ddb91b76239 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/jobs_exist_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/jobs_exist_spaces.ts @@ -23,15 +23,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(space: string, expectedStatusCode: number, jobIds?: string[]) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/jobs/jobs_exist`) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send({ jobIds }) - .expect(expectedStatusCode); + .send({ jobIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts b/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts index b846dd38d5c8ec..38d257837fbf5c 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts @@ -174,12 +174,12 @@ export default ({ getService }: FtrProviderContext) => { requestBody: object, expectedResponsecode: number ): Promise { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/jobs_summary') .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedResponsecode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedResponsecode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/jobs_summary_spaces.ts b/x-pack/test/api_integration/apis/ml/jobs/jobs_summary_spaces.ts index e0cf731e16ede5..afc9b9085ea2ff 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/jobs_summary_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/jobs_summary_spaces.ts @@ -21,15 +21,15 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(space: string, expectedStatusCode: number, jobIds?: string[]) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/jobs/jobs_summary`) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send({ jobIds }) - .expect(expectedStatusCode); + .send({ jobIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds.ts b/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds.ts index 593dfdd2fdfe70..f837f58543f5b2 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds.ts @@ -24,12 +24,12 @@ export default ({ getService }: FtrProviderContext) => { requestBody: object, expectedResponsecode: number ): Promise> { - const { body } = await supertest + const { body, status } = await supertest .post('/api/ml/jobs/stop_datafeeds') .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedResponsecode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedResponsecode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds_spaces.ts b/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds_spaces.ts index 0e1ac038dc962d..7af6a6f3ea1bcf 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/stop_datafeeds_spaces.ts @@ -30,15 +30,15 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, datafeedIds: string[] ): Promise> { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${space}/api/ml/jobs/stop_datafeeds`) .auth( USER.ML_POWERUSER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) ) .set(COMMON_REQUEST_HEADERS) - .send({ datafeedIds }) - .expect(expectedStatusCode); + .send({ datafeedIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/modules/get_module.ts b/x-pack/test/api_integration/apis/ml/modules/get_module.ts index b36b67bbb813b3..325e89dbb058af 100644 --- a/x-pack/test/api_integration/apis/ml/modules/get_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/get_module.ts @@ -46,11 +46,11 @@ export default ({ getService }: FtrProviderContext) => { const ml = getService('ml'); async function executeGetModuleRequest(module: string, user: USER, rspCode: number) { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/modules/get_module/${module}`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(rspCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(rspCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index 0cfa90a8c3a882..ed9aec07acffac 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -205,11 +205,11 @@ export default ({ getService }: FtrProviderContext) => { ]; async function executeRecognizeModuleRequest(indexPattern: string, user: USER, rspCode: number) { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/modules/recognize/${indexPattern}`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(rspCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(rspCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 37878a60c6e974..c9a1c79c260fa8 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -965,12 +965,12 @@ export default ({ getService }: FtrProviderContext) => { rqBody: object, rspCode: number ) { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/modules/setup/${module}`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(rqBody) - .expect(rspCode); + .send(rqBody); + ml.api.assertResponseStatusCode(rspCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts b/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts index 9596cf7ab7d9b6..d062b1880e1bf6 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts @@ -72,12 +72,12 @@ export default ({ getService }: FtrProviderContext) => { maxRecords: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/anomalies_table_data`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(200); + .send(requestBody); + ml.api.assertResponseStatusCode(200, status, body); expect(body.interval).to.eql('hour'); expect(body.anomalies.length).to.eql(13); @@ -97,12 +97,12 @@ export default ({ getService }: FtrProviderContext) => { maxRecords: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/anomalies_table_data`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(400); + .send(requestBody); + ml.api.assertResponseStatusCode(400, status, body); expect(body.error).to.eql('Bad Request'); expect(body.message).to.eql( @@ -122,12 +122,12 @@ export default ({ getService }: FtrProviderContext) => { dateFormatTz: 'UTC', maxRecords: 500, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/anomalies_table_data`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(403); + .send(requestBody); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.eql('Forbidden'); expect(body.message).to.eql('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts index 5555d4a91cd1ae..c63729cd7ac96b 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts @@ -65,11 +65,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch all the categorizer stats for job id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/results/${jobId}/categorizer_stats`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); body.forEach((doc: AnomalyCategorizerStatsDoc) => { expect(doc.job_id).to.eql(jobId); @@ -80,11 +80,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch categorizer stats for job id for user with view permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/results/${jobId}/categorizer_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); body.forEach((doc: AnomalyCategorizerStatsDoc) => { expect(doc.job_id).to.eql(jobId); @@ -95,23 +95,24 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not fetch categorizer stats for job id for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/results/${jobId}/categorizer_stats`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.be('Forbidden'); expect(body.message).to.be('Forbidden'); }); it('should fetch all the categorizer stats with per-partition value for job id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/results/${jobId}/categorizer_stats`) .query({ partitionByValue: 'sample_web_logs' }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + body.forEach((doc: AnomalyCategorizerStatsDoc) => { expect(doc.job_id).to.eql(jobId); expect(doc.result_type).to.eql('categorizer_stats'); @@ -121,12 +122,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should fetch categorizer stats with per-partition value for user with view permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/results/${jobId}/categorizer_stats`) .query({ partitionByValue: 'sample_web_logs' }) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); body.forEach((doc: AnomalyCategorizerStatsDoc) => { expect(doc.job_id).to.eql(jobId); @@ -137,12 +138,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not fetch categorizer stats with per-partition value for unauthorized user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/results/${jobId}/categorizer_stats`) .query({ partitionByValue: 'sample_web_logs' }) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.be('Forbidden'); expect(body.message).to.be('Forbidden'); diff --git a/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts b/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts index 36f0f3946ac665..a0dfa0e795f5a2 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_category_definition.ts @@ -66,12 +66,12 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/results/category_definition`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send({ jobId, categoryId }) - .expect(expectedStatusCode); + .send({ jobId, categoryId }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts b/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts index 79586daaa4aac4..3f410dc3ce3cde 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_category_examples.ts @@ -67,12 +67,12 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/results/category_examples`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send({ jobId, categoryIds, maxExamples }) - .expect(expectedStatusCode); + .send({ jobId, categoryIds, maxExamples }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts index d33062af07b68a..2ce6e8da49797b 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts @@ -102,12 +102,13 @@ export default ({ getService }: FtrProviderContext) => { it('should fetch all the stopped partitions correctly', async () => { const { jobId } = testSetUps[0]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/category_stopped_partitions`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .send({ jobIds: [jobId] }) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.jobs).to.not.be(undefined); expect(body.jobs[jobId]).to.be.an('array'); expect(body.jobs[jobId].length).to.be.greaterThan(0); @@ -115,24 +116,25 @@ export default ({ getService }: FtrProviderContext) => { it('should not return jobId in response if stopped_on_warn is false', async () => { const { jobId } = testSetUps[1]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/category_stopped_partitions`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .send({ jobIds: [jobId] }) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.jobs).to.not.be(undefined); expect(body.jobs).to.not.have.property(jobId); }); it('should fetch stopped partitions for user with view permission', async () => { const { jobId } = testSetUps[2]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/category_stopped_partitions`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .send({ jobIds: [jobId] }) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.jobs).to.not.be(undefined); expect(body.jobs[jobId]).to.be.an('array'); @@ -142,24 +144,25 @@ export default ({ getService }: FtrProviderContext) => { it('should not fetch stopped partitions for unauthorized user', async () => { const { jobId } = testSetUps[3]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/category_stopped_partitions`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) .send({ jobIds: [jobId] }) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); expect(body.error).to.be('Forbidden'); expect(body.message).to.be('Forbidden'); }); it('should fetch stopped partitions for multiple job ids', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/category_stopped_partitions`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .send({ jobIds: testJobIds }) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.jobs).to.not.be(undefined); expect(body.jobs).to.not.have.property(testSetUps[1].jobId); @@ -171,12 +174,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return array of jobIds with stopped_partitions for multiple job ids when bucketed by job_id', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/results/category_stopped_partitions`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .send({ jobIds: testJobIds, fieldToBucket: 'job_id' }) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.jobs).to.not.be(undefined); body.jobs.forEach((currentJobId: string) => { diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts b/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts index 19d50474fcc733..8218b9cd84a940 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts @@ -29,12 +29,13 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, space?: string ) { - const { body } = await supertest + const { body, status } = await supertest .post(`${space ? `/s/${space}` : ''}/api/ml/saved_objects/can_delete_job/${jobType}`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send({ jobIds }) - .expect(expectedStatusCode); + .send({ jobIds }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); + return body; } diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/initialize.ts b/x-pack/test/api_integration/apis/ml/saved_objects/initialize.ts index 47aa9603a645e4..e9a510b1ac2a2c 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/initialize.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/initialize.ts @@ -21,11 +21,11 @@ export default ({ getService }: FtrProviderContext) => { const dfaJobId = 'ihp_od'; async function runRequest(user: USER, expectedStatusCode: number) { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/saved_objects/initialize`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/jobs_spaces.ts b/x-pack/test/api_integration/apis/ml/saved_objects/jobs_spaces.ts index 9dbf6657cd5937..2aa2c7e13c4abd 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/jobs_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/jobs_spaces.ts @@ -24,11 +24,11 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(expectedStatusCode: number, user: USER) { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/saved_objects/jobs_spaces`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/status.ts b/x-pack/test/api_integration/apis/ml/saved_objects/status.ts index 41c94e3b46683c..ec5429193dd3fb 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/status.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/status.ts @@ -26,14 +26,14 @@ export default ({ getService }: FtrProviderContext) => { const idSpace2 = 'space2'; async function runRequest(expectedStatusCode: number) { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/saved_objects/status`) .auth( USER.ML_VIEWER_ALL_SPACES, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) ) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts b/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts index b5d37730eb787c..695705b0bc44f7 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts @@ -24,22 +24,22 @@ export default ({ getService }: FtrProviderContext) => { const idSpace1 = 'space1'; async function runSyncRequest(user: USER, expectedStatusCode: number) { - const { body } = await supertest + const { body, status } = await supertest .get(`/s/${idSpace1}/api/ml/saved_objects/sync`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(expectedStatusCode); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } async function runSyncCheckRequest(user: USER, jobType: JobType, expectedStatusCode: number) { - const { body } = await supertest + const { body, status } = await supertest .post(`/s/${idSpace1}/api/ml/saved_objects/sync_check`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send({ jobType }) - .expect(expectedStatusCode); + .send({ jobType }); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/update_jobs_spaces.ts b/x-pack/test/api_integration/apis/ml/saved_objects/update_jobs_spaces.ts index 2a8fc9b3d230ab..969161cf4479bc 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/update_jobs_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/update_jobs_spaces.ts @@ -33,12 +33,12 @@ export default ({ getService }: FtrProviderContext) => { expectedStatusCode: number, user: USER ) { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/ml/saved_objects/update_jobs_spaces`) .auth(user, ml.securityCommon.getPasswordForUser(user)) .set(COMMON_REQUEST_HEADERS) - .send(requestBody) - .expect(expectedStatusCode); + .send(requestBody); + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/system/capabilities.ts b/x-pack/test/api_integration/apis/ml/system/capabilities.ts index aba433cc92e4ab..9d5d5a355e7c60 100644 --- a/x-pack/test/api_integration/apis/ml/system/capabilities.ts +++ b/x-pack/test/api_integration/apis/ml/system/capabilities.ts @@ -19,11 +19,11 @@ export default ({ getService }: FtrProviderContext) => { const ml = getService('ml'); async function runRequest(user: USER): Promise { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/ml_capabilities`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts b/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts index cbd38cdfdc6145..23b36a2601c094 100644 --- a/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts +++ b/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts @@ -23,11 +23,11 @@ export default ({ getService }: FtrProviderContext) => { const ml = getService('ml'); async function runRequest(user: USER, space?: string): Promise { - const { body } = await supertest + const { body, status } = await supertest .get(`${space ? `/s/${space}` : ''}/api/ml/ml_capabilities`) .auth(user, ml.securityCommon.getPasswordForUser(user)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); return body; } diff --git a/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts index 7124a9566c7105..4c41c7de88addc 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts @@ -25,43 +25,43 @@ export default ({ getService }: FtrProviderContext) => { }); it('deletes trained model by id', async () => { - const { body: deleteResponseBody } = await supertest + const { body: deleteResponseBody, status: deleteResponseStatus } = await supertest .delete(`/api/ml/trained_models/dfa_regression_model_n_0`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, deleteResponseStatus, deleteResponseBody); expect(deleteResponseBody).to.eql({ acknowledged: true }); // verify that model is actually deleted - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_0`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); }); it('returns 404 if requested trained model does not exist', async () => { - await supertest + const { body, status } = await supertest .delete(`/api/ml/trained_models/not_existing_model`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); }); it('does not allow to delete trained model if the user does not have required permissions', async () => { - await supertest + const { body: deleteResponseBody, status: deleteResponseStatus } = await supertest .delete(`/api/ml/trained_models/dfa_regression_model_n_1`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, deleteResponseStatus, deleteResponseBody); // verify that model has not been deleted - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_1`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts index 9600972e3e8bee..27859d850e1191 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts @@ -29,11 +29,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('returns trained model pipelines by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_0/pipelines`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.length).to.eql(1); expect(body[0].model_id).to.eql('dfa_regression_model_n_0'); @@ -41,11 +41,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('returns an error in case user does not have required permission', async () => { - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_0/pipelines`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts index 48040959f0e4bd..ea5e49619032ba 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts @@ -25,30 +25,30 @@ export default ({ getService }: FtrProviderContext) => { }); it('returns trained model stats by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_0/_stats`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); expect(body.count).to.eql(1); expect(body.trained_model_stats[0].model_id).to.eql('dfa_regression_model_n_0'); }); it('returns 404 if requested trained model does not exist', async () => { - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/not_existing_model/_stats`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); }); it('returns an error for unauthorized user', async () => { - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_0/_stats`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts index ec33ef316828c5..86a65295833ae8 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts @@ -35,11 +35,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('returns all trained models with associated pipelines including aliases', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models?with_pipelines=true`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + // Created models + system model expect(body.length).to.eql(6); @@ -48,11 +49,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('returns models without pipeline in case user does not have required permission', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models?with_pipelines=true`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + // Created models + system model expect(body.length).to.eql(6); const sampleModel = body.find((v: any) => v.model_id === 'dfa_regression_model_n_0'); @@ -60,29 +62,30 @@ export default ({ getService }: FtrProviderContext) => { }); it('returns trained model by id', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_1`) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(200, status, body); + expect(body.length).to.eql(1); expect(body[0].model_id).to.eql('dfa_regression_model_n_1'); }); it('returns 404 if requested trained model does not exist', async () => { - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/not_existing_model`) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) - .set(COMMON_REQUEST_HEADERS) - .expect(404); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(404, status, body); }); it('returns an error for unauthorized user', async () => { - await supertest + const { body, status } = await supertest .get(`/api/ml/trained_models/dfa_regression_model_n_1`) .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) - .set(COMMON_REQUEST_HEADERS) - .expect(403); + .set(COMMON_REQUEST_HEADERS); + ml.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/transform/delete_transforms.ts b/x-pack/test/api_integration/apis/transform/delete_transforms.ts index 5dfc318571e9ae..b823c46509a636 100644 --- a/x-pack/test/api_integration/apis/transform/delete_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/delete_transforms.ts @@ -54,15 +54,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: DeleteTransformsRequestSchema = { transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }], }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body[transformId].transformDeleted.success).to.eql(true); expect(body[transformId].destIndexDeleted.success).to.eql(false); @@ -75,15 +75,16 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: DeleteTransformsRequestSchema = { transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }], }; - await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(403); + .send(reqBody); + transform.api.assertResponseStatusCode(403, status, body); + await transform.api.waitForTransformToExist(transformId); await transform.api.waitForIndicesToExist(destinationIndex); }); @@ -94,15 +95,16 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: DeleteTransformsRequestSchema = { transformsInfo: [{ id: 'invalid_transform_id', state: TRANSFORM_STATE.STOPPED }], }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); + expect(body.invalid_transform_id.transformDeleted.success).to.eql(false); expect(body.invalid_transform_id.transformDeleted).to.have.property('error'); }); @@ -131,15 +133,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should delete multiple transforms by transformIds', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); await asyncForEach( reqBody.transformsInfo, @@ -155,7 +157,7 @@ export default ({ getService }: FtrProviderContext) => { it('should delete multiple transforms by transformIds, even if one of the transformIds is invalid', async () => { const invalidTransformId = 'invalid_transform_id'; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, @@ -168,8 +170,8 @@ export default ({ getService }: FtrProviderContext) => { ...reqBody.transformsInfo, { id: invalidTransformId, state: TRANSFORM_STATE.STOPPED }, ], - }) - .expect(200); + }); + transform.api.assertResponseStatusCode(200, status, body); await asyncForEach( reqBody.transformsInfo, @@ -205,15 +207,15 @@ export default ({ getService }: FtrProviderContext) => { transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }], deleteDestIndex: true, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body[transformId].transformDeleted.success).to.eql(true); expect(body[transformId].destIndexDeleted.success).to.eql(true); @@ -244,15 +246,15 @@ export default ({ getService }: FtrProviderContext) => { deleteDestIndex: false, deleteDestIndexPattern: true, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body[transformId].transformDeleted.success).to.eql(true); expect(body[transformId].destIndexDeleted.success).to.eql(false); @@ -284,15 +286,15 @@ export default ({ getService }: FtrProviderContext) => { deleteDestIndex: true, deleteDestIndexPattern: true, }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/delete_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body[transformId].transformDeleted.success).to.eql(true); expect(body[transformId].destIndexDeleted.success).to.eql(true); diff --git a/x-pack/test/api_integration/apis/transform/reset_transforms.ts b/x-pack/test/api_integration/apis/transform/reset_transforms.ts index 16d92ff133400d..f94a12e4d8e8a3 100644 --- a/x-pack/test/api_integration/apis/transform/reset_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/reset_transforms.ts @@ -72,15 +72,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: ResetTransformsRequestSchema = { transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }], }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/reset_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); // Check that transform was reset and assert stats. expect(body[transformId].transformReset.success).to.eql(true); @@ -102,15 +102,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: ResetTransformsRequestSchema = { transformsInfo: [{ id: transformId, state: TRANSFORM_STATE.STOPPED }], }; - await supertest + const { body, status } = await supertest .post(`/api/transform/reset_transforms`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(403); + .send(reqBody); + transform.api.assertResponseStatusCode(403, status, body); await transform.api.waitForTransformToExist(transformId); // Check that transform was not reset by asserting unchanged stats. @@ -126,15 +126,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: ResetTransformsRequestSchema = { transformsInfo: [{ id: 'invalid_transform_id', state: TRANSFORM_STATE.STOPPED }], }; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/reset_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body.invalid_transform_id.transformReset.success).to.eql(false); expect(body.invalid_transform_id.transformReset).to.have.property('error'); @@ -174,15 +174,15 @@ export default ({ getService }: FtrProviderContext) => { } ); - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/reset_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); await asyncForEach( reqBody.transformsInfo, @@ -213,7 +213,7 @@ export default ({ getService }: FtrProviderContext) => { ); const invalidTransformId = 'invalid_transform_id'; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/reset_transforms`) .auth( USER.TRANSFORM_POWERUSER, @@ -226,8 +226,8 @@ export default ({ getService }: FtrProviderContext) => { ...reqBody.transformsInfo, { id: invalidTransformId, state: TRANSFORM_STATE.STOPPED }, ], - }) - .expect(200); + }); + transform.api.assertResponseStatusCode(200, status, body); await asyncForEach( reqBody.transformsInfo, diff --git a/x-pack/test/api_integration/apis/transform/start_transforms.ts b/x-pack/test/api_integration/apis/transform/start_transforms.ts index 5abfa92bd5d9b3..07a7f6715305a9 100644 --- a/x-pack/test/api_integration/apis/transform/start_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/start_transforms.ts @@ -48,15 +48,15 @@ export default ({ getService }: FtrProviderContext) => { it('should start the transform by transformId', async () => { const reqBody: StartTransformsRequestSchema = [{ id: transformId }]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/start_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body[transformId].success).to.eql(true); expect(typeof body[transformId].error).to.eql('undefined'); @@ -66,15 +66,15 @@ export default ({ getService }: FtrProviderContext) => { it('should return 200 with success:false for unauthorized user', async () => { const reqBody: StartTransformsRequestSchema = [{ id: transformId }]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/start_transforms`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body[transformId].success).to.eql(false); expect(typeof body[transformId].error).to.eql('object'); @@ -87,15 +87,15 @@ export default ({ getService }: FtrProviderContext) => { describe('single transform start with invalid transformId', function () { it('should return 200 with error in response if invalid transformId', async () => { const reqBody: StartTransformsRequestSchema = [{ id: 'invalid_transform_id' }]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/start_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(body.invalid_transform_id.success).to.eql(false); expect(body.invalid_transform_id).to.have.property('error'); @@ -123,15 +123,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should start multiple transforms by transformIds', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/start_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); await asyncForEach(reqBody, async ({ id: transformId }: { id: string }, idx: number) => { expect(body[transformId].success).to.eql(true); @@ -142,15 +142,15 @@ export default ({ getService }: FtrProviderContext) => { it('should start multiple transforms by transformIds, even if one of the transformIds is invalid', async () => { const invalidTransformId = 'invalid_transform_id'; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/start_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send([{ id: reqBody[0].id }, { id: invalidTransformId }, { id: reqBody[1].id }]) - .expect(200); + .send([{ id: reqBody[0].id }, { id: invalidTransformId }, { id: reqBody[1].id }]); + transform.api.assertResponseStatusCode(200, status, body); await asyncForEach(reqBody, async ({ id: transformId }: { id: string }, idx: number) => { expect(body[transformId].success).to.eql(true); diff --git a/x-pack/test/api_integration/apis/transform/stop_transforms.ts b/x-pack/test/api_integration/apis/transform/stop_transforms.ts index 295c6cea7f5952..af07dec07e547b 100644 --- a/x-pack/test/api_integration/apis/transform/stop_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/stop_transforms.ts @@ -66,15 +66,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: StopTransformsRequestSchema = [ { id: transformId, state: TRANSFORM_STATE.STARTED }, ]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/stop_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body[transformId].success).to.eql(true); @@ -87,15 +87,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: StopTransformsRequestSchema = [ { id: transformId, state: TRANSFORM_STATE.STARTED }, ]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/stop_transforms`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body[transformId].success).to.eql(false); @@ -111,15 +111,15 @@ export default ({ getService }: FtrProviderContext) => { const reqBody: StopTransformsRequestSchema = [ { id: 'invalid_transform_id', state: TRANSFORM_STATE.STARTED }, ]; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/stop_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(isStopTransformsResponseSchema(body)).to.eql(true); expect(body.invalid_transform_id.success).to.eql(false); @@ -148,15 +148,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should stop multiple transforms by transformIds', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/stop_transforms`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(reqBody) - .expect(200); + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); expect(isStopTransformsResponseSchema(body)).to.eql(true); @@ -169,7 +169,7 @@ export default ({ getService }: FtrProviderContext) => { it('should stop multiple transforms by transformIds, even if one of the transformIds is invalid', async () => { const invalidTransformId = 'invalid_transform_id'; - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/stop_transforms`) .auth( USER.TRANSFORM_POWERUSER, @@ -180,8 +180,8 @@ export default ({ getService }: FtrProviderContext) => { { id: reqBody[0].id, state: reqBody[0].state }, { id: invalidTransformId, state: TRANSFORM_STATE.STOPPED }, { id: reqBody[1].id, state: reqBody[1].state }, - ]) - .expect(200); + ]); + transform.api.assertResponseStatusCode(200, status, body); expect(isStopTransformsResponseSchema(body)).to.eql(true); diff --git a/x-pack/test/api_integration/apis/transform/transforms.ts b/x-pack/test/api_integration/apis/transform/transforms.ts index 9993cbe0328ff5..d962aa1f08eb2c 100644 --- a/x-pack/test/api_integration/apis/transform/transforms.ts +++ b/x-pack/test/api_integration/apis/transform/transforms.ts @@ -94,29 +94,29 @@ export default ({ getService }: FtrProviderContext) => { describe('/transforms', function () { it('should return a list of transforms for super-user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get('/api/transform/transforms') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertTransformsResponseBody(body); }); it('should return a list of transforms for transform view-only user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/transform/transforms`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertTransformsResponseBody(body); }); @@ -124,43 +124,43 @@ export default ({ getService }: FtrProviderContext) => { describe('/transforms/{transformId}', function () { it('should return a specific transform configuration for super-user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get('/api/transform/transforms/transform-test-get-1') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertSingleTransformResponseBody(body); }); it('should return a specific transform configuration transform view-only user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/transform/transforms/transform-test-get-1`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertSingleTransformResponseBody(body); }); it('should report 404 for a non-existing transform', async () => { - await supertest + const { body, status } = await supertest .get('/api/transform/transforms/the-non-existing-transform') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(404); + .send(); + transform.api.assertResponseStatusCode(404, status, body); }); }); }); diff --git a/x-pack/test/api_integration/apis/transform/transforms_create.ts b/x-pack/test/api_integration/apis/transform/transforms_create.ts index 46112ced0619ed..512bd16969e2df 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_create.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_create.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext) => { it('should not allow pivot and latest configs in same transform', async () => { const transformId = 'test_transform_id'; - const { body } = await supertest + const { body, status } = await supertest .put(`/api/transform/transforms/${transformId}`) .auth( USER.TRANSFORM_POWERUSER, @@ -43,8 +43,8 @@ export default ({ getService }: FtrProviderContext) => { unique_key: ['country', 'gender'], sort: 'infected', }, - }) - .expect(400); + }); + transform.api.assertResponseStatusCode(400, status, body); expect(body.message).to.eql('[request body]: pivot and latest are not allowed together'); }); @@ -54,15 +54,15 @@ export default ({ getService }: FtrProviderContext) => { const { pivot, ...config } = generateTransformConfig(transformId); - const { body } = await supertest + const { body, status } = await supertest .put(`/api/transform/transforms/${transformId}`) .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(config) - .expect(400); + .send(config); + transform.api.assertResponseStatusCode(400, status, body); expect(body.message).to.eql( '[request body]: pivot or latest is required for transform configuration' diff --git a/x-pack/test/api_integration/apis/transform/transforms_nodes.ts b/x-pack/test/api_integration/apis/transform/transforms_nodes.ts index 0fc93289195d9d..ef39e963f137d4 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_nodes.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_nodes.ts @@ -32,43 +32,43 @@ export default ({ getService }: FtrProviderContext) => { describe('/api/transform/transforms/_nodes', function () { it('should return the number of available transform nodes for a power user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get('/api/transform/transforms/_nodes') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertTransformsNodesResponseBody(body); }); it('should return the number of available transform nodes for a viewer user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get('/api/transform/transforms/_nodes') .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertTransformsNodesResponseBody(body); }); it('should not return the number of available transform nodes for an unauthorized user', async () => { - await supertest + const { body, status } = await supertest .get('/api/transform/transforms/_nodes') .auth( USER.TRANSFORM_UNAUTHORIZED, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_UNAUTHORIZED) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(403); + .send(); + transform.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/transform/transforms_preview.ts b/x-pack/test/api_integration/apis/transform/transforms_preview.ts index 5fd75a6bb98abb..ec35f04ab817cf 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_preview.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_preview.ts @@ -43,15 +43,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a transform preview', async () => { - const { body } = await supertest + const { body, status } = await supertest .post('/api/transform/transforms/_preview') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send(getTransformPreviewConfig()) - .expect(200); + .send(getTransformPreviewConfig()); + transform.api.assertResponseStatusCode(200, status, body); expect(body.preview).to.have.length(expected.apiTransformTransformsPreview.previewItemCount); expect(typeof body.generated_dest_index).to.eql( @@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a correct error for transform preview', async () => { - const { body } = await supertest + const { body, status } = await supertest .post(`/api/transform/transforms/_preview`) .auth( USER.TRANSFORM_POWERUSER, @@ -75,8 +75,8 @@ export default ({ getService }: FtrProviderContext) => { '@timestamp.value_count': { value_countt: { field: '@timestamp' } }, }, }, - }) - .expect(400); + }); + transform.api.assertResponseStatusCode(400, status, body); expect(body.message).to.contain( '[parsing_exception] Unknown aggregation type [value_countt] did you mean [value_count]?, with line=1 & col=43' @@ -84,15 +84,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 403 for transform view-only user', async () => { - await supertest + const { body, status } = await supertest .post(`/api/transform/transforms/_preview`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send(getTransformPreviewConfig()) - .expect(403); + .send(getTransformPreviewConfig()); + transform.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/apis/transform/transforms_stats.ts b/x-pack/test/api_integration/apis/transform/transforms_stats.ts index 82a7d7efcc270c..4d6b761a7828f1 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_stats.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_stats.ts @@ -73,29 +73,29 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a list of transforms statistics for super-user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get('/api/transform/transforms/_stats') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertTransformsStatsResponseBody(body); }); it('should return a list of transforms statistics view-only user', async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/transform/transforms/_stats`) .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, status, body); assertTransformsStatsResponseBody(body); }); diff --git a/x-pack/test/api_integration/apis/transform/transforms_update.ts b/x-pack/test/api_integration/apis/transform/transforms_update.ts index e5f1ce76a997bd..f08dd282bed011 100644 --- a/x-pack/test/api_integration/apis/transform/transforms_update.ts +++ b/x-pack/test/api_integration/apis/transform/transforms_update.ts @@ -70,15 +70,15 @@ export default ({ getService }: FtrProviderContext) => { it('should update a transform', async () => { // assert the original transform for comparison - const { body: transformOriginalBody } = await supertest + const { body: transformOriginalBody, status: transformOriginalStatus } = await supertest .get('/api/transform/transforms/transform-test-update-1') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, transformOriginalStatus, transformOriginalBody); expect(transformOriginalBody.count).to.eql(expected.transformOriginalConfig.count); expect(transformOriginalBody.transforms).to.have.length( @@ -92,15 +92,20 @@ export default ({ getService }: FtrProviderContext) => { expect(transformOriginalConfig.settings).to.eql({}); // update the transform and assert the response - const { body: transformUpdateResponseBody } = await supertest - .post('/api/transform/transforms/transform-test-update-1/_update') - .auth( - USER.TRANSFORM_POWERUSER, - transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) - ) - .set(COMMON_REQUEST_HEADERS) - .send(getTransformUpdateConfig()) - .expect(200); + const { body: transformUpdateResponseBody, status: transformUpdatedResponseStatus } = + await supertest + .post('/api/transform/transforms/transform-test-update-1/_update') + .auth( + USER.TRANSFORM_POWERUSER, + transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) + ) + .set(COMMON_REQUEST_HEADERS) + .send(getTransformUpdateConfig()); + transform.api.assertResponseStatusCode( + 200, + transformUpdatedResponseStatus, + transformUpdateResponseBody + ); const expectedUpdateConfig = getTransformUpdateConfig(); expect(transformUpdateResponseBody.id).to.eql(expected.transformOriginalConfig.id); @@ -112,15 +117,15 @@ export default ({ getService }: FtrProviderContext) => { expect(transformUpdateResponseBody.settings).to.eql({}); // assert the updated transform for comparison - const { body: transformUpdatedBody } = await supertest + const { body: transformUpdatedBody, status: transformUpdatedStatus } = await supertest .get('/api/transform/transforms/transform-test-update-1') .auth( USER.TRANSFORM_POWERUSER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) ) .set(COMMON_REQUEST_HEADERS) - .send() - .expect(200); + .send(); + transform.api.assertResponseStatusCode(200, transformUpdatedStatus, transformUpdatedBody); expect(transformUpdatedBody.count).to.eql(expected.transformOriginalConfig.count); expect(transformUpdatedBody.transforms).to.have.length( @@ -138,15 +143,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 403 for transform view-only user', async () => { - await supertest + const { body, status } = await supertest .post('/api/transform/transforms/transform-test-update-1/_update') .auth( USER.TRANSFORM_VIEWER, transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) ) .set(COMMON_REQUEST_HEADERS) - .send(getTransformUpdateConfig()) - .expect(403); + .send(getTransformUpdateConfig()); + transform.api.assertResponseStatusCode(403, status, body); }); }); }; diff --git a/x-pack/test/api_integration/services/ml.ts b/x-pack/test/api_integration/services/ml.ts index 295ee400aa6f0b..728abd9bc6e8f0 100644 --- a/x-pack/test/api_integration/services/ml.ts +++ b/x-pack/test/api_integration/services/ml.ts @@ -20,7 +20,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const commonConfig = MachineLearningCommonConfigsProvider(context); const securityCommon = MachineLearningSecurityCommonProvider(context); const testExecution = MachineLearningTestExecutionProvider(context); - const testResources = MachineLearningTestResourcesProvider(context); + const testResources = MachineLearningTestResourcesProvider(context, api); return { api, diff --git a/x-pack/test/api_integration/services/transform.ts b/x-pack/test/api_integration/services/transform.ts index 59e662fab20003..16876413b1239f 100644 --- a/x-pack/test/api_integration/services/transform.ts +++ b/x-pack/test/api_integration/services/transform.ts @@ -9,12 +9,14 @@ import { FtrProviderContext } from '../../functional/ftr_provider_context'; import { TransformAPIProvider } from '../../functional/services/transform/api'; import { TransformSecurityCommonProvider } from '../../functional/services/transform/security_common'; +import { MachineLearningAPIProvider } from '../../functional/services/ml/api'; import { MachineLearningTestResourcesProvider } from '../../functional/services/ml/test_resources'; export function TransformProvider(context: FtrProviderContext) { const api = TransformAPIProvider(context); + const mlApi = MachineLearningAPIProvider(context); const securityCommon = TransformSecurityCommonProvider(context); - const testResources = MachineLearningTestResourcesProvider(context); + const testResources = MachineLearningTestResourcesProvider(context, mlApi); return { api, diff --git a/x-pack/test/functional/services/ml/alerting.ts b/x-pack/test/functional/services/ml/alerting.ts index 241ffde6f7628e..45c4c9ba39ed19 100644 --- a/x-pack/test/functional/services/ml/alerting.ts +++ b/x-pack/test/functional/services/ml/alerting.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MlApi } from './api'; import { MlCommonUI } from './common_ui'; import { ML_ALERT_TYPES } from '../../../../plugins/ml/common/constants/alerts'; import { Alert } from '../../../../plugins/alerting/common'; @@ -14,6 +15,7 @@ import { MlAnomalyDetectionAlertParams } from '../../../../plugins/ml/common/typ export function MachineLearningAlertingProvider( { getService }: FtrProviderContext, + mlApi: MlApi, mlCommonUI: MlCommonUI ) { const retry = getService('retry'); @@ -153,16 +155,19 @@ export function MachineLearningAlertingProvider( }, async cleanAnomalyDetectionRules() { - const { body: anomalyDetectionRules } = await supertest + const { body: anomalyDetectionRules, status: findResponseStatus } = await supertest .get(`/api/alerting/rules/_find`) .query({ filter: `alert.attributes.alertTypeId:${ML_ALERT_TYPES.ANOMALY_DETECTION}` }) - .set('kbn-xsrf', 'foo') - .expect(200); + .set('kbn-xsrf', 'foo'); + mlApi.assertResponseStatusCode(200, findResponseStatus, anomalyDetectionRules); for (const rule of anomalyDetectionRules.data as Array< Alert >) { - await supertest.delete(`/api/alerting/rule/${rule.id}`).set('kbn-xsrf', 'foo').expect(204); + const { body, status } = await supertest + .delete(`/api/alerting/rule/${rule.id}`) + .set('kbn-xsrf', 'foo'); + mlApi.assertResponseStatusCode(204, status, body); } }, }; diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index a93d85cc918557..1111429a46c77e 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -19,7 +19,6 @@ import { DataFrameTaskStateType } from '../../../../plugins/ml/common/types/data import { DATA_FRAME_TASK_STATE } from '../../../../plugins/ml/common/constants/data_frame_analytics'; import { Datafeed, Job } from '../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { JobType } from '../../../../plugins/ml/common/types/saved_objects'; -export type MlApi = ProvidedType; import { ML_ANNOTATIONS_INDEX_ALIAS_READ, ML_ANNOTATIONS_INDEX_ALIAS_WRITE, @@ -27,6 +26,8 @@ import { import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common_api'; import { PutTrainedModelConfig } from '../../../../plugins/ml/common/types/trained_models'; +export type MlApi = ProvidedType; + type ModelType = 'regression' | 'classification'; export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { @@ -38,6 +39,15 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const esDeleteAllIndices = getService('esDeleteAllIndices'); return { + assertResponseStatusCode(expectedStatus: number, actualStatus: number, responseBody: object) { + expect(actualStatus).to.eql( + expectedStatus, + `Expected status code ${expectedStatus}, got ${actualStatus} with body '${JSON.stringify( + responseBody + )}'` + ); + }, + async hasJobResults(jobId: string): Promise { const body = await es.search({ index: '.ml-anomalies-*', @@ -225,10 +235,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getADJobStats(jobId: string): Promise { log.debug(`Fetching anomaly detection job stats for job ${jobId}...`); - const jobStats = await esSupertest - .get(`/_ml/anomaly_detectors/${jobId}/_stats`) - .expect(200) - .then((res: any) => res.body); + const { body: jobStats, status } = await esSupertest.get( + `/_ml/anomaly_detectors/${jobId}/_stats` + ); + this.assertResponseStatusCode(200, status, jobStats); log.debug('> AD job stats fetched.'); return jobStats; @@ -251,10 +261,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getDatafeedState(datafeedId: string): Promise { log.debug(`Fetching datafeed state for datafeed ${datafeedId}`); - const datafeedStats = await esSupertest - .get(`/_ml/datafeeds/${datafeedId}/_stats`) - .expect(200) - .then((res: any) => res.body); + const { body: datafeedStats, status } = await esSupertest.get( + `/_ml/datafeeds/${datafeedId}/_stats` + ); + this.assertResponseStatusCode(200, status, datafeedStats); expect(datafeedStats.datafeeds).to.have.length( 1, @@ -286,10 +296,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getDFAJobStats(analyticsId: string): Promise { log.debug(`Fetching data frame analytics job stats for job ${analyticsId}...`); - const analyticsStats = await esSupertest - .get(`/_ml/data_frame/analytics/${analyticsId}/_stats`) - .expect(200) - .then((res: any) => res.body); + const { body: analyticsStats, status } = await esSupertest.get( + `/_ml/data_frame/analytics/${analyticsId}/_stats` + ); + this.assertResponseStatusCode(200, status, analyticsStats); log.debug('> DFA job stats fetched.'); return analyticsStats; @@ -398,7 +408,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async getCalendar(calendarId: string, expectedCode = 200) { - return await esSupertest.get(`/_ml/calendars/${calendarId}`).expect(expectedCode); + const response = await esSupertest.get(`/_ml/calendars/${calendarId}`); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; }, async createCalendar( @@ -406,7 +418,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { requestBody: Partial = { description: '', job_ids: [] } ) { log.debug(`Creating calendar with id '${calendarId}'...`); - await esSupertest.put(`/_ml/calendars/${calendarId}`).send(requestBody).expect(200); + const { body, status } = await esSupertest + .put(`/_ml/calendars/${calendarId}`) + .send(requestBody); + this.assertResponseStatusCode(200, status, body); + await this.waitForCalendarToExist(calendarId); log.debug('> Calendar created.'); }, @@ -441,13 +457,19 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async createCalendarEvents(calendarId: string, events: estypes.MlCalendarEvent[]) { log.debug(`Creating events for calendar with id '${calendarId}'...`); - await esSupertest.post(`/_ml/calendars/${calendarId}/events`).send({ events }).expect(200); + const { body, status } = await esSupertest + .post(`/_ml/calendars/${calendarId}/events`) + .send({ events }); + this.assertResponseStatusCode(200, status, body); + await this.waitForEventsToExistInCalendar(calendarId, events); log.debug('> Calendar events created.'); }, async getCalendarEvents(calendarId: string, expectedCode = 200) { - return await esSupertest.get(`/_ml/calendars/${calendarId}/events`).expect(expectedCode); + const response = await esSupertest.get(`/_ml/calendars/${calendarId}/events`); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; }, assertAllEventsExistInCalendar: ( @@ -509,7 +531,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async getAnomalyDetectionJob(jobId: string) { - return await esSupertest.get(`/_ml/anomaly_detectors/${jobId}`).expect(200); + const response = await esSupertest.get(`/_ml/anomaly_detectors/${jobId}`); + this.assertResponseStatusCode(200, response.status, response.body); + return response; }, async adJobExist(jobId: string) { @@ -534,7 +558,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async waitForAnomalyDetectionJobNotToExist(jobId: string, timeout: number = 5 * 1000) { await retry.waitForWithTimeout(`'${jobId}' to not exist`, timeout, async () => { - if (await esSupertest.get(`/_ml/anomaly_detectors/${jobId}`).expect(404)) { + const { status } = await esSupertest.get(`/_ml/anomaly_detectors/${jobId}`); + + if (status === 404) { return true; } else { throw new Error(`expected anomaly detection job '${jobId}' not to exist`); @@ -550,11 +576,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }...` ); - await kbnSupertest + const { body, status } = await kbnSupertest .put(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}`) .set(COMMON_REQUEST_HEADERS) - .send(jobConfig) - .expect(200); + .send(jobConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForAnomalyDetectionJobToExist(jobId); log.debug('> AD job created.'); @@ -564,7 +590,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const jobId = jobConfig.job_id; log.debug(`Creating anomaly detection job with id '${jobId}' via ES API...`); - await esSupertest.put(`/_ml/anomaly_detectors/${jobId}`).send(jobConfig).expect(200); + const { body, status } = await esSupertest + .put(`/_ml/anomaly_detectors/${jobId}`) + .send(jobConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForAnomalyDetectionJobToExist(jobId); log.debug('> AD job created.'); @@ -583,18 +612,19 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { await this.deleteDatafeedES(datafeedId); } - await esSupertest + const { body, status } = await esSupertest .delete(`/_ml/anomaly_detectors/${jobId}`) - .query({ force: true }) - .expect(200); + .query({ force: true }); + this.assertResponseStatusCode(200, status, body); await this.waitForAnomalyDetectionJobNotToExist(jobId); log.debug('> AD job deleted.'); }, async getDatafeed(datafeedId: string) { - return await esSupertest.get(`/_ml/datafeeds/${datafeedId}`).expect(200); - // return await kbnSupertest.get(`/api/ml/datafeeds/${datafeedId}`).expect(200); + const response = await esSupertest.get(`/_ml/datafeeds/${datafeedId}`); + this.assertResponseStatusCode(200, response.status, response.body); + return response; }, async datafeedExist(datafeedId: string) { @@ -631,11 +661,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug( `Creating datafeed with id '${datafeedId}' ${space ? `in space '${space}' ` : ''}...` ); - await kbnSupertest + const { body, status } = await kbnSupertest .put(`${space ? `/s/${space}` : ''}/api/ml/datafeeds/${datafeedId}`) .set(COMMON_REQUEST_HEADERS) - .send(datafeedConfig) - .expect(200); + .send(datafeedConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForDatafeedToExist(datafeedId); log.debug('> Datafeed created.'); @@ -644,7 +674,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async createDatafeedES(datafeedConfig: Datafeed) { const datafeedId = datafeedConfig.datafeed_id; log.debug(`Creating datafeed with id '${datafeedId}' via ES API ...`); - await esSupertest.put(`/_ml/datafeeds/${datafeedId}`).send(datafeedConfig).expect(200); + const { body, status } = await esSupertest + .put(`/_ml/datafeeds/${datafeedId}`) + .send(datafeedConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForDatafeedToExist(datafeedId); log.debug('> Datafeed created.'); @@ -652,7 +685,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async deleteDatafeedES(datafeedId: string) { log.debug(`Deleting datafeed with id '${datafeedId}' ...`); - await esSupertest.delete(`/_ml/datafeeds/${datafeedId}`).query({ force: true }).expect(200); + const { body, status } = await esSupertest + .delete(`/_ml/datafeeds/${datafeedId}`) + .query({ force: true }); + this.assertResponseStatusCode(200, status, body); await this.waitForDatafeedToNotExist(datafeedId); log.debug('> Datafeed deleted.'); @@ -660,12 +696,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async openAnomalyDetectionJob(jobId: string) { log.debug(`Opening anomaly detection job '${jobId}'...`); - const openResponse = await esSupertest + const { body: openResponse, status } = await esSupertest .post(`/_ml/anomaly_detectors/${jobId}/_open`) .send({ timeout: '10s' }) - .set({ 'Content-Type': 'application/json' }) - .expect(200) - .then((res: any) => res.body); + .set({ 'Content-Type': 'application/json' }); + this.assertResponseStatusCode(200, status, openResponse); expect(openResponse) .to.have.property('opened') @@ -675,12 +710,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async closeAnomalyDetectionJob(jobId: string) { log.debug(`Closing anomaly detection job '${jobId}'...`); - const closeResponse = await esSupertest + const { body: closeResponse, status } = await esSupertest .post(`/_ml/anomaly_detectors/${jobId}/_close`) .send({ timeout: '10s' }) - .set({ 'Content-Type': 'application/json' }) - .expect(200) - .then((res: any) => res.body); + .set({ 'Content-Type': 'application/json' }); + this.assertResponseStatusCode(200, status, closeResponse); expect(closeResponse) .to.have.property('closed') @@ -695,12 +729,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug( `Starting datafeed '${datafeedId}' with start: '${startConfig.start}', end: '${startConfig.end}'...` ); - const startResponse = await esSupertest + const { body: startResponse, status } = await esSupertest .post(`/_ml/datafeeds/${datafeedId}/_start`) .send(startConfig) - .set({ 'Content-Type': 'application/json' }) - .expect(200) - .then((res: any) => res.body); + .set({ 'Content-Type': 'application/json' }); + this.assertResponseStatusCode(200, status, startResponse); expect(startResponse) .to.have.property('started') @@ -710,11 +743,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async stopDatafeed(datafeedId: string) { log.debug(`Stopping datafeed '${datafeedId}'...`); - const stopResponse = await esSupertest + const { body: stopResponse, status } = await esSupertest .post(`/_ml/datafeeds/${datafeedId}/_stop`) - .set({ 'Content-Type': 'application/json' }) - .expect(200) - .then((res: any) => res.body); + .set({ 'Content-Type': 'application/json' }); + this.assertResponseStatusCode(200, status, stopResponse); expect(stopResponse) .to.have.property('stopped') @@ -737,9 +769,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getDataFrameAnalyticsJob(analyticsId: string, statusCode = 200) { log.debug(`Fetching data frame analytics job '${analyticsId}'...`); - const response = await esSupertest - .get(`/_ml/data_frame/analytics/${analyticsId}`) - .expect(statusCode); + const response = await esSupertest.get(`/_ml/data_frame/analytics/${analyticsId}`); + this.assertResponseStatusCode(statusCode, response.status, response.body); + log.debug('> DFA job fetched.'); return response; }, @@ -781,11 +813,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { space ? `in space '${space}' ` : '' }...` ); - await kbnSupertest + const { body, status } = await kbnSupertest .put(`${space ? `/s/${space}` : ''}/api/ml/data_frame/analytics/${analyticsId}`) .set(COMMON_REQUEST_HEADERS) - .send(analyticsConfig) - .expect(200); + .send(analyticsConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForDataFrameAnalyticsJobToExist(analyticsId); log.debug('> DFA job created.'); @@ -794,11 +826,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async createDataFrameAnalyticsJobES(jobConfig: DataFrameAnalyticsConfig) { const { id: analyticsId, ...analyticsConfig } = jobConfig; log.debug(`Creating data frame analytic job with id '${analyticsId}' via ES API...`); - await esSupertest + const { body, status } = await esSupertest .put(`/_ml/data_frame/analytics/${analyticsId}`) .set(COMMON_REQUEST_HEADERS) - .send(analyticsConfig) - .expect(200); + .send(analyticsConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForDataFrameAnalyticsJobToExist(analyticsId); log.debug('> DFA job created.'); @@ -812,10 +844,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return; } - await esSupertest + const { body, status } = await esSupertest .delete(`/_ml/data_frame/analytics/${analyticsId}`) - .query({ force: true }) - .expect(200); + .query({ force: true }); + this.assertResponseStatusCode(200, status, body); await this.waitForDataFrameAnalyticsJobNotToExist(analyticsId); log.debug('> DFA job deleted.'); @@ -851,12 +883,15 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async getFilter(filterId: string, expectedCode = 200) { - return await esSupertest.get(`/_ml/filters/${filterId}`).expect(expectedCode); + const response = await esSupertest.get(`/_ml/filters/${filterId}`); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; }, async createFilter(filterId: string, requestBody: object) { log.debug(`Creating filter with id '${filterId}'...`); - await esSupertest.put(`/_ml/filters/${filterId}`).send(requestBody).expect(200); + const { body, status } = await esSupertest.put(`/_ml/filters/${filterId}`).send(requestBody); + this.assertResponseStatusCode(200, status, body); await this.waitForFilterToExist(filterId, `expected filter '${filterId}' to be created`); log.debug('> Filter created.'); @@ -969,11 +1004,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async runDFAJob(dfaId: string) { log.debug(`Starting data frame analytics job '${dfaId}'...`); - const startResponse = await esSupertest + const { body: startResponse, status } = await esSupertest .post(`/_ml/data_frame/analytics/${dfaId}/_start`) - .set({ 'Content-Type': 'application/json' }) - .expect(200) - .then((res: any) => res.body); + .set({ 'Content-Type': 'application/json' }); + this.assertResponseStatusCode(200, status, startResponse); expect(startResponse) .to.have.property('acknowledged') @@ -996,20 +1030,20 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { spacesToRemove: string[], space?: string ) { - const { body } = await kbnSupertest + const { body, status } = await kbnSupertest .post(`${space ? `/s/${space}` : ''}/api/ml/saved_objects/update_jobs_spaces`) .set(COMMON_REQUEST_HEADERS) - .send({ jobType, jobIds: [jobId], spacesToAdd, spacesToRemove }) - .expect(200); + .send({ jobType, jobIds: [jobId], spacesToAdd, spacesToRemove }); + this.assertResponseStatusCode(200, status, body); expect(body).to.eql({ [jobId]: { success: true, type: 'ml-job' } }); }, async assertJobSpaces(jobId: string, jobType: JobType, expectedSpaces: string[]) { - const { body } = await kbnSupertest + const { body, status } = await kbnSupertest .get('/api/ml/saved_objects/jobs_spaces') - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + this.assertResponseStatusCode(200, status, body); if (expectedSpaces.length > 0) { // Should list expected spaces correctly @@ -1026,21 +1060,21 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async syncSavedObjects(simulate: boolean = false, space?: string) { - const { body } = await kbnSupertest + const { body, status } = await kbnSupertest .get(`${space ? `/s/${space}` : ''}/api/ml/saved_objects/sync?simulate=${simulate}`) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + this.assertResponseStatusCode(200, status, body); + return body; }, async createTrainedModel(modelId: string, body: PutTrainedModelConfig, space?: string) { log.debug(`Creating trained model with id "${modelId}"`); - const model = await kbnSupertest + const { body: model, status } = await kbnSupertest .put(`${space ? `/s/${space}` : ''}/api/ml/trained_models/${modelId}`) .set(COMMON_REQUEST_HEADERS) - .send(body) - .expect(200) - .then((res: any) => res.body); + .send(body); + this.assertResponseStatusCode(200, status, model); log.debug('> Trained model created'); return model; @@ -1098,9 +1132,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async createModelAlias(modelId: string, modelAlias: string) { log.debug(`Creating alias for model "${modelId}"`); - await esSupertest - .put(`/_ml/trained_models/${modelId}/model_aliases/${modelAlias}?reassign=true`) - .expect(200); + const { body, status } = await esSupertest.put( + `/_ml/trained_models/${modelId}/model_aliases/${modelAlias}?reassign=true` + ); + this.assertResponseStatusCode(200, status, body); + log.debug('> Model alias created'); }, @@ -1110,7 +1146,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { */ async createIngestPipeline(modelId: string) { log.debug(`Creating ingest pipeline for trained model with id "${modelId}"`); - const ingestPipeline = await esSupertest + const { body: ingestPipeline, status } = await esSupertest .put(`/_ingest/pipeline/pipeline_${modelId}`) .send({ processors: [ @@ -1120,9 +1156,8 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, }, ], - }) - .expect(200) - .then((res) => res.body); + }); + this.assertResponseStatusCode(200, status, ingestPipeline); log.debug('> Ingest pipeline crated'); return ingestPipeline; @@ -1130,7 +1165,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async deleteIngestPipeline(modelId: string) { log.debug(`Deleting ingest pipeline for trained model with id "${modelId}"`); - await esSupertest.delete(`/_ingest/pipeline/pipeline_${modelId}`).expect(200); + const { body, status } = await esSupertest.delete(`/_ingest/pipeline/pipeline_${modelId}`); + this.assertResponseStatusCode(200, status, body); + log.debug('> Ingest pipeline deleted'); }, }; diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index ae596abb562d62..42cde18079aab0 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -123,8 +123,8 @@ export function MachineLearningProvider(context: FtrProviderContext) { dataFrameAnalyticsTable ); const testExecution = MachineLearningTestExecutionProvider(context); - const testResources = MachineLearningTestResourcesProvider(context); - const alerting = MachineLearningAlertingProvider(context, commonUI); + const testResources = MachineLearningTestResourcesProvider(context, api); + const alerting = MachineLearningAlertingProvider(context, api, commonUI); const swimLane = SwimLaneProvider(context); const trainedModels = TrainedModelsProvider(context, api, commonUI); const trainedModelsTable = TrainedModelsTableProvider(context, commonUI); diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index d52ee857c1d9c5..e5115aca99e187 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test'; import { savedSearches, dashboards } from './test_resources_data'; import { COMMON_REQUEST_HEADERS } from './common_api'; +import { MlApi } from './api'; import { FtrProviderContext } from '../../ftr_provider_context'; import { JobType } from '../../../../plugins/ml/common/types/saved_objects'; @@ -23,7 +24,10 @@ export enum SavedObjectType { export type MlTestResourcesi = ProvidedType; -export function MachineLearningTestResourcesProvider({ getService }: FtrProviderContext) { +export function MachineLearningTestResourcesProvider( + { getService }: FtrProviderContext, + mlApi: MlApi +) { const kibanaServer = getService('kibanaServer'); const log = getService('log'); const supertest = getService('supertest'); @@ -59,11 +63,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider objectType: SavedObjectType ): Promise { log.debug(`Searching for '${objectType}' with title '${title}'...`); - const findResponse = await supertest + const { body: findResponse, status } = await supertest .get(`/api/saved_objects/_find?type=${objectType}&per_page=10000`) - .set(COMMON_REQUEST_HEADERS) - .expect(200) - .then((res: any) => res.body); + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, findResponse); for (const savedObject of findResponse.saved_objects) { const objectTitle = savedObject.attributes.title; @@ -79,11 +82,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider const savedObjectIds: string[] = []; log.debug(`Searching for '${objectType}' ...`); - const findResponse = await supertest + const { body: findResponse, status } = await supertest .get(`/api/saved_objects/_find?type=${objectType}&per_page=10000`) - .set(COMMON_REQUEST_HEADERS) - .expect(200) - .then((res: any) => res.body); + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, findResponse); findResponse.saved_objects.forEach((element: any) => { savedObjectIds.push(element.id); @@ -115,12 +117,11 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider }` ); - const createResponse = await supertest + const { body: createResponse, status } = await supertest .post(`/api/saved_objects/${SavedObjectType.INDEX_PATTERN}`) .set(COMMON_REQUEST_HEADERS) - .send({ attributes: { title, timeFieldName } }) - .expect(200) - .then((res: any) => res.body); + .send({ attributes: { title, timeFieldName } }); + mlApi.assertResponseStatusCode(200, status, createResponse); await this.assertIndexPatternExistByTitle(title); @@ -131,12 +132,11 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider async createBulkSavedObjects(body: object[]): Promise { log.debug(`Creating bulk saved objects'`); - const createResponse = await supertest + const { body: createResponse, status } = await supertest .post(`/api/saved_objects/_bulk_create`) .set(COMMON_REQUEST_HEADERS) - .send(body) - .expect(200) - .then((res: any) => res.body); + .send(body); + mlApi.assertResponseStatusCode(200, status, createResponse); log.debug(` > Created bulk saved objects'`); return createResponse; @@ -159,12 +159,11 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider async createSavedSearch(title: string, body: object): Promise { log.debug(`Creating saved search with title '${title}'`); - const createResponse = await supertest + const { body: createResponse, status } = await supertest .post(`/api/saved_objects/${SavedObjectType.SEARCH}`) .set(COMMON_REQUEST_HEADERS) - .send(body) - .expect(200) - .then((res: any) => res.body); + .send(body); + mlApi.assertResponseStatusCode(200, status, createResponse); await this.assertSavedSearchExistByTitle(title); @@ -175,12 +174,11 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider async createDashboard(title: string, body: object): Promise { log.debug(`Creating dashboard with title '${title}'`); - const createResponse = await supertest + const { body: createResponse, status } = await supertest .post(`/api/saved_objects/${SavedObjectType.DASHBOARD}`) .set(COMMON_REQUEST_HEADERS) - .send(body) - .expect(200) - .then((res: any) => res.body); + .send(body); + mlApi.assertResponseStatusCode(200, status, createResponse); log.debug(` > Created with id '${createResponse.id}'`); return createResponse.id; @@ -282,11 +280,11 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider log.debug(`${objectType} with id '${id}' does not exists. Nothing to delete.`); return; } else { - await supertest + const { body, status } = await supertest .delete(`/api/saved_objects/${objectType}/${id}`) .set(COMMON_REQUEST_HEADERS) - .query({ force }) - .expect(200); + .query({ force }); + mlApi.assertResponseStatusCode(200, status, body); await this.assertSavedObjectNotExistsById(id, objectType); @@ -475,7 +473,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider async setupFleet() { log.debug(`Setting up Fleet`); await retry.tryForTime(2 * 60 * 1000, async () => { - await supertest.post(`/api/fleet/setup`).set(COMMON_REQUEST_HEADERS).expect(200); + const { body, status } = await supertest + .post(`/api/fleet/setup`) + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, body); }); log.debug(` > Setup done`); }, @@ -486,10 +487,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider const version = await this.getFleetPackageVersion(packageName); await retry.tryForTime(30 * 1000, async () => { - await supertest + const { body, status } = await supertest .post(`/api/fleet/epm/packages/${packageName}/${version}`) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, body); }); log.debug(` > Installed`); @@ -500,10 +501,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider log.debug(`Removing Fleet package '${packageName}-${version}'`); await retry.tryForTime(30 * 1000, async () => { - await supertest + const { body, status } = await supertest .delete(`/api/fleet/epm/packages/${packageName}/${version}`) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, body); }); log.debug(` > Removed`); @@ -514,10 +515,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider let packageVersion = ''; await retry.tryForTime(10 * 1000, async () => { - const { body } = await supertest + const { body, status } = await supertest .get(`/api/fleet/epm/packages?experimental=true`) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, body); packageVersion = body.response.find( @@ -551,10 +552,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider async installKibanaSampleData(sampleDataId: 'ecommerce' | 'flights' | 'logs') { log.debug(`Installing Kibana sample data '${sampleDataId}'`); - await supertest + const { body, status } = await supertest .post(`/api/sample_data/${sampleDataId}`) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(200, status, body); log.debug(` > Installed`); }, @@ -562,10 +563,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider async removeKibanaSampleData(sampleDataId: 'ecommerce' | 'flights' | 'logs') { log.debug(`Removing Kibana sample data '${sampleDataId}'`); - await supertest + const { body, status } = await supertest .delete(`/api/sample_data/${sampleDataId}`) - .set(COMMON_REQUEST_HEADERS) - .expect(204); // No Content + .set(COMMON_REQUEST_HEADERS); + mlApi.assertResponseStatusCode(204, status, body); log.debug(` > Removed`); }, diff --git a/x-pack/test/functional/services/transform/api.ts b/x-pack/test/functional/services/transform/api.ts index e30146e5bdd690..748694ce4e8045 100644 --- a/x-pack/test/functional/services/transform/api.ts +++ b/x-pack/test/functional/services/transform/api.ts @@ -30,6 +30,15 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { const esDeleteAllIndices = getService('esDeleteAllIndices'); return { + assertResponseStatusCode(expectedStatus: number, actualStatus: number, responseBody: object) { + expect(actualStatus).to.eql( + expectedStatus, + `Expected status code ${expectedStatus}, got ${actualStatus} with body '${JSON.stringify( + responseBody + )}'` + ); + }, + async createIndices(indices: string) { log.debug(`Creating indices: '${indices}'...`); if ((await es.indices.exists({ index: indices, allow_no_indices: false })) === true) { @@ -91,18 +100,24 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { async cleanTransformIndices() { // Delete all transforms using the API since we mustn't just delete // all `.transform-*` indices since this might result in orphaned ES tasks. - const { - body: { transforms }, - } = await esSupertest.get(`/_transform/`).expect(200); - const transformIds = transforms.map((t: { id: string }) => t.id); + const { body: getRspBody, status: getRspStatus } = await esSupertest.get(`/_transform/`); + this.assertResponseStatusCode(200, getRspStatus, getRspBody); + + const transformIds = getRspBody.transforms.map((t: { id: string }) => t.id); await asyncForEach(transformIds, async (transformId: string) => { - await esSupertest - .post(`/_transform/${transformId}/_stop?force=true&wait_for_completion`) - .expect(200); + const { body: stopRspBody, status: stopRspStatus } = await esSupertest.post( + `/_transform/${transformId}/_stop?force=true&wait_for_completion` + ); + this.assertResponseStatusCode(200, stopRspStatus, stopRspBody); + await this.waitForTransformState(transformId, TRANSFORM_STATE.STOPPED); - await esSupertest.delete(`/_transform/${transformId}`).expect(200); + const { body: deleteRspBody, status: deleteRspstatus } = await esSupertest.delete( + `/_transform/${transformId}` + ); + this.assertResponseStatusCode(200, deleteRspstatus, deleteRspBody); + await this.waitForTransformNotToExist(transformId); }); @@ -113,10 +128,10 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { async getTransformStats(transformId: string): Promise { log.debug(`Fetching transform stats for transform ${transformId}`); - const statsResponse = await esSupertest - .get(`/_transform/${transformId}/_stats`) - .expect(200) - .then((res: any) => res.body); + const { body: statsResponse, status } = await esSupertest.get( + `/_transform/${transformId}/_stats` + ); + this.assertResponseStatusCode(200, status, statsResponse); expect(statsResponse.transforms).to.have.length( 1, @@ -176,30 +191,36 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { }, async getTransformList(size: number = 10): Promise { - return (await esSupertest - .get(`/_transform`) - .expect(200) - .then((response) => response.body)) as GetTransformsResponseSchema; + const { body, status } = await esSupertest.get(`/_transform`); + this.assertResponseStatusCode(200, status, body); + + return body as GetTransformsResponseSchema; }, async getTransform(transformId: string, expectedCode = 200) { - return await esSupertest.get(`/_transform/${transformId}`).expect(expectedCode); + const response = await esSupertest.get(`/_transform/${transformId}`); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; }, async updateTransform( transformId: string, updates: Partial ): Promise { - return await esSupertest + const { body, status } = await esSupertest .post(`/_transform/${transformId}/_update`) - .send(updates) - .expect(200) - .then((response: { body: TransformPivotConfig }) => response.body); + .send(updates); + this.assertResponseStatusCode(200, status, body); + + return body as TransformPivotConfig; }, async createTransform(transformId: string, transformConfig: PutTransformsRequestSchema) { log.debug(`Creating transform with id '${transformId}'...`); - await esSupertest.put(`/_transform/${transformId}`).send(transformConfig).expect(200); + const { body, status } = await esSupertest + .put(`/_transform/${transformId}`) + .send(transformConfig); + this.assertResponseStatusCode(200, status, body); await this.waitForTransformToExist( transformId, @@ -229,12 +250,14 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { async startTransform(transformId: string) { log.debug(`Starting transform '${transformId}' ...`); - await esSupertest.post(`/_transform/${transformId}/_start`).expect(200); + const { body, status } = await esSupertest.post(`/_transform/${transformId}/_start`); + this.assertResponseStatusCode(200, status, body); }, async stopTransform(transformId: string) { log.debug(`Stopping transform '${transformId}' ...`); - await esSupertest.post(`/_transform/${transformId}/_stop`).expect(200); + const { body, status } = await esSupertest.post(`/_transform/${transformId}/_stop`); + this.assertResponseStatusCode(200, status, body); }, async createAndRunTransform(transformId: string, transformConfig: PutTransformsRequestSchema) { diff --git a/x-pack/test/functional/services/transform/index.ts b/x-pack/test/functional/services/transform/index.ts index c9179cc307aaf3..75f0df67f0919b 100644 --- a/x-pack/test/functional/services/transform/index.ts +++ b/x-pack/test/functional/services/transform/index.ts @@ -19,10 +19,12 @@ import { TransformTableProvider } from './transform_table'; import { TransformTestExecutionProvider } from './test_execution'; import { TransformWizardProvider } from './wizard'; +import { MachineLearningAPIProvider } from '../ml/api'; import { MachineLearningTestResourcesProvider } from '../ml/test_resources'; export function TransformProvider(context: FtrProviderContext) { const api = TransformAPIProvider(context); + const mlApi = MachineLearningAPIProvider(context); const discover = TransformDiscoverProvider(context); const editFlyout = TransformEditFlyoutProvider(context); const management = TransformManagementProvider(context); @@ -32,7 +34,7 @@ export function TransformProvider(context: FtrProviderContext) { const sourceSelection = TransformSourceSelectionProvider(context); const table = TransformTableProvider(context); const testExecution = TransformTestExecutionProvider(context); - const testResources = MachineLearningTestResourcesProvider(context); + const testResources = MachineLearningTestResourcesProvider(context, mlApi); const wizard = TransformWizardProvider(context); return { From 3cc429d2cbe18f67009c73639d33da5aba592521 Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Thu, 17 Mar 2022 18:18:49 +0200 Subject: [PATCH 10/10] [i18n] a few docs updates (#127964) --- docs/developer/contributing/index.asciidoc | 6 ++--- .../external-plugin-localization.asciidoc | 24 +++++++------------ docs/settings/i18n-settings.asciidoc | 1 + 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/docs/developer/contributing/index.asciidoc b/docs/developer/contributing/index.asciidoc index 1cf96d19bfb2b2..82ce24d5109316 100644 --- a/docs/developer/contributing/index.asciidoc +++ b/docs/developer/contributing/index.asciidoc @@ -30,8 +30,8 @@ Please make sure you have signed the [Contributor License Agreement](http://www. Read <> for details on our localization practices. -Note that we cannot support accepting contributions to the translations from any source other than the translators we have engaged to do the work. -We are still to develop a proper process to accept any contributed translations. We certainly appreciate that people care enough about the localization effort to want to help improve the quality. We aim to build out a more comprehensive localization process for the future and will notify you once contributions can be supported, but for the time being, we are not able to incorporate suggestions. +Note that we cannot support accepting contributions to the translations from any source other than the translators we have engaged in doing the work. +We are yet to develop a proper process to accept any contributed translations. We certainly appreciate that people care enough about the localization effort to want to help improve the quality. We aim to build out a more comprehensive localization process for the future and will notify you once Kibana supports external contributions. Still, for the time being, we cannot incorporate suggestions. [discrete] [[kibana-release-notes-process]] @@ -73,7 +73,7 @@ To make sure that your PR is included in the Release Notes, add the right label. * `release_note:fix` — Fixes for bugs that existed in the previous release. * `release_note:deprecation` — Deprecates functionality that existed in previous releases. * `release_note:breaking` — Breaking changes that weren't present in previous releases. - * `release_note:skip` — Changes that should not appear in the Release Notes. For example, docs, build, and test fixes, or unreleased issues that are only in `master`. + * `release_note:skip` — Changes that should not appear in the Release Notes. For example, docs, build, and test fixes, or unreleased issues that are only in `main`. include::development-github.asciidoc[leveloffset=+1] diff --git a/docs/developer/plugin/external-plugin-localization.asciidoc b/docs/developer/plugin/external-plugin-localization.asciidoc index 36cb91a42203a2..9fbf7cdab9b769 100644 --- a/docs/developer/plugin/external-plugin-localization.asciidoc +++ b/docs/developer/plugin/external-plugin-localization.asciidoc @@ -83,18 +83,12 @@ node scripts/i18n_check --fix --include-config ../kibana-extra/myPlugin/.i18nrc. [discrete] === Implementing i18n in the UI -{kib} relies on several UI frameworks (ReactJS and AngularJS) and -requires localization in different environments (browser and NodeJS). +{kib} relies on ReactJS and requires localization in different environments (browser and NodeJS). The internationalization engine is framework agnostic and consumable in -all parts of {kib} (ReactJS, AngularJS and NodeJS). +all parts of {kib} (ReactJS, and NodeJS). -To simplify -internationalization in UI frameworks, additional abstractions are -built around the I18n engine: `react-intl` for React and custom -components for AngularJS. https://github.com/yahoo/react-intl[React-intl] -is built around https://github.com/yahoo/intl-messageformat[intl-messageformat], -so both React and AngularJS frameworks use the same engine and the same -message syntax. +To simplify internationalization in React, an additional abstraction is +built around the I18n engine using https://github.com/yahoo/react-intl[React-intl] for React. [discrete] @@ -109,7 +103,7 @@ export const HELLO_WORLD = i18n.translate('hello.wonderful.world', { }); ----------- -Full details are {kib-repo}tree/master/packages/kbn-i18n#vanilla-js[here]. +Full details are {kib-repo}tree/main/packages/kbn-i18n#vanilla-js[here]. [discrete] ==== i18n for React @@ -133,14 +127,14 @@ export const Component = () => { }; ----------- -Full details are {kib-repo}tree/master/packages/kbn-i18n#react[here]. +Full details are {kib-repo}tree/main/packages/kbn-i18n#react[here]. [discrete] === Resources -To learn more about i18n tooling, see {blob}src/dev/i18n/README.md[i18n dev tooling]. +To learn more about i18n tooling, see {kib-repo}blob/{branch}src/dev/i18n/README.md[i18n dev tooling]. To learn more about implementing i18n in the UI, use the following links: -* {blob}packages/kbn-i18n/README.md[i18n plugin] -* {blob}packages/kbn-i18n/GUIDELINE.md[i18n guidelines] +* {kib-repo}blob/{branch}packages/kbn-i18n/README.md[i18n plugin] +* {kib-repo}blob/{branch}packages/kbn-i18n/GUIDELINE.md[i18n guidelines] \ No newline at end of file diff --git a/docs/settings/i18n-settings.asciidoc b/docs/settings/i18n-settings.asciidoc index 8f498c507a8c2f..67b858a81c42e8 100644 --- a/docs/settings/i18n-settings.asciidoc +++ b/docs/settings/i18n-settings.asciidoc @@ -16,3 +16,4 @@ You do not need to configure any settings to run Kibana in English. * English - `en` (default) * Chinese - `zh-CN` * Japanese - `ja-JP` + * French - `fr-FR`