Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Versioning all ML APIs #156949

Merged
merged 32 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1ca33c2
[ML] Versioning all ML APIs
jgowdyelastic May 7, 2023
072757d
removing constant from infra
jgowdyelastic May 7, 2023
0dd0794
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 15, 2023
f01bd5d
updating server routes
jgowdyelastic May 15, 2023
c1fa1f9
renaming api to internal
jgowdyelastic May 15, 2023
c266ab7
updating sync version
jgowdyelastic May 16, 2023
4d92c0e
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 16, 2023
deb51ab
fixing version for internal api
jgowdyelastic May 16, 2023
25a9f6f
fixing tests
jgowdyelastic May 16, 2023
b979575
small refactor
jgowdyelastic May 16, 2023
0c0e003
reverting test change
jgowdyelastic May 16, 2023
0d767d0
adding version header to api tests
jgowdyelastic May 17, 2023
94e77fc
rewriting AnomalyDetectorService to use standard ml api service
jgowdyelastic May 17, 2023
47f2185
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 17, 2023
b57f000
reverting error message
jgowdyelastic May 17, 2023
e209129
fixing transforms tests
jgowdyelastic May 17, 2023
c05264d
fixing tests
jgowdyelastic May 17, 2023
24c3818
removing fetch call from index data viz
jgowdyelastic May 17, 2023
1a14d02
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 17, 2023
412b5c5
fixing types
jgowdyelastic May 17, 2023
03d60fe
fixing header includes
jgowdyelastic May 17, 2023
7b1c693
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 17, 2023
a5bc2cc
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 17, 2023
8adac57
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 18, 2023
5ed56f3
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 22, 2023
df24ed4
adding missing version
jgowdyelastic May 22, 2023
87013eb
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 22, 2023
de5f053
adding missing version
jgowdyelastic May 22, 2023
ebb12df
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 23, 2023
8052aac
fixing test
jgowdyelastic May 23, 2023
134c219
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 23, 2023
9671116
Merge branch 'main' into versioning-all-ml-apis
jgowdyelastic May 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type { GetTimeFieldRangeResponse } from './types';
*/
export type SetFullTimeRangeApiPath =
| '/internal/file_upload/time_field_range'
| '/api/ml/fields_service/time_field_range';
| '/internal/ml/fields_service/time_field_range';

/**
* Determines the full available time range of the given Data View and updates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('MLModelsApiLogic', () => {
it('calls the ml api', async () => {
http.get.mockResolvedValue(mlModelStats);
const result = await getMLModelsStats();
expect(http.get).toHaveBeenCalledWith('/api/ml/trained_models/_stats');
expect(http.get).toHaveBeenCalledWith('/internal/ml/trained_models/_stats', { version: '1' });
expect(result).toEqual(mlModelStats);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ export interface GetMlModelsStatsResponse {
}

export const getMLModelsStats = async () => {
return await HttpLogic.values.http.get<GetMlModelsStatsResponse>('/api/ml/trained_models/_stats');
return await HttpLogic.values.http.get<GetMlModelsStatsResponse>(
'/internal/ml/trained_models/_stats',
{ version: '1' }
);
};

export const MLModelsStatsApiLogic = createApiLogic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ describe('MLModelsApiLogic', () => {
it('calls the ml api', async () => {
http.get.mockResolvedValue(mlModels);
const result = await getMLModels();
expect(http.get).toHaveBeenCalledWith('/api/ml/trained_models', {
expect(http.get).toHaveBeenCalledWith('/internal/ml/trained_models', {
query: { size: 1000, with_pipelines: true },
version: '1',
});
expect(result).toEqual(mlModels);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ export type GetMlModelsArgs = number | undefined;
export type GetMlModelsResponse = TrainedModelConfigResponse[];

export const getMLModels = async (size: GetMlModelsArgs = 1000) => {
return await HttpLogic.values.http.get<TrainedModelConfigResponse[]>('/api/ml/trained_models', {
query: { size, with_pipelines: true },
});
return await HttpLogic.values.http.get<TrainedModelConfigResponse[]>(
'/internal/ml/trained_models',
{
query: { size, with_pipelines: true },
version: '1',
}
);
};

export const MLModelsApiLogic = createApiLogic(['ml_models_api_logic'], getMLModels, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const callDeleteJobs = async <JobType extends string>(
const { spaceId, logViewId, jobTypes } = requestArgs;

// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
const deleteJobsResponse = await fetch('/api/ml/jobs/delete_jobs', {
const deleteJobsResponse = await fetch('/internal/ml/jobs/delete_jobs', {
method: 'POST',
version: '1',
body: JSON.stringify(
deleteJobsRequestPayloadRT.encode({
jobIds: jobTypes.map((jobType) => getJobId(spaceId, logViewId, jobType)),
Expand All @@ -37,7 +38,9 @@ export const callDeleteJobs = async <JobType extends string>(
};

export const callGetJobDeletionTasks = async (fetch: HttpHandler) => {
const jobDeletionTasksResponse = await fetch('/api/ml/jobs/deleting_jobs_tasks');
const jobDeletionTasksResponse = await fetch('/internal/ml/jobs/deleting_jobs_tasks', {
version: '1',
});

return decodeOrThrow(getJobDeletionTasksResponsePayloadRT)(jobDeletionTasksResponse);
};
Expand All @@ -55,8 +58,9 @@ export const callStopDatafeeds = async <JobType extends string>(
const { spaceId, logViewId, jobTypes } = requestArgs;

// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
const stopDatafeedResponse = await fetch('/api/ml/jobs/stop_datafeeds', {
const stopDatafeedResponse = await fetch('/internal/ml/jobs/stop_datafeeds', {
method: 'POST',
version: '1',
body: JSON.stringify(
stopDatafeedsRequestPayloadRT.encode({
datafeedIds: jobTypes.map((jobType) => getDatafeedId(spaceId, logViewId, jobType)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ export const callJobsSummaryAPI = async <JobType extends string>(
fetch: HttpHandler
) => {
const { spaceId, logViewId, jobTypes } = requestArgs;
const response = await fetch('/api/ml/jobs/jobs_summary', {
const response = await fetch('/internal/ml/jobs/jobs_summary', {
method: 'POST',
version: '1',
body: JSON.stringify(
fetchJobStatusRequestPayloadRT.encode({
jobIds: jobTypes.map((jobType) => getJobId(spaceId, logViewId, jobType)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { jobCustomSettingsRT } from '../../../../../common/log_analysis';
import { decodeOrThrow } from '../../../../../common/runtime_types';

export const callGetMlModuleAPI = async (moduleId: string, fetch: HttpHandler) => {
const response = await fetch(`/api/ml/modules/get_module/${moduleId}`, {
const response = await fetch(`/internal/ml/modules/get_module/${moduleId}`, {
method: 'GET',
version: '1',
});

return decodeOrThrow(getMlModuleResponsePayloadRT)(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: Http
useDedicatedIndex = false,
} = requestArgs;

const response = await fetch(`/api/ml/modules/setup/${moduleId}`, {
const response = await fetch(`/internal/ml/modules/setup/${moduleId}`, {
method: 'POST',
version: '1',
body: JSON.stringify(
setupMlModuleRequestPayloadRT.encode({
start,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export const useLogAnalysisCapabilities = () => {
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
const rawResponse = await services.http.fetch('/api/ml/ml_capabilities');
const rawResponse = await services.http.fetch('/internal/ml/ml_capabilities', {
version: '1',
});

return decodeOrThrow(getMlCapabilitiesResponsePayloadRT)(rawResponse);
},
Expand Down
10 changes: 7 additions & 3 deletions x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ export const callDeleteJobs = async <JobType extends string>(
const { spaceId, sourceId, jobTypes } = requestArgs;

// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
const deleteJobsResponse = await fetch('/api/ml/jobs/delete_jobs', {
const deleteJobsResponse = await fetch('/internal/ml/jobs/delete_jobs', {
method: 'POST',
version: '1',
body: JSON.stringify(
deleteJobsRequestPayloadRT.encode({
jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)),
Expand All @@ -36,7 +37,9 @@ export const callDeleteJobs = async <JobType extends string>(
};

export const callGetJobDeletionTasks = async (fetch: HttpHandler) => {
const jobDeletionTasksResponse = await fetch('/api/ml/jobs/deleting_jobs_tasks');
const jobDeletionTasksResponse = await fetch('/internal/ml/jobs/deleting_jobs_tasks', {
version: '1',
});

return decodeOrThrow(getJobDeletionTasksResponsePayloadRT)(jobDeletionTasksResponse);
};
Expand All @@ -54,8 +57,9 @@ export const callStopDatafeeds = async <JobType extends string>(
const { spaceId, sourceId, jobTypes } = requestArgs;

// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
const stopDatafeedResponse = await fetch('/api/ml/jobs/stop_datafeeds', {
const stopDatafeedResponse = await fetch('/internal/ml/jobs/stop_datafeeds', {
method: 'POST',
version: '1',
body: JSON.stringify(
stopDatafeedsRequestPayloadRT.encode({
datafeedIds: jobTypes.map((jobType) => getDatafeedId(spaceId, sourceId, jobType)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ export const callJobsSummaryAPI = async <JobType extends string>(
fetch: HttpHandler
) => {
const { spaceId, sourceId, jobTypes } = requestArgs;
const response = await fetch('/api/ml/jobs/jobs_summary', {
const response = await fetch('/internal/ml/jobs/jobs_summary', {
method: 'POST',
version: '1',
body: JSON.stringify(
fetchJobStatusRequestPayloadRT.encode({
jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { jobCustomSettingsRT } from '../../../../common/log_analysis';
import { decodeOrThrow } from '../../../../common/runtime_types';

export const callGetMlModuleAPI = async (moduleId: string, fetch: HttpHandler) => {
const response = await fetch(`/api/ml/modules/get_module/${moduleId}`, {
const response = await fetch(`/internal/ml/modules/get_module/${moduleId}`, {
method: 'GET',
version: '1',
});

return decodeOrThrow(getMlModuleResponsePayloadRT)(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: Http
query,
} = requestArgs;

const response = await fetch(`/api/ml/modules/setup/${moduleId}`, {
const response = await fetch(`/internal/ml/modules/setup/${moduleId}`, {
method: 'POST',
version: '1',
body: JSON.stringify(
setupMlModuleRequestPayloadRT.encode({
start,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export const useInfraMLCapabilities = () => {
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
const rawResponse = await services.http.fetch('/api/ml/ml_capabilities');
const rawResponse = await services.http.fetch('/internal/ml/ml_capabilities', {
version: '1',
});

return pipe(
getMlCapabilitiesResponsePayloadRT.decode(rawResponse),
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/ml/common/constants/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export const PLUGIN_ICON_SOLUTION = 'logoKibana';
export const ML_APP_NAME = i18n.translate('xpack.ml.navMenu.mlAppNameText', {
defaultMessage: 'Machine Learning',
});
export const ML_BASE_PATH = '/api/ml';
export const ML_INTERNAL_BASE_PATH = '/internal/ml';
export const ML_EXTERNAL_BASE_PATH = '/api/ml';
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export interface MlSummaryJob {

/**
* Used in older implementations of the job config, where the datafeed was placed inside the job for convenience.
* This will be populated if the job's id has been passed to the /api/ml/jobs/jobs_summary endpoint.
* This will be populated if the job's id has been passed to the /internal/ml/jobs/jobs_summary endpoint.
*/
fullJob?: CombinedJob;

Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/ml/common/types/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ export interface Module {
kibana: KibanaObjects;
}

export interface RecognizeResult {
id: string;
title: string;
query: any;
description: string;
logo: Logo;
}

export interface FileBasedModule extends Omit<Module, 'jobs' | 'datafeeds' | 'kibana'> {
jobs: Array<{ file: string; id: string }>;
datafeeds: Array<{ file: string; job_id: string; id: string }>;
Expand Down
17 changes: 17 additions & 0 deletions x-pack/plugins/ml/common/util/job_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,3 +916,20 @@ export function isKnownEmptyQuery(query: QueryDslQueryContainer) {

return false;
}

/**
* Extract unique influencers from the job or collection of jobs
* @param jobs
*/
export function extractInfluencers(jobs: Job | Job[]): string[] {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved from AnomalyDetectorService as it doesn't need to live inside the class.

if (!Array.isArray(jobs)) {
jobs = [jobs];
}
const influencers = new Set<string>();
for (const job of jobs) {
for (const influencer of job.analysis_config.influencers || []) {
influencers.add(influencer);
}
}
return Array.from(influencers);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,18 @@ import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_
import { checkPermission } from '../../capabilities/check_capabilities';
import { MlPageHeader } from '../../components/page_header';

interface RecognizerModule {
id: string;
title: string;
query: Record<string, object>;
description: string;
logo: {
icon: string;
};
}

export const IndexDataVisualizerPage: FC = () => {
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const {
services: {
http,
docLinks,
dataVisualizer,
data: {
dataViews: { get: getDataView },
},
mlServices: {
mlApiServices: { recognizeIndex },
},
},
} = useMlKibana();
const mlLocator = useMlLocator()!;
Expand Down Expand Up @@ -140,18 +132,14 @@ export const IndexDataVisualizerPage: FC = () => {
const getAsyncRecognizedModuleCards = async (params: GetAdditionalLinksParams) => {
const { dataViewId, dataViewTitle } = params;
try {
const modules = await http.fetch<RecognizerModule[]>(
`/api/ml/modules/recognize/${dataViewTitle}`,
{
method: 'GET',
}
);
const modules = await recognizeIndex({ indexPatternTitle: dataViewTitle! });

return modules?.map(
(m): ResultLink => ({
id: m.id,
title: m.title,
description: m.description,
icon: m.logo.icon,
icon: m.logo?.icon ?? '',
type: 'index',
getUrl: async () => {
return await mlLocator.getUrl({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import moment from 'moment';
import { FullTimeRangeSelector, FROZEN_TIER_PREFERENCE } from '@kbn/ml-date-picker';
import { useTimefilter, type GetTimeFieldRangeResponse } from '@kbn/ml-date-picker';
import { useStorage } from '@kbn/ml-local-storage';
import { ML_INTERNAL_BASE_PATH } from '../../../../../../../common/constants/app';
import { WizardNav } from '../wizard_nav';
import { StepProps, WIZARD_STEPS } from '../step_types';
import { JobCreatorContext } from '../job_creator_context';
Expand Down Expand Up @@ -135,7 +136,7 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
disabled={false}
callback={fullTimeRangeCallback}
timefilter={timefilter}
apiPath="/api/ml/fields_service/time_field_range"
apiPath={`${ML_INTERNAL_BASE_PATH}/fields_service/time_field_range`}
/>
</EuiFlexItem>
<EuiFlexItem />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,32 @@

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Job, JobId } from '../../../common/types/anomaly_detection_jobs';
import { basePath } from './ml_api_service';
import type { Job, JobId } from '../../../common/types/anomaly_detection_jobs';
import { HttpService } from './http_service';
import { type MlApiServices, mlApiServicesProvider } from './ml_api_service';

export class AnomalyDetectorService {
private readonly apiBasePath = basePath() + '/anomaly_detectors';
private mlApiServices: MlApiServices;

constructor(private httpService: HttpService) {}
constructor(httpService: HttpService) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this class to remove the API calls directly using http.
It now uses our standard api service where I've added a observable based version of getJobs.

I've also removed the extractInfluencers method as has no dependencies on class member data and so can live with our other job utils functions.

this.mlApiServices = mlApiServicesProvider(httpService);
}

/**
* Fetches a single job object
* @param jobId
*/
getJobById$(jobId: JobId): Observable<Job> {
return this.httpService
.http$<{ count: number; jobs: Job[] }>({
path: `${this.apiBasePath}/${jobId}`,
})
.pipe(map((response) => response.jobs[0]));
return this.getJobs$([jobId]).pipe(map((jobs) => jobs[0]));
}

/**
* Fetches anomaly detection jobs by ids
* @param jobIds
*/
getJobs$(jobIds: JobId[]): Observable<Job[]> {
return this.httpService
.http$<{ count: number; jobs: Job[] }>({
path: `${this.apiBasePath}/${jobIds.join(',')}`,
})
return this.mlApiServices
.getJobs$({ jobId: jobIds.join(',') })
.pipe(map((response) => response.jobs));
}

/**
* Extract unique influencers from the job or collection of jobs
* @param jobs
*/
extractInfluencers(jobs: Job | Job[]): string[] {
if (!Array.isArray(jobs)) {
jobs = [jobs];
}
const influencers = new Set<string>();
for (const job of jobs) {
for (const influencer of job.analysis_config.influencers || []) {
influencers.add(influencer);
}
}
return Array.from(influencers);
}
}
Loading