[ML] Fixing file import, module creation and results viewing permission checks (#72825)

* [ML] Fixing file import and module creation permission checks

* correcting searches on results index

* fixing test

* removing unnecessary index

* updating apidoc

* fixing test

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2020-07-23 16:11:15 +01:00 committed by GitHub
parent 2d9eaf013b
commit 18df677da7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 260 additions and 218 deletions

View file

@ -72,6 +72,7 @@ export function getPluginPrivileges() {
const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities); const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities);
const allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys]; const allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys];
// TODO: include ML in base privileges for the `8.0` release: https://github.com/elastic/kibana/issues/71422 // TODO: include ML in base privileges for the `8.0` release: https://github.com/elastic/kibana/issues/71422
const savedObjects = ['index-pattern', 'dashboard', 'search', 'visualization'];
const privilege = { const privilege = {
app: [PLUGIN_ID, 'kibana'], app: [PLUGIN_ID, 'kibana'],
excludeFromBasePrivileges: true, excludeFromBasePrivileges: true,
@ -79,10 +80,6 @@ export function getPluginPrivileges() {
insightsAndAlerting: ['jobsListLink'], insightsAndAlerting: ['jobsListLink'],
}, },
catalogue: [PLUGIN_ID], catalogue: [PLUGIN_ID],
savedObject: {
all: [],
read: ['index-pattern', 'dashboard', 'search', 'visualization'],
},
}; };
return { return {
@ -90,11 +87,19 @@ export function getPluginPrivileges() {
...privilege, ...privilege,
api: allMlCapabilitiesKeys.map((k) => `ml:${k}`), api: allMlCapabilitiesKeys.map((k) => `ml:${k}`),
ui: allMlCapabilitiesKeys, ui: allMlCapabilitiesKeys,
savedObject: {
all: savedObjects,
read: savedObjects,
},
}, },
user: { user: {
...privilege, ...privilege,
api: userMlCapabilitiesKeys.map((k) => `ml:${k}`), api: userMlCapabilitiesKeys.map((k) => `ml:${k}`),
ui: userMlCapabilitiesKeys, ui: userMlCapabilitiesKeys,
savedObject: {
all: [],
read: savedObjects,
},
}, },
}; };
} }

View file

@ -39,7 +39,7 @@ export const BottomBar: FC<BottomBarProps> = ({ mode, onChangeMode, onCancel, di
disableImport ? ( disableImport ? (
<FormattedMessage <FormattedMessage
id="xpack.ml.fileDatavisualizer.bottomBar.missingImportPrivilegesMessage" id="xpack.ml.fileDatavisualizer.bottomBar.missingImportPrivilegesMessage"
defaultMessage="You don't have the privileges required to import data" defaultMessage="You require the ingest_admin role to enable data importing"
/> />
) : null ) : null
} }

View file

@ -18,6 +18,7 @@ import {
} from '@elastic/eui'; } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { debounce } from 'lodash';
import { importerFactory } from './importer'; import { importerFactory } from './importer';
import { ResultsLinks } from '../results_links'; import { ResultsLinks } from '../results_links';
import { FilebeatConfigFlyout } from '../filebeat_config_flyout'; import { FilebeatConfigFlyout } from '../filebeat_config_flyout';
@ -66,6 +67,7 @@ const DEFAULT_STATE = {
indexPatternNameError: '', indexPatternNameError: '',
timeFieldName: undefined, timeFieldName: undefined,
isFilebeatFlyoutVisible: false, isFilebeatFlyoutVisible: false,
checkingValidIndex: false,
}; };
export class ImportView extends Component { export class ImportView extends Component {
@ -76,14 +78,12 @@ export class ImportView extends Component {
} }
componentDidMount() { componentDidMount() {
this.loadIndexNames();
this.loadIndexPatternNames(); this.loadIndexPatternNames();
} }
clickReset = () => { clickReset = () => {
const state = getDefaultState(this.state, this.props.results); const state = getDefaultState(this.state, this.props.results);
this.setState(state, () => { this.setState(state, () => {
this.loadIndexNames();
this.loadIndexPatternNames(); this.loadIndexPatternNames();
}); });
}; };
@ -326,21 +326,33 @@ export class ImportView extends Component {
}; };
onIndexChange = (e) => { onIndexChange = (e) => {
const name = e.target.value; const index = e.target.value;
const { indexNames, indexPattern, indexPatternNames } = this.state;
this.setState({ this.setState({
index: name, index,
indexNameError: isIndexNameValid(name, indexNames), checkingValidIndex: true,
// if index pattern has been altered, check that it still matches the inputted index
...(indexPattern === ''
? {}
: {
indexPatternNameError: isIndexPatternNameValid(indexPattern, indexPatternNames, name),
}),
}); });
this.debounceIndexCheck(index);
}; };
debounceIndexCheck = debounce(async (index) => {
if (index === '') {
this.setState({ checkingValidIndex: false });
return;
}
const { exists } = await ml.checkIndexExists({ index });
const indexNameError = exists ? (
<FormattedMessage
id="xpack.ml.fileDatavisualizer.importView.indexNameAlreadyExistsErrorMessage"
defaultMessage="Index name already exists"
/>
) : (
isIndexNameValid(index)
);
this.setState({ checkingValidIndex: false, indexNameError });
}, 500);
onIndexPatternChange = (e) => { onIndexPatternChange = (e) => {
const name = e.target.value; const name = e.target.value;
const { indexPatternNames, index } = this.state; const { indexPatternNames, index } = this.state;
@ -396,12 +408,6 @@ export class ImportView extends Component {
this.props.showBottomBar(); this.props.showBottomBar();
}; };
async loadIndexNames() {
const indices = await ml.getIndices();
const indexNames = indices.map((i) => i.name);
this.setState({ indexNames });
}
async loadIndexPatternNames() { async loadIndexPatternNames() {
await loadIndexPatterns(this.props.indexPatterns); await loadIndexPatterns(this.props.indexPatterns);
const indexPatternNames = getIndexPatternNames(); const indexPatternNames = getIndexPatternNames();
@ -437,6 +443,7 @@ export class ImportView extends Component {
indexPatternNameError, indexPatternNameError,
timeFieldName, timeFieldName,
isFilebeatFlyoutVisible, isFilebeatFlyoutVisible,
checkingValidIndex,
} = this.state; } = this.state;
const createPipeline = pipelineString !== ''; const createPipeline = pipelineString !== '';
@ -459,7 +466,8 @@ export class ImportView extends Component {
index === '' || index === '' ||
indexNameError !== '' || indexNameError !== '' ||
(createIndexPattern === true && indexPatternNameError !== '') || (createIndexPattern === true && indexPatternNameError !== '') ||
initialized === true; initialized === true ||
checkingValidIndex === true;
return ( return (
<EuiPage data-test-subj="mlPageFileDataVisImport"> <EuiPage data-test-subj="mlPageFileDataVisImport">
@ -655,16 +663,7 @@ function getDefaultState(state, results) {
}; };
} }
function isIndexNameValid(name, indexNames) { function isIndexNameValid(name) {
if (indexNames.find((i) => i === name)) {
return (
<FormattedMessage
id="xpack.ml.fileDatavisualizer.importView.indexNameAlreadyExistsErrorMessage"
defaultMessage="Index name already exists"
/>
);
}
const reg = new RegExp('[\\\\/*?"<>|\\s,#]+'); const reg = new RegExp('[\\\\/*?"<>|\\s,#]+');
if ( if (
name !== name.toLowerCase() || // name should be lowercase name !== name.toLowerCase() || // name should be lowercase

View file

@ -11,7 +11,6 @@ import url from 'url';
import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../../src/plugins/dashboard/public'; import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../../src/plugins/dashboard/public';
import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns';
import { getPartitioningFieldNames } from '../../../../../common/util/job_utils'; import { getPartitioningFieldNames } from '../../../../../common/util/job_utils';
import { parseInterval } from '../../../../../common/util/parse_interval'; import { parseInterval } from '../../../../../common/util/parse_interval';
import { replaceTokensInUrlValue, isValidLabel } from '../../../util/custom_url_utils'; import { replaceTokensInUrlValue, isValidLabel } from '../../../util/custom_url_utils';
@ -295,11 +294,11 @@ export function getTestUrl(job, customUrl) {
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ml.esSearch({ ml.results
index: ML_RESULTS_INDEX_PATTERN, .anomalySearch({
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
body, body,
}) })
.then((resp) => { .then((resp) => {
if (resp.hits.total > 0) { if (resp.hits.total > 0) {
const record = resp.hits.hits[0]._source; const record = resp.hits.hits[0]._source;

View file

@ -6,7 +6,6 @@
import { get } from 'lodash'; import { get } from 'lodash';
import { ML_RESULTS_INDEX_PATTERN } from '../../../../../../common/constants/index_patterns';
import { escapeForElasticsearchQuery } from '../../../../util/string_utils'; import { escapeForElasticsearchQuery } from '../../../../util/string_utils';
import { ml } from '../../../../services/ml_api_service'; import { ml } from '../../../../services/ml_api_service';
@ -53,78 +52,78 @@ export function getScoresByRecord(
jobIdFilterStr += `"${String(firstSplitField.value).replace(/\\/g, '\\\\')}"`; jobIdFilterStr += `"${String(firstSplitField.value).replace(/\\/g, '\\\\')}"`;
} }
ml.esSearch({ ml.results
index: ML_RESULTS_INDEX_PATTERN, .anomalySearch({
size: 0, size: 0,
body: { body: {
query: { query: {
bool: { bool: {
filter: [ filter: [
{ {
query_string: { query_string: {
query: 'result_type:record', query: 'result_type:record',
},
}, },
}, {
{ bool: {
bool: { must: [
must: [ {
{ range: {
range: { timestamp: {
timestamp: { gte: earliestMs,
gte: earliestMs, lte: latestMs,
lte: latestMs, format: 'epoch_millis',
format: 'epoch_millis', },
}, },
}, },
{
query_string: {
query: jobIdFilterStr,
},
},
],
},
},
],
},
},
aggs: {
detector_index: {
terms: {
field: 'detector_index',
order: {
recordScore: 'desc',
},
},
aggs: {
recordScore: {
max: {
field: 'record_score',
},
},
byTime: {
date_histogram: {
field: 'timestamp',
interval,
min_doc_count: 1,
extended_bounds: {
min: earliestMs,
max: latestMs,
}, },
{ },
query_string: { aggs: {
query: jobIdFilterStr, recordScore: {
max: {
field: 'record_score',
}, },
}, },
],
},
},
],
},
},
aggs: {
detector_index: {
terms: {
field: 'detector_index',
order: {
recordScore: 'desc',
},
},
aggs: {
recordScore: {
max: {
field: 'record_score',
},
},
byTime: {
date_histogram: {
field: 'timestamp',
interval,
min_doc_count: 1,
extended_bounds: {
min: earliestMs,
max: latestMs,
},
},
aggs: {
recordScore: {
max: {
field: 'record_score',
},
}, },
}, },
}, },
}, },
}, },
}, },
}, })
})
.then((resp: any) => { .then((resp: any) => {
const detectorsByIndex = get(resp, ['aggregations', 'detector_index', 'buckets'], []); const detectorsByIndex = get(resp, ['aggregations', 'detector_index', 'buckets'], []);
detectorsByIndex.forEach((dtr: any) => { detectorsByIndex.forEach((dtr: any) => {

View file

@ -9,7 +9,6 @@
import _ from 'lodash'; import _ from 'lodash';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { ml } from './ml_api_service'; import { ml } from './ml_api_service';
// Gets a basic summary of the most recently run forecasts for the specified // Gets a basic summary of the most recently run forecasts for the specified
@ -48,19 +47,19 @@ function getForecastsSummary(job, query, earliestMs, maxResults) {
filterCriteria.push(query); filterCriteria.push(query);
} }
ml.esSearch({ ml.results
index: ML_RESULTS_INDEX_PATTERN, .anomalySearch({
size: maxResults, size: maxResults,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
body: { body: {
query: { query: {
bool: { bool: {
filter: filterCriteria, filter: filterCriteria,
},
}, },
sort: [{ forecast_create_timestamp: { order: 'desc' } }],
}, },
sort: [{ forecast_create_timestamp: { order: 'desc' } }], })
},
})
.then((resp) => { .then((resp) => {
if (resp.hits.total !== 0) { if (resp.hits.total !== 0) {
obj.forecasts = resp.hits.hits.map((hit) => hit._source); obj.forecasts = resp.hits.hits.map((hit) => hit._source);
@ -106,29 +105,29 @@ function getForecastDateRange(job, forecastId) {
// TODO - add in criteria for detector index and entity fields (by, over, partition) // TODO - add in criteria for detector index and entity fields (by, over, partition)
// once forecasting with these parameters is supported. // once forecasting with these parameters is supported.
ml.esSearch({ ml.results
index: ML_RESULTS_INDEX_PATTERN, .anomalySearch({
size: 0, size: 0,
body: { body: {
query: { query: {
bool: { bool: {
filter: filterCriteria, filter: filterCriteria,
},
},
aggs: {
earliest: {
min: {
field: 'timestamp',
}, },
}, },
latest: { aggs: {
max: { earliest: {
field: 'timestamp', min: {
field: 'timestamp',
},
},
latest: {
max: {
field: 'timestamp',
},
}, },
}, },
}, },
}, })
})
.then((resp) => { .then((resp) => {
obj.earliest = _.get(resp, 'aggregations.earliest.value', null); obj.earliest = _.get(resp, 'aggregations.earliest.value', null);
obj.latest = _.get(resp, 'aggregations.latest.value', null); obj.latest = _.get(resp, 'aggregations.latest.value', null);
@ -243,9 +242,8 @@ function getForecastData(
min: aggType.min, min: aggType.min,
}; };
return ml return ml.results
.esSearch$({ .anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
query: { query: {
@ -343,18 +341,18 @@ function getForecastRequestStats(job, forecastId) {
}, },
]; ];
ml.esSearch({ ml.results
index: ML_RESULTS_INDEX_PATTERN, .anomalySearch({
size: 1, size: 1,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
body: { body: {
query: { query: {
bool: { bool: {
filter: filterCriteria, filter: filterCriteria,
},
}, },
}, },
}, })
})
.then((resp) => { .then((resp) => {
if (resp.hits.total !== 0) { if (resp.hits.total !== 0) {
obj.stats = _.first(resp.hits.hits)._source; obj.stats = _.first(resp.hits.hits)._source;

View file

@ -96,4 +96,22 @@ export const resultsApiProvider = (httpService: HttpService) => ({
body, body,
}); });
}, },
anomalySearch(obj: any) {
const body = JSON.stringify(obj);
return httpService.http<any>({
path: `${basePath()}/results/anomaly_search`,
method: 'POST',
body,
});
},
anomalySearch$(obj: any) {
const body = JSON.stringify(obj);
return httpService.http$<any>({
path: `${basePath()}/results/anomaly_search`,
method: 'POST',
body,
});
},
}); });

View file

@ -262,8 +262,8 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) {
}, },
]; ];
return mlApiServices return mlApiServices.results
.esSearch$({ .anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
@ -399,8 +399,8 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) {
}); });
}); });
return mlApiServices return mlApiServices.results
.esSearch$({ .anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
size: maxResults !== undefined ? maxResults : 100, size: maxResults !== undefined ? maxResults : 100,
@ -484,8 +484,8 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) {
}); });
} }
return mlApiServices return mlApiServices.results
.esSearch$({ .anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {

View file

@ -8,7 +8,6 @@ import _ from 'lodash';
import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils';
import { escapeForElasticsearchQuery } from '../../util/string_utils'; import { escapeForElasticsearchQuery } from '../../util/string_utils';
import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns';
import { import {
ANOMALY_SWIM_LANE_HARD_LIMIT, ANOMALY_SWIM_LANE_HARD_LIMIT,
SWIM_LANE_DEFAULT_PAGE_SIZE, SWIM_LANE_DEFAULT_PAGE_SIZE,
@ -66,9 +65,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
query: { query: {
@ -238,9 +236,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
query: { query: {
@ -378,9 +375,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
query: { query: {
@ -560,9 +556,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
query: { query: {
@ -721,9 +716,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: maxResults !== undefined ? maxResults : 100, size: maxResults !== undefined ? maxResults : 100,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
body: { body: {
@ -854,9 +848,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: maxResults !== undefined ? maxResults : 100, size: maxResults !== undefined ? maxResults : 100,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
body: { body: {
@ -980,9 +973,8 @@ export function resultsServiceProvider(mlApiServices) {
} }
} }
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: maxResults !== undefined ? maxResults : 100, size: maxResults !== undefined ? maxResults : 100,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
body: { body: {
@ -1307,9 +1299,8 @@ export function resultsServiceProvider(mlApiServices) {
}); });
}); });
mlApiServices mlApiServices.results
.esSearch({ .anomalySearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
query: { query: {

View file

@ -18,17 +18,17 @@ import {
// - ML_ANNOTATIONS_INDEX_ALIAS_READ alias is present // - ML_ANNOTATIONS_INDEX_ALIAS_READ alias is present
// - ML_ANNOTATIONS_INDEX_ALIAS_WRITE alias is present // - ML_ANNOTATIONS_INDEX_ALIAS_WRITE alias is present
export async function isAnnotationsFeatureAvailable({ export async function isAnnotationsFeatureAvailable({
callAsCurrentUser, callAsInternalUser,
}: ILegacyScopedClusterClient) { }: ILegacyScopedClusterClient) {
try { try {
const indexParams = { index: ML_ANNOTATIONS_INDEX_PATTERN }; const indexParams = { index: ML_ANNOTATIONS_INDEX_PATTERN };
const annotationsIndexExists = await callAsCurrentUser('indices.exists', indexParams); const annotationsIndexExists = await callAsInternalUser('indices.exists', indexParams);
if (!annotationsIndexExists) { if (!annotationsIndexExists) {
return false; return false;
} }
const annotationsReadAliasExists = await callAsCurrentUser('indices.existsAlias', { const annotationsReadAliasExists = await callAsInternalUser('indices.existsAlias', {
index: ML_ANNOTATIONS_INDEX_ALIAS_READ, index: ML_ANNOTATIONS_INDEX_ALIAS_READ,
name: ML_ANNOTATIONS_INDEX_ALIAS_READ, name: ML_ANNOTATIONS_INDEX_ALIAS_READ,
}); });
@ -37,7 +37,7 @@ export async function isAnnotationsFeatureAvailable({
return false; return false;
} }
const annotationsWriteAliasExists = await callAsCurrentUser('indices.existsAlias', { const annotationsWriteAliasExists = await callAsInternalUser('indices.existsAlias', {
index: ML_ANNOTATIONS_INDEX_ALIAS_WRITE, index: ML_ANNOTATIONS_INDEX_ALIAS_WRITE,
name: ML_ANNOTATIONS_INDEX_ALIAS_WRITE, name: ML_ANNOTATIONS_INDEX_ALIAS_WRITE,
}); });

View file

@ -52,8 +52,8 @@ describe('annotation_service', () => {
const response = await deleteAnnotation(annotationMockId); const response = await deleteAnnotation(annotationMockId);
expect(mockFunct.callAsCurrentUser.mock.calls[0][0]).toBe('delete'); expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('delete');
expect(mockFunct.callAsCurrentUser.mock.calls[0][1]).toEqual(deleteParamsMock); expect(mockFunct.callAsInternalUser.mock.calls[0][1]).toEqual(deleteParamsMock);
expect(response).toBe(acknowledgedResponseMock); expect(response).toBe(acknowledgedResponseMock);
done(); done();
}); });
@ -73,8 +73,8 @@ describe('annotation_service', () => {
const response: GetResponse = await getAnnotations(indexAnnotationArgsMock); const response: GetResponse = await getAnnotations(indexAnnotationArgsMock);
expect(mockFunct.callAsCurrentUser.mock.calls[0][0]).toBe('search'); expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('search');
expect(mockFunct.callAsCurrentUser.mock.calls[0][1]).toEqual(getAnnotationsRequestMock); expect(mockFunct.callAsInternalUser.mock.calls[0][1]).toEqual(getAnnotationsRequestMock);
expect(Object.keys(response.annotations)).toHaveLength(1); expect(Object.keys(response.annotations)).toHaveLength(1);
expect(response.annotations[jobIdMock]).toHaveLength(2); expect(response.annotations[jobIdMock]).toHaveLength(2);
expect(isAnnotations(response.annotations[jobIdMock])).toBeTruthy(); expect(isAnnotations(response.annotations[jobIdMock])).toBeTruthy();
@ -89,7 +89,7 @@ describe('annotation_service', () => {
}; };
const mlClusterClientSpyError: any = { const mlClusterClientSpyError: any = {
callAsCurrentUser: jest.fn(() => { callAsInternalUser: jest.fn(() => {
return Promise.resolve(mockEsError); return Promise.resolve(mockEsError);
}), }),
}; };
@ -124,10 +124,10 @@ describe('annotation_service', () => {
const response = await indexAnnotation(annotationMock, usernameMock); const response = await indexAnnotation(annotationMock, usernameMock);
expect(mockFunct.callAsCurrentUser.mock.calls[0][0]).toBe('index'); expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('index');
// test if the annotation has been correctly augmented // test if the annotation has been correctly augmented
const indexParamsCheck = mockFunct.callAsCurrentUser.mock.calls[0][1]; const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[0][1];
const annotation = indexParamsCheck.body; const annotation = indexParamsCheck.body;
expect(annotation.create_username).toBe(usernameMock); expect(annotation.create_username).toBe(usernameMock);
expect(annotation.modified_username).toBe(usernameMock); expect(annotation.modified_username).toBe(usernameMock);
@ -154,10 +154,10 @@ describe('annotation_service', () => {
const response = await indexAnnotation(annotationMock, usernameMock); const response = await indexAnnotation(annotationMock, usernameMock);
expect(mockFunct.callAsCurrentUser.mock.calls[0][0]).toBe('index'); expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('index');
// test if the annotation has been correctly augmented // test if the annotation has been correctly augmented
const indexParamsCheck = mockFunct.callAsCurrentUser.mock.calls[0][1]; const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[0][1];
const annotation = indexParamsCheck.body; const annotation = indexParamsCheck.body;
expect(annotation.create_username).toBe(usernameMock); expect(annotation.create_username).toBe(usernameMock);
expect(annotation.modified_username).toBe(usernameMock); expect(annotation.modified_username).toBe(usernameMock);
@ -196,9 +196,9 @@ describe('annotation_service', () => {
await indexAnnotation(annotation, modifiedUsernameMock); await indexAnnotation(annotation, modifiedUsernameMock);
expect(mockFunct.callAsCurrentUser.mock.calls[1][0]).toBe('index'); expect(mockFunct.callAsInternalUser.mock.calls[1][0]).toBe('index');
// test if the annotation has been correctly updated // test if the annotation has been correctly updated
const indexParamsCheck = mockFunct.callAsCurrentUser.mock.calls[1][1]; const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[1][1];
const modifiedAnnotation = indexParamsCheck.body; const modifiedAnnotation = indexParamsCheck.body;
expect(modifiedAnnotation.annotation).toBe(modifiedAnnotationText); expect(modifiedAnnotation.annotation).toBe(modifiedAnnotationText);
expect(modifiedAnnotation.create_username).toBe(originalUsernameMock); expect(modifiedAnnotation.create_username).toBe(originalUsernameMock);

View file

@ -76,7 +76,7 @@ export interface DeleteParams {
id: string; id: string;
} }
export function annotationProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { export function annotationProvider({ callAsInternalUser }: ILegacyScopedClusterClient) {
async function indexAnnotation(annotation: Annotation, username: string) { async function indexAnnotation(annotation: Annotation, username: string) {
if (isAnnotation(annotation) === false) { if (isAnnotation(annotation) === false) {
// No need to translate, this will not be exposed in the UI. // No need to translate, this will not be exposed in the UI.
@ -103,7 +103,7 @@ export function annotationProvider({ callAsCurrentUser }: ILegacyScopedClusterCl
delete params.body.key; delete params.body.key;
} }
return await callAsCurrentUser('index', params); return await callAsInternalUser('index', params);
} }
async function getAnnotations({ async function getAnnotations({
@ -286,7 +286,7 @@ export function annotationProvider({ callAsCurrentUser }: ILegacyScopedClusterCl
}; };
try { try {
const resp = await callAsCurrentUser('search', params); const resp = await callAsInternalUser('search', params);
if (resp.error !== undefined && resp.message !== undefined) { if (resp.error !== undefined && resp.message !== undefined) {
// No need to translate, this will not be exposed in the UI. // No need to translate, this will not be exposed in the UI.
@ -335,7 +335,7 @@ export function annotationProvider({ callAsCurrentUser }: ILegacyScopedClusterCl
refresh: 'wait_for', refresh: 'wait_for',
}; };
return await callAsCurrentUser('delete', param); return await callAsInternalUser('delete', param);
} }
return { return {

View file

@ -23,7 +23,7 @@ interface BoolQuery {
bool: { [key: string]: any }; bool: { [key: string]: any };
} }
export function analyticsAuditMessagesProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { export function analyticsAuditMessagesProvider({ callAsInternalUser }: ILegacyScopedClusterClient) {
// search for audit messages, // search for audit messages,
// analyticsId is optional. without it, all analytics will be listed. // analyticsId is optional. without it, all analytics will be listed.
async function getAnalyticsAuditMessages(analyticsId: string) { async function getAnalyticsAuditMessages(analyticsId: string) {
@ -69,7 +69,7 @@ export function analyticsAuditMessagesProvider({ callAsCurrentUser }: ILegacySco
} }
try { try {
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_NOTIFICATION_INDEX_PATTERN, index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true, ignore_unavailable: true,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,

View file

@ -34,7 +34,7 @@ const anomalyDetectorTypeFilter = {
}, },
}; };
export function jobAuditMessagesProvider({ callAsCurrentUser, callAsInternalUser }) { export function jobAuditMessagesProvider({ callAsInternalUser }) {
// search for audit messages, // search for audit messages,
// jobId is optional. without it, all jobs will be listed. // jobId is optional. without it, all jobs will be listed.
// from is optional and should be a string formatted in ES time units. e.g. 12h, 1d, 7d // from is optional and should be a string formatted in ES time units. e.g. 12h, 1d, 7d
@ -100,7 +100,7 @@ export function jobAuditMessagesProvider({ callAsCurrentUser, callAsInternalUser
} }
try { try {
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_NOTIFICATION_INDEX_PATTERN, index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true, ignore_unavailable: true,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
@ -155,7 +155,7 @@ export function jobAuditMessagesProvider({ callAsCurrentUser, callAsInternalUser
levelsPerJobAggSize = jobIds.length; levelsPerJobAggSize = jobIds.length;
} }
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_NOTIFICATION_INDEX_PATTERN, index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true, ignore_unavailable: true,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,

View file

@ -48,7 +48,7 @@ interface Results {
} }
export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) {
const { callAsCurrentUser, callAsInternalUser } = mlClusterClient; const { callAsInternalUser } = mlClusterClient;
const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(mlClusterClient); const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(mlClusterClient);
const { getAuditMessagesSummary } = jobAuditMessagesProvider(mlClusterClient); const { getAuditMessagesSummary } = jobAuditMessagesProvider(mlClusterClient);
@ -400,7 +400,7 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) {
const detailed = true; const detailed = true;
const jobIds = []; const jobIds = [];
try { try {
const tasksList = await callAsCurrentUser('tasks.list', { actions, detailed }); const tasksList = await callAsInternalUser('tasks.list', { actions, detailed });
Object.keys(tasksList.nodes).forEach((nodeId) => { Object.keys(tasksList.nodes).forEach((nodeId) => {
const tasks = tasksList.nodes[nodeId].tasks; const tasks = tasksList.nodes[nodeId].tasks;
Object.keys(tasks).forEach((taskId) => { Object.keys(tasks).forEach((taskId) => {

View file

@ -9,9 +9,9 @@ import { ILegacyScopedClusterClient } from 'kibana/server';
import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns'; import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns';
import { CategoryId, Category } from '../../../../../common/types/categories'; import { CategoryId, Category } from '../../../../../common/types/categories';
export function topCategoriesProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClusterClient) {
async function getTotalCategories(jobId: string): Promise<{ total: number }> { async function getTotalCategories(jobId: string): Promise<{ total: number }> {
const totalResp = await callAsCurrentUser('search', { const totalResp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
@ -37,7 +37,7 @@ export function topCategoriesProvider({ callAsCurrentUser }: ILegacyScopedCluste
} }
async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { async function getTopCategoryCounts(jobId: string, numberOfCategories: number) {
const top: SearchResponse<any> = await callAsCurrentUser('search', { const top: SearchResponse<any> = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
@ -99,7 +99,7 @@ export function topCategoriesProvider({ callAsCurrentUser }: ILegacyScopedCluste
field: 'category_id', field: 'category_id',
}, },
}; };
const result: SearchResponse<any> = await callAsCurrentUser('search', { const result: SearchResponse<any> = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size, size,
body: { body: {

View file

@ -75,7 +75,6 @@ function getFieldObject(fieldType: PartitionFieldsType, aggs: any) {
} }
export const getPartitionFieldsValuesFactory = ({ export const getPartitionFieldsValuesFactory = ({
callAsCurrentUser,
callAsInternalUser, callAsInternalUser,
}: ILegacyScopedClusterClient) => }: ILegacyScopedClusterClient) =>
/** /**
@ -102,7 +101,7 @@ export const getPartitionFieldsValuesFactory = ({
const isModelPlotEnabled = job?.model_plot_config?.enabled; const isModelPlotEnabled = job?.model_plot_config?.enabled;
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {

View file

@ -31,7 +31,7 @@ interface Influencer {
} }
export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClient) { export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClient) {
const { callAsCurrentUser } = mlClusterClient; const { callAsInternalUser } = mlClusterClient;
// Obtains data for the anomalies table, aggregating anomalies by day or hour as requested. // Obtains data for the anomalies table, aggregating anomalies by day or hour as requested.
// Return an Object with properties 'anomalies' and 'interval' (interval used to aggregate anomalies, // Return an Object with properties 'anomalies' and 'interval' (interval used to aggregate anomalies,
// one of day, hour or second. Note 'auto' can be provided as the aggregationInterval in the request, // one of day, hour or second. Note 'auto' can be provided as the aggregationInterval in the request,
@ -134,7 +134,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
}); });
} }
const resp: SearchResponse<any> = await callAsCurrentUser('search', { const resp: SearchResponse<any> = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
size: maxRecords, size: maxRecords,
@ -288,7 +288,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
}, },
}; };
const resp = await callAsCurrentUser('search', query); const resp = await callAsInternalUser('search', query);
const maxScore = _.get(resp, ['aggregations', 'max_score', 'value'], null); const maxScore = _.get(resp, ['aggregations', 'max_score', 'value'], null);
return { maxScore }; return { maxScore };
@ -326,7 +326,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
// Size of job terms agg, consistent with maximum number of jobs supported by Java endpoints. // Size of job terms agg, consistent with maximum number of jobs supported by Java endpoints.
const maxJobs = 10000; const maxJobs = 10000;
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
size: 0, size: 0,
body: { body: {
@ -370,7 +370,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
// from the given index and job ID. // from the given index and job ID.
// Returned response consists of a list of examples against category ID. // Returned response consists of a list of examples against category ID.
async function getCategoryExamples(jobId: string, categoryIds: any, maxExamples: number) { async function getCategoryExamples(jobId: string, categoryIds: any, maxExamples: number) {
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
size: ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, // Matches size of records in anomaly summary table. size: ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, // Matches size of records in anomaly summary table.
@ -405,7 +405,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
// Returned response contains four properties - categoryId, regex, examples // Returned response contains four properties - categoryId, regex, examples
// and terms (space delimited String of the common tokens matched in values of the category). // and terms (space delimited String of the common tokens matched in values of the category).
async function getCategoryDefinition(jobId: string, categoryId: string) { async function getCategoryDefinition(jobId: string, categoryId: string) {
const resp = await callAsCurrentUser('search', { const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true, rest_total_hits_as_int: true,
size: 1, size: 1,

View file

@ -48,6 +48,7 @@
"GetMaxAnomalyScore", "GetMaxAnomalyScore",
"GetCategoryExamples", "GetCategoryExamples",
"GetPartitionFieldsValues", "GetPartitionFieldsValues",
"AnomalySearch",
"Modules", "Modules",
"DataRecognizer", "DataRecognizer",

View file

@ -513,7 +513,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat
mlLicense.fullLicenseAPIGuard(async (context, request, response) => { mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
try { try {
const { analyticsId } = request.params; const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser( const results = await context.ml!.mlClient.callAsInternalUser(
'ml.updateDataFrameAnalytics', 'ml.updateDataFrameAnalytics',
{ {
body: request.body, body: request.body,

View file

@ -5,6 +5,7 @@
*/ */
import { RequestHandlerContext } from 'kibana/server'; import { RequestHandlerContext } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { wrapError } from '../client/error_wrapper'; import { wrapError } from '../client/error_wrapper';
import { RouteInitialization } from '../types'; import { RouteInitialization } from '../types';
import { import {
@ -15,6 +16,7 @@ import {
partitionFieldValuesSchema, partitionFieldValuesSchema,
} from './schemas/results_service_schema'; } from './schemas/results_service_schema';
import { resultsServiceProvider } from '../models/results_service'; import { resultsServiceProvider } from '../models/results_service';
import { ML_RESULTS_INDEX_PATTERN } from '../../common/constants/index_patterns';
function getAnomaliesTableData(context: RequestHandlerContext, payload: any) { function getAnomaliesTableData(context: RequestHandlerContext, payload: any) {
const rs = resultsServiceProvider(context.ml!.mlClient); const rs = resultsServiceProvider(context.ml!.mlClient);
@ -232,4 +234,35 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization)
} }
}) })
); );
/**
* @apiGroup ResultsService
*
* @api {post} /api/ml/results/anomaly_search Performs a search on the anomaly results index
* @apiName AnomalySearch
*/
router.post(
{
path: '/api/ml/results/anomaly_search',
validate: {
body: schema.maybe(schema.any()),
},
options: {
tags: ['access:ml:canGetJobs'],
},
},
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
const body = {
...request.body,
index: ML_RESULTS_INDEX_PATTERN,
};
try {
return response.ok({
body: await context.ml!.mlClient.callAsInternalUser('search', body),
});
} catch (error) {
return response.customError(wrapError(error));
}
})
);
} }

View file

@ -37,7 +37,7 @@ export function getMlSystemProvider(
return { return {
mlSystemProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) { mlSystemProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) {
// const hasMlCapabilities = getHasMlCapabilities(request); // const hasMlCapabilities = getHasMlCapabilities(request);
const { callAsCurrentUser, callAsInternalUser } = mlClusterClient; const { callAsInternalUser } = mlClusterClient;
return { return {
async mlCapabilities() { async mlCapabilities() {
isMinimumLicense(); isMinimumLicense();
@ -77,7 +77,7 @@ export function getMlSystemProvider(
// integration and currently alerting does not supply a request object. // integration and currently alerting does not supply a request object.
// await hasMlCapabilities(['canAccessML']); // await hasMlCapabilities(['canAccessML']);
return callAsCurrentUser('search', { return callAsInternalUser('search', {
...searchParams, ...searchParams,
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,
}); });

View file

@ -101,7 +101,7 @@ export function MachineLearningDataVisualizerFileBasedProvider(
}, },
async startImportAndWaitForProcessing() { async startImportAndWaitForProcessing() {
await testSubjects.click('mlFileDataVisImportButton'); await testSubjects.clickWhenNotDisabled('mlFileDataVisImportButton');
await retry.tryForTime(60 * 1000, async () => { await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail('mlFileImportSuccessCallout'); await testSubjects.existOrFail('mlFileImportSuccessCallout');
}); });