Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
d5805dd
wip esql favorites
Dosant Oct 30, 2024
82359b0
clean up
Dosant Oct 30, 2024
628e51c
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Oct 30, 2024
536023f
improve
Dosant Oct 30, 2024
3a1e9bb
Merge branch 'd/2024-10-30-esql-favorites' of github.com:Dosant/kiban…
Dosant Oct 30, 2024
cda738a
[CI] Auto-commit changed files from 'node scripts/jest_integration -u…
kibanamachine Oct 30, 2024
6eced44
add a limit
Dosant Nov 1, 2024
a219e22
Merge branch 'd/2024-10-30-esql-favorites' of github.com:Dosant/kiban…
Dosant Nov 1, 2024
eaf6d31
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 4, 2024
40a9c7c
[ES|QL] Add starred queries in the editor
stratoula Nov 4, 2024
0a9b4f4
Initialize the favorite service
stratoula Nov 4, 2024
aed7f83
improve client types to support metadata
Dosant Nov 4, 2024
43483c5
Use the hooks
stratoula Nov 4, 2024
3931ae8
Merge branch 'd/2024-10-30-esql-favorites' into esql-starred-queries
stratoula Nov 4, 2024
2c5b569
fix important order lint
Dosant Nov 4, 2024
1ba7968
Add to the service - it fails
stratoula Nov 4, 2024
a503b87
register to the esql plugin
stratoula Nov 4, 2024
4a06d68
fix client
Dosant Nov 4, 2024
e09946f
Basic implementation
stratoula Nov 5, 2024
6e83139
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 6, 2024
071a87b
Merge branch 'd/2024-10-30-esql-favorites' into esql-starred-queries
stratoula Nov 6, 2024
15e9f75
Remove duplicate
stratoula Nov 6, 2024
4f1ab27
Add a help text
stratoula Nov 6, 2024
3d1f093
Test the tabs component
stratoula Nov 6, 2024
3913d94
Make it responsive
stratoula Nov 6, 2024
4e5a3b8
Adds a unit test
stratoula Nov 6, 2024
47b04ed
Cleanup
stratoula Nov 6, 2024
fd1a79f
Merge branch 'main' of github.com:elastic/kibana into d/2024-10-30-es…
Dosant Nov 6, 2024
82117a5
fix client metadata
Dosant Nov 6, 2024
f8e0b16
make FAVORITES_LIMIT exportable
Dosant Nov 6, 2024
a36d071
Merge pull request #9 from stratoula/esql-starred-queries
Dosant Nov 6, 2024
cad2ef3
Retrieve the limnit from the CM service
stratoula Nov 6, 2024
da48fcf
lint ts config
Dosant Nov 6, 2024
9f3e1e3
adjust tests
Dosant Nov 6, 2024
a290768
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 7, 2024
e2573aa
Change the design of the stars as decided
stratoula Nov 7, 2024
9a8c3db
Discard the query modal
stratoula Nov 7, 2024
ec92897
Disable the icon and add tooltip in case of limit
stratoula Nov 7, 2024
4b55cb5
Small changes in the date format
stratoula Nov 7, 2024
b2b537a
Small refactoring of the history components FTs
stratoula Nov 7, 2024
70b61eb
Cleanup
stratoula Nov 7, 2024
52f98b8
No meow
stratoula Nov 7, 2024
1a3d888
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 8, 2024
0d25d4b
Adds an FT
stratoula Nov 8, 2024
b4279a6
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Nov 8, 2024
7c2e860
Commit forgotten file
stratoula Nov 8, 2024
4b21f5e
Fix mock file
stratoula Nov 8, 2024
0150961
[CI] Auto-commit changed files from 'node scripts/notice'
kibanamachine Nov 8, 2024
b7a8163
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 11, 2024
5ab8bf4
Fix linting
stratoula Nov 11, 2024
b36c274
Fix a small bug in remove from fav
stratoula Nov 13, 2024
b8f0ec3
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 13, 2024
f6c7c82
tiny date change
Dosant Nov 13, 2024
935ae66
Merge pull request #10 from Dosant/d/2024-11-13-date-clean
Dosant Nov 13, 2024
7d7e206
createdAt - timeRun
Dosant Nov 13, 2024
b67abe3
[ES|QL] Change in the history time implementation
stratoula Nov 13, 2024
31fdbe3
Fixes sorting
stratoula Nov 13, 2024
53c2fd3
Convert to iso
stratoula Nov 13, 2024
d0b3d3e
Merge pull request #11 from Dosant/d/2024-11-13-date-clean
Dosant Nov 13, 2024
42f4cb4
Correct sorting
stratoula Nov 13, 2024
497d247
Merge branch 'd/2024-10-30-esql-favorites' into change-history-time-i…
stratoula Nov 13, 2024
2099375
Nit
stratoula Nov 13, 2024
6e6d38b
Merge pull request #12 from stratoula/change-history-time-implementation
Dosant Nov 13, 2024
c6ef76e
enabled:false -> dynamic:false
Dosant Nov 14, 2024
cb3d487
Cleanup of old properties
stratoula Nov 14, 2024
0a25240
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Nov 14, 2024
e6a6760
[CI] Auto-commit changed files from 'node scripts/jest_integration -u…
kibanamachine Nov 14, 2024
43fd224
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 18, 2024
ad15bc7
Merge with main and resolve conflicts
stratoula Nov 18, 2024
cee13af
Merge branch 'main' into d/2024-10-30-esql-favorites
stratoula Nov 18, 2024
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
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