Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions x-pack/examples/files_example/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
{showFilePickerModal && (
<MyFilePicker
onClose={() => setShowFilePickerModal(false)}
onUpload={() => {
notifications.toasts.addSuccess({
title: 'Uploaded files',
});
}}
onDone={(ids) => {
notifications.toasts.addSuccess({
title: 'Selected files!',
Expand Down
13 changes: 11 additions & 2 deletions x-pack/examples/files_example/public/components/file_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,18 @@ import { FilePicker } from '../imports';

interface Props {
onClose: () => void;
onUpload: (ids: string[]) => void;
onDone: (ids: string[]) => void;
}

export const MyFilePicker: FunctionComponent<Props> = ({ onClose, onDone }) => {
return <FilePicker kind={exampleFileKind.id} onClose={onClose} onDone={onDone} pageSize={50} />;
export const MyFilePicker: FunctionComponent<Props> = ({ onClose, onDone, onUpload }) => {
return (
<FilePicker
kind={exampleFileKind.id}
onClose={onClose}
onDone={onDone}
onUpload={(n) => onUpload(n.map(({ id }) => id))}
pageSize={50}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { css } from '@emotion/react';
import { useFilePickerContext } from '../context';

import { i18nTexts } from '../i18n_texts';
import { useBehaviorSubject } from '../../use_behavior_subject';

interface Props {
onClick: () => void;
}

export const ClearFilterButton: FunctionComponent<Props> = ({ onClick }) => {
const { state } = useFilePickerContext();
const isUploading = useBehaviorSubject(state.isUploading$);
const query = useObservable(state.queryDebounced$);
if (!query) {
return null;
Expand All @@ -30,7 +32,9 @@ export const ClearFilterButton: FunctionComponent<Props> = ({ onClick }) => {
place-items: center;
`}
>
<EuiLink onClick={onClick}>{i18nTexts.clearFilterButton}</EuiLink>
<EuiLink disabled={isUploading} onClick={onClick}>
{i18nTexts.clearFilterButton}
</EuiLink>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React from 'react';
import React, { useMemo } from 'react';
import type { FunctionComponent } from 'react';
import numeral from '@elastic/numeral';
import useObservable from 'react-use/lib/useObservable';
Expand All @@ -26,8 +26,8 @@ export const FileCard: FunctionComponent<Props> = ({ file }) => {
const { kind, state, client } = useFilePickerContext();
const { euiTheme } = useEuiTheme();
const displayImage = isImage({ type: file.mimeType });

const isSelected = useObservable(state.watchFileSelected$(file.id), false);
const isSelected$ = useMemo(() => state.watchFileSelected$(file.id), [file.id, state]);
const isSelected = useObservable(isSelected$, false);

const imageHeight = `calc(${euiTheme.size.xxxl} * 2)`;
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,72 @@
* 2.0.
*/

import { EuiFlexGroup, EuiModalFooter } from '@elastic/eui';
import { EuiModalFooter } from '@elastic/eui';
import { css } from '@emotion/react';
import type { FunctionComponent } from 'react';
import React from 'react';
import React, { useCallback } from 'react';

import { UploadFile } from '../../upload_file';
import type { Props as FilePickerProps } from '../file_picker';
import { useFilePickerContext } from '../context';
import { i18nTexts } from '../i18n_texts';
import { Pagination } from './pagination';
import { SelectButton, Props as SelectButtonProps } from './select_button';

interface Props {
kind: string;
onDone: SelectButtonProps['onClick'];
onUpload?: FilePickerProps['onUpload'];
}

export const ModalFooter: FunctionComponent<Props> = ({ onDone }) => {
export const ModalFooter: FunctionComponent<Props> = ({ kind, onDone, onUpload }) => {
const { state } = useFilePickerContext();
const onUploadStart = useCallback(() => state.setIsUploading(true), [state]);
const onUploadEnd = useCallback(() => state.setIsUploading(false), [state]);
return (
<EuiModalFooter>
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween" alignItems="center">
<Pagination />
<SelectButton onClick={onDone} />
</EuiFlexGroup>
<div
css={css`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
width: 100%;
`}
>
<div
css={css`
place-self: stretch;
`}
>
<UploadFile
onDone={(n) => {
state.selectFile(n.map(({ id }) => id));
state.resetFilters();
onUpload?.(n);
}}
onUploadStart={onUploadStart}
onUploadEnd={onUploadEnd}
kind={kind}
initialPromptText={i18nTexts.uploadFilePlaceholderText}
multiple
compressed
/>
</div>
<div
css={css`
place-self: center;
`}
>
<Pagination />
</div>
<div
css={css`
place-self: end;
`}
>
<SelectButton onClick={onDone} />
</div>
</div>
</EuiModalFooter>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@
import React from 'react';
import type { FunctionComponent } from 'react';
import { EuiPagination } from '@elastic/eui';
import useObservable from 'react-use/lib/useObservable';
import { useFilePickerContext } from '../context';
import { useBehaviorSubject } from '../../use_behavior_subject';

export const Pagination: FunctionComponent = () => {
const { state } = useFilePickerContext();
const page = useBehaviorSubject(state.currentPage$);
const files = useObservable(state.files$, []);
const pageCount = useBehaviorSubject(state.totalPages$);
return <EuiPagination onPageClick={state.setPage} pageCount={pageCount} activePage={page} />;
const isUploading = useBehaviorSubject(state.isUploading$);
if (files.length === 0) {
return null;
}
return (
<EuiPagination
data-test-subj="paginationControls"
onPageClick={isUploading ? () => {} : state.setPage}
pageCount={pageCount}
activePage={page}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ export const SearchField: FunctionComponent = () => {
const query = useBehaviorSubject(state.query$);
const isLoading = useBehaviorSubject(state.isLoading$);
const hasFiles = useBehaviorSubject(state.hasFiles$);
const isUploading = useBehaviorSubject(state.isUploading$);
return (
<EuiFieldSearch
data-test-subj="searchField"
disabled={!query && !hasFiles}
disabled={isUploading || (!query && !hasFiles)}
isLoading={isLoading}
value={query ?? ''}
placeholder={i18nTexts.searchFieldPlaceholder}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ export interface Props {

export const SelectButton: FunctionComponent<Props> = ({ onClick }) => {
const { state } = useFilePickerContext();
const isUploading = useBehaviorSubject(state.isUploading$);
const selectedFiles = useBehaviorSubject(state.selectedFileIds$);
return (
<EuiButton
data-test-subj="selectButton"
disabled={!state.hasFilesSelected()}
disabled={isUploading || !state.hasFilesSelected()}
onClick={() => onClick(selectedFiles)}
>
{selectedFiles.length > 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('FilePicker', () => {
selectButton: `${baseTestSubj}.selectButton`,
loadingSpinner: `${baseTestSubj}.loadingSpinner`,
fileGrid: `${baseTestSubj}.fileGrid`,
paginationControls: `${baseTestSubj}.paginationControls`,
};

return {
Expand Down Expand Up @@ -126,4 +127,10 @@ describe('FilePicker', () => {
expect(onDone).toHaveBeenCalledTimes(1);
expect(onDone).toHaveBeenNthCalledWith(1, ['a', 'b']);
});
it('hides pagination if there are no files', async () => {
client.list.mockImplementation(() => Promise.resolve({ files: [] as FileJSON[], total: 2 }));
const { actions, testSubjects, exists } = await initTestBed();
await actions.waitUntilLoaded();
expect(exists(testSubjects.paginationControls)).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
EuiFlexGroup,
} from '@elastic/eui';

import type { DoneNotification } from '../upload_file';
import { useBehaviorSubject } from '../use_behavior_subject';
import { useFilePickerContext, FilePickerContext } from './context';

Expand All @@ -43,13 +44,17 @@ export interface Props<Kind extends string = string> {
* Will be called after a user has a selected a set of files
*/
onDone: (fileIds: string[]) => void;
/**
* When a user has succesfully uploaded some files this callback will be called
*/
onUpload?: (done: DoneNotification[]) => void;
/**
* The number of results to show per page.
*/
pageSize?: number;
}

const Component: FunctionComponent<Props> = ({ onClose, onDone }) => {
const Component: FunctionComponent<Props> = ({ onClose, onDone, onUpload }) => {
const { state, kind } = useFilePickerContext();

const hasFiles = useBehaviorSubject(state.hasFiles$);
Expand All @@ -59,7 +64,7 @@ const Component: FunctionComponent<Props> = ({ onClose, onDone }) => {

useObservable(state.files$);

const renderFooter = () => <ModalFooter onDone={onDone} />;
const renderFooter = () => <ModalFooter kind={kind} onDone={onDone} onUpload={onUpload} />;

return (
<EuiModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,16 @@ describe('FilePickerState', () => {
expectObservable(filePickerState.loadingError$).toBe('a-b---c-', {});
});
});
it('does not allow fetching files while an upload is in progress', () => {
getTestScheduler().run(({ expectObservable, cold }) => {
const files = [] as FileJSON[];
filesClient.list.mockImplementation(() => of({ files }) as any);
const uploadInput = '---a|';
const queryInput = ' -----a|';
const upload$ = cold(uploadInput).pipe(tap(() => filePickerState.setIsUploading(true)));
const query$ = cold(queryInput).pipe(tap((q) => filePickerState.setQuery(q)));
expectObservable(merge(upload$, query$)).toBe('---a-a|');
expectObservable(filePickerState.files$).toBe('a------', { a: [] });
});
});
});
Loading