Skip to content

Commit

Permalink
Merge branch 'main' of github.com:elastic/kibana into eem/kubernetes-…
Browse files Browse the repository at this point in the history
…definitions
  • Loading branch information
simianhacker committed Nov 18, 2024
2 parents 666e34e + dd50002 commit 7129305
Show file tree
Hide file tree
Showing 324 changed files with 7,549 additions and 1,838 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ packages/cloud @elastic/kibana-core
packages/content-management/content_editor @elastic/appex-sharedux
packages/content-management/content_insights/content_insights_public @elastic/appex-sharedux
packages/content-management/content_insights/content_insights_server @elastic/appex-sharedux
packages/content-management/favorites/favorites_common @elastic/appex-sharedux
packages/content-management/favorites/favorites_public @elastic/appex-sharedux
packages/content-management/favorites/favorites_server @elastic/appex-sharedux
packages/content-management/tabbed_table_list_view @elastic/appex-sharedux
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
"@kbn/content-management-content-insights-public": "link:packages/content-management/content_insights/content_insights_public",
"@kbn/content-management-content-insights-server": "link:packages/content-management/content_insights/content_insights_server",
"@kbn/content-management-examples-plugin": "link:examples/content_management_examples",
"@kbn/content-management-favorites-common": "link:packages/content-management/favorites/favorites_common",
"@kbn/content-management-favorites-public": "link:packages/content-management/favorites/favorites_public",
"@kbn/content-management-favorites-server": "link:packages/content-management/favorites/favorites_server",
"@kbn/content-management-plugin": "link:src/plugins/content_management",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/content-management-favorites-common

Shared client & server code for the favorites packages.
11 changes: 11 additions & 0 deletions packages/content-management/favorites/favorites_common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

// Limit the number of favorites to prevent too large objects due to metadata
export const FAVORITES_LIMIT = 100;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../..',
roots: ['<rootDir>/packages/content-management/favorites/favorites_common'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/content-management-favorites-common",
"owner": "@elastic/appex-sharedux"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/content-management-favorites-common",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node"
]
},
"include": [
"**/*.ts",
],
"exclude": [
"target/**/*"
],
"kbn_references": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,52 @@

import type { HttpStart } from '@kbn/core-http-browser';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { GetFavoritesResponse } from '@kbn/content-management-favorites-server';
import type {
GetFavoritesResponse as GetFavoritesResponseServer,
AddFavoriteResponse,
RemoveFavoriteResponse,
} from '@kbn/content-management-favorites-server';

export interface FavoritesClientPublic {
getFavorites(): Promise<GetFavoritesResponse>;
addFavorite({ id }: { id: string }): Promise<GetFavoritesResponse>;
removeFavorite({ id }: { id: string }): Promise<GetFavoritesResponse>;
export interface GetFavoritesResponse<Metadata extends object | void = void>
extends GetFavoritesResponseServer {
favoriteMetadata: Metadata extends object ? Record<string, Metadata> : never;
}

type AddFavoriteRequest<Metadata extends object | void> = Metadata extends object
? { id: string; metadata: Metadata }
: { id: string };

export interface FavoritesClientPublic<Metadata extends object | void = void> {
getFavorites(): Promise<GetFavoritesResponse<Metadata>>;
addFavorite(params: AddFavoriteRequest<Metadata>): Promise<AddFavoriteResponse>;
removeFavorite(params: { id: string }): Promise<RemoveFavoriteResponse>;

getFavoriteType(): string;
reportAddFavoriteClick(): void;
reportRemoveFavoriteClick(): void;
}

export class FavoritesClient implements FavoritesClientPublic {
export class FavoritesClient<Metadata extends object | void = void>
implements FavoritesClientPublic<Metadata>
{
constructor(
private readonly appName: string,
private readonly favoriteObjectType: string,
private readonly deps: { http: HttpStart; usageCollection?: UsageCollectionStart }
) {}

public async getFavorites(): Promise<GetFavoritesResponse> {
public async getFavorites(): Promise<GetFavoritesResponse<Metadata>> {
return this.deps.http.get(`/internal/content_management/favorites/${this.favoriteObjectType}`);
}

public async addFavorite({ id }: { id: string }): Promise<GetFavoritesResponse> {
public async addFavorite(params: AddFavoriteRequest<Metadata>): Promise<AddFavoriteResponse> {
return this.deps.http.post(
`/internal/content_management/favorites/${this.favoriteObjectType}/${id}/favorite`
`/internal/content_management/favorites/${this.favoriteObjectType}/${params.id}/favorite`,
{ body: 'metadata' in params ? JSON.stringify({ metadata: params.metadata }) : undefined }
);
}

public async removeFavorite({ id }: { id: string }): Promise<GetFavoritesResponse> {
public async removeFavorite({ id }: { id: string }): Promise<RemoveFavoriteResponse> {
return this.deps.http.post(
`/internal/content_management/favorites/${this.favoriteObjectType}/${id}/unfavorite`
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import React from 'react';

import type { IHttpFetchError } from '@kbn/core-http-browser';
import { useFavoritesClient, useFavoritesContext } from './favorites_context';

const favoritesKeys = {
Expand Down Expand Up @@ -54,14 +55,14 @@ export const useAddFavorite = () => {
onSuccess: (data) => {
queryClient.setQueryData(favoritesKeys.byType(favoritesClient!.getFavoriteType()), data);
},
onError: (error: Error) => {
onError: (error: IHttpFetchError<{ message?: string }>) => {
notifyError?.(
<>
{i18n.translate('contentManagement.favorites.addFavoriteError', {
defaultMessage: 'Error adding to Starred',
})}
</>,
error?.message
error?.body?.message ?? error.message
);
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { registerFavorites, type GetFavoritesResponse } from './src';
export {
registerFavorites,
type GetFavoritesResponse,
type FavoritesSetup,
type AddFavoriteResponse,
type RemoveFavoriteResponse,
} from './src';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ObjectType } from '@kbn/config-schema';

interface FavoriteTypeConfig {
typeMetadataSchema?: ObjectType;
}

export type FavoritesRegistrySetup = Pick<FavoritesRegistry, 'registerFavoriteType'>;

export class FavoritesRegistry {
private favoriteTypes = new Map<string, FavoriteTypeConfig>();

registerFavoriteType(type: string, config: FavoriteTypeConfig = {}) {
if (this.favoriteTypes.has(type)) {
throw new Error(`Favorite type ${type} is already registered`);
}

this.favoriteTypes.set(type, config);
}

hasType(type: string) {
return this.favoriteTypes.has(type);
}

validateMetadata(type: string, metadata?: object) {
if (!this.hasType(type)) {
throw new Error(`Favorite type ${type} is not registered`);
}

const typeConfig = this.favoriteTypes.get(type)!;
const typeMetadataSchema = typeConfig.typeMetadataSchema;

if (typeMetadataSchema) {
typeMetadataSchema.validate(metadata);
} else {
if (metadata === undefined) {
return; /* ok */
} else {
throw new Error(`Favorite type ${type} does not support metadata`);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,55 @@ import {
SECURITY_EXTENSION_ID,
} from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { FavoritesService } from './favorites_service';
import { FavoritesService, FavoritesLimitExceededError } from './favorites_service';
import { favoritesSavedObjectType } from './favorites_saved_object';

// only dashboard is supported for now
// TODO: make configurable or allow any string
const typeSchema = schema.oneOf([schema.literal('dashboard')]);
import { FavoritesRegistry } from './favorites_registry';

/**
* @public
* Response for get favorites API
*/
export interface GetFavoritesResponse {
favoriteIds: string[];
favoriteMetadata?: Record<string, object>;
}

export interface AddFavoriteResponse {
favoriteIds: string[];
}

export function registerFavoritesRoutes({ core, logger }: { core: CoreSetup; logger: Logger }) {
export interface RemoveFavoriteResponse {
favoriteIds: string[];
}

export function registerFavoritesRoutes({
core,
logger,
favoritesRegistry,
}: {
core: CoreSetup;
logger: Logger;
favoritesRegistry: FavoritesRegistry;
}) {
const typeSchema = schema.string({
validate: (type) => {
if (!favoritesRegistry.hasType(type)) {
return `Unknown favorite type: ${type}`;
}
},
});

const metadataSchema = schema.maybe(
schema.object(
{
// validated later by the registry depending on the type
},
{
unknowns: 'allow',
}
)
);

const router = core.http.createRouter();

const getSavedObjectClient = (coreRequestHandlerContext: CoreRequestHandlerContext) => {
Expand All @@ -49,6 +82,13 @@ export function registerFavoritesRoutes({ core, logger }: { core: CoreSetup; log
id: schema.string(),
type: typeSchema,
}),
body: schema.maybe(
schema.nullable(
schema.object({
metadata: metadataSchema,
})
)
),
},
// we don't protect the route with any access tags as
// we only give access to the current user's favorites ids
Expand All @@ -67,13 +107,35 @@ export function registerFavoritesRoutes({ core, logger }: { core: CoreSetup; log
const favorites = new FavoritesService(type, userId, {
savedObjectClient: getSavedObjectClient(coreRequestHandlerContext),
logger,
favoritesRegistry,
});

const favoriteIds: GetFavoritesResponse = await favorites.addFavorite({
id: request.params.id,
});
const id = request.params.id;
const metadata = request.body?.metadata;

return response.ok({ body: favoriteIds });
try {
favoritesRegistry.validateMetadata(type, metadata);
} catch (e) {
return response.badRequest({ body: { message: e.message } });
}

try {
const favoritesResult = await favorites.addFavorite({
id,
metadata,
});
const addFavoritesResponse: AddFavoriteResponse = {
favoriteIds: favoritesResult.favoriteIds,
};

return response.ok({ body: addFavoritesResponse });
} catch (e) {
if (e instanceof FavoritesLimitExceededError) {
return response.forbidden({ body: { message: e.message } });
}

throw e; // unexpected error, let the global error handler deal with it
}
}
);

Expand Down Expand Up @@ -102,12 +164,18 @@ export function registerFavoritesRoutes({ core, logger }: { core: CoreSetup; log
const favorites = new FavoritesService(type, userId, {
savedObjectClient: getSavedObjectClient(coreRequestHandlerContext),
logger,
favoritesRegistry,
});

const favoriteIds: GetFavoritesResponse = await favorites.removeFavorite({
const favoritesResult: GetFavoritesResponse = await favorites.removeFavorite({
id: request.params.id,
});
return response.ok({ body: favoriteIds });

const removeFavoriteResponse: RemoveFavoriteResponse = {
favoriteIds: favoritesResult.favoriteIds,
};

return response.ok({ body: removeFavoriteResponse });
}
);

Expand Down Expand Up @@ -135,12 +203,18 @@ export function registerFavoritesRoutes({ core, logger }: { core: CoreSetup; log
const favorites = new FavoritesService(type, userId, {
savedObjectClient: getSavedObjectClient(coreRequestHandlerContext),
logger,
favoritesRegistry,
});

const getFavoritesResponse: GetFavoritesResponse = await favorites.getFavorites();
const favoritesResult = await favorites.getFavorites();

const favoritesResponse: GetFavoritesResponse = {
favoriteIds: favoritesResult.favoriteIds,
favoriteMetadata: favoritesResult.favoriteMetadata,
};

return response.ok({
body: getFavoritesResponse,
body: favoritesResponse,
});
}
);
Expand Down
Loading

0 comments on commit 7129305

Please sign in to comment.