This commit is contained in:
parent
010101de43
commit
1a6d7308e5
|
@ -341,7 +341,7 @@
|
|||
"@babel/traverse": "^7.11.5",
|
||||
"@babel/types": "^7.11.0",
|
||||
"@cypress/snapshot": "^2.1.7",
|
||||
"@cypress/webpack-preprocessor": "^5.4.10",
|
||||
"@cypress/webpack-preprocessor": "^5.4.11",
|
||||
"@elastic/apm-rum": "^5.6.1",
|
||||
"@elastic/apm-rum-react": "^1.2.5",
|
||||
"@elastic/charts": "24.3.0",
|
||||
|
@ -604,7 +604,7 @@
|
|||
"cpy": "^8.1.1",
|
||||
"cronstrue": "^1.51.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"cypress": "^5.5.0",
|
||||
"cypress": "^6.0.1",
|
||||
"cypress-cucumber-preprocessor": "^2.5.2",
|
||||
"cypress-multi-reporters": "^1.4.0",
|
||||
"d3": "3.5.17",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:5601",
|
||||
"defaultCommandTimeout": 120000,
|
||||
"experimentalNetworkStubbing": true,
|
||||
"defaultCommandTimeout": 60000,
|
||||
"retries": {
|
||||
"runMode": 2
|
||||
},
|
||||
|
|
|
@ -20,10 +20,9 @@ const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_expo
|
|||
describe('Export rules', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('export_rule');
|
||||
cy.server();
|
||||
cy.route(
|
||||
cy.intercept(
|
||||
'POST',
|
||||
'**api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson*'
|
||||
'/api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson'
|
||||
).as('export');
|
||||
});
|
||||
|
||||
|
@ -37,9 +36,9 @@ describe('Export rules', () => {
|
|||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
exportFirstRule();
|
||||
cy.wait('@export').then((xhr) => {
|
||||
cy.wait('@export').then(({ response }) => {
|
||||
cy.readFile(EXPECTED_EXPORTED_RULE_FILE_PATH).then(($expectedExportedJson) => {
|
||||
cy.wrap(xhr.responseBody).should('eql', $expectedExportedJson);
|
||||
cy.wrap(response!.body).should('eql', $expectedExportedJson);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,21 +28,20 @@ import { CONNECTOR_CARD_DETAILS, CONNECTOR_TITLE } from '../screens/case_details
|
|||
|
||||
describe('Cases connector incident fields', () => {
|
||||
before(() => {
|
||||
cy.server();
|
||||
cy.route('GET', '**/api/cases/configure/connectors/_find', mockConnectorsResponse);
|
||||
cy.route2('POST', `**/api/actions/action/${connectorIds.jira}/_execute`, (req) => {
|
||||
cy.intercept('GET', '/api/cases/configure/connectors/_find', mockConnectorsResponse);
|
||||
cy.intercept('POST', `/api/actions/action/${connectorIds.jira}/_execute`, (req) => {
|
||||
const response =
|
||||
JSON.parse(req.body).params.subAction === 'issueTypes'
|
||||
req.body.params.subAction === 'issueTypes'
|
||||
? executeResponses.jira.issueTypes
|
||||
: executeResponses.jira.fieldsByIssueType;
|
||||
req.reply(JSON.stringify(response));
|
||||
req.reply(response);
|
||||
});
|
||||
cy.route2('POST', `**/api/actions/action/${connectorIds.resilient}/_execute`, (req) => {
|
||||
cy.intercept('POST', `/api/actions/action/${connectorIds.resilient}/_execute`, (req) => {
|
||||
const response =
|
||||
JSON.parse(req.body).params.subAction === 'incidentTypes'
|
||||
req.body.params.subAction === 'incidentTypes'
|
||||
? executeResponses.resilient.incidentTypes
|
||||
: executeResponses.resilient.severity;
|
||||
req.reply(JSON.stringify(response));
|
||||
req.reply(response);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -19,9 +19,8 @@ import { CASES_URL } from '../urls/navigation';
|
|||
|
||||
describe('Cases connectors', () => {
|
||||
before(() => {
|
||||
cy.server();
|
||||
cy.route('POST', '**/api/actions/action').as('createConnector');
|
||||
cy.route('POST', '**/api/cases/configure').as('saveConnector');
|
||||
cy.intercept('POST', '/api/actions/action').as('createConnector');
|
||||
cy.intercept('POST', '/api/cases/configure').as('saveConnector');
|
||||
});
|
||||
|
||||
it('Configures a new connector', () => {
|
||||
|
@ -30,13 +29,15 @@ describe('Cases connectors', () => {
|
|||
openAddNewConnectorOption();
|
||||
addServiceNowConnector(serviceNowConnector);
|
||||
|
||||
cy.wait('@createConnector').its('status').should('eql', 200);
|
||||
cy.get(TOASTER).should('have.text', "Created 'New connector'");
|
||||
cy.get(TOASTER).should('not.exist');
|
||||
cy.wait('@createConnector').then(({ response }) => {
|
||||
cy.wrap(response!.statusCode).should('eql', 200);
|
||||
cy.get(TOASTER).should('have.text', "Created 'New connector'");
|
||||
cy.get(TOASTER).should('not.exist');
|
||||
|
||||
selectLastConnectorCreated();
|
||||
selectLastConnectorCreated(response!.body.id);
|
||||
|
||||
cy.wait('@saveConnector', { timeout: 10000 }).its('status').should('eql', 200);
|
||||
cy.get(TOASTER).should('have.text', 'Saved external connection settings');
|
||||
cy.wait('@saveConnector', { timeout: 10000 }).its('response.statusCode').should('eql', 200);
|
||||
cy.get(TOASTER).should('have.text', 'Saved external connection settings');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
FAVORITE_TIMELINE,
|
||||
LOCKED_ICON,
|
||||
NOTES,
|
||||
NOTES_BUTTON,
|
||||
NOTES_COUNT,
|
||||
NOTES_TAB_BUTTON,
|
||||
// NOTES_COUNT,
|
||||
NOTES_TEXT_AREA,
|
||||
PIN_EVENT,
|
||||
TIMELINE_DESCRIPTION,
|
||||
|
@ -32,7 +32,6 @@ import {
|
|||
addFilter,
|
||||
addNameToTimeline,
|
||||
addNotesToTimeline,
|
||||
closeNotes,
|
||||
closeTimeline,
|
||||
createNewTimeline,
|
||||
markAsFavorite,
|
||||
|
@ -47,12 +46,9 @@ import { OVERVIEW_URL } from '../urls/navigation';
|
|||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/79389
|
||||
describe.skip('Timelines', () => {
|
||||
before(() => {
|
||||
cy.server();
|
||||
cy.route('PATCH', '**/api/timeline').as('timeline');
|
||||
});
|
||||
it('Creates a timeline', () => {
|
||||
cy.intercept('PATCH', '/api/timeline').as('timeline');
|
||||
|
||||
it('Creates a timeline', async () => {
|
||||
loginAndWaitForPage(OVERVIEW_URL);
|
||||
openTimelineUsingToggle();
|
||||
populateTimeline();
|
||||
|
@ -64,37 +60,36 @@ describe.skip('Timelines', () => {
|
|||
|
||||
addNameToTimeline(timeline.title);
|
||||
|
||||
const response = await cy.wait('@timeline').promisify();
|
||||
const timelineId = JSON.parse(response.xhr.responseText).data.persistTimeline.timeline
|
||||
.savedObjectId;
|
||||
cy.wait('@timeline').then(({ response }) => {
|
||||
const timelineId = response!.body.data.persistTimeline.timeline.savedObjectId;
|
||||
|
||||
addDescriptionToTimeline(timeline.description);
|
||||
addNotesToTimeline(timeline.notes);
|
||||
closeNotes();
|
||||
markAsFavorite();
|
||||
waitForTimelineChanges();
|
||||
createNewTimeline();
|
||||
closeTimeline();
|
||||
openTimelineFromSettings();
|
||||
addDescriptionToTimeline(timeline.description);
|
||||
addNotesToTimeline(timeline.notes);
|
||||
markAsFavorite();
|
||||
waitForTimelineChanges();
|
||||
createNewTimeline();
|
||||
closeTimeline();
|
||||
openTimelineFromSettings();
|
||||
|
||||
cy.contains(timeline.title).should('exist');
|
||||
cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
|
||||
cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_FAVORITE).first().should('exist');
|
||||
cy.contains(timeline.title).should('exist');
|
||||
cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
|
||||
cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_FAVORITE).first().should('exist');
|
||||
|
||||
openTimeline(timelineId);
|
||||
openTimeline(timelineId);
|
||||
|
||||
cy.get(FAVORITE_TIMELINE).should('exist');
|
||||
cy.get(TIMELINE_TITLE).should('have.attr', 'value', timeline.title);
|
||||
cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', timeline.description);
|
||||
cy.get(TIMELINE_QUERY).should('have.text', timeline.query);
|
||||
// Comments this assertion until we agreed what to do with the filters.
|
||||
// cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
|
||||
cy.get(NOTES_COUNT).should('have.text', '1');
|
||||
cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event');
|
||||
cy.get(NOTES_BUTTON).click();
|
||||
cy.get(NOTES_TEXT_AREA).should('have.attr', 'placeholder', 'Add a Note');
|
||||
cy.get(NOTES).should('have.text', timeline.notes);
|
||||
cy.get(FAVORITE_TIMELINE).should('exist');
|
||||
cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
|
||||
cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
|
||||
cy.get(TIMELINE_QUERY).should('have.text', timeline.query);
|
||||
// Comments this assertion until we agreed what to do with the filters.
|
||||
// cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
|
||||
// cy.get(NOTES_COUNT).should('have.text', '1');
|
||||
cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event');
|
||||
cy.get(NOTES_TAB_BUTTON).click();
|
||||
cy.get(NOTES_TEXT_AREA).should('exist');
|
||||
cy.get(NOTES).should('have.text', timeline.notes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,12 +20,11 @@ import {
|
|||
|
||||
import { loginAndWaitForPage } from '../tasks/login';
|
||||
import { openTimelineUsingToggle } from '../tasks/security_main';
|
||||
import { createNewTimeline } from '../tasks/timeline';
|
||||
import { closeTimeline, createNewTimeline } from '../tasks/timeline';
|
||||
|
||||
import { HOSTS_URL } from '../urls/navigation';
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/62060
|
||||
describe.skip('timeline data providers', () => {
|
||||
describe('timeline data providers', () => {
|
||||
before(() => {
|
||||
loginAndWaitForPage(HOSTS_URL);
|
||||
waitForAllHostsToBeLoaded();
|
||||
|
@ -33,6 +32,7 @@ describe.skip('timeline data providers', () => {
|
|||
|
||||
afterEach(() => {
|
||||
createNewTimeline();
|
||||
closeTimeline();
|
||||
});
|
||||
|
||||
it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
FAVORITE_TIMELINE,
|
||||
LOCKED_ICON,
|
||||
NOTES,
|
||||
NOTES_BUTTON,
|
||||
NOTES_COUNT,
|
||||
NOTES_TAB_BUTTON,
|
||||
// NOTES_COUNT,
|
||||
NOTES_TEXT_AREA,
|
||||
PIN_EVENT,
|
||||
TIMELINE_DESCRIPTION,
|
||||
|
@ -31,7 +31,6 @@ import {
|
|||
addFilter,
|
||||
addNameToTimeline,
|
||||
addNotesToTimeline,
|
||||
closeNotes,
|
||||
closeTimeline,
|
||||
createNewTimelineTemplate,
|
||||
markAsFavorite,
|
||||
|
@ -43,11 +42,9 @@ import { openTimeline } from '../tasks/timelines';
|
|||
|
||||
import { OVERVIEW_URL } from '../urls/navigation';
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/79967
|
||||
describe.skip('Timeline Templates', () => {
|
||||
describe('Timeline Templates', () => {
|
||||
before(() => {
|
||||
cy.server();
|
||||
cy.route('PATCH', '**/api/timeline').as('timeline');
|
||||
cy.intercept('PATCH', '/api/timeline').as('timeline');
|
||||
});
|
||||
|
||||
it('Creates a timeline template', async () => {
|
||||
|
@ -65,36 +62,35 @@ describe.skip('Timeline Templates', () => {
|
|||
|
||||
addNameToTimeline(timeline.title);
|
||||
|
||||
const response = await cy.wait('@timeline').promisify();
|
||||
const timelineId = JSON.parse(response.xhr.responseText).data.persistTimeline.timeline
|
||||
.savedObjectId;
|
||||
cy.wait('@timeline').then(({ response }) => {
|
||||
const timelineId = response!.body.data.persistTimeline.timeline.savedObjectId;
|
||||
|
||||
addDescriptionToTimeline(timeline.description);
|
||||
addNotesToTimeline(timeline.notes);
|
||||
closeNotes();
|
||||
markAsFavorite();
|
||||
waitForTimelineChanges();
|
||||
createNewTimelineTemplate();
|
||||
closeTimeline();
|
||||
openTimelineTemplateFromSettings(timelineId);
|
||||
addDescriptionToTimeline(timeline.description);
|
||||
addNotesToTimeline(timeline.notes);
|
||||
markAsFavorite();
|
||||
waitForTimelineChanges();
|
||||
createNewTimelineTemplate();
|
||||
closeTimeline();
|
||||
openTimelineTemplateFromSettings(timelineId);
|
||||
|
||||
cy.contains(timeline.title).should('exist');
|
||||
cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
|
||||
cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_FAVORITE).first().should('exist');
|
||||
cy.contains(timeline.title).should('exist');
|
||||
cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
|
||||
cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1');
|
||||
cy.get(TIMELINES_FAVORITE).first().should('exist');
|
||||
|
||||
openTimeline(timelineId);
|
||||
openTimeline(timelineId);
|
||||
|
||||
cy.get(FAVORITE_TIMELINE).should('exist');
|
||||
cy.get(TIMELINE_TITLE).should('have.attr', 'value', timeline.title);
|
||||
cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', timeline.description);
|
||||
cy.get(TIMELINE_QUERY).should('have.text', timeline.query);
|
||||
// Comments this assertion until we agreed what to do with the filters.
|
||||
// cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
|
||||
cy.get(NOTES_COUNT).should('have.text', '1');
|
||||
cy.get(NOTES_BUTTON).click();
|
||||
cy.get(NOTES_TEXT_AREA).should('have.attr', 'placeholder', 'Add a Note');
|
||||
cy.get(NOTES).should('have.text', timeline.notes);
|
||||
cy.get(FAVORITE_TIMELINE).should('exist');
|
||||
cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
|
||||
cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
|
||||
cy.get(TIMELINE_QUERY).should('have.text', timeline.query);
|
||||
// Comments this assertion until we agreed what to do with the filters.
|
||||
// cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
|
||||
// cy.get(NOTES_COUNT).should('have.text', '1');
|
||||
cy.get(NOTES_TAB_BUTTON).click();
|
||||
cy.get(NOTES_TEXT_AREA).should('exist');
|
||||
cy.get(NOTES).should('have.text', timeline.notes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { exportTimeline } from '../tasks/timelines';
|
||||
import { esArchiverLoad } from '../tasks/es_archiver';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
|
||||
import { timeline as timelineTemplate } from '../objects/timeline';
|
||||
|
||||
|
@ -15,31 +15,34 @@ import { addNameToTimeline, closeTimeline, createNewTimelineTemplate } from '../
|
|||
describe('Export timelines', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('timeline');
|
||||
cy.server();
|
||||
cy.route('PATCH', '**/api/timeline').as('timeline');
|
||||
cy.route('POST', '**api/timeline/_export?file_name=timelines_export.ndjson*').as('export');
|
||||
cy.intercept('PATCH', '/api/timeline').as('timeline');
|
||||
cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export');
|
||||
});
|
||||
|
||||
it('Exports a custom timeline template', async () => {
|
||||
after(() => {
|
||||
esArchiverUnload('timeline');
|
||||
});
|
||||
|
||||
it('Exports a custom timeline template', () => {
|
||||
loginAndWaitForPageWithoutDateRange(TIMELINE_TEMPLATES_URL);
|
||||
createNewTimelineTemplate();
|
||||
addNameToTimeline(timelineTemplate.title);
|
||||
closeTimeline();
|
||||
|
||||
const result = await cy.wait('@timeline').promisify();
|
||||
cy.wait('@timeline').then(({ response }) => {
|
||||
const {
|
||||
savedObjectId: timelineId,
|
||||
templateTimelineId,
|
||||
} = response!.body.data.persistTimeline.timeline;
|
||||
|
||||
const timelineId = JSON.parse(result.xhr.responseText).data.persistTimeline.timeline
|
||||
.savedObjectId;
|
||||
const templateTimelineId = JSON.parse(result.xhr.responseText).data.persistTimeline.timeline
|
||||
.templateTimelineId;
|
||||
exportTimeline(timelineId);
|
||||
|
||||
await exportTimeline(timelineId);
|
||||
|
||||
cy.wait('@export').then((response) => {
|
||||
cy.wrap(JSON.parse(response.xhr.responseText).templateTimelineId).should(
|
||||
'eql',
|
||||
templateTimelineId
|
||||
);
|
||||
cy.wait('@export').then(({ response: exportResponse }) => {
|
||||
cy.wrap(JSON.parse(exportResponse!.body as string).templateTimelineId).should(
|
||||
'eql',
|
||||
templateTimelineId
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,10 +11,12 @@ import {
|
|||
TIMESTAMP_TOGGLE_FIELD,
|
||||
} from '../screens/timeline';
|
||||
|
||||
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
|
||||
import { loginAndWaitForPage } from '../tasks/login';
|
||||
import { openTimelineUsingToggle } from '../tasks/security_main';
|
||||
import {
|
||||
checkIdToggleField,
|
||||
closeTimeline,
|
||||
createNewTimeline,
|
||||
dragAndDropIdToggleFieldToTimeline,
|
||||
expandFirstTimelineEventDetails,
|
||||
|
@ -26,9 +28,14 @@ import { HOSTS_URL } from '../urls/navigation';
|
|||
|
||||
describe('toggle column in timeline', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('timeline');
|
||||
loginAndWaitForPage(HOSTS_URL);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
esArchiverUnload('timeline');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
openTimelineUsingToggle();
|
||||
populateTimeline();
|
||||
|
@ -36,6 +43,7 @@ describe('toggle column in timeline', () => {
|
|||
|
||||
afterEach(() => {
|
||||
createNewTimeline();
|
||||
closeTimeline();
|
||||
});
|
||||
|
||||
it('displays a checked Toggle field checkbox for `@timestamp`, a default timeline column', () => {
|
||||
|
@ -44,6 +52,7 @@ describe('toggle column in timeline', () => {
|
|||
});
|
||||
|
||||
it('displays an Unchecked Toggle field checkbox for `_id`, because it is NOT a default timeline column', () => {
|
||||
expandFirstTimelineEventDetails();
|
||||
cy.get(ID_TOGGLE_FIELD).should('not.be.checked');
|
||||
});
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@ const EXPECTED_EXPORTED_TIMELINE_PATH = 'cypress/test_files/expected_timelines_e
|
|||
describe('Export timelines', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('timeline');
|
||||
cy.server();
|
||||
cy.route('POST', '**api/timeline/_export?file_name=timelines_export.ndjson*').as('export');
|
||||
cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
|
@ -32,9 +31,9 @@ describe('Export timelines', () => {
|
|||
const timelineId = parsedJson.savedObjectId;
|
||||
exportTimeline(timelineId);
|
||||
|
||||
cy.wait('@export').then((response) => {
|
||||
cy.wrap(response.status).should('eql', 200);
|
||||
cy.wrap(response.xhr.responseText).should('eql', $expectedExportedJson);
|
||||
cy.wait('@export').then(({ response }) => {
|
||||
cy.wrap(response!.statusCode).should('eql', 200);
|
||||
cy.wrap(response!.body).should('eql', $expectedExportedJson);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,12 +31,7 @@ import { openAllHosts } from '../tasks/hosts/main';
|
|||
import { waitForIpsTableToBeLoaded } from '../tasks/network/flows';
|
||||
import { clearSearchBar, kqlSearch, navigateFromHeaderTo } from '../tasks/security_header';
|
||||
import { openTimelineUsingToggle } from '../tasks/security_main';
|
||||
import {
|
||||
addNameToTimeline,
|
||||
closeTimeline,
|
||||
populateTimeline,
|
||||
waitForTimelineChanges,
|
||||
} from '../tasks/timeline';
|
||||
import { addNameToTimeline, closeTimeline, populateTimeline } from '../tasks/timeline';
|
||||
|
||||
import { HOSTS_URL } from '../urls/navigation';
|
||||
import { ABSOLUTE_DATE_RANGE } from '../urls/state';
|
||||
|
@ -225,18 +220,14 @@ describe('url state', () => {
|
|||
openTimelineUsingToggle();
|
||||
populateTimeline();
|
||||
|
||||
cy.server();
|
||||
cy.route('PATCH', '**/api/timeline').as('timeline');
|
||||
cy.intercept('PATCH', '/api/timeline').as('timeline');
|
||||
|
||||
waitForTimelineChanges();
|
||||
addNameToTimeline(timeline.title);
|
||||
waitForTimelineChanges();
|
||||
|
||||
cy.wait('@timeline').then((response) => {
|
||||
cy.wait('@timeline').then(({ response }) => {
|
||||
closeTimeline();
|
||||
cy.wrap(response.status).should('eql', 200);
|
||||
const JsonResponse = JSON.parse(response.xhr.responseText);
|
||||
const timelineId = JsonResponse.data.persistTimeline.timeline.savedObjectId;
|
||||
cy.wrap(response!.statusCode).should('eql', 200);
|
||||
const timelineId = response!.body.data.persistTimeline.timeline.savedObjectId;
|
||||
cy.visit('/app/home');
|
||||
cy.visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`);
|
||||
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('exist');
|
||||
|
|
|
@ -160,62 +160,61 @@ describe('value lists', () => {
|
|||
});
|
||||
|
||||
describe('export list types', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
cy.route('POST', '**/api/lists/items/_export?list_id=*').as('exportList');
|
||||
});
|
||||
|
||||
it('exports a "keyword" list from an uploaded file', () => {
|
||||
const listName = 'value_list.txt';
|
||||
cy.intercept('POST', `/api/lists/items/_export?list_id=${listName}`).as('exportList');
|
||||
importValueList('value_list.txt', 'keyword');
|
||||
openValueListsModal();
|
||||
exportValueList();
|
||||
cy.wait('@exportList').then((xhr) => {
|
||||
cy.wait('@exportList').then(({ response }) => {
|
||||
cy.fixture(listName).then((list: string) => {
|
||||
const [lineOne, lineTwo] = list.split('\n');
|
||||
expect(xhr.responseBody).to.contain(lineOne);
|
||||
expect(xhr.responseBody).to.contain(lineTwo);
|
||||
expect(response!.body).to.contain(lineOne);
|
||||
expect(response!.body).to.contain(lineTwo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('exports a "text" list from an uploaded file', () => {
|
||||
const listName = 'value_list.txt';
|
||||
cy.intercept('POST', `/api/lists/items/_export?list_id=${listName}`).as('exportList');
|
||||
importValueList(listName, 'text');
|
||||
openValueListsModal();
|
||||
exportValueList();
|
||||
cy.wait('@exportList').then((xhr) => {
|
||||
cy.wait('@exportList').then(({ response }) => {
|
||||
cy.fixture(listName).then((list: string) => {
|
||||
const [lineOne, lineTwo] = list.split('\n');
|
||||
expect(xhr.responseBody).to.contain(lineOne);
|
||||
expect(xhr.responseBody).to.contain(lineTwo);
|
||||
expect(response!.body).to.contain(lineOne);
|
||||
expect(response!.body).to.contain(lineTwo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('exports a "ip" list from an uploaded file', () => {
|
||||
const listName = 'ip_list.txt';
|
||||
cy.intercept('POST', `/api/lists/items/_export?list_id=${listName}`).as('exportList');
|
||||
importValueList(listName, 'ip');
|
||||
openValueListsModal();
|
||||
exportValueList();
|
||||
cy.wait('@exportList').then((xhr) => {
|
||||
cy.wait('@exportList').then(({ response }) => {
|
||||
cy.fixture(listName).then((list: string) => {
|
||||
const [lineOne, lineTwo] = list.split('\n');
|
||||
expect(xhr.responseBody).to.contain(lineOne);
|
||||
expect(xhr.responseBody).to.contain(lineTwo);
|
||||
expect(response!.body).to.contain(lineOne);
|
||||
expect(response!.body).to.contain(lineTwo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('exports a "ip_range" list from an uploaded file', () => {
|
||||
const listName = 'cidr_list.txt';
|
||||
cy.intercept('POST', `/api/lists/items/_export?list_id=${listName}`).as('exportList');
|
||||
importValueList(listName, 'ip_range', ['192.168.100.0']);
|
||||
openValueListsModal();
|
||||
exportValueList();
|
||||
cy.wait('@exportList').then((xhr) => {
|
||||
cy.wait('@exportList').then(({ response }) => {
|
||||
cy.fixture(listName).then((list: string) => {
|
||||
const [lineOne] = list.split('\n');
|
||||
expect(xhr.responseBody).to.contain(lineOne);
|
||||
expect(response!.body).to.contain(lineOne);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,8 +23,6 @@ export const CASE = (id: string) => {
|
|||
return `[data-test-subj="cases-table-row-${id}"]`;
|
||||
};
|
||||
|
||||
export const CLOSE_NOTES_BTN = '[data-test-subj="notesModal"] .euiButtonIcon';
|
||||
|
||||
export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]';
|
||||
|
||||
export const COMBO_BOX = '.euiComboBoxOption__content';
|
||||
|
@ -38,6 +36,8 @@ export const DRAGGABLE_HEADER =
|
|||
|
||||
export const FAVORITE_TIMELINE = '[data-test-subj="timeline-favorite-filled-star"]';
|
||||
|
||||
export const GRAPH_TAB_BUTTON = '[data-test-subj="timelineTabs-graph"]';
|
||||
|
||||
export const HEADER = '[data-test-subj="header"]';
|
||||
|
||||
export const HEADERS_GROUP = '[data-test-subj="headers-group"]';
|
||||
|
@ -50,11 +50,11 @@ export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]';
|
|||
|
||||
export const LOCKED_ICON = '[data-test-subj="timeline-date-picker-lock-button"]';
|
||||
|
||||
export const NOTES = '[data-test-subj="markdown-root"]';
|
||||
export const NOTES = '[data-test-subj="note-card-body"]';
|
||||
|
||||
export const NOTES_TEXT_AREA = '[data-test-subj="add-a-note"]';
|
||||
export const NOTES_TEXT_AREA = '[data-test-subj="add-a-note"] textarea';
|
||||
|
||||
export const NOTES_BUTTON = '[data-test-subj="timeline-notes-button-large"]';
|
||||
export const NOTES_TAB_BUTTON = '[data-test-subj="timelineTabs-notes"]';
|
||||
|
||||
export const NOTES_COUNT = '[data-test-subj="timeline-notes-count"]';
|
||||
|
||||
|
@ -65,6 +65,8 @@ export const OPEN_TIMELINE_TEMPLATE_ICON =
|
|||
|
||||
export const PIN_EVENT = '[data-test-subj="pin"]';
|
||||
|
||||
export const PINNED_TAB_BUTTON = '[data-test-subj="timelineTabs-pinned"]';
|
||||
|
||||
export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]';
|
||||
|
||||
export const REMOVE_COLUMN = '[data-test-subj="remove-column"]';
|
||||
|
@ -92,7 +94,7 @@ export const TIMELINE_DATA_PROVIDERS_EMPTY =
|
|||
|
||||
export const TIMELINE_DESCRIPTION = '[data-test-subj="timeline-description"]';
|
||||
|
||||
export const TIMELINE_DESCRIPTION_INPUT = '[data-test-subj="timeline-description-input"]';
|
||||
export const TIMELINE_DESCRIPTION_INPUT = '[data-test-subj="timeline-description-textarea"]';
|
||||
|
||||
export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContainer"]';
|
||||
|
||||
|
@ -137,3 +139,5 @@ export const TOGGLE_TIMELINE_EXPAND_EVENT = '[data-test-subj="expand-event"]';
|
|||
export const TIMELINE_EDIT_MODAL_OPEN_BUTTON = '[data-test-subj="save-timeline-button-icon"]';
|
||||
|
||||
export const TIMELINE_EDIT_MODAL_SAVE_BUTTON = '[data-test-subj="save-button"]';
|
||||
|
||||
export const QUERY_TAB_BUTTON = '[data-test-subj="timelineTabs-query"]';
|
||||
|
|
|
@ -48,11 +48,9 @@ const getFindRequestConfig = (searchStrategyName, factoryQueryType) => {
|
|||
Cypress.Commands.add(
|
||||
'stubSearchStrategyApi',
|
||||
function (stubObject, factoryQueryType, searchStrategyName = 'securitySolutionSearchStrategy') {
|
||||
cy.route2('POST', '/internal/bsearch', (req) => {
|
||||
const bodyObj = JSON.parse(req.body);
|
||||
cy.intercept('POST', '/internal/bsearch', (req) => {
|
||||
const findRequestConfig = getFindRequestConfig(searchStrategyName, factoryQueryType);
|
||||
|
||||
const requestIndex = findIndex(findRequestConfig, bodyObj.batch);
|
||||
const requestIndex = findIndex(findRequestConfig, req.body.batch);
|
||||
|
||||
if (requestIndex > -1) {
|
||||
return req.reply((res) => {
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
import 'cypress-promise/register';
|
||||
|
||||
Cypress.Cookies.defaults({
|
||||
preserve: 'sid',
|
||||
|
@ -33,10 +32,5 @@ Cypress.on('uncaught:exception', (err) => {
|
|||
}
|
||||
});
|
||||
|
||||
Cypress.on('window:before:load', (win) => {
|
||||
win.fetch = null;
|
||||
win.Blob = null;
|
||||
});
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
|
|
@ -126,7 +126,7 @@ export const waitForRulesToBeLoaded = () => {
|
|||
};
|
||||
|
||||
export const checkAutoRefresh = (ms: number, condition: string) => {
|
||||
cy.get(ASYNC_LOADING_PROGRESS).should('not.be.visible');
|
||||
cy.get(ASYNC_LOADING_PROGRESS).should('not.exist');
|
||||
cy.tick(ms);
|
||||
cy.get(ASYNC_LOADING_PROGRESS).should(condition);
|
||||
};
|
||||
|
@ -136,7 +136,7 @@ export const dismissAllRulesIdleModal = () => {
|
|||
.eq(1)
|
||||
.should('exist')
|
||||
.click({ force: true, multiple: true });
|
||||
cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.be.visible');
|
||||
cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.exist');
|
||||
};
|
||||
|
||||
export const checkAllRulesIdleModal = (condition: string) => {
|
||||
|
|
|
@ -38,11 +38,7 @@ export const openAddNewConnectorOption = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const selectLastConnectorCreated = () => {
|
||||
export const selectLastConnectorCreated = (id: string) => {
|
||||
cy.get(CONNECTORS_DROPDOWN).click({ force: true });
|
||||
cy.get('@createConnector')
|
||||
.its('response')
|
||||
.then((response) => {
|
||||
cy.get(CONNECTOR(response.body.id)).click();
|
||||
});
|
||||
cy.get(CONNECTOR(id)).click();
|
||||
};
|
||||
|
|
|
@ -24,12 +24,11 @@ import {
|
|||
} from '../screens/rule_details';
|
||||
|
||||
export const activatesRule = () => {
|
||||
cy.server();
|
||||
cy.route('PATCH', '**/api/detection_engine/rules/_bulk_update').as('bulk_update');
|
||||
cy.intercept('PATCH', '/api/detection_engine/rules/_bulk_update').as('bulk_update');
|
||||
cy.get(RULE_SWITCH).should('be.visible');
|
||||
cy.get(RULE_SWITCH).click();
|
||||
cy.wait('@bulk_update').then((response) => {
|
||||
cy.wrap(response.status).should('eql', 200);
|
||||
cy.wait('@bulk_update').then(({ response }) => {
|
||||
cy.wrap(response!.statusCode).should('eql', 200);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -50,7 +49,7 @@ export const addsException = (exception: Exception) => {
|
|||
cy.get(CLOSE_ALERTS_CHECKBOX).click({ force: true });
|
||||
cy.get(CONFIRM_BTN).click();
|
||||
cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
|
||||
cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled');
|
||||
cy.get(CONFIRM_BTN).should('not.exist');
|
||||
};
|
||||
|
||||
export const addsExceptionFromRuleSettings = (exception: Exception) => {
|
||||
|
@ -68,7 +67,7 @@ export const addsExceptionFromRuleSettings = (exception: Exception) => {
|
|||
cy.get(CLOSE_ALERTS_CHECKBOX).click({ force: true });
|
||||
cy.get(CONFIRM_BTN).click();
|
||||
cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
|
||||
cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled');
|
||||
cy.get(CONFIRM_BTN).should('not.exist');
|
||||
};
|
||||
|
||||
export const goToAlertsTab = () => {
|
||||
|
|
|
@ -16,14 +16,13 @@ import {
|
|||
ATTACH_TIMELINE_TO_NEW_CASE_ICON,
|
||||
CASE,
|
||||
CLOSE_TIMELINE_BTN,
|
||||
CLOSE_NOTES_BTN,
|
||||
COMBO_BOX,
|
||||
CREATE_NEW_TIMELINE,
|
||||
HEADER,
|
||||
ID_FIELD,
|
||||
ID_HEADER_FIELD,
|
||||
ID_TOGGLE_FIELD,
|
||||
NOTES_BUTTON,
|
||||
NOTES_TAB_BUTTON,
|
||||
NOTES_TEXT_AREA,
|
||||
OPEN_TIMELINE_ICON,
|
||||
PIN_EVENT,
|
||||
|
@ -34,7 +33,7 @@ import {
|
|||
SERVER_SIDE_EVENT_COUNT,
|
||||
STAR_ICON,
|
||||
TIMELINE_CHANGES_IN_PROGRESS,
|
||||
TIMELINE_DESCRIPTION,
|
||||
TIMELINE_DESCRIPTION_INPUT,
|
||||
TIMELINE_FIELDS_BUTTON,
|
||||
TIMELINE_FILTER_FIELD,
|
||||
TIMELINE_FILTER_OPERATOR,
|
||||
|
@ -49,6 +48,7 @@ import {
|
|||
OPEN_TIMELINE_TEMPLATE_ICON,
|
||||
TIMELINE_EDIT_MODAL_OPEN_BUTTON,
|
||||
TIMELINE_EDIT_MODAL_SAVE_BUTTON,
|
||||
QUERY_TAB_BUTTON,
|
||||
} from '../screens/timeline';
|
||||
import { TIMELINES_TABLE } from '../screens/timelines';
|
||||
|
||||
|
@ -57,8 +57,11 @@ import { drag, drop } from '../tasks/common';
|
|||
export const hostExistsQuery = 'host.name: *';
|
||||
|
||||
export const addDescriptionToTimeline = (description: string) => {
|
||||
cy.get(TIMELINE_DESCRIPTION).type(`${description}{enter}`);
|
||||
cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', description);
|
||||
cy.get(TIMELINE_EDIT_MODAL_OPEN_BUTTON).first().click();
|
||||
cy.get(TIMELINE_DESCRIPTION_INPUT).type(`${description}{enter}`);
|
||||
cy.get(TIMELINE_DESCRIPTION_INPUT).invoke('val').should('equal', description);
|
||||
cy.get(TIMELINE_EDIT_MODAL_SAVE_BUTTON).click();
|
||||
cy.get(TIMELINE_TITLE_INPUT).should('not.exist');
|
||||
};
|
||||
|
||||
export const addNameToTimeline = (name: string) => {
|
||||
|
@ -66,12 +69,14 @@ export const addNameToTimeline = (name: string) => {
|
|||
cy.get(TIMELINE_TITLE_INPUT).type(`${name}{enter}`);
|
||||
cy.get(TIMELINE_TITLE_INPUT).should('have.attr', 'value', name);
|
||||
cy.get(TIMELINE_EDIT_MODAL_SAVE_BUTTON).click();
|
||||
cy.get(TIMELINE_TITLE_INPUT).should('not.exist');
|
||||
};
|
||||
|
||||
export const addNotesToTimeline = (notes: string) => {
|
||||
cy.get(NOTES_BUTTON).click();
|
||||
cy.get(NOTES_TAB_BUTTON).click();
|
||||
cy.get(NOTES_TEXT_AREA).type(notes);
|
||||
cy.get(ADD_NOTE_BUTTON).click();
|
||||
cy.get(QUERY_TAB_BUTTON).click();
|
||||
};
|
||||
|
||||
export const addFilter = (filter: TimelineFilter) => {
|
||||
|
@ -107,10 +112,6 @@ export const checkIdToggleField = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const closeNotes = () => {
|
||||
cy.get(CLOSE_NOTES_BTN).click();
|
||||
};
|
||||
|
||||
export const closeTimeline = () => {
|
||||
cy.get(CLOSE_TIMELINE_BTN).filter(':visible').click({ force: true });
|
||||
};
|
||||
|
@ -119,7 +120,6 @@ export const createNewTimeline = () => {
|
|||
cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click({ force: true });
|
||||
cy.get(CREATE_NEW_TIMELINE).should('be.visible');
|
||||
cy.get(CREATE_NEW_TIMELINE).click();
|
||||
cy.get(CLOSE_TIMELINE_BTN).filter(':visible').click({ force: true });
|
||||
};
|
||||
|
||||
export const createNewTimelineTemplate = () => {
|
||||
|
@ -149,7 +149,7 @@ export const openTimelineInspectButton = () => {
|
|||
};
|
||||
|
||||
export const openTimelineFromSettings = () => {
|
||||
cy.get(TIMELINE_SETTINGS_ICON).click({ force: true });
|
||||
cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click({ force: true });
|
||||
cy.get(OPEN_TIMELINE_ICON).click({ force: true });
|
||||
};
|
||||
|
||||
|
|
|
@ -136,12 +136,9 @@ const TimelineNameComponent: React.FC<FlyoutHeaderProps> = ({ timelineId }) => {
|
|||
const content = useMemo(() => (title.length ? title : placeholder), [title, placeholder]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<h3 data-test-subj="timeline-title">{content}</h3>
|
||||
</EuiText>
|
||||
<SaveTimelineButton timelineId={timelineId} />
|
||||
</>
|
||||
<EuiText>
|
||||
<h3 data-test-subj="timeline-title">{content}</h3>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -158,12 +155,9 @@ const TimelineDescriptionComponent: React.FC<FlyoutHeaderProps> = ({ timelineId
|
|||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText size="s" data-test-subj="timeline-description">
|
||||
{content}
|
||||
</EuiText>
|
||||
<SaveTimelineButton timelineId={timelineId} />
|
||||
</>
|
||||
<EuiText size="s" data-test-subj="timeline-description">
|
||||
{content}
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -209,9 +203,11 @@ const FlyoutHeaderComponent: React.FC<FlyoutHeaderProps> = ({ timelineId }) => (
|
|||
<EuiFlexGroup data-test-subj="properties-left" direction="column" gutterSize="none">
|
||||
<RowFlexItem>
|
||||
<TimelineName timelineId={timelineId} />
|
||||
<SaveTimelineButton timelineId={timelineId} />
|
||||
</RowFlexItem>
|
||||
<RowFlexItem>
|
||||
<TimelineDescription timelineId={timelineId} />
|
||||
<SaveTimelineButton timelineId={timelineId} />
|
||||
</RowFlexItem>
|
||||
<EuiFlexItem>
|
||||
<TimelineStatusInfo timelineId={timelineId} />
|
||||
|
|
|
@ -7,19 +7,13 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { TimelineTitleAndDescription } from './title_and_description';
|
||||
import { useShallowEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { useCreateTimelineButton } from '../properties/use_create_timeline';
|
||||
import { TimelineType } from '../../../../../common/types/timeline';
|
||||
import * as i18n from './translations';
|
||||
|
||||
jest.mock('../../../../common/hooks/use_selector', () => ({
|
||||
useShallowEqualSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../timelines/store/timeline', () => ({
|
||||
timelineSelectors: {
|
||||
selectTimeline: jest.fn(),
|
||||
},
|
||||
useDeepEqualSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../properties/use_create_timeline', () => ({
|
||||
|
@ -47,7 +41,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
const mockGetButton = jest.fn().mockReturnValue(<div data-test-subj="mock-discard-button" />);
|
||||
|
||||
beforeEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue({
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
description: '',
|
||||
isSaving: true,
|
||||
savedObjectId: null,
|
||||
|
@ -60,7 +54,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReset();
|
||||
(useDeepEqualSelector as jest.Mock).mockReset();
|
||||
(useCreateTimelineButton as jest.Mock).mockReset();
|
||||
mockGetButton.mockClear();
|
||||
});
|
||||
|
@ -78,7 +72,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
});
|
||||
|
||||
test('Show correct header for save timeline template modal', () => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue({
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
description: '',
|
||||
isSaving: true,
|
||||
savedObjectId: null,
|
||||
|
@ -124,7 +118,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
const mockGetButton = jest.fn().mockReturnValue(<div data-test-subj="mock-discard-button" />);
|
||||
|
||||
beforeEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue({
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
description: 'xxxx',
|
||||
isSaving: true,
|
||||
savedObjectId: '1234',
|
||||
|
@ -137,7 +131,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReset();
|
||||
(useDeepEqualSelector as jest.Mock).mockReset();
|
||||
(useCreateTimelineButton as jest.Mock).mockReset();
|
||||
mockGetButton.mockClear();
|
||||
});
|
||||
|
@ -155,7 +149,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
});
|
||||
|
||||
test('Show correct header for save timeline template modal', () => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue({
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
description: 'xxxx',
|
||||
isSaving: true,
|
||||
savedObjectId: '1234',
|
||||
|
@ -197,7 +191,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
const mockGetButton = jest.fn().mockReturnValue(<div data-test-subj="mock-discard-button" />);
|
||||
|
||||
beforeEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue({
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
description: '',
|
||||
isSaving: true,
|
||||
savedObjectId: null,
|
||||
|
@ -211,7 +205,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReset();
|
||||
(useDeepEqualSelector as jest.Mock).mockReset();
|
||||
(useCreateTimelineButton as jest.Mock).mockReset();
|
||||
mockGetButton.mockClear();
|
||||
});
|
||||
|
@ -237,7 +231,7 @@ describe('TimelineTitleAndDescription', () => {
|
|||
});
|
||||
|
||||
test('get discardTimelineTemplateButton with correct props', () => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue({
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
description: 'xxxx',
|
||||
isSaving: true,
|
||||
savedObjectId: null,
|
||||
|
|
|
@ -15,11 +15,11 @@ import {
|
|||
EuiProgress,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { TimelineType } from '../../../../../common/types/timeline';
|
||||
import { useShallowEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline';
|
||||
import { TimelineInput } from '../../../store/timeline/actions';
|
||||
import { Description, Name } from '../properties/helpers';
|
||||
|
@ -62,9 +62,10 @@ const usePrevious = (value: unknown) => {
|
|||
// the unsaved timeline / template
|
||||
export const TimelineTitleAndDescription = React.memo<TimelineTitleAndDescriptionProps>(
|
||||
({ timelineId, toggleSaveTimeline, showWarning }) => {
|
||||
const timeline = useShallowEqualSelector((state) =>
|
||||
timelineSelectors.selectTimeline(state, timelineId)
|
||||
);
|
||||
// TODO: Refactor to use useForm() instead
|
||||
const [isFormSubmitted, setFormSubmitted] = useState(false);
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const timeline = useDeepEqualSelector((state) => getTimeline(state, timelineId));
|
||||
|
||||
const { isSaving, savedObjectId, title, timelineType } = timeline;
|
||||
|
||||
|
@ -76,10 +77,12 @@ export const TimelineTitleAndDescription = React.memo<TimelineTitleAndDescriptio
|
|||
);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
// TODO: Refactor action to take only title and description as params not the whole timeline
|
||||
onSaveTimeline({
|
||||
...timeline,
|
||||
id: timelineId,
|
||||
});
|
||||
setFormSubmitted(true);
|
||||
}, [onSaveTimeline, timeline, timelineId]);
|
||||
|
||||
const { getButton } = useCreateTimelineButton({ timelineId, timelineType });
|
||||
|
@ -99,10 +102,10 @@ export const TimelineTitleAndDescription = React.memo<TimelineTitleAndDescriptio
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSaving && prevIsSaving) {
|
||||
if (isFormSubmitted && !isSaving && prevIsSaving) {
|
||||
toggleSaveTimeline();
|
||||
}
|
||||
}, [isSaving, prevIsSaving, toggleSaveTimeline]);
|
||||
}, [isFormSubmitted, isSaving, prevIsSaving, toggleSaveTimeline]);
|
||||
|
||||
const modalHeader =
|
||||
savedObjectId == null
|
||||
|
|
|
@ -17,7 +17,7 @@ import { useSourcererScope } from '../../../common/containers/sourcerer';
|
|||
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
||||
import { FlyoutHeader, FlyoutHeaderPanel } from '../flyout/header';
|
||||
import { TimelineType } from '../../../../common/types/timeline';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { activeTimeline } from '../../containers/active_timeline_context';
|
||||
import * as i18n from './translations';
|
||||
import { TabsContent } from './tabs_content';
|
||||
|
@ -40,13 +40,24 @@ export interface Props {
|
|||
timelineId: string;
|
||||
}
|
||||
|
||||
const TimelineSavingProgressComponent: React.FC<Props> = ({ timelineId }) => {
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const isSaving = useShallowEqualSelector(
|
||||
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).isSaving
|
||||
);
|
||||
|
||||
return isSaving ? <EuiProgress size="s" color="primary" position="absolute" /> : null;
|
||||
};
|
||||
|
||||
const TimelineSavingProgress = React.memo(TimelineSavingProgressComponent);
|
||||
|
||||
const StatefulTimelineComponent: React.FC<Props> = ({ timelineId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const { selectedPatterns } = useSourcererScope(SourcererScopeName.timeline);
|
||||
const { graphEventId, isSaving, savedObjectId, timelineType } = useDeepEqualSelector((state) =>
|
||||
const { graphEventId, savedObjectId, timelineType } = useDeepEqualSelector((state) =>
|
||||
pick(
|
||||
['graphEventId', 'isSaving', 'savedObjectId', 'timelineType'],
|
||||
['graphEventId', 'savedObjectId', 'timelineType'],
|
||||
getTimeline(state, timelineId) ?? timelineDefaults
|
||||
)
|
||||
);
|
||||
|
@ -68,7 +79,7 @@ const StatefulTimelineComponent: React.FC<Props> = ({ timelineId }) => {
|
|||
|
||||
return (
|
||||
<TimelineContainer data-test-subj="timeline">
|
||||
{isSaving && <EuiProgress size="s" color="primary" position="absolute" />}
|
||||
<TimelineSavingProgress timelineId={timelineId} />
|
||||
{timelineType === TimelineType.template && (
|
||||
<TimelineTemplateBadge>{i18n.TIMELINE_TEMPLATE}</TimelineTemplateBadge>
|
||||
)}
|
||||
|
|
|
@ -79,6 +79,7 @@ const AddToFavoritesButtonComponent: React.FC<AddToFavoritesButtonProps> = ({ ti
|
|||
fill={isFavorite}
|
||||
iconType={isFavorite ? 'starFilled' : 'starEmpty'}
|
||||
onClick={handleClick}
|
||||
data-test-subj={`timeline-favorite-${isFavorite ? 'filled' : 'empty'}-star`}
|
||||
>
|
||||
{isFavorite ? i18n.REMOVE_FROM_FAVORITES : i18n.ADD_TO_FAVORITES}
|
||||
</EuiButton>
|
||||
|
|
|
@ -61,41 +61,42 @@ const PinnedTab: React.FC<BasicTimelineTab> = memo(({ timelineId }) => (
|
|||
));
|
||||
PinnedTab.displayName = 'PinnedTab';
|
||||
|
||||
const ActiveTimelineTab: React.FC<BasicTimelineTab & { activeTimelineTab: TimelineTabs }> = memo(
|
||||
({ activeTimelineTab, timelineId }) => {
|
||||
const getTab = useCallback(
|
||||
(tab: TimelineTabs) => {
|
||||
switch (tab) {
|
||||
case TimelineTabs.graph:
|
||||
return <GraphTab timelineId={timelineId} />;
|
||||
case TimelineTabs.notes:
|
||||
return <NotesTab timelineId={timelineId} />;
|
||||
case TimelineTabs.pinned:
|
||||
return <PinnedTab timelineId={timelineId} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[timelineId]
|
||||
);
|
||||
type ActiveTimelineTabProps = BasicTimelineTab & { activeTimelineTab: TimelineTabs };
|
||||
|
||||
const ActiveTimelineTab = memo<ActiveTimelineTabProps>(({ activeTimelineTab, timelineId }) => {
|
||||
const getTab = useCallback(
|
||||
(tab: TimelineTabs) => {
|
||||
switch (tab) {
|
||||
case TimelineTabs.graph:
|
||||
return <GraphTab timelineId={timelineId} />;
|
||||
case TimelineTabs.notes:
|
||||
return <NotesTab timelineId={timelineId} />;
|
||||
case TimelineTabs.pinned:
|
||||
return <PinnedTab timelineId={timelineId} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[timelineId]
|
||||
);
|
||||
|
||||
/* Future developer -> why are we doing that
|
||||
* It is really expansive to re-render the QueryTab because the drag/drop
|
||||
* Therefore, we are only hiding its dom when switching to another tab
|
||||
* to avoid mounting/un-mounting === re-render
|
||||
*/
|
||||
return (
|
||||
<>
|
||||
<HideShowContainer $isVisible={TimelineTabs.query === activeTimelineTab}>
|
||||
<QueryTab timelineId={timelineId} />
|
||||
</HideShowContainer>
|
||||
<HideShowContainer $isVisible={TimelineTabs.query !== activeTimelineTab}>
|
||||
{activeTimelineTab !== TimelineTabs.query && getTab(activeTimelineTab)}
|
||||
</HideShowContainer>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
/* Future developer -> why are we doing that
|
||||
* It is really expansive to re-render the QueryTab because the drag/drop
|
||||
* Therefore, we are only hiding its dom when switching to another tab
|
||||
* to avoid mounting/un-mounting === re-render
|
||||
*/
|
||||
return (
|
||||
<>
|
||||
<HideShowContainer $isVisible={TimelineTabs.query === activeTimelineTab}>
|
||||
<QueryTab timelineId={timelineId} />
|
||||
</HideShowContainer>
|
||||
<HideShowContainer $isVisible={TimelineTabs.query !== activeTimelineTab}>
|
||||
{activeTimelineTab !== TimelineTabs.query && getTab(activeTimelineTab)}
|
||||
</HideShowContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
ActiveTimelineTab.displayName = 'ActiveTimelineTab';
|
||||
|
||||
const TabsContentComponent: React.FC<BasicTimelineTab> = ({ timelineId, graphEventId }) => {
|
||||
|
@ -145,6 +146,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ timelineId, graphEve
|
|||
<>
|
||||
<EuiTabs>
|
||||
<EuiTab
|
||||
data-test-subj={`timelineTabs-${TimelineTabs.query}`}
|
||||
onClick={setQueryAsActiveTab}
|
||||
isSelected={activeTab === TimelineTabs.query}
|
||||
disabled={false}
|
||||
|
@ -153,6 +155,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ timelineId, graphEve
|
|||
{i18n.QUERY_TAB}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
data-test-subj={`timelineTabs-${TimelineTabs.graph}`}
|
||||
onClick={setGraphAsActiveTab}
|
||||
isSelected={activeTab === TimelineTabs.graph}
|
||||
disabled={!graphEventId}
|
||||
|
@ -161,6 +164,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ timelineId, graphEve
|
|||
{i18n.GRAPH_TAB}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
data-test-subj={`timelineTabs-${TimelineTabs.notes}`}
|
||||
onClick={setNotesAsActiveTab}
|
||||
isSelected={activeTab === TimelineTabs.notes}
|
||||
disabled={false}
|
||||
|
@ -169,6 +173,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ timelineId, graphEve
|
|||
{i18n.NOTES_TAB}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
data-test-subj={`timelineTabs-${TimelineTabs.pinned}`}
|
||||
onClick={setPinnedAsActiveTab}
|
||||
isSelected={activeTab === TimelineTabs.pinned}
|
||||
disabled={true}
|
||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -1369,10 +1369,10 @@
|
|||
snap-shot-compare "2.8.3"
|
||||
snap-shot-store "1.2.3"
|
||||
|
||||
"@cypress/webpack-preprocessor@^5.4.10":
|
||||
version "5.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.10.tgz#ed0a3ed495f5455899f7c567830b37477c3f26f5"
|
||||
integrity sha512-6j809mAQcZsAbpIaQFlKwFQPKv1Y+ZyLr9bpW7d2utMGVll0W8Up4RPhIuAf4q9Tx+DOBQoiCpy/n0cRPxporw==
|
||||
"@cypress/webpack-preprocessor@^5.4.11":
|
||||
version "5.4.11"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.11.tgz#77f86e399f04969d5d8692ada96c794c52d38f87"
|
||||
integrity sha512-6kj0HsaWf1s0UT4qkABuwl676sW8S8lSTai3NUcF3BWj9BqhN4JPd2DdGcHNkNmYRkjpklPeGUHqAOyqMDj5+A==
|
||||
dependencies:
|
||||
bluebird "^3.7.1"
|
||||
debug "^4.1.1"
|
||||
|
@ -9978,6 +9978,11 @@ commander@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0"
|
||||
integrity sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ==
|
||||
|
||||
commander@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
common-tags@1.8.0, common-tags@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
|
||||
|
@ -10823,10 +10828,10 @@ cypress-promise@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25"
|
||||
integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA==
|
||||
|
||||
cypress@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.5.0.tgz#1da0355794a43247f8a80cb7f505e83e1cf847cb"
|
||||
integrity sha512-UHEiTca8AUTevbT2pWkHQlxoHtXmbq+h6Eiu/Mz8DqpNkF98zjTBLv/HFiKJUU5rQzp9EwSWtms33p5TWCJ8tQ==
|
||||
cypress@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.0.1.tgz#86857ca2f527c3723575737deab42fd8f2a209df"
|
||||
integrity sha512-3xtQZ0YM65soLgKQUgn2wg2IbWsM6A2yBg6L4RF31mZHr5LNKdO2/9sgiwxEVMKu2C2m6+IQ75zHP41kZP5rPg==
|
||||
dependencies:
|
||||
"@cypress/listr-verbose-renderer" "^0.4.1"
|
||||
"@cypress/request" "^2.88.5"
|
||||
|
@ -10840,7 +10845,7 @@ cypress@^5.5.0:
|
|||
chalk "^4.1.0"
|
||||
check-more-types "^2.24.0"
|
||||
cli-table3 "~0.6.0"
|
||||
commander "^4.1.1"
|
||||
commander "^5.1.0"
|
||||
common-tags "^1.8.0"
|
||||
debug "^4.1.1"
|
||||
eventemitter2 "^6.4.2"
|
||||
|
|
Loading…
Reference in a new issue