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] File user action in Case detail view #153957

Conversation

adcoelho
Copy link
Contributor

Fixes #152088

Summary

Users are now able to see file activity in the case detail view.

  • Image files have a different icon and a clickable file name to preview
  • Other files have a standard "document" icon and the name is not clickable
  • The file can be downloaded by clicking the download icon

Screenshot 2023-03-29 at 17 31 06

If the file has invalid metadata (not likely) we display the activity item below:

Screenshot 2023-03-29 at 17 36 25

Registered FileType in attachment registry.
Created FileDownloadButtonIcon.
Created FileNameLink.
Added filesContext to FilePreview.
Updated file external reference metadata.
Added missing translations.
Added hideDefaultActions in attachment props.
Created isValidFileExternalReferenceMetadata util.
Added tests.
@adcoelho adcoelho added enhancement New value added to drive a business result Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Feature:Cases Cases feature v8.8.0 labels Mar 29, 2023
@adcoelho adcoelho self-assigned this Mar 29, 2023
@adcoelho adcoelho requested a review from a team as a code owner March 29, 2023 15:39
@elasticmachine
Copy link
Contributor

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

@elasticmachine
Copy link
Contributor

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

Copy link
Contributor

@jonathan-buttner jonathan-buttner left a comment

Choose a reason for hiding this comment

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

Looking good, I left a few questions

iconType={'download'}
aria-label={i18n.DOWNLOAD}
href={filesClient.getDownloadHref({
fileKind: constructFileKindIdByOwner(owner[0] as Owner),
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah bummer that we need the cast, maybe constructFileKindIdByOwner should just take a string 🤔

x-pack/plugins/cases/public/components/files/utils.tsx Outdated Show resolved Hide resolved
const fileId = props.externalReferenceId;

// @ts-ignore
const partialFileJSON = props.externalReferenceMetadata?.files[0] as Partial<FileJSON>;
Copy link
Contributor

Choose a reason for hiding this comment

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

How about we create a subset type so we don't need to cast here?

Something like this:

type DownloadableFile = Pick<FileJSON, 'extension' | 'fileKind' | 'id' | 'mimeType' | 'name'>;

And use that throughout where we need it. That way we don't have to cast and we don't have to fake it being a FileJSON.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But won't we have to cast it instead to DownloadableFile? I am not sure I understand your suggestion 😅

The problem here is that if I don't cast we get

Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'string | number | boolean | JsonObject | JsonArray'.
  Property '0' does not exist on type 'string | number | boolean | JsonObject | JsonArray'

Weird that 0 does not exist on JsonArray.

Still, this comes from CommentRequestExternalReferenceType['externalReferenceMetadata']; where we can't really say what the metadata is going to be since it depends on the attachment type ™️.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moreover, I don't actually need a cast here the @ts-ignore is rather for the indexing error mentioned above 😫

Copy link
Member

@cnasikas cnasikas Mar 31, 2023

Choose a reason for hiding this comment

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

To remove the @ts-ignore you can do

import { JsonArray } from '@kbn/utility-types';

const files = props.externalReferenceMetadata?.files as JsonArray;
const partialFileJSON = files[0] as Partial<FileJSON>;

I don't think we can avoid the casting as the framework has a bug with the types and cannot detect the correct types based on the registered type.

{!attachmentViewObject.hideDefaultActions && (
<RegisteredAttachmentsPropertyActions
isLoading={isLoading}
onDelete={() => handleDeleteComment(comment.id)}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we allow deleting a file via the attachment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Next PR I will create the DeleteFileButton component and I will use it here instead of the RegisteredAttachmentsPropertyActions and in the files table.

Copy link
Member

Choose a reason for hiding this comment

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

Also, the hideDefaultActions attribute hides the delete action from files.

Fixed typechek.
Addressed PR comments.
@adcoelho adcoelho force-pushed the cases-detail-file-user-activity branch from 4579f5d to 16d8b80 Compare March 30, 2023 12:42
<EuiButtonIcon
iconType={'download'}
aria-label={i18n.DOWNLOAD}
href={filesClient.getDownloadHref({
Copy link
Member

@cnasikas cnasikas Mar 31, 2023

Choose a reason for hiding this comment

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

I noticed through the reviews of the files PRs that we use this pattern a lot. Should we extract the logic of the construction of the href to a util function? For example:

const getFileDownloadHref = ({ filesClient, fileId, owner }) => filesClient.getDownloadHref({
        fileKind: constructFileKindIdByOwner(owner[0] as Owner),
        id: fileId,
      })

and use it like:

href={getFileDownloadHref( { filesClient, fileId, owner: owner[0] } )}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can but I don't see much difference between

filesClient.getDownloadHref(A, B)

and

newFunction(filesClient.getDownloadHref, A, B) 😄

Copy link
Member

Choose a reason for hiding this comment

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

The only difference is this Cases logic: fileKind: constructFileKindIdByOwner(owner[0] as Owner) is replicated over and over again. If we need to change it in the future we have to go to all places to do it. I am fine with leaving it as it is. Not a big deal. I get your point.

Comment on lines 28 to 31
const [isPreviewVisible, setIsPreviewVisible] = useState(false);

const closePreview = () => setIsPreviewVisible(false);
const showPreview = () => setIsPreviewVisible(true);
Copy link
Member

@cnasikas cnasikas Mar 31, 2023

Choose a reason for hiding this comment

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

I think we use the same logic elsewhere (x-pack/plugins/cases/public/components/files/files_table.tsx). What about creating a hook (inside the files folder) called useFilePreview that encapsulates the previewing logic? For example:

const useFilePreview = () => {
  const [isPreviewVisible, setIsPreviewVisible] = useState(false);

  const closePreview = () => setIsPreviewVisible(false);
  const showPreview = () => setIsPreviewVisible(true);

  return { showPreview,  closePreview }
}

// consume it as
const { showPreview,  closePreview } = useFilePreview();


export const getFileType = (): ExternalReferenceAttachmentType => ({
id: FILE_ATTACHMENT_TYPE,
icon: 'image',
Copy link
Member

Choose a reason for hiding this comment

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

nit: This is reserved for future use. It means to be an icon to represent the registered attachment (all files). I think we should change it to document as it is more generic.

expect(isValidFileExternalReferenceMetadata({ files: 'bar' })).toBeFalsy();
});

it('should return false if file.length !== 1', () => {
Copy link
Member

Choose a reason for hiding this comment

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

nit: I don't think that the function returns false because of the file.length !== 1. I think it returns false because it is not an array of valid metadata. The check in the isValidFileExternalReferenceMetadata is externalReferenceMetadata?.files?.length >= 1. Maybe we can delete the test or we can change it to check that it validates an array of two valid metadata.

Copy link
Contributor Author

@adcoelho adcoelho Mar 31, 2023

Choose a reason for hiding this comment

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

You're right. This is a leftover from the previous implementation of isValidFileExternalReferenceMetadata. I'll just remove it altogether.

let AttachmentElement: React.ReactElement;

const renderCallback = (props: object) => {
const attachmentViewObject = attachmentType.getAttachmentViewObject(
props as ExternalReferenceAttachmentViewProps
Copy link
Member

Choose a reason for hiding this comment

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

In combination with the comment here https://github.com/elastic/kibana/pull/153957/files#r1154257681 we should remove the casting.

Copy link
Member

Choose a reason for hiding this comment

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

nit: Also to avoid the extra call to getAttachmentViewObject maybe we can pass the attachmentViewObject as an argument to renderCallback.

const props = {
...getAttachmentViewProps(),
caseData: { id: caseData.id, title: caseData.title },
};
const attachmentViewObject = attachmentType.getAttachmentViewObject(
props as ExternalReferenceAttachmentViewProps
Copy link
Member

@cnasikas cnasikas Mar 31, 2023

Choose a reason for hiding this comment

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

Same here.

Removed test from files utils.
Created useFilePreview hook.
And tests.
Removed unnecessary type casts.
@kibana-ci
Copy link
Collaborator

kibana-ci commented Mar 31, 2023

💔 Build Failed

Failed CI Steps

Metrics [docs]

‼️ ERROR: no builds found for mergeBase sha [5dfc418]

History

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

cc @adcoelho

@adcoelho adcoelho merged commit 0b060bd into elastic:cases-detail-view-files-tab Apr 1, 2023
@cnasikas cnasikas mentioned this pull request Apr 3, 2023
5 tasks
adcoelho added a commit that referenced this pull request Apr 18, 2023
Fixes #151595 

## Summary

In this PR we will be merging a feature branch into `main`.

This feature branch is a collection of several different PRs with file
functionality for cases.

- #152941
- #153957
- #154432
- #153853

Most of the code was already reviewed so this will mainly be used for
testing.

- Files tab in the case detail view.
- Attach files to a case.
- View a list of all files attached to a case (with pagination).
- Preview image files attached to a case.
- Search for files attached to a case by file name.
- Download files attached to a case.
- Users are now able to see file activity in the case detail view.
- Image files have a different icon and a clickable file name to
preview.
- Other files have a standard "document" icon and the name is not
clickable.
- The file can be downloaded by clicking the download icon.

## Release notes

Support file attachments in Cases.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New value added to drive a business result Feature:Cases Cases feature 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.

5 participants