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

Cases delete files case deletion #153979

Merged

Conversation

jonathan-buttner
Copy link
Contributor

@jonathan-buttner jonathan-buttner commented Mar 29, 2023

This PR extends the case deletion functionality to delete any files that are attached to the case when a case is deleted.

To avoid attempting to delete too many files at once I chunk case ids into 50 at a time such that we're at most only deleting 5000 (50 case * 100 files per case) at once. That way we don't exceed the 10k find api limit.

Testing

Run the python script from here: https://github.com/elastic/cases-files-generator to generate some files for a case

Deleting the case should remove all files

@jonathan-buttner jonathan-buttner added release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Feature:Cases Cases feature v8.8.0 labels Mar 29, 2023
@cnasikas cnasikas mentioned this pull request Apr 3, 2023
5 tasks
@@ -105,5 +105,5 @@ export interface FindFileArgs extends Pagination {
/**
* File metadata values. These values are governed by the consumer.
*/
meta?: Record<string, string>;
meta?: Record<string, string | string[]>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To avoid typescript complaining we need this as a string array because we want to find any files that are associated to an array of ids.

When I looked at the way the metadata query is being built here: https://github.com/elastic/kibana/blob/main/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts it looks like the file service already handles an array of strings. Let me know if that's not correct though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it seems so 👍

@@ -39,6 +40,10 @@ export const getCaseCommentDetailsUrl = (caseId: string, commentId: string): str
return CASE_COMMENT_DETAILS_URL.replace('{case_id}', caseId).replace('{comment_id}', commentId);
};

export const getCaseFindAttachmentsUrl = (caseId: string): string => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just adding this to make the integration tests a little easier

@@ -77,3 +88,29 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
});
}
}

export const getFileEntities = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Chunking logic is here

import { MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
import type { OwnerEntity } from '../../authorization';

export const createFileEntities = (files: FileJSON[]): OwnerEntity[] => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just moved these to be shared between the bulk delete files API within cases and the delete case code.

};

export const deleteFiles = async (fileIds: string[], fileService: FileServiceStart) => {
pMap(fileIds, async (fileId: string) => fileService.delete({ id: fileId }), {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@elastic/appex-sharedux how feasible would it be to have a bulk delete files API?

Issue: #154286

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for creating an issue, we will triage.
I am not sure if there were any technical or API design limitations to not adding this api.

ids: schema.arrayOf(schema.string()),
ids: schema.arrayOf(schema.string({ minLength: 1 }), {
minSize: 1,
maxSize: MAX_DELETE_CASE_IDS,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Limiting the deletion API based on the issue here: #146945

Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, we cannot do that as it will be a breaking change.

@jonathan-buttner jonathan-buttner marked this pull request as ready for review April 3, 2023 20:25
@jonathan-buttner jonathan-buttner requested review from a team as code owners April 3, 2023 20:25
@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops (Team:ResponseOps)

@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops-cases (Feature:Cases)

@@ -105,5 +105,5 @@ export interface FindFileArgs extends Pagination {
/**
* File metadata values. These values are governed by the consumer.
*/
meta?: Record<string, string>;
meta?: Record<string, string | string[]>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it seems so 👍

};

export const deleteFiles = async (fileIds: string[], fileService: FileServiceStart) => {
pMap(fileIds, async (fileId: string) => fileService.delete({ id: fileId }), {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for creating an issue, we will triage.
I am not sure if there were any technical or API design limitations to not adding this api.

Copy link
Member

@cnasikas cnasikas left a comment

Choose a reason for hiding this comment

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

Nice work! I left some comments.

@@ -47,6 +47,7 @@ export const CASE_CONFIGURE_DETAILS_URL = `${CASES_URL}/configure/{configuration
export const CASE_CONFIGURE_CONNECTORS_URL = `${CASE_CONFIGURE_URL}/connectors` as const;

export const CASE_COMMENTS_URL = `${CASE_DETAILS_URL}/comments` as const;
export const CASE_FIND_ATTACHMENTS_URL = `${CASE_COMMENTS_URL}/_find` as const;
Copy link
Member

Choose a reason for hiding this comment

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

Do you mind if you change the path attribute here x-pack/plugins/cases/server/routes/api/comments/find_comments.ts to use the new variable (CASE_FIND_ATTACHMENTS_URL)?

ids: schema.arrayOf(schema.string()),
ids: schema.arrayOf(schema.string({ minLength: 1 }), {
minSize: 1,
maxSize: MAX_DELETE_CASE_IDS,
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, we cannot do that as it will be a breaking change.

Copy link
Member

Choose a reason for hiding this comment

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

Can you add some unit tests for the two functions?

const chunkSize = MAX_DELETE_CASE_IDS / 2;
const chunkedIds = chunk(caseIds, chunkSize);

const fileResults = await pMap(chunkedIds, async (ids: string[]) => {
Copy link
Member

Choose a reason for hiding this comment

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

What is the difference between this and createPointInTimeFinder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We talked about this offline, unfortunately we can't use a createPointInTimeFinder here since we're leveraging the file service.

Comment on lines 112 to 113
const files = fileResults.flatMap((res) => res.files);
const fileEntities = createFileEntities(files);
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to do it inside pMap so we don't have to iterate a large list of files?

@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
cases 133.5KB 133.6KB +54.0B
Unknown metric groups

ESLint disabled line counts

id before after diff
securitySolution 433 436 +3

Total ESLint disabled count

id before after diff
securitySolution 513 516 +3

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@jonathan-buttner jonathan-buttner merged commit b6a113c into elastic:main Apr 10, 2023
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Apr 10, 2023
@jonathan-buttner jonathan-buttner deleted the cases-delete-files-case-deletion branch April 10, 2023 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting Feature:Cases Cases feature release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v8.8.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants