Skip to content

Commit

Permalink
[Files] Distinct FilesClient and ScopedFilesClient (elastic#139718)
Browse files Browse the repository at this point in the history
* FilesClient -> ScopedFilesClient

* added unscoped, global files client

* slight update to types

* also export the FilesClient type

* update JSDocs

* refactor
  • Loading branch information
jloleysens authored Sep 7, 2022
1 parent a5a4884 commit b8a2068
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 67 deletions.
4 changes: 2 additions & 2 deletions 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, FilesClient } from '@kbn/files-plugin/public';
import type { FilesSetup, FilesStart, ScopedFilesClient } from '@kbn/files-plugin/public';

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

export interface FileClients {
// Example file kind
example: FilesClient;
example: ScopedFilesClient;
}

export interface AppPluginStartDependencies {
Expand Down
85 changes: 55 additions & 30 deletions x-pack/plugins/files/public/files_client/files_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { pipe } from 'fp-ts/lib/function';
import * as qs from 'query-string';
import type { HttpStart } from '@kbn/core/public';
import type { FilesClient } from '../types';
import type { ScopedFilesClient, FilesClient } from '../types';
import {
API_BASE_PATH,
FILES_API_BASE_PATH,
Expand Down Expand Up @@ -65,68 +65,96 @@ export const apiRoutes = {
getMetricsRoute: () => `${API_BASE_PATH}/metrics`,
};

interface Args {
fileKind: string;
/**
* Arguments to create a new {@link FileClient}.
*/
export interface Args {
/**
* The http start service from core.
*/
http: HttpStart;
}

/**
* Arguments to create a new {@link ScopedFilesClient}.
*/
export interface ScopedArgs extends Args {
/**
* The file kind to scope all requests to where file kinds are needed.
*/
fileKind: string;
}

const commonBodyHeaders = {
headers: {
'content-type': 'application/json',
},
};

export const createFilesClient = ({ http, fileKind }: Args): FilesClient => {
export function createFilesClient(args: Args): FilesClient;
export function createFilesClient(scopedArgs: ScopedArgs): ScopedFilesClient;
export function createFilesClient({
http,
fileKind: scopedFileKind,
}: {
http: HttpStart;
fileKind?: string;
}): FilesClient | ScopedFilesClient {
const api: FilesClient = {
create: (args) => {
return http.post(apiRoutes.getCreateFileRoute(fileKind), {
create: ({ kind, ...args }) => {
return http.post(apiRoutes.getCreateFileRoute(scopedFileKind ?? kind), {
headers: commonBodyHeaders,
body: JSON.stringify(args),
});
},
delete: (args) => {
return http.delete(apiRoutes.getDeleteRoute(fileKind, args.id));
delete: ({ kind, ...args }) => {
return http.delete(apiRoutes.getDeleteRoute(scopedFileKind ?? kind, args.id));
},
download: (args) => {
return http.get(apiRoutes.getDownloadRoute(fileKind, args.id, args.fileName), {
download: ({ kind, ...args }) => {
return http.get(apiRoutes.getDownloadRoute(scopedFileKind ?? kind, args.id, args.fileName), {
headers: { Accept: '*/*' },
});
},
getById: (args) => {
return http.get(apiRoutes.getByIdRoute(fileKind, args.id));
getById: ({ kind, ...args }) => {
return http.get(apiRoutes.getByIdRoute(scopedFileKind ?? kind, args.id));
},
list(args = {}) {
return http.get(apiRoutes.getListRoute(fileKind, args.page, args.perPage));
list({ kind, ...args } = { kind: '' }) {
return http.get(apiRoutes.getListRoute(scopedFileKind ?? kind, args.page, args.perPage));
},
update: ({ id, ...body }) => {
return http.patch(apiRoutes.getUpdateRoute(fileKind, id), {
update: ({ kind, id, ...body }) => {
return http.patch(apiRoutes.getUpdateRoute(scopedFileKind ?? kind, id), {
headers: commonBodyHeaders,
body: JSON.stringify(body),
});
},
upload: (args) => {
return http.put(apiRoutes.getUploadRoute(fileKind, args.id), {
upload: ({ kind, ...args }) => {
return http.put(apiRoutes.getUploadRoute(scopedFileKind ?? kind, args.id), {
headers: {
'Content-Type': 'application/octet-stream',
},

body: args.body as BodyInit,
});
},
share: ({ fileId, name, validUntil }) => {
return http.post(apiRoutes.getShareRoute(fileKind, fileId), {
getDownloadHref: ({ fileKind: kind, id }) => {
return `${http.basePath.prepend(apiRoutes.getDownloadRoute(scopedFileKind ?? kind, id))}`;
},
share: ({ kind, fileId, name, validUntil }) => {
return http.post(apiRoutes.getShareRoute(scopedFileKind ?? kind, fileId), {
headers: commonBodyHeaders,
body: JSON.stringify({ name, validUntil }),
});
},
unshare: ({ id }) => {
return http.delete(apiRoutes.getShareRoute(fileKind, id));
unshare: ({ kind, id }) => {
return http.delete(apiRoutes.getShareRoute(scopedFileKind ?? kind, id));
},
getShare: ({ id }) => {
return http.get(apiRoutes.getShareRoute(fileKind, id));
getShare: ({ kind, id }) => {
return http.get(apiRoutes.getShareRoute(scopedFileKind ?? kind, id));
},
listShares: ({ forFileId, page, perPage }) => {
return http.get(apiRoutes.getListSharesRoute(fileKind, page, perPage, forFileId));
listShares: ({ kind, forFileId, page, perPage }) => {
return http.get(
apiRoutes.getListSharesRoute(scopedFileKind ?? kind, page, perPage, forFileId)
);
},
find: ({ page, perPage, ...filterArgs }) => {
return http.post(apiRoutes.getFindRoute(page, perPage), {
Expand All @@ -140,9 +168,6 @@ export const createFilesClient = ({ http, fileKind }: Args): FilesClient => {
publicDownload: ({ token, fileName }) => {
return http.get(apiRoutes.getPublicDownloadRoute(token, fileName));
},
getDownloadHref: ({ id }) => {
return `${http.basePath.prepend(apiRoutes.getDownloadRoute(fileKind, id))}`;
},
};
return api;
};
}
7 changes: 6 additions & 1 deletion x-pack/plugins/files/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

import { FilesPlugin } from './plugin';
export type { FilesSetup, FilesStart } from './plugin';
export type { FilesClient, FilesClientFactory, FilesClientResponses } from './types';
export type {
FilesClient,
ScopedFilesClient,
FilesClientFactory,
FilesClientResponses,
} from './types';

export function plugin() {
return new FilesPlugin();
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/files/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export class FilesPlugin implements Plugin<FilesSetup, FilesStart> {
asScoped(fileKind: string) {
return createFilesClient({ fileKind, http: core.http });
},
asUnscoped() {
return createFilesClient({ http: core.http });
},
},
};
return this.api;
Expand Down
86 changes: 52 additions & 34 deletions x-pack/plugins/files/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,43 @@ import type {
HttpApiInterfaceEntryDefinition,
} from '../common/api_routes';

type UnscopedClientMethodFrom<E extends HttpApiInterfaceEntryDefinition> = (
args: E['inputs']['body'] & E['inputs']['params'] & E['inputs']['query']
) => Promise<E['output']>;

/**
* @param args - Input to the endpoint which includes body, params and query of the RESTful endpoint.
*/
type ClientMethodFrom<E extends HttpApiInterfaceEntryDefinition> = (
args: E['inputs']['body'] & E['inputs']['params'] & E['inputs']['query']
args: Parameters<UnscopedClientMethodFrom<E>>[0] & { kind: string }
) => Promise<E['output']>;

type ClientMethodOptionalArgsFrom<E extends HttpApiInterfaceEntryDefinition> = (
args?: E['inputs']['body'] & E['inputs']['params'] & E['inputs']['query']
) => Promise<E['output']>;
interface GlobalEndpoints {
/**
* Get metrics of file system, like storage usage.
*
* @param args - Get metrics arguments
*/
getMetrics: () => Promise<FilesMetricsHttpEndpoint['output']>;
/**
* Download a file, bypassing regular security by way of a
* secret share token.
*
* @param args - Get public download arguments.
*/
publicDownload: UnscopedClientMethodFrom<FilePublicDownloadHttpEndpoint>;
/**
* Find a set of files given some filters.
*
* @param args - File filters
*/
find: UnscopedClientMethodFrom<FindFilesHttpEndpoint>;
}

/**
* A client that can be used to manage a specific {@link FileKind}.
*/
export interface FilesClient {
export interface FilesClient extends GlobalEndpoints {
/**
* Create a new file object with the provided metadata.
*
Expand All @@ -62,13 +84,7 @@ export interface FilesClient {
*
* @param args - list files args
*/
list: ClientMethodOptionalArgsFrom<ListFileKindHttpEndpoint>;
/**
* Find a set of files given some filters.
*
* @param args - File filters
*/
find: ClientMethodFrom<FindFilesHttpEndpoint>;
list: ClientMethodFrom<ListFileKindHttpEndpoint>;
/**
* Update a set of of metadata values of the file object.
*
Expand All @@ -87,6 +103,11 @@ export interface FilesClient {
* @param args - download file args
*/
download: ClientMethodFrom<DownloadFileKindHttpEndpoint>;
/**
* Get a string for downloading a file that can be passed to a button element's
* href for download.
*/
getDownloadHref: (file: FileJSON) => string;
/**
* Share a file by creating a new file share instance.
*
Expand Down Expand Up @@ -115,39 +136,36 @@ export interface FilesClient {
* @param args - Get file share arguments
*/
listShares: ClientMethodFrom<FileListSharesHttpEndpoint>;
/**
* Get metrics of file system, like storage usage.
*
* @param args - Get metrics arguments
*/
getMetrics: ClientMethodFrom<FilesMetricsHttpEndpoint>;
/**
* Download a file, bypassing regular security by way of a
* secret share token.
*
* @param args - Get public download arguments.
*/
publicDownload: ClientMethodFrom<FilePublicDownloadHttpEndpoint>;

/**
* Get a string for downloading a file that can be passed to a button element's
* href for download.
*/
getDownloadHref: (file: FileJSON) => string;
}

export type FilesClientResponses = {
[K in keyof FilesClient]: Awaited<ReturnType<FilesClient[K]>>;
};

/**
* A factory for creating a {@link FilesClient}
* A files client that is scoped to a specific {@link FileKind}.
*
* More convenient if you want to re-use the same client for the same file kind
* and not specify the kind every time.
*/
export type ScopedFilesClient = {
[K in keyof FilesClient]: K extends 'list'
? (arg?: Omit<Parameters<FilesClient[K]>[0], 'kind'>) => ReturnType<FilesClient[K]>
: (arg: Omit<Parameters<FilesClient[K]>[0], 'kind'>) => ReturnType<FilesClient[K]>;
};

/**
* A factory for creating a {@link ScopedFilesClient}
*/
export interface FilesClientFactory {
/**
* Create a {@link FileClient} for a given {@link FileKind}.
* Create a files client.
*/
asUnscoped(): FilesClient;
/**
* Create a {@link ScopedFileClient} for a given {@link FileKind}.
*
* @param fileKind - The {@link FileKind} to create a client for.
*/
asScoped(fileKind: string): FilesClient;
asScoped(fileKind: string): ScopedFilesClient;
}

0 comments on commit b8a2068

Please sign in to comment.