Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
aabfe0f
First draft of horizontal/vertical layout
Heenawter Jul 18, 2023
a5e7909
Reorganize files + remove max width
Heenawter Jul 18, 2023
8b68eca
Doing some clean up
Heenawter Jul 19, 2023
e95b2c6
Apply i18n to strings
Heenawter Jul 19, 2023
8c93802
Add first draft of error catching
Heenawter Jul 19, 2023
bab2f2c
Fix unique key error
Heenawter Jul 19, 2023
8c2067a
Switch link colours + show custom label even on dashboard error
Heenawter Jul 20, 2023
5f0dd80
Move caching to dashboard service
Heenawter Jul 20, 2023
8ae31fa
Fix truncation bug
Heenawter Jul 20, 2023
95393f0
Make dashboard warning less threatening
Heenawter Jul 20, 2023
235ebaf
Update cache when deleting dashboard(s)
Heenawter Jul 24, 2023
e278911
Change rendering of error link + clean up
Heenawter Jul 24, 2023
b4838cc
Clean up the cache logic
Heenawter Jul 24, 2023
62f9053
Swap colours back + fix error tooltip location + clean up
Heenawter Jul 24, 2023
07b94ce
Add italics to generic error message
Heenawter Jul 24, 2023
09091ea
Clean up linting
Heenawter Jul 24, 2023
c92f589
More code clean up
Heenawter Jul 25, 2023
b72f492
Allow error state to be cleared
Heenawter Jul 25, 2023
580f918
Improved a11y
Heenawter Jul 25, 2023
f030bdd
Add descriptive tooltip to component for dashboard links
Heenawter Jul 26, 2023
6dad3bd
Fix SASS linting
Heenawter Jul 26, 2023
163037a
Add title to panel editor error tooltip
Heenawter Jul 26, 2023
2c532a9
Switch order of layout options
Heenawter Jul 28, 2023
0217a53
[Dashboard Navigation] Design cleanup (#163223)
andreadelrio Aug 7, 2023
72a3a62
Clean up `scss`
Heenawter Aug 7, 2023
07c5f75
Clean up tooltip logic
Heenawter Aug 8, 2023
9670811
Use `findDashboardById` rather than `findDashboardByIds`
Heenawter Aug 8, 2023
7d3a7ea
Switch to `LRUCache`
Heenawter Aug 8, 2023
2a1b6ed
Fix stub
Heenawter Aug 8, 2023
db2439f
Flip order of layout and links in flyout
Heenawter Aug 9, 2023
5882fb3
Change size of cache
Heenawter Aug 9, 2023
faa435c
Merge branch 'navigation-embeddable' of github.com:elastic/kibana int…
Heenawter Aug 11, 2023
3eca107
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Aug 11, 2023
40f31b1
Add extra padding to current link
Heenawter Aug 11, 2023
0bc4f49
Fix import
Heenawter Aug 11, 2023
455a555
Address another round of feedback
Heenawter Aug 11, 2023
8cfeb29
Update mappings
Heenawter Aug 14, 2023
1011d65
Add `dynamic: false` to mappings
Heenawter Aug 14, 2023
d8cfa36
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Aug 14, 2023
1d3a042
Change to `EuiSwitch` for by-reference saving
Heenawter Aug 15, 2023
a13170b
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Aug 15, 2023
d9da2bc
Change "Edit Links" to "Edit links" in config menu
Heenawter Aug 16, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,7 @@
}
},
"navigation_embeddable": {
"dynamic": false,
"properties": {
"id": {
"type": "text"
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/dashboard/public/dashboard_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,14 @@ export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2;

export const CHANGE_CHECK_DEBOUNCE = 100;

// ------------------------------------------------------------------
// Content Management
// ------------------------------------------------------------------
export { CONTENT_ID as DASHBOARD_CONTENT_ID } from '../common/content_management/constants';

export const DASHBOARD_CACHE_SIZE = 20; // only store a max of 20 dashboards
export const DASHBOARD_CACHE_TTL = 1000 * 60 * 5; // time to live = 5 minutes

// ------------------------------------------------------------------
// Default State
// ------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const dashboardContentManagementServiceFactory: DashboardContentManagemen
hits,
});
}),
findById: jest.fn(),
findByIds: jest.fn().mockImplementation(() =>
Promise.resolve([
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import LRUCache from 'lru-cache';
import { DashboardCrudTypes } from '../../../common/content_management';
import { DASHBOARD_CACHE_SIZE, DASHBOARD_CACHE_TTL } from '../../dashboard_constants';

export class DashboardContentManagementCache {
private cache: LRUCache<string, DashboardCrudTypes['GetOut']>;

constructor() {
this.cache = new LRUCache<string, DashboardCrudTypes['GetOut']>({
max: DASHBOARD_CACHE_SIZE,
maxAge: DASHBOARD_CACHE_TTL,
});
}

/** Fetch the dashboard with `id` from the cache */
public fetchDashboard(id: string) {
return this.cache.get(id);
}

/** Add the fetched dashboard to the cache */
public addDashboard({ item: dashboard, meta }: DashboardCrudTypes['GetOut']) {
this.cache.set(dashboard.id, {
meta,
item: dashboard,
});
}

/** Delete the dashboard with `id` from the cache */
public deleteDashboard(id: string) {
this.cache.del(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { checkForDuplicateDashboardTitle } from './lib/check_for_duplicate_dashb

import {
searchDashboards,
findDashboardById,
findDashboardsByIds,
findDashboardIdByTitle,
} from './lib/find_dashboards';
Expand All @@ -23,13 +24,16 @@ import type {
} from './types';
import { loadDashboardState } from './lib/load_dashboard_state';
import { deleteDashboards } from './lib/delete_dashboards';
import { DashboardContentManagementCache } from './dashboard_content_management_cache';

export type DashboardContentManagementServiceFactory = KibanaPluginServiceFactory<
DashboardContentManagementService,
DashboardStartDependencies,
DashboardContentManagementRequiredServices
>;

export const dashboardContentManagementCache = new DashboardContentManagementCache();

export const dashboardContentManagementServiceFactory: DashboardContentManagementServiceFactory = (
{ startPlugins: { contentManagement } },
requiredServices
Expand Down Expand Up @@ -74,6 +78,7 @@ export const dashboardContentManagementServiceFactory: DashboardContentManagemen
search,
size,
}),
findById: (id) => findDashboardById(contentManagement, id),
findByIds: (ids) => findDashboardsByIds(contentManagement, ids),
findByTitle: (title) => findDashboardIdByTitle(contentManagement, title),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@
import { DashboardStartDependencies } from '../../../plugin';
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
import { DashboardCrudTypes } from '../../../../common/content_management';
import { dashboardContentManagementCache } from '../dashboard_content_management_service';

export const deleteDashboards = async (
ids: string[],
contentManagement: DashboardStartDependencies['contentManagement']
) => {
const deletePromises = ids.map((id) =>
contentManagement.client.delete<
const deletePromises = ids.map((id) => {
dashboardContentManagementCache.deleteDashboard(id);
return contentManagement.client.delete<
DashboardCrudTypes['DeleteIn'],
DashboardCrudTypes['DeleteOut']
>({
contentTypeId: DASHBOARD_CONTENT_ID,
id,
})
);
});
});

await Promise.all(deletePromises);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '../../../../common/content_management';
import { DashboardStartDependencies } from '../../../plugin';
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
import { dashboardContentManagementCache } from '../dashboard_content_management_service';

export interface SearchDashboardsArgs {
contentManagement: DashboardStartDependencies['contentManagement'];
Expand Down Expand Up @@ -67,23 +68,41 @@ export type FindDashboardsByIdResponse = { id: string } & (
| { status: 'error'; error: SavedObjectError }
);

export async function findDashboardsByIds(
export async function findDashboardById(
contentManagement: DashboardStartDependencies['contentManagement'],
ids: string[]
): Promise<FindDashboardsByIdResponse[]> {
const findPromises = ids.map((id) =>
contentManagement.client.get<DashboardCrudTypes['GetIn'], DashboardCrudTypes['GetOut']>({
id: string
): Promise<FindDashboardsByIdResponse> {
/** If the dashboard exists in the cache, then return the result from that */
const cachedDashboard = dashboardContentManagementCache.fetchDashboard(id);
if (cachedDashboard) {
return {
id,
status: 'success',
attributes: cachedDashboard.item.attributes,
};
}
/** Otherwise, fetch the dashboard from the content management client, add it to the cache, and return the result */
const response = await contentManagement.client
.get<DashboardCrudTypes['GetIn'], DashboardCrudTypes['GetOut']>({
contentTypeId: DASHBOARD_CONTENT_ID,
id,
})
);
const results = await Promise.all(findPromises);
.then((result) => {
dashboardContentManagementCache.addDashboard(result);
return { id, status: 'success', attributes: result.item.attributes };
})
.catch((e) => ({ status: 'error', error: e.body, id }));

return results.map((result) => {
if (result.item.error) return { status: 'error', error: result.item.error, id: result.item.id };
const { attributes, id } = result.item;
return { id, status: 'success', attributes };
});
return response as FindDashboardsByIdResponse;
}

export async function findDashboardsByIds(
contentManagement: DashboardStartDependencies['contentManagement'],
ids: string[]
): Promise<FindDashboardsByIdResponse[]> {
const findPromises = ids.map((id) => findDashboardById(contentManagement, id));
const results = await Promise.all(findPromises);
return results as FindDashboardsByIdResponse[];
}

export async function findDashboardIdByTitle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { DashboardCrudTypes } from '../../../../common/content_management';
import type { LoadDashboardFromSavedObjectProps, LoadDashboardReturn } from '../types';
import { DASHBOARD_CONTENT_ID, DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants';
import { dashboardContentManagementCache } from '../dashboard_content_management_service';

export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query {
// Lucene was the only option before, so language-less queries are all lucene
Expand Down Expand Up @@ -58,14 +59,28 @@ export const loadDashboardState = async ({
/**
* Load the saved object from Content Management
*/
const { item: rawDashboardContent, meta: resolveMeta } = await contentManagement.client
.get<DashboardCrudTypes['GetIn'], DashboardCrudTypes['GetOut']>({
contentTypeId: DASHBOARD_CONTENT_ID,
id,
})
.catch((e) => {
throw new SavedObjectNotFound(DASHBOARD_CONTENT_ID, id);
});
let rawDashboardContent;
let resolveMeta;

const cachedDashboard = dashboardContentManagementCache.fetchDashboard(id);
if (cachedDashboard) {
/** If the dashboard exists in the cache, use the cached version to load the dashboard */
({ item: rawDashboardContent, meta: resolveMeta } = cachedDashboard);
} else {
/** Otherwise, fetch and load the dashboard from the content management client, and add it to the cache */
const result = await contentManagement.client
.get<DashboardCrudTypes['GetIn'], DashboardCrudTypes['GetOut']>({
contentTypeId: DASHBOARD_CONTENT_ID,
id,
})
.catch((e) => {
throw new SavedObjectNotFound(DASHBOARD_CONTENT_ID, id);
});

dashboardContentManagementCache.addDashboard(result);
({ item: rawDashboardContent, meta: resolveMeta } = result);
}

if (!rawDashboardContent || !rawDashboardContent.version) {
return {
dashboardInput: newDashboardState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { DashboardStartDependencies } from '../../../plugin';
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
import { DashboardCrudTypes, DashboardAttributes } from '../../../../common/content_management';
import { dashboardSaveToastStrings } from '../../../dashboard_container/_dashboard_container_strings';
import { dashboardContentManagementCache } from '../dashboard_content_management_service';

export const serializeControlGroupInput = (
controlGroupInput: DashboardContainerInput['controlGroupInput']
Expand Down Expand Up @@ -200,6 +201,8 @@ export const saveDashboardState = async ({
if (newId !== lastSavedId) {
dashboardSessionStorage.clearState(lastSavedId);
return { redirectRequired: true, id: newId };
} else {
dashboardContentManagementCache.deleteDashboard(newId); // something changed in an existing dashboard, so delete it from the cache so that it can be re-fetched
}
}
return { id: newId };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface FindDashboardsService {
'hasReference' | 'hasNoReference' | 'search' | 'size' | 'options'
>
) => Promise<SearchDashboardsResponse>;
findById: (id: string) => Promise<FindDashboardsByIdResponse>;
findByIds: (ids: string[]) => Promise<FindDashboardsByIdResponse[]>;
findByTitle: (title: string) => Promise<{ id: string } | undefined>;
}
7 changes: 7 additions & 0 deletions src/plugins/navigation_embeddable/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ export const APP_ICON = 'link';
export const APP_NAME = i18n.translate('navigationEmbeddable.visTypeAlias.title', {
defaultMessage: 'Links',
});

export const EMBEDDABLE_DISPLAY_NAME = i18n.translate(
'navigationEmbeddable.embeddableDisplayName',
{
defaultMessage: 'links',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ export { LATEST_VERSION, CONTENT_ID } from '../constants';
export type { NavigationEmbeddableContentType } from '../types';

export type {
NavigationEmbeddableCrudTypes,
NavigationEmbeddableAttributes,
NavigationEmbeddableItem,
NavigationLinkType,
NavigationLayoutType,
NavigationEmbeddableLink,
NavigationEmbeddableItem,
NavigationEmbeddableCrudTypes,
NavigationEmbeddableAttributes,
} from './latest';

export { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from './latest';
export {
EXTERNAL_LINK_TYPE,
DASHBOARD_LINK_TYPE,
NAV_VERTICAL_LAYOUT,
NAV_HORIZONTAL_LAYOUT,
} from './latest';

export * as NavigationEmbeddableV1 from './v1';
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { schema } from '@kbn/config-schema';
import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning';
import {
savedObjectSchema,
objectTypeToGetResultSchema,
createOptionsSchemas,
updateOptionsSchema,
createResultSchema,
updateOptionsSchema,
createOptionsSchemas,
objectTypeToGetResultSchema,
} from '@kbn/content-management-utils';
import { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from '.';
import { NAV_HORIZONTAL_LAYOUT, NAV_VERTICAL_LAYOUT } from './constants';

const navigationEmbeddableLinkSchema = schema.object({
id: schema.string(),
Expand All @@ -30,6 +31,9 @@ const navigationEmbeddableAttributesSchema = schema.object(
title: schema.string(),
description: schema.maybe(schema.string()),
links: schema.maybe(schema.arrayOf(navigationEmbeddableLinkSchema)),
layout: schema.maybe(
schema.oneOf([schema.literal(NAV_HORIZONTAL_LAYOUT), schema.literal(NAV_VERTICAL_LAYOUT)])
),
},
{ unknowns: 'forbid' }
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
*/

/**
* Dashboard to dashboard links
* Link types
*/
export const DASHBOARD_LINK_TYPE = 'dashboardLink';
export const EXTERNAL_LINK_TYPE = 'externalLink';

/**
* External URL links
* Layout options
*/
export const EXTERNAL_LINK_TYPE = 'externalLink';
export const NAV_HORIZONTAL_LAYOUT = 'horizontal';
export const NAV_VERTICAL_LAYOUT = 'vertical';
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ export type {
NavigationEmbeddableCrudTypes,
NavigationEmbeddableAttributes,
NavigationEmbeddableLink,
NavigationLayoutType,
NavigationLinkType,
} from './types';
export type NavigationEmbeddableItem = NavigationEmbeddableCrudTypes['Item'];
export { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from './constants';
export {
EXTERNAL_LINK_TYPE,
DASHBOARD_LINK_TYPE,
NAV_VERTICAL_LAYOUT,
NAV_HORIZONTAL_LAYOUT,
} from './constants';
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import type {
SavedObjectUpdateOptions,
} from '@kbn/content-management-utils';
import { NavigationEmbeddableContentType } from '../../types';
import { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from './constants';
import {
DASHBOARD_LINK_TYPE,
EXTERNAL_LINK_TYPE,
NAV_HORIZONTAL_LAYOUT,
NAV_VERTICAL_LAYOUT,
} from './constants';

export type NavigationEmbeddableCrudTypes = ContentManagementCrudTypes<
NavigationEmbeddableContentType,
Expand All @@ -38,9 +43,12 @@ export interface NavigationEmbeddableLink {
order: number;
}

export type NavigationLayoutType = typeof NAV_HORIZONTAL_LAYOUT | typeof NAV_VERTICAL_LAYOUT;

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type NavigationEmbeddableAttributes = {
title: string;
description?: string;
links?: NavigationEmbeddableLink[];
layout?: NavigationLayoutType;
};
Loading