[Files] Add meta prop to <FilePicker /> (#151417)

## Summary

Added the `meta` prop to the `FilePicker` component, also pass this down
to the `FileUpload` component so that files created via the picker can
have meta set.

Close https://github.com/elastic/kibana/issues/151375

## How to test

1. Start Kibana with examples `yarn start --run-examples`
2. Go to the "Developer examples" in the side-nav menu under analytics
3. Go to "Files example"
4. Upload a file via the "Select file" button, should present an empty
file picker if you have no files, otherwise use the little upload
component bottom left
5. Either select files or dismiss the modal
6. Inspect the uploaded file and see the `myCool: 'meta'` entry included
with other metadata

## Screenshot

A file uploaded via the `FilePicker` in the "Files example" plugin.

<img width="897" alt="Screenshot 2023-02-16 at 11 41 06"
src="https://user-images.githubusercontent.com/8155004/219342872-c39b5d81-7421-4187-bb1c-d6815d80a3dc.png">


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Jean-Louis Leysens 2023-02-16 12:58:48 +01:00 committed by GitHub
parent 794a9493d5
commit f1b0dd4720
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 33 additions and 6 deletions

View file

@ -174,6 +174,7 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
notifications.toasts.addSuccess({
title: 'Uploaded files',
});
refetch();
}}
onDone={(ids) => {
notifications.toasts.addSuccess({

View file

@ -27,6 +27,7 @@ export const MyFilePicker: FunctionComponent<Props> = ({ onClose, onDone, onUplo
onDone={(files) => onDone(files.map((f) => f.id))}
onUpload={(n) => onUpload(n.map(({ id }) => id))}
pageSize={50}
uploadMeta={{ myCool: 'meta' }}
multiple
/>
);

View file

@ -20,7 +20,7 @@ interface Props {
}
export const EmptyPrompt: FunctionComponent<Props> = ({ kind, multiple }) => {
const { state } = useFilePickerContext();
const { state, uploadMeta } = useFilePickerContext();
const { euiTheme } = useEuiTheme();
return (
<EuiEmptyPrompt
@ -32,6 +32,7 @@ export const EmptyPrompt: FunctionComponent<Props> = ({ kind, multiple }) => {
css={css`
min-width: calc(${euiTheme.size.xxxl} * 6);
`}
meta={uploadMeta as Record<string, unknown>}
kind={kind}
immediate
multiple={multiple}

View file

@ -26,7 +26,7 @@ interface Props {
}
export const ModalFooter: FunctionComponent<Props> = ({ kind, onDone, onUpload, multiple }) => {
const { state } = useFilePickerContext();
const { state, uploadMeta } = useFilePickerContext();
const onUploadStart = useCallback(() => state.setIsUploading(true), [state]);
const onUploadEnd = useCallback(() => state.setIsUploading(false), [state]);
return (
@ -50,6 +50,7 @@ export const ModalFooter: FunctionComponent<Props> = ({ kind, onDone, onUpload,
state.resetFilters();
onUpload?.(n);
}}
meta={uploadMeta as Record<string, unknown>}
onUploadStart={onUploadStart}
onUploadEnd={onUploadEnd}
kind={kind}

View file

@ -15,6 +15,7 @@ import { FilePickerState, createFilePickerState } from './file_picker_state';
interface FilePickerContextValue extends FilesContextValue {
state: FilePickerState;
kind: string;
uploadMeta?: unknown;
shouldAllowDelete?: (file: FileJSON) => boolean;
}
@ -26,6 +27,7 @@ interface FilePickerContextProps
extends Pick<FilePickerContextValue, 'kind' | 'shouldAllowDelete'> {
pageSize: number;
multiple: boolean;
uploadMeta?: unknown;
}
export const FilePickerContext: FunctionComponent<FilePickerContextProps> = ({
@ -34,6 +36,7 @@ export const FilePickerContext: FunctionComponent<FilePickerContextProps> = ({
pageSize,
multiple,
children,
uploadMeta,
}) => {
const filesContext = useFilesContext();
const { client } = filesContext;
@ -43,7 +46,7 @@ export const FilePickerContext: FunctionComponent<FilePickerContextProps> = ({
);
useEffect(() => state.dispose, [state]);
return (
<FilePickerCtx.Provider value={{ state, kind, shouldAllowDelete, ...filesContext }}>
<FilePickerCtx.Provider value={{ state, kind, shouldAllowDelete, uploadMeta, ...filesContext }}>
{children}
</FilePickerCtx.Provider>
);

View file

@ -9,7 +9,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { registerTestBed } from '@kbn/test-jest-helpers';
import { FileUpload } from '@kbn/shared-ux-file-upload';
import { createMockFilesClient } from '@kbn/shared-ux-file-mocks';
import type { FileJSON } from '@kbn/shared-ux-file-types';
import { FilesContext } from '@kbn/shared-ux-file-context';
@ -125,4 +125,18 @@ describe('FilePicker', () => {
await actions.waitUntilLoaded();
expect(exists(testSubjects.paginationControls)).toBe(false);
});
describe('passes "meta" to <FileUpload />', () => {
it('when empty', async () => {
// Empty state
const { component } = await initTestBed({ uploadMeta: { foo: 'bar' } });
expect(component.find(FileUpload).props().meta).toEqual({ foo: 'bar' });
});
it('when there are files', async () => {
const { component } = await initTestBed({ uploadMeta: { bar: 'baz' } });
client.list.mockImplementation(() =>
Promise.resolve({ files: [{ id: 'a' }, { id: 'b' }] as FileJSON[], total: 2 })
);
expect(component.find(FileUpload).props().meta).toEqual({ bar: 'baz' });
});
});
});

View file

@ -55,6 +55,10 @@ export interface Props<Kind extends string = string> {
* When a user has successfully uploaded some files this callback will be called
*/
onUpload?: (done: DoneNotification[]) => void;
/**
* `meta` value to be used for file uploads
*/
uploadMeta?: FileJSON['meta'];
/**
* The number of results to show per page.
*/
@ -151,12 +155,14 @@ export const FilePicker: FunctionComponent<Props> = ({
kind,
shouldAllowDelete,
multiple = false,
uploadMeta,
onUpload = () => {},
...rest
}) => (
<FilePickerContext
pageSize={pageSize}
kind={kind}
uploadMeta={uploadMeta}
multiple={multiple}
shouldAllowDelete={shouldAllowDelete}
>

View file

@ -88,7 +88,7 @@ export interface Props<Kind extends string = string> {
onUploadStart?: () => void;
/**
* Will be called when attempt ends, in error otherwise
* Will always be called when upload ends, whether success or failure
*/
onUploadEnd?: () => void;

View file

@ -67,7 +67,7 @@ describe('UploadState', () => {
});
});
it('uploads all provided files and reports errors', async () => {
it('uploads all provided files', async () => {
testScheduler.run(({ expectObservable, cold, flush }) => {
const file1 = { name: 'test', size: 1 } as File;
const file2 = { name: 'test 2', size: 1 } as File;