Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
617b6e0
update request and response format
angorayc Jul 14, 2025
0cb8067
update response
angorayc Jul 15, 2025
fbeae49
remove console
angorayc Jul 15, 2025
457ad26
type
angorayc Jul 15, 2025
2a15d4a
Update src/platform/plugins/shared/dashboard/server/content_managemen…
angorayc Jul 16, 2025
1d32155
update schema
angorayc Jul 16, 2025
42ca2ab
clean up
angorayc Jul 16, 2025
1b25fe0
clean up types
angorayc Jul 17, 2025
a9953d3
unit tests
angorayc Jul 17, 2025
eaa41bf
Merge branch 'main' of github.com:elastic/kibana into issue-196609
angorayc Jul 17, 2025
e22012d
Merge branch 'main' into issue-196609
nickpeihl Jul 17, 2025
342957f
types
angorayc Jul 22, 2025
e1fbf9f
Merge branch 'issue-196609' of github.com:angorayc/kibana into issue-…
angorayc Jul 22, 2025
d583c40
types
angorayc Jul 24, 2025
9efbd55
move id to the top level
angorayc Aug 1, 2025
5c21ead
move type to the top level
angorayc Aug 5, 2025
649e519
Merge branch 'main' of github.com:elastic/kibana into issue-196609
angorayc Aug 5, 2025
5f5d0ef
update public services
angorayc Aug 6, 2025
49927e2
update itemResultSchema
angorayc Aug 7, 2025
d5fb1f1
dashboard UI
angorayc Aug 11, 2025
55b245f
cms
angorayc Aug 14, 2025
c3f95d2
[CI] Auto-commit changed files from 'node scripts/eslint_all_files --…
kibanamachine Aug 14, 2025
d032586
types
angorayc Aug 14, 2025
9817088
Merge branch 'issue-196609' of github.com:angorayc/kibana into issue-…
angorayc Aug 14, 2025
85143e8
unit tests
angorayc Aug 18, 2025
7d6085a
types
angorayc Aug 19, 2025
5489a38
Merge branch 'main' of github.com:elastic/kibana into issue-196609
angorayc Aug 19, 2025
c160edc
[CI] Auto-commit changed files from 'node scripts/eslint_all_files --…
kibanamachine Aug 19, 2025
f9ca480
Boilerplate to limit scope of PR to just the public API route
nickpeihl Aug 20, 2025
8a16007
types
angorayc Aug 20, 2025
109d3bb
Merge branch 'issue-196609' of github.com:angorayc/kibana into issue-…
angorayc Aug 20, 2025
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 @@ -97,5 +97,5 @@ export type SearchResult<T = unknown, M = void> = M extends void
/** Page number or cursor */
cursor?: string;
};
meta: M;
meta: M[];
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ export interface ProcedureSchemas {

export type ItemResult<T = unknown, M = void> = M extends void
? {
item: T;
id: string;
data: T;
meta?: never;
}
: {
item: T;
id: string;
data: T;
meta: M;
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ export class DashboardContentManagementCache {
}

/** Add the fetched dashboard to the cache */
public addDashboard({ item: dashboard, meta }: DashboardGetOut) {
this.cache.set(dashboard.id, {
public addDashboard({ data: dashboard, meta }: DashboardGetOut) {
this.cache.set(meta.id, {
meta,
item: dashboard,
data: dashboard,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { Reference } from '@kbn/content-management-utils';
import { SavedObjectError, SavedObjectsFindOptionsReference } from '@kbn/core/public';

import type {
DashboardAttributes,
FindDashboardsByIdResponseAttributes,
DashboardGetIn,
DashboardGetOut,
DashboardSearchIn,
Expand Down Expand Up @@ -64,7 +64,7 @@ export async function searchDashboards({
}

export type FindDashboardsByIdResponse = { id: string } & (
| { status: 'success'; attributes: DashboardAttributes; references: Reference[] }
| { status: 'success'; attributes: FindDashboardsByIdResponseAttributes; references: Reference[] }
| { status: 'error'; error: SavedObjectError }
);

Expand All @@ -73,12 +73,14 @@ export async function findDashboardById(id: string): Promise<FindDashboardsByIdR

/** If the dashboard exists in the cache, then return the result from that */
const cachedDashboard = dashboardContentManagementCache.fetchDashboard(id);
const { ...attributes } = cachedDashboard?.data;
const references = cachedDashboard?.data.references ?? [];
if (cachedDashboard) {
return {
id,
status: 'success',
attributes: cachedDashboard.item.attributes,
references: cachedDashboard.item.references,
attributes,
references,
};
}

Expand All @@ -88,16 +90,19 @@ export async function findDashboardById(id: string): Promise<FindDashboardsByIdR
contentTypeId: DASHBOARD_CONTENT_ID,
id,
});
if (response.item.error) {
throw response.item.error;
if ('error' in response) {
throw response.error;
}

dashboardContentManagementCache.addDashboard(response);
const {
data: { references: responseReferences = [], ...responseAttributes },
} = response;
return {
id,
status: 'success',
attributes: response.item.attributes,
references: response.item.references,
attributes: responseAttributes,
references: responseReferences,
};
} catch (e) {
return {
Expand All @@ -115,7 +120,7 @@ export async function findDashboardsByIds(ids: string[]): Promise<FindDashboards
}

export async function findDashboardIdByTitle(title: string): Promise<{ id: string } | undefined> {
const { hits } = await contentManagementService.client.search<
const { hits, meta } = await contentManagementService.client.search<
DashboardSearchIn,
DashboardSearchOut
>({
Expand All @@ -127,10 +132,10 @@ export async function findDashboardIdByTitle(title: string): Promise<{ id: strin
options: { onlyTitle: true },
});
// The search isn't an exact match, lets see if we can find a single exact match to use
const matchingDashboards = hits.filter(
(hit) => hit.attributes.title.toLowerCase() === title.toLowerCase()
const matchingDashboardIndex = hits.findIndex(
(hit) => hit.title.toLowerCase() === title.toLowerCase()
);
if (matchingDashboards.length === 1) {
return { id: matchingDashboards[0].id };
if (matchingDashboardIndex >= 0) {
return { id: meta[matchingDashboardIndex].id };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ export const loadDashboardState = async ({
/**
* Load the saved object from Content Management
*/
let rawDashboardContent: DashboardGetOut['item'];
let rawDashboardContent: DashboardGetOut['data'];
let resolveMeta: DashboardGetOut['meta'];

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);
({ data: 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 contentManagementService.client
Expand All @@ -88,7 +88,7 @@ export const loadDashboardState = async ({
throw new Error(message);
});

({ item: rawDashboardContent, meta: resolveMeta } = result);
({ data: rawDashboardContent, meta: resolveMeta } = result);
const { outcome: loadOutcome } = resolveMeta;
if (loadOutcome !== 'aliasMatch') {
/**
Expand All @@ -108,7 +108,7 @@ export const loadDashboardState = async ({
};
}

const { references, attributes, managed } = rawDashboardContent;
const { references: references = [], ...attributes } = rawDashboardContent;

/**
* Create search source and pull filters and query from it.
Expand Down Expand Up @@ -146,7 +146,7 @@ export const loadDashboardState = async ({
: undefined;

return {
managed,
managed: resolveMeta.managed,
references,
resolveMeta,
dashboardInput: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { DashboardPanel } from '../../../../server';

contentManagementService.client.create = jest.fn().mockImplementation(({ options }) => {
if (options.id === undefined) {
return { item: { id: 'newlyGeneratedId' } };
return { id: 'newlyGeneratedId' };
}

throw new Error('Update should be used when id is provided');
Expand All @@ -24,7 +24,7 @@ contentManagementService.client.update = jest.fn().mockImplementation(({ id }) =
if (id === undefined) {
throw new Error('Update needs an id');
}
return { item: { id } };
return { id };
});

dataService.query.timefilter.timefilter.getTime = jest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ export const saveDashboardState = async ({
references,
},
});
const newId = result.item.id;
console.log('result----------', result);

const newId = result.id;

if (newId) {
coreServices.notifications.toasts.addSuccess({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ import type { Logger } from '@kbn/logging';
import { CONTENT_ID, LATEST_VERSION } from '../../common/content_management';
import { INTERNAL_API_VERSION, PUBLIC_API_PATH } from './constants';
import {
dashboardAttributesSchema,
dashboardGetResultSchema,
dashboardCreateResultSchema,
dashboardSearchResultsSchema,
referenceSchema,
DashboardItem,
} from '../content_management/v1';
import {
dashboardAttributesSchemaRequest,
dashboardCreateRequestAttributesSchema,
} from '../content_management/v1/cm_services';

interface RegisterAPIRoutesArgs {
http: HttpServiceSetup;
Expand Down Expand Up @@ -83,11 +86,7 @@ export function registerAPIRoutes({
})
),
}),
body: schema.object({
attributes: dashboardAttributesSchema,
references: schema.maybe(schema.arrayOf(referenceSchema)),
spaces: schema.maybe(schema.arrayOf(schema.string())),
}),
body: dashboardCreateRequestAttributesSchema,
},
response: {
200: {
Expand All @@ -98,10 +97,10 @@ export function registerAPIRoutes({
},
async (ctx, req, res) => {
const { id } = req.params;
const { attributes, references, spaces: initialNamespaces } = req.body;
const { references, spaces: initialNamespaces, ...attributes } = req.body;
const client = contentManagement.contentClient
.getForRequest({ request: req, requestHandlerContext: ctx })
.for(CONTENT_ID, LATEST_VERSION);
.for<DashboardItem>(CONTENT_ID, LATEST_VERSION);
let result;
try {
({ result } = await client.create(attributes, {
Expand All @@ -122,7 +121,7 @@ export function registerAPIRoutes({
return res.forbidden();
}

return res.badRequest();
return res.badRequest({ body: e });
}

return res.ok({ body: result });
Expand All @@ -147,10 +146,7 @@ export function registerAPIRoutes({
meta: { description: 'A unique identifier for the dashboard.' },
}),
}),
body: schema.object({
attributes: dashboardAttributesSchema,
references: schema.maybe(schema.arrayOf(referenceSchema)),
}),
body: dashboardAttributesSchemaRequest,
},
response: {
200: {
Expand All @@ -160,7 +156,7 @@ export function registerAPIRoutes({
},
},
async (ctx, req, res) => {
const { attributes, references } = req.body;
const { references, ...attributes } = req.body;
const client = contentManagement.contentClient
.getForRequest({ request: req, requestHandlerContext: ctx })
.for(CONTENT_ID, LATEST_VERSION);
Expand All @@ -178,7 +174,7 @@ export function registerAPIRoutes({
if (e.isBoom && e.output.statusCode === 403) {
return res.forbidden();
}
return res.badRequest(e.message);
return res.badRequest({ body: e.output.payload });
}
return res.ok({ body: result });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import { StorageContext } from '@kbn/content-management-plugin/server';
import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server';
import type { SavedObjectReference } from '@kbn/core/server';
import type { ITagsClient, Tag } from '@kbn/saved-objects-tagging-oss-plugin/common';
import { TypeOf } from '@kbn/config-schema';
import { DASHBOARD_SAVED_OBJECT_TYPE } from '../dashboard_saved_object';
import { cmServicesDefinition } from './cm_services';
import { DashboardSavedObjectAttributes } from '../dashboard_saved_object';
import { savedObjectToItem, transformDashboardIn } from './latest';
import type {
DashboardAttributes,
DashboardItem,
DashboardCreateOut,
DashboardCreateOptions,
DashboardGetOut,
Expand All @@ -32,6 +32,11 @@ import type {
DashboardUpdateOut,
DashboardSearchOptions,
} from './latest';
import {
dashboardAttributesSchemaResponse,
dashboardResponseMetaSchema,
dashboardGetResultSchema,
} from './v1/cm_services';

const getRandomColor = (): string => {
return '#' + String(Math.floor(Math.random() * 16777215).toString(16)).padStart(6, '0');
Expand Down Expand Up @@ -194,7 +199,16 @@ export class DashboardStorage {
throw Boom.badRequest(`Invalid response. ${itemError.message}`);
}

const response = { item, meta: { aliasPurpose, aliasTargetId, outcome } };
// Handle the null case
if (!item || !('data' in item)) {
throw Boom.badRequest('No item returned from savedObjectToItem');
}

const response: TypeOf<typeof dashboardGetResultSchema> = {
data: item.data,
meta: { ...item.meta, aliasPurpose, aliasTargetId, outcome },
id: item.id,
};

const validationError = transforms.get.out.result.validate(response);
if (validationError) {
Expand All @@ -207,8 +221,8 @@ export class DashboardStorage {

// Validate response and DOWN transform to the request version
const { value, error: resultError } = transforms.get.out.result.down<
DashboardGetOut,
DashboardGetOut
TypeOf<typeof dashboardGetResultSchema>,
TypeOf<typeof dashboardGetResultSchema>
>(
response,
undefined, // do not override version
Expand Down Expand Up @@ -274,16 +288,16 @@ export class DashboardStorage {
soAttributes,
{ ...optionsToLatest, references: soReferences }
);

const { item, error: itemError } = savedObjectToItem(savedObject, false, {
getTagNamesFromReferences: (references: SavedObjectReference[]) =>
this.getTagNamesFromReferences(references, allTags),
});

if (itemError) {
throw Boom.badRequest(`Invalid response. ${itemError.message}`);
}

const validationError = transforms.create.out.result.validate({ item });
const validationError = transforms.create.out.result.validate(item);
if (validationError) {
if (this.throwOnResultValidationError) {
throw Boom.badRequest(`Invalid response. ${validationError.message}`);
Expand All @@ -294,13 +308,19 @@ export class DashboardStorage {

// Validate DB response and DOWN transform to the request version
const { value, error: resultError } = transforms.create.out.result.down<
CreateResult<DashboardItem>
CreateResult<
TypeOf<typeof dashboardAttributesSchemaResponse>,
TypeOf<typeof dashboardResponseMetaSchema>
>,
CreateResult<
TypeOf<typeof dashboardAttributesSchemaResponse>,
TypeOf<typeof dashboardResponseMetaSchema>
>
>(
{ item },
item,
undefined, // do not override version
{ validate: false } // validation is done above
);

if (resultError) {
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
}
Expand Down Expand Up @@ -335,7 +355,6 @@ export class DashboardStorage {
if (optionsError) {
throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
}

const {
attributes: soAttributes,
references: soReferences,
Expand Down Expand Up @@ -380,15 +399,14 @@ export class DashboardStorage {
DashboardUpdateOut,
DashboardUpdateOut
>(
{ item },
item,
undefined, // do not override version
{ validate: false } // validation is done above
);

if (resultError) {
throw Boom.badRequest(`Invalid response. ${resultError.message}`);
}

return value;
}

Expand Down
Loading