[Cases] Handle lens actions in Serverless (#163581)

## Summary

fixes https://github.com/elastic/kibana/issues/160126

This PR 
- hides owner selection while adding lens to case from dashboard for
observability and securitySolution serverless
- hides add to new case and add to existing case lens action for
Elasticsearch serverless

**How to test**

- Run Observability/Securtiy solution serverless project and add lens
visualization to case from dashboard
- Run ES serverless project and check that lens do not have option to
add to case
- Classic kibana works as before (check dashboard from securitySolution
and generic dashboard as well)

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Janki Salvi 2023-08-14 13:35:56 +02:00 committed by GitHub
parent e0c3c525ed
commit ebfb8322e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 316 additions and 7 deletions

View file

@ -24,18 +24,18 @@ import { useCaseConfigureResponse } from '../configure_cases/__mock__';
import { TestProviders } from '../../common/mock';
import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors';
import { useGetTags } from '../../containers/use_get_tags';
import { useAvailableCasesOwners } from '../app/use_available_owners';
jest.mock('../../containers/use_get_tags');
jest.mock('../../containers/configure/use_get_supported_action_connectors');
jest.mock('../../containers/configure/use_configure');
jest.mock('../markdown_editor/plugins/lens/use_lens_draft_comment');
jest.mock('../app/use_available_owners', () => ({
useAvailableCasesOwners: () => ['securitySolution', 'observability'],
}));
jest.mock('../app/use_available_owners');
const useGetTagsMock = useGetTags as jest.Mock;
const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock;
const useCaseConfigureMock = useCaseConfigure as jest.Mock;
const useAvailableOwnersMock = useAvailableCasesOwners as jest.Mock;
const initialCaseValue: FormProps = {
description: '',
@ -77,6 +77,7 @@ describe('CreateCaseForm', () => {
beforeEach(() => {
jest.clearAllMocks();
useAvailableOwnersMock.mockReturnValue(['securitySolution', 'observability']);
useGetTagsMock.mockReturnValue({ data: ['test'] });
useGetConnectorsMock.mockReturnValue({ isLoading: false, data: connectorsMock });
useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
@ -138,6 +139,18 @@ describe('CreateCaseForm', () => {
expect(wrapper.find(`[data-test-subj="caseOwnerSelector"]`).exists()).toBeTruthy();
});
it('does not render solution picker when only one owner is available', async () => {
useAvailableOwnersMock.mockReturnValue(['securitySolution']);
const wrapper = mount(
<MockHookWrapperComponent>
<CreateCaseForm {...casesFormProps} />
</MockHookWrapperComponent>
);
expect(wrapper.find(`[data-test-subj="caseOwnerSelector"]`).exists()).toBeFalsy();
});
it('hides the sync alerts toggle', () => {
const { queryByText } = render(
<MockHookWrapperComponent testProviderProps={{ features: { alerts: { sync: false } } }}>

View file

@ -86,9 +86,8 @@ export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.m
({ connectors, isLoadingConnectors, withSteps, owner, draftStorageKey }) => {
const { isSubmitting } = useFormContext();
const { isSyncAlertsEnabled, caseAssignmentAuthorized } = useCasesFeatures();
const availableOwners = useAvailableCasesOwners();
const canShowCaseSolutionSelection = !owner.length && availableOwners.length;
const canShowCaseSolutionSelection = !owner.length && availableOwners.length > 1;
const firstStep = useMemo(
() => ({

View file

@ -26,6 +26,7 @@ import {
getConnectorsFormDeserializer,
getConnectorsFormSerializer,
} from '../utils';
import { useAvailableCasesOwners } from '../app/use_available_owners';
import type { CaseAttachmentsWithoutOwner } from '../../types';
import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors';
import { useCreateCaseWithAttachmentsTransaction } from '../../common/apm/use_cases_transactions';
@ -68,6 +69,7 @@ export const FormContext: React.FC<Props> = ({
const { mutateAsync: createAttachments } = useCreateAttachments();
const { mutateAsync: pushCaseToExternalService } = usePostPushToService();
const { startTransaction } = useCreateCaseWithAttachmentsTransaction();
const availableOwners = useAvailableCasesOwners();
const submitCase = useCallback(
async (
@ -82,6 +84,7 @@ export const FormContext: React.FC<Props> = ({
if (isValid) {
const { selectedOwner, ...userFormData } = dataWithoutConnectorId;
const caseConnector = getConnectorById(dataConnectorId, connectors);
const defaultOwner = owner[0] ?? availableOwners[0];
startTransaction({ appId, attachments });
@ -94,7 +97,7 @@ export const FormContext: React.FC<Props> = ({
...userFormData,
connector: connectorToUpdate,
settings: { syncAlerts },
owner: selectedOwner ?? owner[0],
owner: selectedOwner ?? defaultOwner,
},
});
@ -131,6 +134,7 @@ export const FormContext: React.FC<Props> = ({
attachments,
postCase,
owner,
availableOwners,
afterCaseCreated,
onSuccess,
createAttachments,

View file

@ -27,6 +27,7 @@ export const createCase = async (
const { body: theCase } = await apiCall
.set('kbn-xsrf', 'true')
.set('x-elastic-internal-origin', 'foo')
.set(headers)
.send(params)
.expect(expectedHttpCode);

View file

@ -26,7 +26,6 @@ export function createTestConfig(options: CreateTestConfigOptions) {
...svlSharedConfig.get('kbnTestServer.serverArgs'),
`--serverless=${options.serverlessProject}`,
`--xpack.alerting.enableFrameworkAlerts=true`,
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
'--xpack.observability.unsafe.thresholdRule.enabled=true',
'--server.publicBaseUrl=https://localhost:5601',
],

View file

@ -0,0 +1,117 @@
/*
* 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 { expect } from 'expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ getPageObject, getService }: FtrProviderContext) => {
const dashboard = getPageObject('dashboard');
const lens = getPageObject('lens');
const svlCommonNavigation = getPageObject('svlCommonNavigation');
const svlObltNavigation = getService('svlObltNavigation');
const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const cases = getService('cases');
const find = getService('find');
describe('persistable attachment', () => {
describe('lens visualization', () => {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json'
);
await svlObltNavigation.navigateToLandingPage();
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'dashboards' });
await dashboard.clickNewDashboard();
await lens.createAndAddLensFromDashboard({});
await dashboard.waitForRenderComplete();
});
after(async () => {
await cases.api.deleteAllCases();
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.unload(
'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json'
);
});
it('adds lens visualization to a new case', async () => {
const caseTitle = 'case created in observability from my dashboard with lens visualization';
await testSubjects.click('embeddablePanelToggleMenuIcon');
await testSubjects.click('embeddablePanelMore-mainMenu');
await testSubjects.click('embeddablePanelAction-embeddable_addToNewCase');
await testSubjects.existOrFail('create-case-flyout');
await testSubjects.setValue('input', caseTitle);
await testSubjects.setValue('euiMarkdownEditorTextArea', 'test description');
// verify that solution picker is not visible
await testSubjects.missingOrFail('caseOwnerSelector');
await testSubjects.click('create-case-submit');
await cases.common.expectToasterToContain(`${caseTitle} has been updated`);
await testSubjects.click('toaster-content-case-view-link');
if (await testSubjects.exists('appLeaveConfirmModal')) {
await testSubjects.exists('confirmModalConfirmButton');
await testSubjects.click('confirmModalConfirmButton');
}
const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]');
expect(await title.getVisibleText()).toEqual(caseTitle);
await testSubjects.existOrFail('comment-persistableState-.lens');
});
it('adds lens visualization to an existing case from dashboard', async () => {
const theCaseTitle = 'case already exists in observability!!';
const theCase = await cases.api.createCase({
title: theCaseTitle,
description: 'This is a test case to verify existing action scenario!!',
owner: 'observability',
});
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'dashboards' });
await testSubjects.click('embeddablePanelToggleMenuIcon');
await testSubjects.click('embeddablePanelMore-mainMenu');
await testSubjects.click('embeddablePanelAction-embeddable_addToExistingCase');
// verify that solution filter is not visible
await testSubjects.missingOrFail('solution-filter-popover-button');
await testSubjects.click(`cases-table-row-select-${theCase.id}`);
await cases.common.expectToasterToContain(`${theCaseTitle} has been updated`);
await testSubjects.click('toaster-content-case-view-link');
if (await testSubjects.exists('appLeaveConfirmModal')) {
await testSubjects.exists('confirmModalConfirmButton');
await testSubjects.click('confirmModalConfirmButton');
}
const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]');
expect(await title.getVisibleText()).toEqual(theCaseTitle);
await testSubjects.existOrFail('comment-persistableState-.lens');
});
});
});
};

View file

@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./landing_page'));
loadTestFile(require.resolve('./navigation'));
loadDiscoverLogExplorerSuite(loadTestFile);
loadTestFile(require.resolve('./cases/attachment_framework'));
});
}

View file

@ -0,0 +1,51 @@
/*
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ getPageObject, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const dashboard = getPageObject('dashboard');
const lens = getPageObject('lens');
const svlSearchNavigation = getService('svlSearchNavigation');
const svlCommonNavigation = getPageObject('svlCommonNavigation');
describe('persistable attachment', () => {
describe('lens visualization', () => {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json'
);
await svlSearchNavigation.navigateToLandingPage();
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'dashboards' });
await dashboard.clickNewDashboard();
await lens.createAndAddLensFromDashboard({});
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.unload(
'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json'
);
});
it('does not show actions to add lens visualization to case', async () => {
await testSubjects.click('embeddablePanelToggleMenuIcon');
await testSubjects.click('embeddablePanelMore-mainMenu');
await testSubjects.missingOrFail('embeddablePanelAction-embeddable_addToNewCase');
await testSubjects.missingOrFail('embeddablePanelAction-embeddable_addToExistingCase');
});
});
});
};

View file

@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless search UI', function () {
loadTestFile(require.resolve('./landing_page'));
loadTestFile(require.resolve('./navigation'));
loadTestFile(require.resolve('./cases/attachment_framework'));
});
}

View file

@ -0,0 +1,121 @@
/*
* 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 { expect } from 'expect';
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default ({ getPageObject, getService }: FtrProviderContext) => {
const dashboard = getPageObject('dashboard');
const lens = getPageObject('lens');
const svlSecNavigation = getService('svlSecNavigation');
const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const cases = getService('cases');
const find = getService('find');
describe('persistable attachment', () => {
describe('lens visualization', () => {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/dashboard/feature_controls/security/security.json'
);
await svlSecNavigation.navigateToLandingPage();
await testSubjects.click('solutionSideNavItemLink-dashboards');
await dashboard.clickNewDashboard();
await lens.createAndAddLensFromDashboard({});
await dashboard.waitForRenderComplete();
});
after(async () => {
await cases.api.deleteAllCases();
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.unload(
'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json'
);
});
it('adds lens visualization to a new case', async () => {
const caseTitle =
'case created in security solution from my dashboard with lens visualization';
await testSubjects.click('embeddablePanelToggleMenuIcon');
await testSubjects.click('embeddablePanelMore-mainMenu');
await testSubjects.click('embeddablePanelAction-embeddable_addToNewCase');
await testSubjects.existOrFail('create-case-flyout');
await testSubjects.setValue('input', caseTitle);
await testSubjects.setValue('euiMarkdownEditorTextArea', 'test description');
// verify that solution picker is not visible
await testSubjects.missingOrFail('caseOwnerSelector');
await testSubjects.click('create-case-submit');
await cases.common.expectToasterToContain(`${caseTitle} has been updated`);
await testSubjects.click('toaster-content-case-view-link');
if (await testSubjects.exists('appLeaveConfirmModal')) {
await testSubjects.exists('confirmModalConfirmButton');
await testSubjects.click('confirmModalConfirmButton');
}
const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]');
expect(await title.getVisibleText()).toEqual(caseTitle);
await testSubjects.existOrFail('comment-persistableState-.lens');
});
it('adds lens visualization to an existing case from dashboard', async () => {
const theCaseTitle = 'case already exists in security solution!!';
const theCase = await cases.api.createCase({
title: theCaseTitle,
description: 'This is a test case to verify existing action scenario!!',
owner: 'securitySolution',
});
await testSubjects.click('solutionSideNavItemLink-dashboards');
if (await testSubjects.exists('edit-unsaved-New-Dashboard')) {
await testSubjects.click('edit-unsaved-New-Dashboard');
}
await testSubjects.click('embeddablePanelToggleMenuIcon');
await testSubjects.click('embeddablePanelMore-mainMenu');
await testSubjects.click('embeddablePanelAction-embeddable_addToExistingCase');
// verify that solution filter is not visible
await testSubjects.missingOrFail('solution-filter-popover-button');
await testSubjects.click(`cases-table-row-select-${theCase.id}`);
await cases.common.expectToasterToContain(`${theCaseTitle} has been updated`);
await testSubjects.click('toaster-content-case-view-link');
if (await testSubjects.exists('appLeaveConfirmModal')) {
await testSubjects.exists('confirmModalConfirmButton');
await testSubjects.click('confirmModalConfirmButton');
}
const title = await find.byCssSelector('[data-test-subj="editable-title-header-value"]');
expect(await title.getVisibleText()).toEqual(theCaseTitle);
await testSubjects.existOrFail('comment-persistableState-.lens');
});
});
});
};

View file

@ -12,5 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./ftr/landing_page'));
loadTestFile(require.resolve('./ftr/navigation'));
loadTestFile(require.resolve('./ftr/management'));
loadTestFile(require.resolve('./ftr/cases/attachment_framework'));
});
}

View file

@ -60,6 +60,7 @@ export default async () => {
appenders: ['deprecation'],
},
])}`,
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
],
},