diff --git a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/hardware_monitoring.conf b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/hardware_monitoring.conf new file mode 100644 index 00000000000000..82e2e3f9b8f7a6 --- /dev/null +++ b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/hardware_monitoring.conf @@ -0,0 +1,124 @@ +{ + "queries": { + "acpi_tables": { + "query": "select * from acpi_tables;", + "interval": 86400, + "platform": "posix", + "version": "1.3.0", + "description": "General reporting and heuristics monitoring." + }, + "cpuid": { + "query": "select feature, value, output_register, output_bit, input_eax from cpuid;", + "interval": 86400, + "version": "1.0.4", + "description": "General reporting and heuristics monitoring." + }, + "smbios_tables": { + "query": "select * from smbios_tables;", + "interval": 86400, + "platform": "posix", + "version": "1.3.0", + "description": "General reporting and heuristics monitoring." + }, + "nvram": { + "query": "select * from nvram where name not in ('backlight-level', 'SystemAudioVolumeDB', 'SystemAudioVolume');", + "interval": 7200, + "platform": "darwin", + "version": "1.0.2", + "description": "Report on crashes, alternate boots, and boot arguments." + }, + "kernel_info": { + "query": "select * from kernel_info join hash using (path);", + "interval": 7200, + "version": "1.4.0", + "description": "Report the booted kernel, potential arguments, and the device." + }, + "pci_devices": { + "query": "select * from pci_devices;", + "interval": 7200, + "platform": "posix", + "version": "1.0.4", + "description": "Report an inventory of PCI devices. Attaches and detaches will show up in hardware_events." + }, + "fan_speeds": { + "query": "select * from fan_speed_sensors;", + "interval": 7200, + "platform": "darwin", + "version": "1.7.1", + "description": "Report current fan speeds in the target OSX system." + }, + "temperatures": { + "query": "select * from temperature_sensors;", + "interval": 7200, + "platform": "darwin", + "version": "1.7.1", + "description": "Report current machine temperatures in the target OSX system." + }, + "usb_devices": { + "query": "select * from usb_devices;", + "interval": 7200, + "platform": "posix", + "version": "1.2.0", + "description": "Report an inventory of USB devices. Attaches and detaches will show up in hardware_events." + }, + "hardware_events": { + "query" : "select * from hardware_events where path <> '' or model <> '';", + "interval" : 7200, + "platform": "posix", + "removed": false, + "version" : "1.4.5", + "description" : "Retrieves all the hardware related events in the target OSX system.", + "value" : "Determine if a third party device was attached to the system." + }, + "darwin_kernel_system_controls": { + "query": "select * from system_controls where subsystem = 'kern' and (name like '%boot%' or name like '%secure%' or name like '%single%');", + "interval": 7200, + "platform": "darwin", + "version": "1.4.3", + "description": "Double check the information reported in kernel_info and report the kernel signature." + }, + "iokit_devicetree": { + "query": "select * from iokit_devicetree;", + "interval": 86400, + "platform": "darwin", + "version": "1.3.0", + "description": "General inventory of IOKit's devices on OS X." + }, + "efi_file_hashes": { + "query": "select file.path, uid, gid, mode, 0 as atime, mtime, ctime, md5, sha1, sha256 from (select * from file where path like '/System/Library/CoreServices/%.efi' union select * from file where path like '/System/Library/LaunchDaemons/com.apple%efi%') file join hash using (path);", + "interval": 7200, + "removed": false, + "version": "1.6.1", + "platform": "darwin", + "description": "Hash files related to EFI platform updates and EFI bootloaders on primary boot partition. This does not hash bootloaders on the EFI/boot partition." + }, + "kernel_extensions": { + "query" : "select * from kernel_extensions;", + "interval" : "7200", + "platform" : "darwin", + "version" : "1.4.5", + "description" : "Retrieves all the information about the current kernel extensions for the target OSX system." + }, + "kernel_modules": { + "query" : "select * from kernel_modules;", + "interval" : "7200", + "platform" : "linux", + "version" : "1.4.5", + "description" : "Retrieves all the information for the current kernel modules in the target Linux system." + }, + "windows_drivers": { + "query" : "select * from drivers;", + "interval" : "7200", + "platform" : "windows", + "version" : "2.2.0", + "description" : "Retrieves all the information for the current windows drivers in the target Windows system." + }, + "device_nodes": { + "query": "select file.path, uid, gid, mode, 0 as atime, mtime, ctime, block_size, type from file where directory = '/dev/';", + "interval": "7200", + "platform": "posix", + "version": "1.6.0", + "description": "Inventory all 'device' nodes in /dev/." + } + } +} diff --git a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/hardware_monitoring.ndjson b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/hardware_monitoring.ndjson new file mode 100644 index 00000000000000..1d420cf9482087 --- /dev/null +++ b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/hardware_monitoring.ndjson @@ -0,0 +1 @@ +{"attributes":{"created_at":"2021-12-29T09:23:21.137Z","created_by":"elastic","enabled":true,"name":"hardware-monitoring","queries":[{"id":"acpi_tables","interval":86400,"platform":"darwin,linux","query":"select * from acpi_tables;","version":"1.3.0"},{"id":"cpuid","interval":86400,"query":"select feature, value, output_register, output_bit, input_eax from cpuid;","version":"1.0.4"},{"id":"smbios_tables","interval":86400,"platform":"darwin,linux","query":"select * from smbios_tables;","version":"1.3.0"},{"id":"nvram","interval":7200,"platform":"darwin","query":"select * from nvram where name not in ('backlight-level', 'SystemAudioVolumeDB', 'SystemAudioVolume');","version":"1.0.2"},{"id":"kernel_info","interval":7200,"query":"select * from kernel_info join hash using (path);","version":"1.4.0"},{"id":"pci_devices","interval":7200,"platform":"darwin,linux","query":"select * from pci_devices;","version":"1.0.4"},{"id":"fan_speeds","interval":7200,"platform":"darwin","query":"select * from fan_speed_sensors;","version":"1.7.1"},{"id":"temperatures","interval":7200,"platform":"darwin","query":"select * from temperature_sensors;","version":"1.7.1"},{"id":"usb_devices","interval":7200,"platform":"darwin,linux","query":"select * from usb_devices;","version":"1.2.0"},{"id":"hardware_events","interval":7200,"platform":"darwin,linux","query":"select * from hardware_events where path <> '' or model <> '';","version":"1.4.5"},{"id":"darwin_kernel_system_controls","interval":7200,"platform":"darwin","query":"select * from system_controls where subsystem = 'kern' and (name like '%boot%' or name like '%secure%' or name like '%single%');","version":"1.4.3"},{"id":"iokit_devicetree","interval":86400,"platform":"darwin","query":"select * from iokit_devicetree;","version":"1.3.0"},{"id":"efi_file_hashes","interval":7200,"platform":"darwin","query":"select file.path, uid, gid, mode, 0 as atime, mtime, ctime, md5, sha1, sha256 from (select * from file where path like '/System/Library/CoreServices/%.efi' union select * from file where path like '/System/Library/LaunchDaemons/com.apple%efi%') file join hash using (path);","version":"1.6.1"},{"id":"kernel_extensions","interval":7200,"platform":"darwin","query":"select * from kernel_extensions;","version":"1.4.5"},{"id":"kernel_modules","interval":7200,"platform":"linux","query":"select * from kernel_modules;","version":"1.4.5"},{"id":"windows_drivers","interval":7200,"platform":"windows","query":"select * from drivers;","version":"2.2.0"},{"id":"device_nodes","interval":7200,"platform":"darwin,linux","query":"select file.path, uid, gid, mode, 0 as atime, mtime, ctime, block_size, type from file where directory = '/dev/';","version":"1.6.0"}],"updated_at":"2021-12-29T09:23:21.137Z","updated_by":"elastic"},"coreMigrationVersion":"8.1.0","id":"f70e1920-6888-11ec-9276-97ce5eb54433","references":[],"type":"osquery-pack","updated_at":"2021-12-29T09:23:21.147Z","version":"WzI4NDMxLDJd"} diff --git a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/saved_query.ndjson b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/saved_query.ndjson index 2f9dd45dae6201..b29c4e28e731d6 100644 --- a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/saved_query.ndjson +++ b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/saved_query.ndjson @@ -9,12 +9,6 @@ "value": { "field": "hours" } - }, - { - "key": "message", - "value": { - "field": "seconds" - } } ], "id": "Saved-Query-Id", diff --git a/x-pack/plugins/osquery/cypress/integration/superuser/add_integration.spec.ts b/x-pack/plugins/osquery/cypress/integration/superuser/add_integration.spec.ts index 7b117b7cd5ff3b..4f9fb4304fd284 100644 --- a/x-pack/plugins/osquery/cypress/integration/superuser/add_integration.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/superuser/add_integration.spec.ts @@ -9,14 +9,61 @@ import { FLEET_AGENT_POLICIES } from '../../tasks/navigation'; import { addIntegration } from '../../tasks/integrations'; import { login } from '../../tasks/login'; +// import { findAndClickButton, findFormFieldByRowsLabelAndType } from '../../tasks/live_query'; +import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; describe('Super User - Add Integration', () => { const integration = 'Osquery Manager'; + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query'); + }); beforeEach(() => { login(); }); - it('should display Osquery integration in the Policies list once installed ', () => { + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query'); + }); + + // it('should add the old integration and be able to upgrade it', () => { + // cy.visit(OLD_OSQUERY_MANAGER); + // cy.contains(integration).click(); + // addIntegration(); + // cy.contains('osquery_manager-1'); + // cy.visit('app/fleet/policies'); + // cy.contains(/^Default Fleet Server policy$/).click(); + // cy.contains('Actions').click(); + // cy.contains('View policy').click(); + // cy.contains('name: osquery_manager-1'); + // cy.contains(`version: 0.7.4`); + // cy.contains('Close').click(); + // cy.contains(/^Osquery Manager$/).click(); + // cy.contains(/^Settings$/).click(); + // cy.contains(/^Upgrade to latest version$/).click(); + // closeModalIfVisible(); + // cy.contains('Updated Osquery Manager and upgraded policies', { timeout: 60000 }); + // cy.visit('app/fleet/policies'); + // cy.contains(/^Default Fleet Server policy$/).click(); + // cy.contains('Actions').click(); + // cy.contains('View policy').click(); + // cy.contains('name: osquery_manager-1'); + // cy.contains(`version: 0.8.1`); + // cy.visit('app/integrations/detail/osquery_manager/policies'); + // cy.contains('Loading integration policies').should('exist'); + // cy.contains('Loading integration policies').should('not.exist'); + // cy.getBySel('integrationPolicyTable') + // .get('.euiTableRow', { timeout: 60000 }) + // .should('have.lengthOf.above', 0); + // cy.get('.euiTableCellContent').get('.euiPopover__anchor').get(`[aria-label="Open"]`).click(); + // cy.contains(/^Delete integration$/).click(); + // closeModalIfVisible(); + // cy.contains(/^Settings$/).click(); + // cy.contains(/^Uninstall Osquery Manager$/).click(); + // closeModalIfVisible(); + // cy.contains(/^Successfully uninstalled Osquery Manager$/); + // }); + + it('add integration', () => { cy.visit(FLEET_AGENT_POLICIES); cy.contains('Default Fleet Server policy').click(); cy.contains('Add integration').click(); @@ -24,4 +71,53 @@ describe('Super User - Add Integration', () => { addIntegration(); cy.contains('osquery_manager-'); }); + // it('should have integration and packs copied when upgrading integration', () => { + // const packageName = 'osquery_manager'; + // const oldVersion = '0.7.4'; + // const newVersion = '0.8.1'; + // + // cy.visit(`app/integrations/detail/${packageName}-${oldVersion}/overview`); + // cy.contains('Add Osquery Manager').click(); + // cy.contains('Save and continue').click(); + // cy.contains('Add Elastic Agent later').click(); + // cy.contains('Upgrade'); + // cy.contains('Default policy').click(); + // cy.get('tr') + // .should('contain', 'osquery_manager-2') + // .and('contain', 'Osquery Manager') + // .and('contain', `v${oldVersion}`); + // cy.contains('Actions').click(); + // cy.contains('View policy').click(); + // cy.contains('name: osquery_manager-2'); + // cy.contains(`version: ${oldVersion}`); + // cy.contains('Close').click(); + // navigateTo('app/osquery/packs'); + // findAndClickButton('Add pack'); + // findFormFieldByRowsLabelAndType('Name', 'Integration'); + // findFormFieldByRowsLabelAndType('Scheduled agent policies (optional)', '{downArrow} {enter}'); + // findAndClickButton('Add query'); + // cy.react('EuiComboBox', { props: { placeholder: 'Search for saved queries' } }) + // .click() + // .type('{downArrow} {enter}'); + // cy.contains(/^Save$/).click(); + // cy.contains(/^Save pack$/).click(); + // cy.visit('app/fleet/policies'); + // cy.contains('Default policy').click(); + // cy.contains('Upgrade').click(); + // cy.contains(/^Advanced$/).click(); + // cy.contains('"Integration":'); + // cy.contains(/^Upgrade integration$/).click(); + // cy.contains(/^osquery_manager-2$/).click(); + // cy.contains(/^Advanced$/).click(); + // cy.contains('"Integration":'); + // cy.contains('Cancel').click(); + // cy.get('tr') + // .should('contain', 'osquery_manager-2') + // .and('contain', 'Osquery Manager') + // .and('contain', `v${newVersion}`); + // cy.contains('Actions').click(); + // cy.contains('View policy').click(); + // cy.contains('name: osquery_manager-2'); + // cy.contains(`version: ${newVersion}`); + // }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/superuser/delete_all_ecs_mappings.spec.ts b/x-pack/plugins/osquery/cypress/integration/superuser/delete_all_ecs_mappings.spec.ts new file mode 100644 index 00000000000000..689450d8838eee --- /dev/null +++ b/x-pack/plugins/osquery/cypress/integration/superuser/delete_all_ecs_mappings.spec.ts @@ -0,0 +1,47 @@ +/* + * 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 { navigateTo } from '../../tasks/navigation'; +import { login } from '../../tasks/login'; +import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; + +describe('SuperUser - Delete ECS Mappings', () => { + const SAVED_QUERY_ID = 'Saved-Query-Id'; + + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query'); + }); + beforeEach(() => { + login(); + navigateTo('/app/osquery/saved_queries'); + }); + + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query'); + }); + + it('to click the edit button and edit pack', () => { + cy.react('CustomItemAction', { + props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } }, + }).click(); + cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}').should( + 'exist' + ); + cy.contains('Hours of uptime').should('exist'); + cy.react('EuiButtonIcon', { props: { id: 'labels-trash' } }).click(); + cy.react('EuiButton').contains('Update query').click(); + cy.wait(1000); + + cy.react('CustomItemAction', { + props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } }, + }).click(); + cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}').should( + 'not.exist' + ); + cy.contains('Hours of uptime').should('not.exist'); + }); +}); diff --git a/x-pack/plugins/osquery/cypress/integration/superuser/live_query.spec.ts b/x-pack/plugins/osquery/cypress/integration/superuser/live_query.spec.ts index dde93b391d12b6..7006e0a0b76277 100644 --- a/x-pack/plugins/osquery/cypress/integration/superuser/live_query.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/superuser/live_query.spec.ts @@ -30,10 +30,10 @@ describe('Super User - Live Query', () => { checkResults(); cy.react('EuiDataGridHeaderCellWrapper', { - props: { id: 'osquery.days', index: 1 }, + props: { id: 'osquery.days.number', index: 1 }, }); cy.react('EuiDataGridHeaderCellWrapper', { - props: { id: 'osquery.hours', index: 2 }, + props: { id: 'osquery.hours.number', index: 2 }, }); cy.react('EuiAccordion', { props: { buttonContent: 'Advanced' } }).click(); @@ -46,7 +46,7 @@ describe('Super User - Live Query', () => { props: { id: 'message', index: 1 }, }); cy.react('EuiDataGridHeaderCellWrapper', { - props: { id: 'osquery.days', index: 2 }, + props: { id: 'osquery.days.number', index: 2 }, }).react('EuiIconIndexMapping'); }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/superuser/metrics.spec.ts b/x-pack/plugins/osquery/cypress/integration/superuser/metrics.spec.ts index e1b0eec6985931..a9524a509c0a19 100644 --- a/x-pack/plugins/osquery/cypress/integration/superuser/metrics.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/superuser/metrics.spec.ts @@ -23,12 +23,12 @@ describe('Super User - Metrics', () => { }); it('should be able to run the query', () => { - cy.get('[data-test-subj="toggleNavButton"]').click(); + cy.getBySel('toggleNavButton').click(); cy.contains('Metrics').click(); cy.wait(1000); - cy.get('[data-test-subj="nodeContainer"]').click(); + cy.getBySel('nodeContainer').click(); cy.contains('Osquery').click(); inputQuery('select * from uptime;'); @@ -36,17 +36,17 @@ describe('Super User - Metrics', () => { checkResults(); }); it('should be able to run the previously saved query', () => { - cy.get('[data-test-subj="toggleNavButton"]').click(); - cy.get('[data-test-subj="collapsibleNavAppLink"').contains('Metrics').click(); + cy.getBySel('toggleNavButton').click(); + cy.getBySel('collapsibleNavAppLink').contains('Metrics').click(); cy.wait(500); - cy.get('[data-test-subj="nodeContainer"]').click(); + cy.getBySel('nodeContainer').click(); cy.contains('Osquery').click(); - cy.get('[data-test-subj="comboBoxInput"]').first().click(); + cy.getBySel('comboBoxInput').first().click(); cy.wait(500); cy.get('div[role=listBox]').should('have.lengthOf.above', 0); - cy.get('[data-test-subj="comboBoxInput"]').first().type('{downArrow}{enter}'); + cy.getBySel('comboBoxInput').first().type('{downArrow}{enter}'); submitQuery(); checkResults(); diff --git a/x-pack/plugins/osquery/cypress/integration/superuser/packs.spec.ts b/x-pack/plugins/osquery/cypress/integration/superuser/packs.spec.ts index 02af440ba0e6a1..01d4e0a3953f4e 100644 --- a/x-pack/plugins/osquery/cypress/integration/superuser/packs.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/superuser/packs.spec.ts @@ -21,87 +21,150 @@ describe('SuperUser - Packs', () => { const PACK_NAME = 'Pack-name'; const NEW_QUERY_NAME = 'new-query-name'; - before(() => { - runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query'); - }); - beforeEach(() => { - login(); - navigateTo('/app/osquery'); - }); + describe('Create and edit a pack', () => { + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query'); + }); + beforeEach(() => { + login(); + navigateTo('/app/osquery'); + }); - after(() => { - runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query'); - }); + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query'); + }); - it('should add a pack from a saved query', () => { - cy.contains('Packs').click(); - findAndClickButton('Add pack'); - findFormFieldByRowsLabelAndType('Name', PACK_NAME); - findFormFieldByRowsLabelAndType('Description (optional)', 'Pack description'); - findFormFieldByRowsLabelAndType( - 'Scheduled agent policies (optional)', - 'Default Fleet Server policy' - ); - cy.react('List').first().click(); - findAndClickButton('Add query'); - cy.contains('Attach next query'); - cy.react('EuiComboBox', { props: { placeholder: 'Search for saved queries' } }) - .click() - .type(SAVED_QUERY_ID); - cy.react('List').first().click(); - cy.react('EuiFormRow', { props: { label: 'Interval (s)' } }) - .click() - .clear() - .type('10'); - cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); - cy.react('EuiTableRow').contains(SAVED_QUERY_ID); - findAndClickButton('Save pack'); - cy.contains('Save and deploy changes'); - findAndClickButton('Save and deploy changes'); - cy.contains(PACK_NAME); - }); + it('should add a pack from a saved query', () => { + cy.contains('Packs').click(); + findAndClickButton('Add pack'); + findFormFieldByRowsLabelAndType('Name', PACK_NAME); + findFormFieldByRowsLabelAndType('Description (optional)', 'Pack description'); + findFormFieldByRowsLabelAndType( + 'Scheduled agent policies (optional)', + 'Default Fleet Server policy' + ); + cy.react('List').first().click(); + findAndClickButton('Add query'); + cy.contains('Attach next query'); + cy.react('EuiComboBox', { props: { placeholder: 'Search for saved queries' } }) + .click() + .type(SAVED_QUERY_ID); + cy.react('List').first().click(); + cy.react('EuiFormRow', { props: { label: 'Interval (s)' } }) + .click() + .clear() + .type('10'); + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + cy.react('EuiTableRow').contains(SAVED_QUERY_ID); + findAndClickButton('Save pack'); + cy.contains('Save and deploy changes'); + findAndClickButton('Save and deploy changes'); + cy.contains(PACK_NAME); + }); + + it('to click the edit button and edit pack', () => { + preparePack(PACK_NAME, SAVED_QUERY_ID); + findAndClickButton('Edit'); + cy.contains(`Edit ${PACK_NAME}`); + findAndClickButton('Add query'); + cy.contains('Attach next query'); + inputQuery('select * from uptime'); + findFormFieldByRowsLabelAndType('ID', NEW_QUERY_NAME); + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + cy.react('EuiTableRow').contains(NEW_QUERY_NAME); + findAndClickButton('Update pack'); + cy.contains('Save and deploy changes'); + findAndClickButton('Save and deploy changes'); + }); + // THIS TESTS TAKES TOO LONG FOR NOW - LET ME THINK IT THROUGH + it('to click the icon and visit discover', () => { + preparePack(PACK_NAME, SAVED_QUERY_ID); + cy.react('CustomItemAction', { + props: { index: 0, item: { id: SAVED_QUERY_ID } }, + }).click(); + cy.getBySel('superDatePickerToggleQuickMenuButton').click(); + cy.getBySel('superDatePickerToggleRefreshButton').click(); + cy.getBySel('superDatePickerRefreshIntervalInput').clear().type('10'); + cy.get('button').contains('Apply').click(); + cy.getBySel('discoverDocTable', { timeout: 60000 }).contains( + `pack_${PACK_NAME}_${SAVED_QUERY_ID}` + ); + }); + it('by clicking in Lens button', () => { + let lensUrl = ''; + cy.window().then((win) => { + cy.stub(win, 'open') + .as('windowOpen') + .callsFake((url) => { + lensUrl = url; + }); + }); + preparePack(PACK_NAME, SAVED_QUERY_ID); + cy.react('CustomItemAction', { + props: { index: 1, item: { id: SAVED_QUERY_ID } }, + }).click(); + cy.window() + .its('open') + .then(() => { + cy.visit(lensUrl); + }); + cy.getBySel('lnsWorkspace'); + cy.getBySel('breadcrumbs').contains(`Action pack_${PACK_NAME}_${SAVED_QUERY_ID} results`); + }); - it('to click the edit button and edit pack', () => { - preparePack(PACK_NAME, SAVED_QUERY_ID); - findAndClickButton('Edit'); - cy.contains(`Edit ${PACK_NAME}`); - findAndClickButton('Add query'); - cy.contains('Attach next query'); - inputQuery('select * from uptime'); - findFormFieldByRowsLabelAndType('ID', NEW_QUERY_NAME); - cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); - cy.react('EuiTableRow').contains(NEW_QUERY_NAME); - findAndClickButton('Update pack'); - cy.contains('Save and deploy changes'); - findAndClickButton('Save and deploy changes'); + // strange behaviour with modal + it('activate and deactive pack', () => { + cy.contains('Packs').click(); + cy.react('ActiveStateSwitchComponent', { + props: { item: { attributes: { name: PACK_NAME } } }, + }).click(); + cy.contains(`Successfully deactivated "${PACK_NAME}" pack`).should('not.exist'); + cy.contains(`Successfully deactivated "${PACK_NAME}" pack`).should('exist'); + cy.react('ActiveStateSwitchComponent', { + props: { item: { attributes: { name: PACK_NAME } } }, + }).click(); + cy.getBySel('confirmModalConfirmButton').click(); + cy.contains(`Successfully activated "${PACK_NAME}" pack`).should('not.exist'); + cy.contains(`Successfully activated "${PACK_NAME}" pack`).should('exist'); + }); + + it('delete all queries in the pack', () => { + preparePack(PACK_NAME, SAVED_QUERY_ID); + cy.contains(/^Edit$/).click(); + + cy.getBySel('checkboxSelectAll').click(); + + cy.contains(/^Delete \d+ quer(y|ies)/).click(); + cy.contains(/^Update pack$/).click(); + cy.react('EuiButtonDisplay') + .contains(/^Save and deploy changes$/) + .click(); + cy.contains(`${PACK_NAME}`).click(); + cy.contains(`${PACK_NAME} details`); + cy.contains(/^No items found/); + }); + + it('to click delete button', () => { + preparePack(PACK_NAME, SAVED_QUERY_ID); + findAndClickButton('Edit'); + deleteAndConfirm('pack'); + }); }); - // THIS TESTS TAKES TOO LONG FOR NOW - LET ME THINK IT THROUGH - // it('to click the icon and visit discover', () => { - // preparePack(PACK_NAME, SAVED_QUERY_ID); - // cy.react('CustomItemAction', { - // props: { index: 0, item: { id: SAVED_QUERY_ID } }, - // }).click(); - // cy.get('[data-test-subj="superDatePickerToggleQuickMenuButton"').click(); - // cy.get('[data-test-subj="superDatePickerToggleRefreshButton"').click(); - // cy.get('[data-test-subj="superDatePickerRefreshIntervalInput"').clear().type('10'); - // cy.get('button').contains('Apply').click(); - // cy.get('[data-test-subj="discoverDocTable"]', { timeout: 60000 }).contains( - // `pack_${PACK_NAME}_${SAVED_QUERY_ID}` - // ); - // }); - // it('by clicking in Lens button', () => { - // preparePack(PACK_NAME, SAVED_QUERY_ID); - // cy.react('CustomItemAction', { - // props: { index: 1, item: { id: SAVED_QUERY_ID } }, - // }).click(); - // cy.get('[data-test-subj="lnsWorkspace"]'); - // cy.get('[data-test-subj="breadcrumbs"]').contains( - // `Action pack_${PACK_NAME}_${SAVED_QUERY_ID} results` - // ); - // }); - it('to click delete button', () => { - preparePack(PACK_NAME, SAVED_QUERY_ID); - findAndClickButton('Edit'); - deleteAndConfirm('pack'); + describe.skip('Remove queries from pack', () => { + const TEST_PACK = 'Test-pack'; + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'hardware_monitoring'); + }); + beforeEach(() => { + login(); + navigateTo('/app/osquery'); + }); + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'hardware_monitoring'); + }); + + it('should remove ALL queries', () => { + preparePack(TEST_PACK, SAVED_QUERY_ID); + }); }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/superuser/saved_queries.spec.ts b/x-pack/plugins/osquery/cypress/integration/superuser/saved_queries.spec.ts index 146083e279d6a6..bfeb5adc11f6e0 100644 --- a/x-pack/plugins/osquery/cypress/integration/superuser/saved_queries.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/superuser/saved_queries.spec.ts @@ -26,59 +26,93 @@ describe('Super User - Saved queries', () => { navigateTo('/app/osquery'); }); - it('should save the query', () => { - cy.contains('New live query').click(); - selectAllAgents(); - inputQuery(DEFAULT_QUERY); - submitQuery(); - checkResults(); - cy.contains('Save for later').click(); - cy.contains('Save query'); - findFormFieldByRowsLabelAndType('ID', SAVED_QUERY_ID); - findFormFieldByRowsLabelAndType('Description', SAVED_QUERY_DESCRIPTION); - cy.react('EuiButtonDisplay').contains('Save').click(); - }); + it( + 'should create a new query and verify: \n ' + + '- hidden columns, full screen and sorting \n' + + '- pagination \n' + + '- query can viewed (status), edited and deleted ', + () => { + cy.contains('New live query').click(); + selectAllAgents(); + inputQuery(DEFAULT_QUERY); + submitQuery(); + checkResults(); + // enter fullscreen + cy.getBySel('dataGridFullScreenButton').trigger('mouseover'); + cy.contains(/Full screen$/).should('exist'); + cy.contains('Exit full screen').should('not.exist'); + cy.getBySel('dataGridFullScreenButton').click(); - it('should view query details in status', () => { - cy.contains('New live query'); - cy.react('ActionTableResultsButton').first().click(); - cy.wait(1000); - cy.contains(DEFAULT_QUERY); - checkResults(); - cy.react('EuiTab', { props: { id: 'status' } }).click(); - cy.wait(1000); - cy.react('EuiTableRow').should('have.lengthOf', 1); - cy.contains('Successful').siblings().contains(1); - }); + cy.getBySel('dataGridFullScreenButton').trigger('mouseover'); + cy.contains(/Full screen$/).should('not.exist'); + cy.contains('Exit full screen').should('exist'); - it('should display a previously saved query and run it', () => { - cy.contains('Saved queries').click(); - cy.contains(SAVED_QUERY_ID); - cy.react('PlayButtonComponent', { - props: { savedQuery: { attributes: { id: SAVED_QUERY_ID } } }, - }).click(); - selectAllAgents(); - submitQuery(); - }); + // hidden columns + cy.react('EuiDataGridHeaderCellWrapper', { props: { id: 'osquery.cmdline' } }).click(); + cy.contains(/Hide column$/).click(); + cy.react('EuiDataGridHeaderCellWrapper', { + props: { id: 'osquery.disk_bytes_written.number' }, + }).click(); + cy.contains(/Hide column$/).click(); + cy.contains('2 columns hidden').should('exist'); + // change pagination + cy.getBySel('pagination-button-next').click().wait(500).click(); + cy.contains('2 columns hidden').should('exist'); - it('should edit the saved query', () => { - cy.contains('Saved queries').click(); - cy.contains(SAVED_QUERY_ID); - cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } }, - }).click(); - findFormFieldByRowsLabelAndType('Description', ' Edited'); - cy.react('EuiButton').contains('Update query').click(); - cy.contains(`${SAVED_QUERY_DESCRIPTION} Edited`); - }); + cy.getBySel('dataGridFullScreenButton').trigger('mouseover'); + cy.contains(/Full screen$/).should('not.exist'); + cy.contains('Exit full screen').should('exist'); + cy.getBySel('dataGridFullScreenButton').click(); - it('should delete the saved query', () => { - cy.contains('Saved queries').click(); - cy.contains(SAVED_QUERY_ID); - cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } }, - }).click(); - deleteAndConfirm('query'); - cy.contains(SAVED_QUERY_ID); - }); + // sorting + cy.react('EuiDataGridHeaderCellWrapper', { + props: { id: 'osquery.egid' }, + }).click(); + cy.contains(/Sort A-Z$/).click(); + cy.contains('2 columns hidden').should('exist'); + cy.getBySel('dataGridFullScreenButton').trigger('mouseover'); + cy.contains(/Full screen$/).should('exist'); + + // save new query + cy.contains('Exit full screen').should('not.exist'); + cy.contains('Save for later').click(); + cy.contains('Save query'); + findFormFieldByRowsLabelAndType('ID', SAVED_QUERY_ID); + findFormFieldByRowsLabelAndType('Description (optional)', SAVED_QUERY_DESCRIPTION); + cy.react('EuiButtonDisplay').contains('Save').click(); + + // visit Status results + cy.react('EuiTab', { props: { id: 'status' } }).click(); + cy.react('EuiTableRow').should('have.lengthOf', 1); + cy.contains('Successful').siblings().contains(1); + + // play saved query + cy.contains('Saved queries').click(); + cy.contains(SAVED_QUERY_ID); + cy.react('PlayButtonComponent', { + props: { savedQuery: { attributes: { id: SAVED_QUERY_ID } } }, + }).click(); + selectAllAgents(); + submitQuery(); + + // edit saved query + cy.contains('Saved queries').click(); + cy.contains(SAVED_QUERY_ID); + cy.react('CustomItemAction', { + props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } }, + }).click(); + findFormFieldByRowsLabelAndType('Description (optional)', ' Edited'); + cy.react('EuiButton').contains('Update query').click(); + cy.contains(`${SAVED_QUERY_DESCRIPTION} Edited`); + + // delete saved query + cy.contains(SAVED_QUERY_ID); + cy.react('CustomItemAction', { + props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } }, + }).click(); + deleteAndConfirm('query'); + cy.contains(SAVED_QUERY_ID); + cy.contains(/^No items found/); + } + ); }); diff --git a/x-pack/plugins/osquery/cypress/support/commands.ts b/x-pack/plugins/osquery/cypress/support/commands.ts index 66f94350355712..a0f3744f992b87 100644 --- a/x-pack/plugins/osquery/cypress/support/commands.ts +++ b/x-pack/plugins/osquery/cypress/support/commands.ts @@ -30,3 +30,7 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +Cypress.Commands.add('getBySel', (selector, ...args) => + cy.get(`[data-test-subj=${selector}]`, ...args) +); diff --git a/x-pack/plugins/osquery/cypress/tasks/integrations.ts b/x-pack/plugins/osquery/cypress/tasks/integrations.ts index e47b4c792b1e80..dd57de32628bb7 100644 --- a/x-pack/plugins/osquery/cypress/tasks/integrations.ts +++ b/x-pack/plugins/osquery/cypress/tasks/integrations.ts @@ -16,14 +16,14 @@ import { export const addIntegration = () => { cy.getBySel(ADD_POLICY_BTN).click(); cy.getBySel(DATA_COLLECTION_SETUP_STEP).find('.euiLoadingSpinner').should('not.exist'); + cy.getBySel('comboBoxInput').click().type('Default fleet {downArrow} {enter}'); cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).click(); // sometimes agent is assigned to default policy, sometimes not closeModalIfVisible(); - cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN, { timeout: 60000 }).should('not.exist'); }; -function closeModalIfVisible() { +export function closeModalIfVisible() { cy.get('body').then(($body) => { if ($body.find(CONFIRM_MODAL_BTN_SEL).length) { cy.getBySel(CONFIRM_MODAL_BTN).click(); diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index 213f949ee84ed6..4e7bfc63c35ac5 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -7,7 +7,7 @@ import { LIVE_QUERY_EDITOR } from '../screens/live_query'; -export const DEFAULT_QUERY = 'select * from processes;'; +export const DEFAULT_QUERY = 'select * from processes, users;'; export const selectAllAgents = () => { cy.react('EuiComboBox', { props: { placeholder: 'Select agents or groups' } }).type('All agents'); @@ -22,10 +22,10 @@ export const inputQuery = (query: string) => cy.get(LIVE_QUERY_EDITOR).type(quer export const submitQuery = () => cy.contains('Submit').click(); export const checkResults = () => - cy.get('[data-test-subj="dataGridRowCell"]', { timeout: 60000 }).should('have.lengthOf.above', 0); + cy.getBySel('dataGridRowCell', { timeout: 60000 }).should('have.lengthOf.above', 0); export const typeInECSFieldInput = (text: string) => - cy.get('[data-test-subj="ECS-field-input"]').click().type(text); + cy.getBySel('ECS-field-input').click().type(text); export const typeInOsqueryFieldInput = (text: string) => cy.react('OsqueryColumnFieldComponent').first().react('ResultComboBox').click().type(text); diff --git a/x-pack/plugins/osquery/cypress/tasks/navigation.ts b/x-pack/plugins/osquery/cypress/tasks/navigation.ts index f1da34cd0fbad6..7b1505eecd698a 100644 --- a/x-pack/plugins/osquery/cypress/tasks/navigation.ts +++ b/x-pack/plugins/osquery/cypress/tasks/navigation.ts @@ -11,6 +11,7 @@ export const INTEGRATIONS = 'app/integrations#/'; export const FLEET = 'app/fleet/'; export const FLEET_AGENT_POLICIES = 'app/fleet/policies'; export const OSQUERY = 'app/osquery'; +export const OLD_OSQUERY_MANAGER = 'app/integrations/detail/osquery_manager-0.7.4/settings'; export const NEW_LIVE_QUERY = 'app/osquery/live_queries/new'; export const OSQUERY_INTEGRATION_PAGE = '/app/fleet/integrations/osquery_manager/add-integration'; export const navigateTo = (page: string, opts?: Partial) => { diff --git a/x-pack/plugins/osquery/cypress/tasks/packs.ts b/x-pack/plugins/osquery/cypress/tasks/packs.ts index 8fa680a5899a2c..3218c792772baa 100644 --- a/x-pack/plugins/osquery/cypress/tasks/packs.ts +++ b/x-pack/plugins/osquery/cypress/tasks/packs.ts @@ -9,6 +9,4 @@ export const preparePack = (packName: string, savedQueryId: string) => { cy.contains('Packs').click(); const createdPack = cy.contains(packName); createdPack.click(); - cy.waitForReact(1000); - cy.react('EuiTableRow').contains(savedQueryId); }; diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx index 95b96ca4546109..7244c2417151ba 100644 --- a/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx @@ -13,7 +13,7 @@ import { getColorForAgentStatus, getLabelForAgentStatus, } from './services/agent_status'; -import type { ActionAgentStatus } from './types'; +import { ActionAgentStatus } from './types'; export const ActionAgentsStatusBadges = memo<{ agentStatus: { [k in ActionAgentStatus]: number }; diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx index 21866566cb7e3a..def52bf5112154 100644 --- a/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx @@ -10,7 +10,7 @@ import { EuiColorPaletteDisplay } from '@elastic/eui'; import React, { useMemo } from 'react'; import { AGENT_STATUSES, getColorForAgentStatus } from './services/agent_status'; -import type { ActionAgentStatus } from './types'; +import { ActionAgentStatus } from './types'; const StyledEuiColorPaletteDisplay = styled(EuiColorPaletteDisplay)` &.osquery-action-agent-status-bar { diff --git a/x-pack/plugins/osquery/public/action_results/helpers.ts b/x-pack/plugins/osquery/public/action_results/helpers.ts deleted file mode 100644 index 171530a77299f9..00000000000000 --- a/x-pack/plugins/osquery/public/action_results/helpers.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { - PaginationInputPaginated, - FactoryQueryTypes, - StrategyResponseType, - Inspect, -} from '../../common/search_strategy'; - -export type InspectResponse = Inspect & { response: string[] }; - -export const generateTablePaginationOptions = ( - activePage: number, - limit: number -): PaginationInputPaginated => { - const cursorStart = activePage * limit; - return { - activePage, - cursorStart, - fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, - querySize: limit, - }; -}; - -export const getInspectResponse = ( - response: StrategyResponseType, - prevResponse: InspectResponse -): InspectResponse => ({ - dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], - response: - response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, -}); diff --git a/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx b/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx index 1a2c9f370bc31a..0f586d10fb6a08 100644 --- a/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx +++ b/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx @@ -8,7 +8,7 @@ import { euiPaletteColorBlindBehindText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { ActionAgentStatus } from '../types'; +import { ActionAgentStatus } from '../types'; const visColors = euiPaletteColorBlindBehindText(); const colorToHexMap = { @@ -20,15 +20,19 @@ const colorToHexMap = { danger: visColors[9], }; -export const AGENT_STATUSES: ActionAgentStatus[] = ['success', 'pending', 'failed']; +export const AGENT_STATUSES: ActionAgentStatus[] = [ + ActionAgentStatus.SUCCESS, + ActionAgentStatus.PENDING, + ActionAgentStatus.FAILED, +]; export function getColorForAgentStatus(agentStatus: ActionAgentStatus): string { switch (agentStatus) { - case 'success': + case ActionAgentStatus.SUCCESS: return colorToHexMap.success; - case 'pending': + case ActionAgentStatus.PENDING: return colorToHexMap.default; - case 'failed': + case ActionAgentStatus.FAILED: return colorToHexMap.danger; default: throw new Error(`Unsupported action agent status ${agentStatus}`); @@ -37,11 +41,11 @@ export function getColorForAgentStatus(agentStatus: ActionAgentStatus): string { export function getLabelForAgentStatus(agentStatus: ActionAgentStatus, expired: boolean): string { switch (agentStatus) { - case 'success': + case ActionAgentStatus.SUCCESS: return i18n.translate('xpack.osquery.liveQueryActionResults.summary.successfulLabelText', { defaultMessage: 'Successful', }); - case 'pending': + case ActionAgentStatus.PENDING: return expired ? i18n.translate('xpack.osquery.liveQueryActionResults.summary.expiredLabelText', { defaultMessage: 'Expired', @@ -49,7 +53,7 @@ export function getLabelForAgentStatus(agentStatus: ActionAgentStatus, expired: : i18n.translate('xpack.osquery.liveQueryActionResults.summary.pendingLabelText', { defaultMessage: 'Not yet responded', }); - case 'failed': + case ActionAgentStatus.FAILED: return i18n.translate('xpack.osquery.liveQueryActionResults.summary.failedLabelText', { defaultMessage: 'Failed', }); diff --git a/x-pack/plugins/osquery/public/action_results/types.ts b/x-pack/plugins/osquery/public/action_results/types.ts index ce9415986ba022..504626445450db 100644 --- a/x-pack/plugins/osquery/public/action_results/types.ts +++ b/x-pack/plugins/osquery/public/action_results/types.ts @@ -5,4 +5,8 @@ * 2.0. */ -export type ActionAgentStatus = 'success' | 'pending' | 'failed'; +export enum ActionAgentStatus { + SUCCESS = 'success', + PENDING = 'pending', + FAILED = 'failed', +} diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts index e4b6ef14eb1e99..0d3396d7331a19 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_results.ts +++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts @@ -9,7 +9,12 @@ import { flatten, reverse, uniqBy } from 'lodash/fp'; import { useQuery } from 'react-query'; import { i18n } from '@kbn/i18n'; -import { createFilter } from '../common/helpers'; +import { + createFilter, + getInspectResponse, + InspectResponse, + generateTablePaginationOptions, +} from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { ResultEdges, @@ -22,7 +27,6 @@ import { import { ESTermQuery } from '../../common/typed_json'; import { queryClient } from '../query_client'; -import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; import { useErrorToast } from '../common/hooks/use_error_toast'; export interface ResultsArgs { diff --git a/x-pack/plugins/osquery/public/actions/helpers.ts b/x-pack/plugins/osquery/public/actions/helpers.ts deleted file mode 100644 index 171530a77299f9..00000000000000 --- a/x-pack/plugins/osquery/public/actions/helpers.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { - PaginationInputPaginated, - FactoryQueryTypes, - StrategyResponseType, - Inspect, -} from '../../common/search_strategy'; - -export type InspectResponse = Inspect & { response: string[] }; - -export const generateTablePaginationOptions = ( - activePage: number, - limit: number -): PaginationInputPaginated => { - const cursorStart = activePage * limit; - return { - activePage, - cursorStart, - fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, - querySize: limit, - }; -}; - -export const getInspectResponse = ( - response: StrategyResponseType, - prevResponse: InspectResponse -): InspectResponse => ({ - dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], - response: - response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, -}); diff --git a/x-pack/plugins/osquery/public/actions/use_action_details.ts b/x-pack/plugins/osquery/public/actions/use_action_details.ts index dfa23247045ef3..61ba6a3340bdbf 100644 --- a/x-pack/plugins/osquery/public/actions/use_action_details.ts +++ b/x-pack/plugins/osquery/public/actions/use_action_details.ts @@ -62,6 +62,7 @@ export const useActionDetails = ({ actionId, filterQuery, skip = false }: UseAct defaultMessage: 'Error while fetching action details', }), }), + refetchOnWindowFocus: false, retryDelay: 1000, } ); diff --git a/x-pack/plugins/osquery/public/actions/use_all_actions.ts b/x-pack/plugins/osquery/public/actions/use_all_actions.ts index ae872d3c1ed523..4951b3c9d8fd1c 100644 --- a/x-pack/plugins/osquery/public/actions/use_all_actions.ts +++ b/x-pack/plugins/osquery/public/actions/use_all_actions.ts @@ -8,7 +8,12 @@ import { useQuery } from 'react-query'; import { i18n } from '@kbn/i18n'; -import { createFilter } from '../common/helpers'; +import { + createFilter, + generateTablePaginationOptions, + getInspectResponse, + InspectResponse, +} from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { ActionEdges, @@ -20,7 +25,6 @@ import { } from '../../common/search_strategy'; import { ESTermQuery } from '../../common/typed_json'; -import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; import { useErrorToast } from '../common/hooks/use_error_toast'; export interface ActionsArgs { diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index a4fee25dfcd9af..105518537384f1 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -84,6 +84,22 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh const defaultValueInitialized = useRef(false); useEffect(() => { + const handleSelectedOptions = (selection: string[], label: string) => { + const agentOptions = find(['label', label], options); + + if (agentOptions) { + const defaultOptions = agentOptions.options?.filter((option) => { + if (option.key) { + return selection.includes(option.key); + } + }); + + if (defaultOptions?.length) { + setSelectedOptions(defaultOptions); + defaultValueInitialized.current = true; + } + } + }; if (agentSelection && !defaultValueInitialized.current && options.length) { if (agentSelection.allAgentsSelected) { const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options); @@ -95,35 +111,11 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh } if (agentSelection.policiesSelected.length) { - const policyOptions = find(['label', AGENT_POLICY_LABEL], options); - - if (policyOptions) { - const defaultOptions = policyOptions.options?.filter((option) => - // @ts-expect-error update types - agentSelection.policiesSelected.includes(option.key) - ); - - if (defaultOptions?.length) { - setSelectedOptions(defaultOptions); - defaultValueInitialized.current = true; - } - } + handleSelectedOptions(agentSelection.policiesSelected, AGENT_POLICY_LABEL); } if (agentSelection.agents.length) { - const agentOptions = find(['label', AGENT_SELECTION_LABEL], options); - - if (agentOptions) { - const defaultOptions = agentOptions.options?.filter((option) => - // @ts-expect-error update types - agentSelection.agents.includes(option.key) - ); - - if (defaultOptions?.length) { - setSelectedOptions(defaultOptions); - defaultValueInitialized.current = true; - } - } + handleSelectedOptions(agentSelection.agents, AGENT_SELECTION_LABEL); } } }, [agentSelection, options, selectedOptions]); diff --git a/x-pack/plugins/osquery/public/agents/helpers.ts b/x-pack/plugins/osquery/public/agents/helpers.ts index 39cbbcc8907774..71a67ef1f623a3 100644 --- a/x-pack/plugins/osquery/public/agents/helpers.ts +++ b/x-pack/plugins/osquery/public/agents/helpers.ts @@ -7,12 +7,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { euiPaletteColorBlindBehindText } from '@elastic/eui'; -import { - PaginationInputPaginated, - FactoryQueryTypes, - StrategyResponseType, - Inspect, -} from '../../common/search_strategy'; import { AGENT_GROUP_KEY, SelectedGroups, @@ -25,8 +19,6 @@ import { GroupOption, } from './types'; -export type InspectResponse = Inspect & { response: string[] }; - export const getNumOverlapped = ( { policy = {}, platform = {} }: SelectedGroups, overlap: Overlap @@ -158,26 +150,3 @@ export const generateAgentSelection = (selection: GroupOption[]) => { } return { newAgentSelection, selectedGroups, selectedAgents }; }; - -export const generateTablePaginationOptions = ( - activePage: number, - limit: number -): PaginationInputPaginated => { - const cursorStart = activePage * limit; - return { - activePage, - cursorStart, - fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, - querySize: limit, - }; -}; - -export const getInspectResponse = ( - response: StrategyResponseType, - prevResponse?: InspectResponse -): InspectResponse => ({ - dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], - // @ts-expect-error update types - response: - response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, -}); diff --git a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts index 4163861166acf8..6821217c30cbfd 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -16,7 +16,8 @@ import { AgentsStrategyResponse, } from '../../common/search_strategy'; -import { generateTablePaginationOptions, processAggregations } from './helpers'; +import { processAggregations } from './helpers'; +import { generateTablePaginationOptions } from '../common/helpers'; import { Overlap, Group } from './types'; import { useErrorToast } from '../common/hooks/use_error_toast'; diff --git a/x-pack/plugins/osquery/public/common/helpers.ts b/x-pack/plugins/osquery/public/common/helpers.ts index adac59211dee34..4f9efbe839ffd1 100644 --- a/x-pack/plugins/osquery/public/common/helpers.ts +++ b/x-pack/plugins/osquery/public/common/helpers.ts @@ -7,7 +7,38 @@ import { isString } from 'lodash/fp'; +import { + PaginationInputPaginated, + FactoryQueryTypes, + StrategyResponseType, + Inspect, +} from '../../common/search_strategy'; + import { ESQuery } from '../../common/typed_json'; export const createFilter = (filterQuery: ESQuery | string | undefined) => isString(filterQuery) ? filterQuery : JSON.stringify(filterQuery); + +export type InspectResponse = Inspect & { response: string[] }; + +export const generateTablePaginationOptions = ( + activePage: number, + limit: number +): PaginationInputPaginated => { + const cursorStart = activePage * limit; + return { + activePage, + cursorStart, + fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, + querySize: limit, + }; +}; + +export const getInspectResponse = ( + response: StrategyResponseType, + prevResponse: InspectResponse +): InspectResponse => ({ + dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], + response: + response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, +}); diff --git a/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx b/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx index 92660943b11707..6bef0ee38c24eb 100644 --- a/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx @@ -156,12 +156,27 @@ const breadcrumbGetters: { }; export function useBreadcrumbs(page: Page, values: DynamicPagePathValues = {}) { - const { chrome, http } = useKibana().services; + const { chrome, http, application } = useKibana().services; + const breadcrumbs: ChromeBreadcrumb[] = - breadcrumbGetters[page]?.(values).map((breadcrumb) => ({ - ...breadcrumb, - href: breadcrumb.href ? http.basePath.prepend(`${BASE_PATH}${breadcrumb.href}`) : undefined, - })) || []; + breadcrumbGetters[page]?.(values).map((breadcrumb) => { + const href = breadcrumb.href + ? http.basePath.prepend(`${BASE_PATH}${breadcrumb.href}`) + : undefined; + return { + ...breadcrumb, + href, + onClick: href + ? (ev: React.MouseEvent) => { + if (ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey) { + return; + } + ev.preventDefault(); + application.navigateToUrl(href); + } + : undefined, + }; + }) || []; const docTitle: string[] = [...breadcrumbs] .reverse() .map((breadcrumb) => breadcrumb.text as string); diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index 9be63b33394ada..0b44739a7f2ed6 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -5,33 +5,16 @@ * 2.0. */ -/* eslint-disable react-hooks/rules-of-hooks */ +import React from 'react'; +import { EuiLoadingElastic, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; -import React, { useMemo } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiTabs, - EuiTab, - EuiLoadingElastic, - EuiPage, - EuiPageBody, - EuiPageContent, -} from '@elastic/eui'; -import { useLocation } from 'react-router-dom'; - -import { Container, Nav, Wrapper } from './layouts'; +import { Container, Wrapper } from './layouts'; import { OsqueryAppRoutes } from '../routes'; -import { useRouterNavigate } from '../common/lib/kibana'; -import { ManageIntegrationLink } from './manage_integration_link'; import { useOsqueryIntegrationStatus } from '../common/hooks'; import { OsqueryAppEmptyState } from './empty_state'; +import { MainNavigation } from './main_navigation'; const OsqueryAppComponent = () => { - const location = useLocation(); - const section = useMemo(() => location.pathname.split('/')[1] ?? 'overview', [location.pathname]); const { data: osqueryIntegration, isFetched } = useOsqueryIntegrationStatus(); if (!isFetched) { @@ -59,55 +42,7 @@ const OsqueryAppComponent = () => { return ( - + diff --git a/x-pack/plugins/osquery/public/components/main_navigation.tsx b/x-pack/plugins/osquery/public/components/main_navigation.tsx new file mode 100644 index 00000000000000..73b6435fd8f338 --- /dev/null +++ b/x-pack/plugins/osquery/public/components/main_navigation.tsx @@ -0,0 +1,76 @@ +/* + * 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, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs } from '@elastic/eui'; +import { useLocation } from 'react-router-dom'; +import { useRouterNavigate } from '../common/lib/kibana'; +import { ManageIntegrationLink } from './manage_integration_link'; +import { Nav } from './layouts'; + +enum Section { + LiveQueries = 'live_queries', + Packs = 'packs', + SavedQueries = 'saved_queries', +} + +export const MainNavigation = () => { + const location = useLocation(); + const section = useMemo(() => location.pathname.split('/')[1] ?? 'overview', [location.pathname]); + return ( + + ); +}; diff --git a/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx b/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx index 4bcc9d9ebf2a10..4da470270de76b 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx @@ -30,7 +30,7 @@ const NavigationButtonsComponent: React.FC = ({ getUrlForApp(PLUGIN_ID, { path: agentPolicyId ? `/live_queries/new?agentPolicyId=${agentPolicyId}` - : ' `/live_queries/new', + : '/live_queries/new', }), [agentPolicyId, getUrlForApp] ); @@ -42,7 +42,7 @@ const NavigationButtonsComponent: React.FC = ({ navigateToApp(PLUGIN_ID, { path: agentPolicyId ? `/live_queries/new?agentPolicyId=${agentPolicyId}` - : ' `/live_queries/new', + : '/live_queries/new', }); } }, diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx index 39975cb65ce2b1..1b7b87fe180bff 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx @@ -311,25 +311,26 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< /* From 0.6.0 we don't provide an input template, so we have to set it here */ if (satisfies(newPolicy?.package?.version, '>=0.6.0')) { const updatedPolicy = produce(newPolicy, (draft) => { - if (!draft.inputs.length) { + if (editMode && policy?.inputs.length) { + set(draft, 'inputs', policy.inputs); + } else { set(draft, 'inputs[0]', { type: 'osquery', enabled: true, streams: [], policy_template: 'osquery_manager', }); - } else { - if (!draft.inputs[0].type) { - set(draft, 'inputs[0].type', 'osquery'); - } - if (!draft.inputs[0].policy_template) { - set(draft, 'inputs[0].policy_template', 'osquery_manager'); - } - if (!draft.inputs[0].enabled) { - set(draft, 'inputs[0].enabled', true); - } } + return draft; }); + + if (updatedPolicy?.inputs[0].config) { + setFieldValue( + 'config', + JSON.stringify(updatedPolicy?.inputs[0].config.osquery.value, null, 2) + ); + } + onChange({ isValid: true, updatedPolicy, diff --git a/x-pack/plugins/osquery/public/live_queries/index.tsx b/x-pack/plugins/osquery/public/live_queries/index.tsx index 2336a1de1d4a05..bf2186c1a3e509 100644 --- a/x-pack/plugins/osquery/public/live_queries/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/index.tsx @@ -45,7 +45,7 @@ const LiveQueryComponent: React.FC = ({ formType, enabled, }) => { - const { data: hasActionResultsPrivileges, isFetched } = useActionResultsPrivileges(); + const { data: hasActionResultsPrivileges, isLoading } = useActionResultsPrivileges(); const defaultValue = useMemo(() => { if (agentId || agentPolicyIds?.length || query?.length) { @@ -70,7 +70,7 @@ const LiveQueryComponent: React.FC = ({ return undefined; }, [agentId, agentIds, agentPolicyIds, ecs_mapping, query, savedQueryId]); - if (!isFetched) { + if (isLoading) { return ; } diff --git a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx index 631fa62e12bfbe..2bfe75e2833aaf 100644 --- a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx @@ -391,13 +391,13 @@ const ScheduledQueryLastResults: React.FC = ({ toggleErrors, expanded, }) => { - const { data: lastResultsData, isFetched } = usePackQueryLastResults({ + const { data: lastResultsData, isLoading } = usePackQueryLastResults({ actionId, interval, logsDataView, }); - const { data: errorsData, isFetched: errorsFetched } = usePackQueryErrors({ + const { data: errorsData, isLoading: errorsLoading } = usePackQueryErrors({ actionId, interval, logsDataView, @@ -408,7 +408,7 @@ const ScheduledQueryLastResults: React.FC = ({ [queryId, interval, toggleErrors] ); - if (!isFetched || !errorsFetched) { + if (isLoading || errorsLoading) { return ; } diff --git a/x-pack/plugins/osquery/public/packs/packs_table.tsx b/x-pack/plugins/osquery/public/packs/packs_table.tsx index 9bea07b7c234ca..f8599cc1fc51e2 100644 --- a/x-pack/plugins/osquery/public/packs/packs_table.tsx +++ b/x-pack/plugins/osquery/public/packs/packs_table.tsx @@ -13,17 +13,18 @@ import { EuiBasicTableColumn, EuiLink, EuiToolTip, + EuiLoadingContent, } from '@elastic/eui'; import moment from 'moment-timezone'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; -import { PackagePolicy } from '../../../fleet/common'; import { useRouterNavigate } from '../common/lib/kibana'; import { usePacks } from './use_packs'; import { ActiveStateSwitch } from './active_state_switch'; import { AgentsPolicyLink } from '../agent_policies/agents_policy_link'; +import { PackSavedObject } from './types'; const UpdatedBy = styled.span` white-space: nowrap; @@ -82,7 +83,7 @@ export const AgentPoliciesPopover = ({ agentPolicyIds }: { agentPolicyIds: strin }; const PacksTableComponent = () => { - const { data } = usePacks({}); + const { data, isLoading } = usePacks({}); const renderAgentPolicy = useCallback( (agentPolicyIds) => , @@ -112,15 +113,14 @@ const PacksTableComponent = () => { ); }, []); - // @ts-expect-error update types - const columns: Array> = useMemo( + const columns: Array> = useMemo( () => [ { field: 'attributes.name', name: i18n.translate('xpack.osquery.packs.table.nameColumnTitle', { defaultMessage: 'Name', }), - sortable: true, + sortable: (item) => item.attributes.name.toLowerCase(), render: renderName, }, { @@ -178,8 +178,12 @@ const PacksTableComponent = () => { [] ); + if (isLoading) { + return ; + } + return ( - + // eslint-disable-next-line react-perf/jsx-no-new-array-as-prop items={data?.saved_objects ?? []} columns={columns} diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index 77acd3d0239cf7..6cbf4dc84635eb 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -763,6 +763,7 @@ export const ECSMappingEditorForm = forwardRef) => ({ description: { type: FIELD_TYPES.TEXT, label: i18n.translate('xpack.osquery.pack.queryFlyoutForm.descriptionFieldLabel', { - defaultMessage: 'Description', + defaultMessage: 'Description (optional)', }), validations: [], }, diff --git a/x-pack/plugins/osquery/public/packs/types.ts b/x-pack/plugins/osquery/public/packs/types.ts index fce37ec495faa1..30cae97b006bb2 100644 --- a/x-pack/plugins/osquery/public/packs/types.ts +++ b/x-pack/plugins/osquery/public/packs/types.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { SavedObject } from 'kibana/server'; export interface IQueryPayload { attributes?: { @@ -11,3 +12,14 @@ export interface IQueryPayload { id: string; }; } + +export type PackSavedObject = SavedObject<{ + name: string; + description: string | undefined; + queries: Array>; + enabled: boolean | undefined; + created_at: string; + created_by: string | undefined; + updated_at: string; + updated_by: string | undefined; +}>; diff --git a/x-pack/plugins/osquery/public/results/helpers.ts b/x-pack/plugins/osquery/public/results/helpers.ts deleted file mode 100644 index 171530a77299f9..00000000000000 --- a/x-pack/plugins/osquery/public/results/helpers.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { - PaginationInputPaginated, - FactoryQueryTypes, - StrategyResponseType, - Inspect, -} from '../../common/search_strategy'; - -export type InspectResponse = Inspect & { response: string[] }; - -export const generateTablePaginationOptions = ( - activePage: number, - limit: number -): PaginationInputPaginated => { - const cursorStart = activePage * limit; - return { - activePage, - cursorStart, - fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, - querySize: limit, - }; -}; - -export const getInspectResponse = ( - response: StrategyResponseType, - prevResponse: InspectResponse -): InspectResponse => ({ - dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], - response: - response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, -}); diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index 461255533c3805..87d71bc3d17c8b 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -105,7 +105,11 @@ const ResultsTableComponent: React.FC = ({ ]); const [columns, setColumns] = useState([]); - const { data: allResultsData, isFetched } = useAllResults({ + const { + data: allResultsData, + isFetched, + isLoading, + } = useAllResults({ actionId, activePage: pagination.pageIndex, limit: pagination.pageSize, @@ -232,15 +236,11 @@ const ResultsTableComponent: React.FC = ({ ); useEffect(() => { - if (!allResultsData?.edges?.length) { + if (!allResultsData?.columns.length) { return; } - const fields = [ - 'agent.name', - ...ecsMappingColumns.sort(), - ...keys(allResultsData?.edges[0]?.fields || {}).sort(), - ]; + const fields = ['agent.name', ...ecsMappingColumns.sort(), ...allResultsData?.columns]; const newColumns = fields.reduce( (acc, fieldName) => { @@ -277,12 +277,15 @@ const ResultsTableComponent: React.FC = ({ if (fieldName.startsWith('osquery.')) { const displayAsText = fieldName.split('.')[1]; + const hasNumberType = fields.includes(`${fieldName}.number`); if (!seen.has(displayAsText)) { + const id = hasNumberType ? fieldName + '.number' : fieldName; data.push({ - id: fieldName, + id, displayAsText, display: getHeaderDisplay(displayAsText), defaultSortDirection: Direction.asc, + ...(hasNumberType ? { schema: 'numeric' } : {}), }); seen.add(displayAsText); } @@ -298,7 +301,8 @@ const ResultsTableComponent: React.FC = ({ !isEqual(map('id', currentColumns), map('id', newColumns)) ? newColumns : currentColumns ); setVisibleColumns(map('id', newColumns)); - }, [allResultsData?.edges, ecsMappingColumns, getHeaderDisplay]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [allResultsData?.columns.length, ecsMappingColumns, getHeaderDisplay]); const toolbarVisibility = useMemo( () => ({ @@ -347,7 +351,7 @@ const ResultsTableComponent: React.FC = ({ ); } - if (!isFetched) { + if (isLoading) { return ; } diff --git a/x-pack/plugins/osquery/public/results/use_all_results.ts b/x-pack/plugins/osquery/public/results/use_all_results.ts index bc7673dd0ffbd3..00c27f11c12aad 100644 --- a/x-pack/plugins/osquery/public/results/use_all_results.ts +++ b/x-pack/plugins/osquery/public/results/use_all_results.ts @@ -8,7 +8,12 @@ import { useQuery } from 'react-query'; import { i18n } from '@kbn/i18n'; -import { createFilter } from '../common/helpers'; +import { + createFilter, + generateTablePaginationOptions, + getInspectResponse, + InspectResponse, +} from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { ResultEdges, @@ -20,7 +25,6 @@ import { } from '../../common/search_strategy'; import { ESTermQuery } from '../../common/typed_json'; -import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; import { useErrorToast } from '../common/hooks/use_error_toast'; export interface ResultsArgs { @@ -78,10 +82,12 @@ export const useAllResults = ({ return { ...responseData, + columns: Object.keys(responseData.edges[0].fields || {}).sort(), inspect: getInspectResponse(responseData, {} as InspectResponse), }; }, { + keepPreviousData: true, refetchInterval: isLive ? 5000 : false, enabled: !skip, onSuccess: () => setErrorToast(), diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx index 0bb162173adc19..f16e32a62cb4f0 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx @@ -13,6 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, + EuiBasicTableColumn, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; @@ -28,10 +29,12 @@ import { useSavedQueries } from '../../../saved_queries/use_saved_queries'; type SavedQuerySO = SavedObject<{ name: string; + id: string; query: string; ecs_mapping: ECSMapping; updated_at: string; }>; + interface PlayButtonProps { disabled: boolean; savedQuery: SavedQuerySO; @@ -141,14 +144,14 @@ const SavedQueriesPageComponent = () => { return updatedAt ? `${moment(updatedAt).fromNow()}${updatedBy}` : '-'; }, []); - const columns = useMemo( + const columns: Array> = useMemo( () => [ { field: 'attributes.id', name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', { defaultMessage: 'Query ID', }), - sortable: true, + sortable: (item) => item.attributes.id.toLowerCase(), truncateText: true, }, { @@ -156,7 +159,6 @@ const SavedQueriesPageComponent = () => { name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', { defaultMessage: 'Description', }), - sortable: true, truncateText: true, }, { @@ -172,7 +174,7 @@ const SavedQueriesPageComponent = () => { name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', { defaultMessage: 'Last updated at', }), - sortable: (item: SavedQuerySO) => + sortable: (item) => item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0, truncateText: true, render: renderUpdatedAt, diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx index 3fd2275477ebfa..b3e0cab60851ea 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -54,7 +54,7 @@ export const useSavedQueryForm = ({ try { await handleSubmit({ ...formData, - ...(isEmpty(ecsFieldValue) ? {} : { ecs_mapping: ecsFieldValue }), + ecs_mapping: ecsFieldValue, }); // eslint-disable-next-line no-empty } catch (e) {} diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx index 7bc54b44de7759..3861784120e0c4 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx @@ -30,7 +30,11 @@ interface OsqueryActionProps { const OsqueryActionComponent: React.FC = ({ metadata }) => { const permissions = useKibana().services.application.capabilities.osquery; const agentId = metadata?.info?.agent?.id ?? undefined; - const { data: agentData, isFetched: agentFetched } = useAgentDetails({ + const { + data: agentData, + isFetched: agentFetched, + isLoading, + } = useAgentDetails({ agentId, silent: true, skip: !agentId, @@ -72,7 +76,7 @@ const OsqueryActionComponent: React.FC = ({ metadata }) => { ); } - if (!agentFetched) { + if (isLoading) { return ; } diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index 88af9040889841..8cf891bff8b997 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -6,19 +6,7 @@ */ import moment from 'moment-timezone'; -import { - isEmpty, - set, - unset, - has, - difference, - filter, - find, - map, - mapKeys, - pickBy, - uniq, -} from 'lodash'; +import { set, unset, has, difference, filter, find, map, mapKeys, uniq } from 'lodash'; import { schema } from '@kbn/config-schema'; import { produce } from 'immer'; import { @@ -126,16 +114,11 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte request.params.id, { enabled, - ...pickBy( - { - name, - description, - queries: queries && convertPackQueriesToSO(queries), - updated_at: moment().toISOString(), - updated_by: currentUser, - }, - (value) => !isEmpty(value) - ), + name, + description: description || '', + queries: queries && convertPackQueriesToSO(queries), + updated_at: moment().toISOString(), + updated_by: currentUser, }, policy_ids ? { diff --git a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts index 21cfd0bd437724..7431050996deb5 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEmpty, filter, pickBy } from 'lodash'; +import { filter } from 'lodash'; import { schema } from '@kbn/config-schema'; import { PLUGIN_ID } from '../../../common'; @@ -77,20 +77,17 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp const updatedSavedQuerySO = await savedObjectsClient.update( savedQuerySavedObjectType, request.params.id, - pickBy( - { - id, - description, - platform, - query, - version, - interval, - ecs_mapping: convertECSMappingToArray(ecs_mapping), - updated_by: currentUser, - updated_at: new Date().toISOString(), - }, - (value) => !isEmpty(value) - ), + { + id, + description: description || '', + platform, + query, + version, + interval, + ecs_mapping: convertECSMappingToArray(ecs_mapping), + updated_by: currentUser, + updated_at: new Date().toISOString(), + }, { refresh: 'wait_for', }