[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 allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys];
// 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 = {
app: [PLUGIN_ID, 'kibana'],
excludeFromBasePrivileges: true,
@ -79,10 +80,6 @@ export function getPluginPrivileges() {
insightsAndAlerting: ['jobsListLink'],
},
catalogue: [PLUGIN_ID],
savedObject: {
all: [],
read: ['index-pattern', 'dashboard', 'search', 'visualization'],
},
};
return {
@ -90,11 +87,19 @@ export function getPluginPrivileges() {
...privilege,
api: allMlCapabilitiesKeys.map((k) => `ml:${k}`),
ui: allMlCapabilitiesKeys,
savedObject: {
all: savedObjects,
read: savedObjects,
},
},
user: {
...privilege,
api: userMlCapabilitiesKeys.map((k) => `ml:${k}`),
ui: userMlCapabilitiesKeys,
savedObject: {
all: [],
read: savedObjects,
},
},
};
}

View file

@ -39,7 +39,7 @@ export const BottomBar: FC<BottomBarProps> = ({ mode, onChangeMode, onCancel, di
disableImport ? (
<FormattedMessage
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
}

View file

@ -18,6 +18,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { debounce } from 'lodash';
import { importerFactory } from './importer';
import { ResultsLinks } from '../results_links';
import { FilebeatConfigFlyout } from '../filebeat_config_flyout';
@ -66,6 +67,7 @@ const DEFAULT_STATE = {
indexPatternNameError: '',
timeFieldName: undefined,
isFilebeatFlyoutVisible: false,
checkingValidIndex: false,
};
export class ImportView extends Component {
@ -76,14 +78,12 @@ export class ImportView extends Component {
}
componentDidMount() {
this.loadIndexNames();
this.loadIndexPatternNames();
}
clickReset = () => {
const state = getDefaultState(this.state, this.props.results);
this.setState(state, () => {
this.loadIndexNames();
this.loadIndexPatternNames();
});
};
@ -326,21 +326,33 @@ export class ImportView extends Component {
};
onIndexChange = (e) => {
const name = e.target.value;
const { indexNames, indexPattern, indexPatternNames } = this.state;
const index = e.target.value;
this.setState({
index: name,
indexNameError: isIndexNameValid(name, indexNames),
// if index pattern has been altered, check that it still matches the inputted index
...(indexPattern === ''
? {}
: {
indexPatternNameError: isIndexPatternNameValid(indexPattern, indexPatternNames, name),
}),
index,
checkingValidIndex: true,
});
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) => {
const name = e.target.value;
const { indexPatternNames, index } = this.state;
@ -396,12 +408,6 @@ export class ImportView extends Component {
this.props.showBottomBar();
};
async loadIndexNames() {
const indices = await ml.getIndices();
const indexNames = indices.map((i) => i.name);
this.setState({ indexNames });
}
async loadIndexPatternNames() {
await loadIndexPatterns(this.props.indexPatterns);
const indexPatternNames = getIndexPatternNames();
@ -437,6 +443,7 @@ export class ImportView extends Component {
indexPatternNameError,
timeFieldName,
isFilebeatFlyoutVisible,
checkingValidIndex,
} = this.state;
const createPipeline = pipelineString !== '';
@ -459,7 +466,8 @@ export class ImportView extends Component {
index === '' ||
indexNameError !== '' ||
(createIndexPattern === true && indexPatternNameError !== '') ||
initialized === true;
initialized === true ||
checkingValidIndex === true;
return (
<EuiPage data-test-subj="mlPageFileDataVisImport">
@ -655,16 +663,7 @@ function getDefaultState(state, results) {
};
}
function isIndexNameValid(name, indexNames) {
if (indexNames.find((i) => i === name)) {
return (
<FormattedMessage
id="xpack.ml.fileDatavisualizer.importView.indexNameAlreadyExistsErrorMessage"
defaultMessage="Index name already exists"
/>
);
}
function isIndexNameValid(name) {
const reg = new RegExp('[\\\\/*?"<>|\\s,#]+');
if (
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 { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns';
import { getPartitioningFieldNames } from '../../../../../common/util/job_utils';
import { parseInterval } from '../../../../../common/util/parse_interval';
import { replaceTokensInUrlValue, isValidLabel } from '../../../util/custom_url_utils';
@ -295,11 +294,11 @@ export function getTestUrl(job, customUrl) {
};
return new Promise((resolve, reject) => {
ml.esSearch({
index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true,
body,
})
ml.results
.anomalySearch({
rest_total_hits_as_int: true,
body,
})
.then((resp) => {
if (resp.hits.total > 0) {
const record = resp.hits.hits[0]._source;

View file

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

View file

@ -9,7 +9,6 @@
import _ from 'lodash';
import { map } from 'rxjs/operators';
import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { ml } from './ml_api_service';
// 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);
}
ml.esSearch({
index: ML_RESULTS_INDEX_PATTERN,
size: maxResults,
rest_total_hits_as_int: true,
body: {
query: {
bool: {
filter: filterCriteria,
ml.results
.anomalySearch({
size: maxResults,
rest_total_hits_as_int: true,
body: {
query: {
bool: {
filter: filterCriteria,
},
},
sort: [{ forecast_create_timestamp: { order: 'desc' } }],
},
sort: [{ forecast_create_timestamp: { order: 'desc' } }],
},
})
})
.then((resp) => {
if (resp.hits.total !== 0) {
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)
// once forecasting with these parameters is supported.
ml.esSearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0,
body: {
query: {
bool: {
filter: filterCriteria,
},
},
aggs: {
earliest: {
min: {
field: 'timestamp',
ml.results
.anomalySearch({
size: 0,
body: {
query: {
bool: {
filter: filterCriteria,
},
},
latest: {
max: {
field: 'timestamp',
aggs: {
earliest: {
min: {
field: 'timestamp',
},
},
latest: {
max: {
field: 'timestamp',
},
},
},
},
},
})
})
.then((resp) => {
obj.earliest = _.get(resp, 'aggregations.earliest.value', null);
obj.latest = _.get(resp, 'aggregations.latest.value', null);
@ -243,9 +242,8 @@ function getForecastData(
min: aggType.min,
};
return ml
.esSearch$({
index: ML_RESULTS_INDEX_PATTERN,
return ml.results
.anomalySearch$({
size: 0,
body: {
query: {
@ -343,18 +341,18 @@ function getForecastRequestStats(job, forecastId) {
},
];
ml.esSearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 1,
rest_total_hits_as_int: true,
body: {
query: {
bool: {
filter: filterCriteria,
ml.results
.anomalySearch({
size: 1,
rest_total_hits_as_int: true,
body: {
query: {
bool: {
filter: filterCriteria,
},
},
},
},
})
})
.then((resp) => {
if (resp.hits.total !== 0) {
obj.stats = _.first(resp.hits.hits)._source;

View file

@ -96,4 +96,22 @@ export const resultsApiProvider = (httpService: HttpService) => ({
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
.esSearch$({
return mlApiServices.results
.anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN,
size: 0,
body: {
@ -399,8 +399,8 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) {
});
});
return mlApiServices
.esSearch$({
return mlApiServices.results
.anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true,
size: maxResults !== undefined ? maxResults : 100,
@ -484,8 +484,8 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) {
});
}
return mlApiServices
.esSearch$({
return mlApiServices.results
.anomalySearch$({
index: ML_RESULTS_INDEX_PATTERN,
size: 0,
body: {

View file

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

View file

@ -18,17 +18,17 @@ import {
// - ML_ANNOTATIONS_INDEX_ALIAS_READ alias is present
// - ML_ANNOTATIONS_INDEX_ALIAS_WRITE alias is present
export async function isAnnotationsFeatureAvailable({
callAsCurrentUser,
callAsInternalUser,
}: ILegacyScopedClusterClient) {
try {
const indexParams = { index: ML_ANNOTATIONS_INDEX_PATTERN };
const annotationsIndexExists = await callAsCurrentUser('indices.exists', indexParams);
const annotationsIndexExists = await callAsInternalUser('indices.exists', indexParams);
if (!annotationsIndexExists) {
return false;
}
const annotationsReadAliasExists = await callAsCurrentUser('indices.existsAlias', {
const annotationsReadAliasExists = await callAsInternalUser('indices.existsAlias', {
index: ML_ANNOTATIONS_INDEX_ALIAS_READ,
name: ML_ANNOTATIONS_INDEX_ALIAS_READ,
});
@ -37,7 +37,7 @@ export async function isAnnotationsFeatureAvailable({
return false;
}
const annotationsWriteAliasExists = await callAsCurrentUser('indices.existsAlias', {
const annotationsWriteAliasExists = await callAsInternalUser('indices.existsAlias', {
index: 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);
expect(mockFunct.callAsCurrentUser.mock.calls[0][0]).toBe('delete');
expect(mockFunct.callAsCurrentUser.mock.calls[0][1]).toEqual(deleteParamsMock);
expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('delete');
expect(mockFunct.callAsInternalUser.mock.calls[0][1]).toEqual(deleteParamsMock);
expect(response).toBe(acknowledgedResponseMock);
done();
});
@ -73,8 +73,8 @@ describe('annotation_service', () => {
const response: GetResponse = await getAnnotations(indexAnnotationArgsMock);
expect(mockFunct.callAsCurrentUser.mock.calls[0][0]).toBe('search');
expect(mockFunct.callAsCurrentUser.mock.calls[0][1]).toEqual(getAnnotationsRequestMock);
expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('search');
expect(mockFunct.callAsInternalUser.mock.calls[0][1]).toEqual(getAnnotationsRequestMock);
expect(Object.keys(response.annotations)).toHaveLength(1);
expect(response.annotations[jobIdMock]).toHaveLength(2);
expect(isAnnotations(response.annotations[jobIdMock])).toBeTruthy();
@ -89,7 +89,7 @@ describe('annotation_service', () => {
};
const mlClusterClientSpyError: any = {
callAsCurrentUser: jest.fn(() => {
callAsInternalUser: jest.fn(() => {
return Promise.resolve(mockEsError);
}),
};
@ -124,10 +124,10 @@ describe('annotation_service', () => {
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
const indexParamsCheck = mockFunct.callAsCurrentUser.mock.calls[0][1];
const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[0][1];
const annotation = indexParamsCheck.body;
expect(annotation.create_username).toBe(usernameMock);
expect(annotation.modified_username).toBe(usernameMock);
@ -154,10 +154,10 @@ describe('annotation_service', () => {
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
const indexParamsCheck = mockFunct.callAsCurrentUser.mock.calls[0][1];
const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[0][1];
const annotation = indexParamsCheck.body;
expect(annotation.create_username).toBe(usernameMock);
expect(annotation.modified_username).toBe(usernameMock);
@ -196,9 +196,9 @@ describe('annotation_service', () => {
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
const indexParamsCheck = mockFunct.callAsCurrentUser.mock.calls[1][1];
const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[1][1];
const modifiedAnnotation = indexParamsCheck.body;
expect(modifiedAnnotation.annotation).toBe(modifiedAnnotationText);
expect(modifiedAnnotation.create_username).toBe(originalUsernameMock);

View file

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

View file

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

View file

@ -48,7 +48,7 @@ interface Results {
}
export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) {
const { callAsCurrentUser, callAsInternalUser } = mlClusterClient;
const { callAsInternalUser } = mlClusterClient;
const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(mlClusterClient);
const { getAuditMessagesSummary } = jobAuditMessagesProvider(mlClusterClient);
@ -400,7 +400,7 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) {
const detailed = true;
const jobIds = [];
try {
const tasksList = await callAsCurrentUser('tasks.list', { actions, detailed });
const tasksList = await callAsInternalUser('tasks.list', { actions, detailed });
Object.keys(tasksList.nodes).forEach((nodeId) => {
const tasks = tasksList.nodes[nodeId].tasks;
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 { CategoryId, Category } from '../../../../../common/types/categories';
export function topCategoriesProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) {
export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClusterClient) {
async function getTotalCategories(jobId: string): Promise<{ total: number }> {
const totalResp = await callAsCurrentUser('search', {
const totalResp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN,
size: 0,
body: {
@ -37,7 +37,7 @@ export function topCategoriesProvider({ callAsCurrentUser }: ILegacyScopedCluste
}
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,
size: 0,
body: {
@ -99,7 +99,7 @@ export function topCategoriesProvider({ callAsCurrentUser }: ILegacyScopedCluste
field: 'category_id',
},
};
const result: SearchResponse<any> = await callAsCurrentUser('search', {
const result: SearchResponse<any> = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN,
size,
body: {

View file

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

View file

@ -31,7 +31,7 @@ interface Influencer {
}
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.
// 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,
@ -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,
rest_total_hits_as_int: true,
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);
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.
const maxJobs = 10000;
const resp = await callAsCurrentUser('search', {
const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN,
size: 0,
body: {
@ -370,7 +370,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
// from the given index and job ID.
// Returned response consists of a list of examples against category ID.
async function getCategoryExamples(jobId: string, categoryIds: any, maxExamples: number) {
const resp = await callAsCurrentUser('search', {
const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true,
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
// and terms (space delimited String of the common tokens matched in values of the category).
async function getCategoryDefinition(jobId: string, categoryId: string) {
const resp = await callAsCurrentUser('search', {
const resp = await callAsInternalUser('search', {
index: ML_RESULTS_INDEX_PATTERN,
rest_total_hits_as_int: true,
size: 1,

View file

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

View file

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

View file

@ -5,6 +5,7 @@
*/
import { RequestHandlerContext } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { wrapError } from '../client/error_wrapper';
import { RouteInitialization } from '../types';
import {
@ -15,6 +16,7 @@ import {
partitionFieldValuesSchema,
} from './schemas/results_service_schema';
import { resultsServiceProvider } from '../models/results_service';
import { ML_RESULTS_INDEX_PATTERN } from '../../common/constants/index_patterns';
function getAnomaliesTableData(context: RequestHandlerContext, payload: any) {
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 {
mlSystemProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) {
// const hasMlCapabilities = getHasMlCapabilities(request);
const { callAsCurrentUser, callAsInternalUser } = mlClusterClient;
const { callAsInternalUser } = mlClusterClient;
return {
async mlCapabilities() {
isMinimumLicense();
@ -77,7 +77,7 @@ export function getMlSystemProvider(
// integration and currently alerting does not supply a request object.
// await hasMlCapabilities(['canAccessML']);
return callAsCurrentUser('search', {
return callAsInternalUser('search', {
...searchParams,
index: ML_RESULTS_INDEX_PATTERN,
});

View file

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