Skip to content

Commit

Permalink
[Files] Use upload component in files example (elastic#141362)
Browse files Browse the repository at this point in the history
* added modal and removed hardcoded image upload

* remove unused var

* added shared imports file and hooked up modal component to example app

* use the Image component from files and update the title for the files modal

* set and show custom metadata

* fix content type not being passed through to upload or to picker component

* remove old commented out code
  • Loading branch information
jloleysens authored Sep 22, 2022
1 parent 2b4fc2e commit 50df463
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 62 deletions.
1 change: 1 addition & 0 deletions x-pack/examples/files_example/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const httpTags = {

export const exampleFileKind: FileKind = {
id: 'filesExample',
allowedMimeTypes: ['image/png'],
http: {
create: httpTags,
delete: httpTags,
Expand Down
5 changes: 4 additions & 1 deletion x-pack/examples/files_example/public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { AppPluginStartDependencies } from './types';
import { FilesExampleApp } from './components/app';
import { FilesContext } from './imports';

const queryClient = new QueryClient();

Expand All @@ -21,7 +22,9 @@ export const renderApp = (
) => {
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<FilesExampleApp files={files} notifications={notifications} />
<FilesContext>
<FilesExampleApp files={files} notifications={notifications} />
</FilesContext>
</QueryClientProvider>,
element
);
Expand Down

This file was deleted.

86 changes: 38 additions & 48 deletions x-pack/examples/files_example/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import { useQuery } from '@tanstack/react-query';
import type { FileJSON } from '@kbn/files-plugin/common';
import type { FilesClientResponses } from '@kbn/files-plugin/public';

const names = ['foo', 'bar', 'baz'];

import {
EuiPageTemplate_Deprecated as EuiPageTemplate,
EuiPageTemplate,
EuiInMemoryTable,
EuiInMemoryTableProps,
EuiButton,
Expand All @@ -26,8 +24,7 @@ import { CoreStart } from '@kbn/core/public';
import { DetailsFlyout } from './details_flyout';
import type { FileClients } from '../types';
import { ConfirmButtonIcon } from './confirm_button';
// @ts-ignore
import imageBase64 from '!!raw-loader!../assets/image.png.base64';
import { Modal } from './modal';

interface FilesExampleAppDeps {
files: FileClients;
Expand All @@ -40,37 +37,15 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
const { data, isLoading, error, refetch } = useQuery<ListResponse>(['files'], () =>
files.example.list()
);
const [isUploadingImage, setIsUploadingImage] = useState(false);
const [showUploadModal, setShowUploadModal] = useState(false);
const [isDeletingFile, setIsDeletingFile] = useState(false);
const [selectedItem, setSelectedItem] = useState<undefined | FileJSON>();

const uploadImage = async () => {
try {
setIsUploadingImage(true);
const { file } = await files.example.create({
name: names[Math.floor(Math.random() * names.length)],
alt: 'My image',
meta: { myValue: 'test' },
mimeType: 'image/png',
});
await refetch();
const blob = new Blob([Uint8Array.from(atob(imageBase64), (c) => c.charCodeAt(0))], {
type: 'image/png',
});
await files.example.upload({ id: file.id, body: blob });
await refetch();
notifications.toasts.addSuccess('Sucessfully uploaded image');
} finally {
setIsUploadingImage(false);
}
};

const renderToolsRight = () => {
return [
<EuiButton
onClick={uploadImage}
isDisabled={isUploadingImage || isLoading || isDeletingFile}
isLoading={isUploadingImage}
onClick={() => setShowUploadModal(true)}
isDisabled={isLoading || isDeletingFile}
iconType="exportAction"
>
Upload image
Expand All @@ -84,7 +59,11 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
{
field: 'name',
name: 'Name',
render: (name, item) => <EuiLink onClick={() => setSelectedItem(item)}>{name}</EuiLink>,
render: (name, item) => (
<EuiLink disabled={isDeletingFile} onClick={() => setSelectedItem(item)}>
{name}
</EuiLink>
),
},
{
field: 'status',
Expand All @@ -107,6 +86,7 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
isPrimary: true,
render: (item) => (
<EuiButtonIcon
disabled={isDeletingFile}
aria-label="View file details"
iconType="eye"
onClick={() => setSelectedItem(item)}
Expand Down Expand Up @@ -139,23 +119,22 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =

return (
<>
<EuiPageTemplate
pageHeader={{
pageTitle: 'Files example',
}}
>
<EuiInMemoryTable
columns={columns}
items={items}
itemId="id"
loading={isLoading || isDeletingFile}
error={error ? JSON.stringify(error) : undefined}
sorting
search={{
toolsRight: renderToolsRight(),
}}
pagination
/>
<EuiPageTemplate restrictWidth>
<EuiPageTemplate.Header pageTitle="Files example" />
<EuiPageTemplate.Section>
<EuiInMemoryTable
columns={columns}
items={items}
itemId="id"
loading={isLoading || isDeletingFile}
error={error ? JSON.stringify(error) : undefined}
sorting
search={{
toolsRight: renderToolsRight(),
}}
pagination
/>
</EuiPageTemplate.Section>
</EuiPageTemplate>
{selectedItem && (
<DetailsFlyout
Expand All @@ -164,6 +143,17 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
onDismiss={() => setSelectedItem(undefined)}
/>
)}
{showUploadModal && (
<Modal
client={files.unscoped}
onDismiss={() => setShowUploadModal(false)}
onUploaded={() => {
notifications.toasts.addSuccess('Uploaded file!');
refetch();
setShowUploadModal(false);
}}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '@elastic/eui';
import type { FileJSON } from '@kbn/files-plugin/common';
import { FileClients } from '../types';
import { Image } from '../imports';

interface Props {
file: FileJSON;
Expand Down Expand Up @@ -66,10 +67,16 @@ export const DetailsFlyout: FunctionComponent<Props> = ({ files, file, onDismiss
title: 'Last updated',
description: moment(file.updated).fromNow(),
},
{
title: 'Custom meta',
description: (
<pre>{file.meta ? JSON.stringify(file.meta, null, 2) : '<no custom metadata>'}</pre>
),
},
]}
/>
<EuiSpacer size="xl" />
<img
<Image
css={css`
height: 400px;
`}
Expand Down
38 changes: 38 additions & 0 deletions x-pack/examples/files_example/public/components/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { FunctionComponent } from 'react';
import React from 'react';
import { EuiModal, EuiModalHeader, EuiModalBody, EuiText } from '@elastic/eui';
import { exampleFileKind } from '../../common';
import { FilesClient, UploadFile } from '../imports';

interface Props {
client: FilesClient;
onDismiss: () => void;
onUploaded: () => void;
}

export const Modal: FunctionComponent<Props> = ({ onDismiss, onUploaded, client }) => {
return (
<EuiModal onClose={onDismiss}>
<EuiModalHeader>
<EuiText>
<h2>Upload image</h2>
</EuiText>
</EuiModalHeader>
<EuiModalBody>
<UploadFile
kind={exampleFileKind.id}
client={client}
onDone={onUploaded}
meta={{ custom: 'meta' }}
/>
</EuiModalBody>
</EuiModal>
);
};
16 changes: 16 additions & 0 deletions x-pack/examples/files_example/public/imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export {
FilesClient,
FilesSetup,
FilesStart,
UploadFile,
FilesContext,
ScopedFilesClient,
Image,
} from '@kbn/files-plugin/public';
10 changes: 6 additions & 4 deletions x-pack/examples/files_example/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,24 @@ import { FilesExamplePluginsStart, FilesExamplePluginsSetup } from './types';
export class FilesExamplePlugin
implements Plugin<unknown, unknown, FilesExamplePluginsSetup, FilesExamplePluginsStart>
{
public setup(core: CoreSetup<FilesExamplePluginsStart>) {
// Register an application into the side navigation menu
public setup(core: CoreSetup<FilesExamplePluginsStart>, { files }: FilesExamplePluginsSetup) {
files.registerFileKind(exampleFileKind);

core.application.register({
id: PLUGIN_ID,
title: PLUGIN_NAME,
async mount(params: AppMountParameters) {
// Load application bundle
const { renderApp } = await import('./application');
// Get start services as specified in kibana.json
const [coreStart, { files }] = await core.getStartServices();
const [coreStart, deps] = await core.getStartServices();
// Render the application
return renderApp(
coreStart,
{
files: {
example: files.filesClientFactory.asScoped(exampleFileKind.id),
unscoped: deps.files.filesClientFactory.asUnscoped(),
example: deps.files.filesClientFactory.asScoped(exampleFileKind.id),
},
},
params
Expand Down
3 changes: 2 additions & 1 deletion x-pack/examples/files_example/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import type { FilesSetup, FilesStart, ScopedFilesClient } from '@kbn/files-plugin/public';
import type { FilesSetup, FilesStart, ScopedFilesClient, FilesClient } from './imports';

export interface FilesExamplePluginsSetup {
files: FilesSetup;
Expand All @@ -16,6 +16,7 @@ export interface FilesExamplePluginsStart {
}

export interface FileClients {
unscoped: FilesClient;
// Example file kind
example: ScopedFilesClient;
}
Expand Down
13 changes: 10 additions & 3 deletions x-pack/plugins/files/public/components/upload_file/upload_file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ export const UploadFile = <Kind extends string = string>({
}: Props<Kind>): ReturnType<FunctionComponent> => {
const { registry } = useFilesContext();
const ref = useRef<null | EuiFilePicker>(null);
const fileKind = registry.get(kindId);
const uploadState = useMemo(
() =>
createUploadState({
client,
fileKind: registry.get(kindId),
fileKind,
allowRepeatedUploads,
}),
[client, kindId, allowRepeatedUploads, registry]
[client, allowRepeatedUploads, fileKind]
);

/**
Expand All @@ -118,7 +119,13 @@ export const UploadFile = <Kind extends string = string>({

return (
<context.Provider value={uploadState}>
<Component ref={ref} meta={meta} immediate={immediate} allowClear={allowClear} />
<Component
ref={ref}
accept={fileKind.allowedMimeTypes?.join(',')}
meta={meta}
immediate={immediate}
allowClear={allowClear}
/>
</context.Provider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export class UploadState {
id: uploadTarget.id,
kind: this.fileKind.id,
abortSignal,
contentType: mime,
})
);
}),
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/files/public/files_client/files_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ export function createFilesClient({
body: JSON.stringify(body),
});
},
upload: ({ kind, abortSignal, ...args }) => {
upload: ({ kind, abortSignal, contentType, ...args }) => {
return http.put(apiRoutes.getUploadRoute(scopedFileKind ?? kind, args.id), {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Type': contentType ?? 'application/octet-stream',
},
signal: abortSignal,
body: args.body as BodyInit,
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/files/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ export interface FilesClient extends GlobalEndpoints {
*
* @param args - upload file args
*/
upload: ClientMethodFrom<UploadFileKindHttpEndpoint, { abortSignal?: AbortSignal }>;
upload: ClientMethodFrom<
UploadFileKindHttpEndpoint,
{ abortSignal?: AbortSignal; contentType?: string }
>;
/**
* Stream a download of the file object's content.
*
Expand Down

0 comments on commit 50df463

Please sign in to comment.