Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9b0da85
parent 6f8bb908bc1eac2679f8bb0aa063b881de4f86c2
dominiqueclarke Jun 10, 2025
10fd7c5
adjust yarn.lock
dominiqueclarke Aug 8, 2025
4853346
adjust test
dominiqueclarke Aug 8, 2025
64d92f8
adjust bundles
dominiqueclarke Aug 8, 2025
a27687a
remove unused hook
dominiqueclarke Aug 8, 2025
b64d218
remove unused types
dominiqueclarke Aug 8, 2025
6ace322
fixed bug to prevent error when reopening modal
dominiqueclarke Aug 9, 2025
ebeb653
Merge branch 'main' into feat/observability-page-attachment-modal
dominiqueclarke Aug 11, 2025
9ff8f03
[CI] Auto-commit changed files from 'ts-node .buildkite/pipeline-reso…
kibanamachine Aug 11, 2025
bc84ba6
adjust test
dominiqueclarke Aug 11, 2025
6a4c663
Merge branch 'feat/observability-page-attachment-modal' of github.com…
dominiqueclarke Aug 11, 2025
89b4e54
[CI] Auto-commit changed files from 'security: 3rd-party dependencies'
kibanamachine Aug 11, 2025
26fca0b
update tests
dominiqueclarke Aug 12, 2025
2dcd67c
Merge branch 'feat/observability-page-attachment-modal' of github.com…
dominiqueclarke Aug 12, 2025
201e83d
Update x-pack/solutions/observability/plugins/observability_shared/pu…
dominiqueclarke Aug 13, 2025
ca168f2
Update x-pack/solutions/observability/plugins/observability_shared/pu…
dominiqueclarke Aug 13, 2025
3d0f490
Update x-pack/solutions/observability/plugins/observability_shared/pu…
dominiqueclarke Aug 13, 2025
4761367
[CI] Auto-commit changed files from 'node scripts/eslint_all_files --…
kibanamachine Aug 13, 2025
2bcb725
Update x-pack/solutions/observability/plugins/observability_shared/pu…
dominiqueclarke Aug 13, 2025
570e140
add dynamic import
dominiqueclarke Aug 18, 2025
acfd082
return empty page state if monitor.name or redirectUrl is undefined
dominiqueclarke Aug 18, 2025
0253d48
adjust converting to absolute time
dominiqueclarke Aug 18, 2025
925f3b2
[CI] Auto-commit changed files from 'security: 3rd-party dependencies'
kibanamachine Aug 18, 2025
dcf387c
Merge branch 'main' into feat/observability-page-attachment-modal
dominiqueclarke Aug 18, 2025
aaf721a
adjust conflict
dominiqueclarke Aug 18, 2025
93ddd65
Merge branch 'feat/observability-page-attachment-modal' of github.com…
dominiqueclarke Aug 18, 2025
b9a9292
[CI] Auto-commit changed files from 'node scripts/notice'
kibanamachine Aug 18, 2025
7ad8159
Merge branch 'feat/observability-page-attachment-modal' of github.com…
dominiqueclarke Aug 18, 2025
3b27554
[CI] Auto-commit changed files from 'node scripts/eslint_all_files --…
kibanamachine Aug 18, 2025
e1c3a97
adjust types
dominiqueclarke Aug 18, 2025
810a135
adjust onClose
dominiqueclarke Aug 18, 2025
15217db
Merge branch 'feat/observability-page-attachment-modal' of github.com…
dominiqueclarke Aug 18, 2025
9018ff7
adjust test
dominiqueclarke Aug 18, 2025
4b9a7ee
Update x-pack/solutions/observability/plugins/synthetics/kibana.jsonc
dominiqueclarke Aug 18, 2025
3b85c8b
Update x-pack/solutions/observability/plugins/observability_shared/ki…
dominiqueclarke Aug 18, 2025
19f7914
adjust test
dominiqueclarke Aug 19, 2025
5ae4e32
Merge branch 'feat/observability-page-attachment-modal' of github.com…
dominiqueclarke Aug 19, 2025
8694eec
adjust yarn.lock
dominiqueclarke Aug 19, 2025
aadf817
merge upstream
dominiqueclarke Aug 19, 2025
bf14dbc
adjust yarn.lock
dominiqueclarke Aug 19, 2025
df00725
remove notifications
dominiqueclarke Aug 19, 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 @@ -35,6 +35,10 @@ export const pageAttachmentPersistedStateSchema = z.object({
* can be provided to an LLM to generate a summary or perform analysis
*/
screenContext: z.array(z.object({ screenDescription: z.string() })).optional(),
/**
* Optional summary of the page.
*/
summary: z.string().optional(),
});

export type PageAttachmentPersistedState = z.infer<typeof pageAttachmentPersistedStateSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function PageAttachmentChildren({
notifications: { toasts },
},
} = useKibana();
const { url } = pageState;
const { url, summary } = pageState;
const label = url?.label;

const href = useMemo(() => {
Expand Down Expand Up @@ -117,6 +117,7 @@ export function PageAttachmentChildren({
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
{summary && <EuiText size="s">{summary}</EuiText>}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
* 2.0.
*/

import type { CasesPublicStart, CasesPublicSetup } from '@kbn/cases-plugin/public';
import { CasesDeepLinkId, getCasesDeepLinks } from '@kbn/cases-plugin/public';
import {
type CasesPublicSetup,
CasesDeepLinkId,
type CasesPublicStart,
getCasesDeepLinks,
} from '@kbn/cases-plugin/public';
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { CloudStart } from '@kbn/cloud-plugin/public';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import type { PageAttachmentPersistedState } from '@kbn/page-attachment-schema';
import type { CasesPublicStart } from '@kbn/cases-plugin/public';
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { AddPageAttachmentToCaseModal } from './add_page_attachment_to_case_modal';

const mockCases: Partial<CasesPublicStart> = mockCasesContract();

describe('AddPageAttachmentToCaseModal', () => {
const notifications = notificationServiceMock.createStartContract();
const pageAttachmentState: PageAttachmentPersistedState = {
type: 'example',
url: {
pathAndQuery: 'http://example.com',
actionLabel: 'Go to Example Page',
label: 'Example Page',
iconType: 'globe',
},
};

beforeEach(() => {
jest.clearAllMocks();

mockCases.helpers = {
canUseCases: jest.fn().mockReturnValue({
read: true,
update: true,
push: true,
all: true,
create: true,
delete: true,
get: true,
connectors: true,
reopenCase: true,
settings: true,
createComment: true,
getCaseUserActions: true,
assign: true,
}),
getUICapabilities: jest.fn().mockReturnValue({}),
getRuleIdFromEvent: jest.fn().mockReturnValue({}),
groupAlertsByRule: jest.fn().mockReturnValue({}),
};
});

it('renders modal when user has permissions', () => {
mockCases.helpers!.canUseCases = jest.fn().mockImplementationOnce(() => ({
read: true,
update: true,
push: true,
}));

render(
<IntlProvider locale="en">
<AddPageAttachmentToCaseModal
pageAttachmentState={pageAttachmentState}
cases={mockCases as CasesPublicStart}
onCloseModal={jest.fn()}
notifications={notifications}
/>
</IntlProvider>
);

expect(screen.getByText('Add page to case')).toBeInTheDocument();
});

it('does not render modal when user lacks permissions', () => {
mockCases.helpers!.canUseCases = jest.fn().mockImplementationOnce(() => ({
read: true,
update: true,
push: false,
}));

render(
<AddPageAttachmentToCaseModal
pageAttachmentState={pageAttachmentState}
cases={mockCases as CasesPublicStart}
onCloseModal={jest.fn()}
notifications={notifications}
/>
);

expect(screen.queryByText('Add page to case')).not.toBeInTheDocument();
});

it('calls onCloseModal when cancel button is clicked', () => {
const onCloseModalMock = jest.fn();

render(
<IntlProvider locale="en">
<AddPageAttachmentToCaseModal
pageAttachmentState={pageAttachmentState}
cases={mockCases as CasesPublicStart}
onCloseModal={onCloseModalMock}
notifications={notifications}
/>
</IntlProvider>
);

fireEvent.click(screen.getByText('Cancel'));
expect(onCloseModalMock).toHaveBeenCalled();
});

it('opens case modal when confirm button is clicked', () => {
const mockCasesModal = {
open: jest.fn(),
close: jest.fn(),
};
mockCases.hooks!.useCasesAddToExistingCaseModal = jest.fn(() => mockCasesModal);
render(
<IntlProvider locale="en">
<AddPageAttachmentToCaseModal
pageAttachmentState={pageAttachmentState}
cases={mockCases as CasesPublicStart}
onCloseModal={jest.fn()}
notifications={notifications}
/>
</IntlProvider>
);

fireEvent.click(screen.getByText('Confirm'));
expect(mockCasesModal.open).toHaveBeenCalled();
});

it('passes correct getAttachments payload when case modal is opened', () => {
const mockCasesModal = {
open: jest.fn(),
close: jest.fn(),
};
mockCases.hooks!.useCasesAddToExistingCaseModal = jest.fn(() => mockCasesModal);
const comment = 'Test comment';

render(
<IntlProvider locale="en">
<AddPageAttachmentToCaseModal
pageAttachmentState={pageAttachmentState}
cases={mockCases as CasesPublicStart}
onCloseModal={jest.fn()}
notifications={notifications}
/>
</IntlProvider>
);

fireEvent.change(screen.getByRole('textbox'), { target: { value: comment } });
fireEvent.click(screen.getByText('Confirm'));

expect(mockCasesModal!.open).toHaveBeenCalledWith({
getAttachments: expect.any(Function),
});

const attachments = mockCasesModal!.open.mock.calls[0][0].getAttachments();
expect(attachments).toEqual([
{
persistableStateAttachmentState: {
...pageAttachmentState,
summary: comment,
},
persistableStateAttachmentTypeId: '.page',
type: 'persistableState',
},
]);
});

it('can update the summary comment', () => {
const mockCasesModal = {
open: jest.fn(),
close: jest.fn(),
};
mockCases.hooks!.useCasesAddToExistingCaseModal = jest.fn(() => mockCasesModal);
const comment = 'Test comment';
render(
<IntlProvider locale="en">
<AddPageAttachmentToCaseModal
pageAttachmentState={pageAttachmentState}
cases={mockCases as CasesPublicStart}
onCloseModal={jest.fn()}
notifications={notifications}
/>
</IntlProvider>
);
fireEvent.change(screen.getByRole('textbox'), { target: { value: comment } });
fireEvent.click(screen.getByText('Confirm'));
expect(mockCasesModal!.open).toHaveBeenCalledWith({
getAttachments: expect.any(Function),
});
const attachments = mockCasesModal!.open.mock.calls[0][0].getAttachments();
expect(attachments).toEqual([
{
persistableStateAttachmentState: {
...pageAttachmentState,
summary: comment,
},
persistableStateAttachmentTypeId: '.page',
type: 'persistableState',
},
]);
});

it('should trigger a warning toast if hasCasesPermissions is false', () => {
const addWarningMock = jest.spyOn(notifications.toasts, 'addWarning');
mockCases.helpers!.canUseCases = jest.fn().mockImplementationOnce(() => ({
read: true,
update: true,
push: false,
}));

render(
<AddPageAttachmentToCaseModal
pageAttachmentState={{} as any}
cases={mockCases as any}
notifications={notifications}
onCloseModal={jest.fn()}
/>
);

expect(addWarningMock).toHaveBeenCalledWith({
title: expect.stringContaining('Insufficient privileges to add page to case'),
});
});
});
Loading