Skip to content

Commit

Permalink
[Reporting] Allow reports to be deleted in Management > Kibana > Repo…
Browse files Browse the repository at this point in the history
…rting (elastic#60077)

* [Reporting] Feature Delete Button in Job Listing

* refactor listing buttons

* multi-delete

* confirm modal

* remove unused

* fix test

* mock the id generator for snapshotting

* simplify

* add search bar above table

* fix types errors
  • Loading branch information
tsullivan committed Mar 19, 2020
1 parent 99f4ce5 commit cfb938d
Show file tree
Hide file tree
Showing 20 changed files with 1,225 additions and 193 deletions.
18 changes: 18 additions & 0 deletions x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';
import Boom from 'boom';
import { errors as elasticsearchErrors } from 'elasticsearch';
import { ElasticsearchServiceSetup } from 'kibana/server';
import { get } from 'lodash';
Expand Down Expand Up @@ -152,5 +154,21 @@ export function jobsQueryFactory(server: ServerFacade, elasticsearch: Elasticsea
return hits[0];
});
},

async delete(deleteIndex: string, id: string) {
try {
const query = { id, index: deleteIndex };
return callAsInternalUser('delete', query);
} catch (error) {
const wrappedError = new Error(
i18n.translate('xpack.reporting.jobsQuery.deleteError', {
defaultMessage: 'Could not delete the report: {error}',
values: { error: error.message },
})
);

throw Boom.boomify(wrappedError, { statusCode: error.status });
}
},
};
}
52 changes: 48 additions & 4 deletions x-pack/legacy/plugins/reporting/server/routes/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import {
} from '../../types';
import { jobsQueryFactory } from '../lib/jobs_query';
import { ReportingSetupDeps, ReportingCore } from '../types';
import { jobResponseHandlerFactory } from './lib/job_response_handler';
import {
deleteJobResponseHandlerFactory,
downloadJobResponseHandlerFactory,
} from './lib/job_response_handler';
import { makeRequestFacade } from './lib/make_request_facade';
import {
getRouteConfigFactoryDeletePre,
getRouteConfigFactoryDownloadPre,
getRouteConfigFactoryManagementPre,
} from './lib/route_config_factories';
Expand All @@ -40,7 +44,6 @@ export function registerJobInfoRoutes(
const { elasticsearch } = plugins;
const jobsQuery = jobsQueryFactory(server, elasticsearch);
const getRouteConfig = getRouteConfigFactoryManagementPre(server, plugins, logger);
const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server, plugins, logger);

// list jobs in the queue, paginated
server.route({
Expand Down Expand Up @@ -138,7 +141,8 @@ export function registerJobInfoRoutes(

// trigger a download of the output from a job
const exportTypesRegistry = reporting.getExportTypesRegistry();
const jobResponseHandler = jobResponseHandlerFactory(server, elasticsearch, exportTypesRegistry);
const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server, plugins, logger);
const downloadResponseHandler = downloadJobResponseHandlerFactory(server, elasticsearch, exportTypesRegistry); // prettier-ignore
server.route({
path: `${MAIN_ENTRY}/download/{docId}`,
method: 'GET',
Expand All @@ -147,7 +151,47 @@ export function registerJobInfoRoutes(
const request = makeRequestFacade(legacyRequest);
const { docId } = request.params;

let response = await jobResponseHandler(
let response = await downloadResponseHandler(
request.pre.management.jobTypes,
request.pre.user,
h,
{ docId }
);

if (isResponse(response)) {
const { statusCode } = response;

if (statusCode !== 200) {
if (statusCode === 500) {
logger.error(`Report ${docId} has failed: ${JSON.stringify(response.source)}`);
} else {
logger.debug(
`Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify(
response.source
)}]`
);
}
}

response = response.header('accept-ranges', 'none');
}

return response;
},
});

// allow a report to be deleted
const getRouteConfigDelete = getRouteConfigFactoryDeletePre(server, plugins, logger);
const deleteResponseHandler = deleteJobResponseHandlerFactory(server, elasticsearch);
server.route({
path: `${MAIN_ENTRY}/delete/{docId}`,
method: 'DELETE',
options: getRouteConfigDelete(),
handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => {
const request = makeRequestFacade(legacyRequest);
const { docId } = request.params;

let response = await deleteResponseHandler(
request.pre.management.jobTypes,
request.pre.user,
h,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface JobResponseHandlerOpts {
excludeContent?: boolean;
}

export function jobResponseHandlerFactory(
export function downloadJobResponseHandlerFactory(
server: ServerFacade,
elasticsearch: ElasticsearchServiceSetup,
exportTypesRegistry: ExportTypesRegistry
Expand All @@ -36,6 +36,7 @@ export function jobResponseHandlerFactory(
opts: JobResponseHandlerOpts = {}
) {
const { docId } = params;
// TODO: async/await
return jobsQuery.get(user, docId, { includeContent: !opts.excludeContent }).then(doc => {
if (!doc) return Boom.notFound();

Expand Down Expand Up @@ -67,3 +68,34 @@ export function jobResponseHandlerFactory(
});
};
}

export function deleteJobResponseHandlerFactory(
server: ServerFacade,
elasticsearch: ElasticsearchServiceSetup
) {
const jobsQuery = jobsQueryFactory(server, elasticsearch);

return async function deleteJobResponseHander(
validJobTypes: string[],
user: any,
h: ResponseToolkit,
params: JobResponseHandlerParams
) {
const { docId } = params;
const doc = await jobsQuery.get(user, docId, { includeContent: false });
if (!doc) return Boom.notFound();

const { jobtype: jobType } = doc._source;
if (!validJobTypes.includes(jobType)) {
return Boom.unauthorized(`Sorry, you are not authorized to delete ${jobType} reports`);
}

try {
const docIndex = doc._index;
await jobsQuery.delete(docIndex, docId);
return h.response({ deleted: true });
} catch (error) {
return Boom.boomify(error, { statusCode: error.statusCode });
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,22 @@ export function getRouteConfigFactoryDownloadPre(
const getManagementRouteConfig = getRouteConfigFactoryManagementPre(server, plugins, logger);
return (): RouteConfigFactory => ({
...getManagementRouteConfig(),
tags: [API_TAG],
tags: [API_TAG, 'download'],
response: {
ranges: false,
},
});
}

export function getRouteConfigFactoryDeletePre(
server: ServerFacade,
plugins: ReportingSetupDeps,
logger: Logger
): GetRouteConfigFactoryFn {
const getManagementRouteConfig = getRouteConfigFactoryManagementPre(server, plugins, logger);
return (): RouteConfigFactory => ({
...getManagementRouteConfig(),
tags: [API_TAG, 'delete'],
response: {
ranges: false,
},
Expand Down
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/reporting/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export interface JobDocPayload<JobParamsType> {

export interface JobSource<JobParamsType> {
_id: string;
_index: string;
_source: {
jobtype: string;
output: JobDocOutput;
Expand Down
Loading

0 comments on commit cfb938d

Please sign in to comment.