mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Files] Use upload component in files example (#141362)
* 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
This commit is contained in:
parent
2b4fc2ed90
commit
50df4630f5
13 changed files with 130 additions and 62 deletions
|
@ -16,6 +16,7 @@ const httpTags = {
|
|||
|
||||
export const exampleFileKind: FileKind = {
|
||||
id: 'filesExample',
|
||||
allowedMimeTypes: ['image/png'],
|
||||
http: {
|
||||
create: httpTags,
|
||||
delete: httpTags,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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)}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
`}
|
||||
|
|
38
x-pack/examples/files_example/public/components/modal.tsx
Normal file
38
x-pack/examples/files_example/public/components/modal.tsx
Normal file
|
@ -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
x-pack/examples/files_example/public/imports.ts
Normal file
16
x-pack/examples/files_example/public/imports.ts
Normal file
|
@ -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';
|
|
@ -12,8 +12,9 @@ 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,
|
||||
|
@ -21,13 +22,14 @@ export class FilesExamplePlugin
|
|||
// 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
|
||||
|
|
|
@ -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;
|
||||
|
@ -16,6 +16,7 @@ export interface FilesExamplePluginsStart {
|
|||
}
|
||||
|
||||
export interface FileClients {
|
||||
unscoped: FilesClient;
|
||||
// Example file kind
|
||||
example: ScopedFilesClient;
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -190,6 +190,7 @@ export class UploadState {
|
|||
id: uploadTarget.id,
|
||||
kind: this.fileKind.id,
|
||||
abortSignal,
|
||||
contentType: mime,
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue