diff --git a/package.json b/package.json index be723374e4356..3c439a01a9b3d 100644 --- a/package.json +++ b/package.json @@ -1265,7 +1265,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^2.0.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^8.9.2", + "backport": "^8.9.4", "callsites": "^3.1.0", "chance": "1.0.18", "chokidar": "^3.5.3", diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts index 7bd2443031c80..133ec096370b9 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts @@ -7,7 +7,6 @@ */ import fetch from 'node-fetch'; -import Semver from 'semver'; import { Logger } from '../../utils/create_logger'; export class ApmSynthtraceKibanaClient { @@ -53,31 +52,33 @@ export class ApmSynthtraceKibanaClient { return kibanaUrl; }); } - async fetchLatestApmPackageVersion(currentKibanaVersion: string) { - const url = `https://epr-snapshot.elastic.co/search?package=apm&prerelease=true&all=true&kibana.version=${currentKibanaVersion}`; - const response = await fetch(url, { method: 'GET' }); - const json = (await response.json()) as Array<{ version: string }>; - const packageVersions = (json ?? []).map((item) => item.version).sort(Semver.rcompare); - const validPackageVersions = packageVersions.filter((v) => Semver.valid(v)); - const bestMatch = validPackageVersions[0]; - if (!bestMatch) { - throw new Error( - `None of the available APM package versions matches the current Kibana version (${currentKibanaVersion}). The latest available version is ${packageVersions[0]}. This can happen if the Kibana version was recently bumped, and no matching APM package was released. Reach out to the fleet team if this persists.` - ); - } - return bestMatch; + + async fetchLatestApmPackageVersion( + kibanaUrl: string, + version: string, + username: string, + password: string + ) { + const url = `${kibanaUrl}/api/fleet/epm/packages/apm`; + const response = await fetch(url, { + method: 'GET', + headers: kibanaHeaders(username, password), + }); + const json = (await response.json()) as { item: { latestVersion: string } }; + const { latestVersion } = json.item; + return latestVersion; } async installApmPackage(kibanaUrl: string, version: string, username: string, password: string) { - const packageVersion = await this.fetchLatestApmPackageVersion(version); + const packageVersion = await this.fetchLatestApmPackageVersion( + kibanaUrl, + version, + username, + password + ); const response = await fetch(`${kibanaUrl}/api/fleet/epm/packages/apm/${packageVersion}`, { method: 'POST', - headers: { - Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), - Accept: 'application/json', - 'Content-Type': 'application/json', - 'kbn-xsrf': 'kibana', - }, + headers: kibanaHeaders(username, password), body: '{"force":true}', }); @@ -93,3 +94,12 @@ export class ApmSynthtraceKibanaClient { } else this.logger.error(responseJson); } } + +function kibanaHeaders(username: string, password: string) { + return { + Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), + Accept: 'application/json', + 'Content-Type': 'application/json', + 'kbn-xsrf': 'kibana', + }; +} diff --git a/src/plugins/console/public/application/components/console_menu.tsx b/src/plugins/console/public/application/components/console_menu.tsx index 3f5113b3ac44f..b22fd7db9baab 100644 --- a/src/plugins/console/public/application/components/console_menu.tsx +++ b/src/plugins/console/public/application/components/console_menu.tsx @@ -128,6 +128,7 @@ export class ConsoleMenu extends Component { const items = [ { @@ -174,7 +175,7 @@ export class ConsoleMenu extends Component { panelPaddingSize="none" anchorPosition="downLeft" > - + ); diff --git a/test/functional/apps/console/_context_menu.ts b/test/functional/apps/console/_context_menu.ts new file mode 100644 index 0000000000000..8114d4d05097e --- /dev/null +++ b/test/functional/apps/console/_context_menu.ts @@ -0,0 +1,104 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'console']); + const browser = getService('browser'); + const toasts = getService('toasts'); + + describe('console context menu', function testContextMenu() { + before(async () => { + await PageObjects.common.navigateToApp('console'); + // Ensure that the text area can be interacted with + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.clearTextArea(); + }); + + it('should open context menu', async () => { + expect(await PageObjects.console.isContextMenuOpen()).to.be(false); + await PageObjects.console.enterRequest(); + await PageObjects.console.clickContextMenu(); + expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true); + }); + + it('should have options to copy as curl, open documentation, and auto indent', async () => { + await PageObjects.console.clickContextMenu(); + expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true); + expect(PageObjects.console.isCopyAsCurlButtonVisible()).to.be.eql(true); + expect(PageObjects.console.isOpenDocumentationButtonVisible()).to.be.eql(true); + expect(PageObjects.console.isAutoIndentButtonVisible()).to.be.eql(true); + }); + + it('should copy as curl and show toast when copy as curl button is clicked', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickCopyAsCurlButton(); + + const resultToast = await toasts.getToastElement(1); + const toastText = await resultToast.getVisibleText(); + + if (toastText.includes('Write permission denied')) { + log.debug('Write permission denied, skipping test'); + return; + } + + expect(toastText).to.be('Request copied as cURL'); + + const canReadClipboard = await browser.checkBrowserPermission('clipboard-read'); + if (canReadClipboard) { + const clipboardText = await browser.getClipboardValue(); + expect(clipboardText).to.contain('curl -XGET'); + } + }); + + it('should open documentation when open documentation button is clicked', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickOpenDocumentationButton(); + + await retry.tryForTime(10000, async () => { + await browser.switchTab(1); + }); + + // Retry until the documentation is loaded + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain('search-search.html'); + }); + + // Close the documentation tab + await browser.closeCurrentWindow(); + await browser.switchTab(0); + }); + + it('should auto indent when auto indent button is clicked', async () => { + await PageObjects.console.clearTextArea(); + await PageObjects.console.enterRequest('GET _search\n{"query": {"match_all": {}}}'); + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is auto indented + await retry.try(async () => { + const request = await PageObjects.console.getRequest(); + expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}'); + }); + }); + + it('should condense when auto indent button is clicked again', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is condensed + await retry.try(async () => { + const request = await PageObjects.console.getRequest(); + expect(request).to.be.eql('GET _search\n{"query":{"match_all":{}}}'); + }); + }); + }); +} diff --git a/test/functional/apps/console/index.js b/test/functional/apps/console/index.js index 9dee3cda26e83..06c29f0e031ec 100644 --- a/test/functional/apps/console/index.js +++ b/test/functional/apps/console/index.js @@ -24,6 +24,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_variables')); loadTestFile(require.resolve('./_xjson')); loadTestFile(require.resolve('./_misc_console_behavior')); + loadTestFile(require.resolve('./_context_menu')); } }); } diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 50cc2e7029d48..9f662a09146e9 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -368,4 +368,40 @@ export class ConsolePageObject extends FtrService { const textArea = await this.testSubjects.find('console-textarea'); await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], '/']); } + + public async clickContextMenu() { + const contextMenu = await this.testSubjects.find('toggleConsoleMenu'); + await contextMenu.click(); + } + + public async isContextMenuOpen() { + return await this.testSubjects.exists('consoleMenu'); + } + + public async isCopyAsCurlButtonVisible() { + return await this.testSubjects.exists('consoleMenuCopyAsCurl'); + } + + public async isOpenDocumentationButtonVisible() { + return await this.testSubjects.exists('consoleMenuOpenDocs'); + } + + public async isAutoIndentButtonVisible() { + return await this.testSubjects.exists('consoleMenuAutoIndent'); + } + + public async clickCopyAsCurlButton() { + const button = await this.testSubjects.find('consoleMenuCopyAsCurl'); + await button.click(); + } + + public async clickOpenDocumentationButton() { + const button = await this.testSubjects.find('consoleMenuOpenDocs'); + await button.click(); + } + + public async clickAutoIndentButton() { + const button = await this.testSubjects.find('consoleMenuAutoIndent'); + await button.click(); + } } diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx index 5bbc0cecae4d4..6c8da64cb0e89 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx @@ -228,9 +228,26 @@ export const SpikeAnalysisGroupsTable: FC = ({ { 'data-test-subj': 'aiopsSpikeAnalysisGroupsTableColumnGroup', field: 'group', - name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTableGroups.groupLabel', { - defaultMessage: 'Group', - }), + name: ( + + <> + + + + + ), render: (_, { group, repeatedValues }) => { const valuesBadges = []; for (const fieldName in group) { @@ -287,7 +304,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTableGroups.logRateColumnTooltip', { defaultMessage: - 'A visual representation of the impact of the field on the message rate difference', + 'A visual representation of the impact of the group on the message rate difference', } )} > @@ -361,9 +378,9 @@ export const SpikeAnalysisGroupsTable: FC = ({ diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx index c3b958c12d22d..8e0d8ed59392b 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx @@ -117,6 +117,8 @@ export const UploadFile = ({ return () => subs.forEach((sub) => sub.unsubscribe()); }, [uploadState, onDone, onError]); + useEffect(() => uploadState.dispose, [uploadState]); + return ( (); + private subscriptions: Subscription[]; + constructor( private readonly fileKind: FileKind, private readonly client: FilesClient, private readonly opts: UploadOptions = { allowRepeatedUploads: false } ) { const latestFiles$ = this.files$$.pipe(switchMap((files$) => combineLatest(files$))); - - latestFiles$ - .pipe( - map((files) => files.some((file) => file.status === 'uploading')), - distinctUntilChanged() - ) - .subscribe(this.uploading$); - - latestFiles$ - .pipe( - map((files) => { - const errorFile = files.find((file) => Boolean(file.error)); - return errorFile ? errorFile.error : undefined; - }), - filter(Boolean) - ) - .subscribe(this.error$); - - latestFiles$ - .pipe( - filter( - (files) => Boolean(files.length) && files.every((file) => file.status === 'uploaded') - ), - map((files) => files.map((file) => ({ id: file.id!, kind: this.fileKind.id }))) - ) - .subscribe(this.done$); + this.subscriptions = [ + latestFiles$ + .pipe( + map((files) => files.some((file) => file.status === 'uploading')), + distinctUntilChanged() + ) + .subscribe(this.uploading$), + + latestFiles$ + .pipe( + map((files) => { + const errorFile = files.find((file) => Boolean(file.error)); + return errorFile ? errorFile.error : undefined; + }), + filter(Boolean) + ) + .subscribe(this.error$), + + latestFiles$ + .pipe( + filter( + (files) => Boolean(files.length) && files.every((file) => file.status === 'uploaded') + ), + map((files) => files.map((file) => ({ id: file.id!, kind: this.fileKind.id }))) + ) + .subscribe(this.done$), + ]; } public isUploading(): boolean { @@ -226,6 +230,10 @@ export class UploadState { return upload$; }; + + public dispose = (): void => { + for (const sub of this.subscriptions) sub.unsubscribe(); + }; } export const createUploadState = ({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx new file mode 100644 index 0000000000000..68a3420780039 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx @@ -0,0 +1,316 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act, render, fireEvent } from '@testing-library/react'; +// eslint-disable-next-line @kbn/eslint/module_migration +import { IntlProvider } from 'react-intl'; + +import { useActionStatus } from '../hooks'; +import { useGetAgentPolicies, useStartServices } from '../../../../hooks'; + +import { AgentActivityFlyout } from './agent_activity_flyout'; + +jest.mock('../hooks'); +jest.mock('../../../../hooks'); + +const mockUseActionStatus = useActionStatus as jest.Mock; +const mockUseGetAgentPolicies = useGetAgentPolicies as jest.Mock; +const mockUseStartServices = useStartServices as jest.Mock; + +describe('AgentActivityFlyout', () => { + const mockOnClose = jest.fn(); + const mockOnAbortSuccess = jest.fn(); + const mockAbortUpgrade = jest.fn(); + + beforeEach(() => { + mockOnClose.mockReset(); + mockOnAbortSuccess.mockReset(); + mockAbortUpgrade.mockReset(); + mockUseActionStatus.mockReset(); + mockUseGetAgentPolicies.mockReturnValue({ + data: { + items: [ + { id: 'policy1', name: 'Policy 1' }, + { id: 'policy2', name: 'Policy 2' }, + ], + }, + }); + mockUseStartServices.mockReturnValue({ + docLinks: { links: { fleet: { upgradeElasticAgent: 'https://elastic.co' } } }, + }); + }); + + beforeEach(() => { + jest.useFakeTimers('modern').setSystemTime(new Date('2022-09-15T10:00:00.000Z')); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const renderComponent = () => { + return render( + + + + ); + }; + + it('should render agent activity for in progress upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.getByText('Agent activity')).toBeInTheDocument(); + + expect( + result.container.querySelector('[data-test-subj="upgradeInProgressTitle"]')!.textContent + ).toEqual('Upgrading 5 agents to version 8.5.0'); + // compare without whitespace,   doesn't match + expect( + result.container + .querySelector('[data-test-subj="upgradeInProgressDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Started on Sep 15, 2022 10:00 AM. Learn more'.replace(/\s/g, '')); + + act(() => { + fireEvent.click(result.getByText('Abort upgrade')); + }); + + expect(mockAbortUpgrade).toHaveBeenCalled(); + }); + + it('should render agent activity for scheduled upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-16T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-17T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.getByText('Agent activity')).toBeInTheDocument(); + + expect( + result.container.querySelector('[data-test-subj="upgradeInProgressTitle"]')!.textContent + ).toEqual('5 agents scheduled to upgrade to version 8.5.0'); + expect( + result.container + .querySelector('[data-test-subj="upgradeInProgressDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Scheduled for Sep 16, 2022 10:00 AM. Learn more'.replace(/\s/g, '')); + + act(() => { + fireEvent.click(result.getByText('Abort upgrade')); + }); + + expect(mockAbortUpgrade).toHaveBeenCalled(); + }); + + it('should render agent activity for complete upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action3', + nbAgentsActionCreated: 2, + nbAgentsAck: 2, + type: 'UPGRADE', + nbAgentsActioned: 2, + status: 'COMPLETE', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + completionTime: '2022-09-15T12:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '2 agents upgraded' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Completed Sep 15, 2022 12:00 PM'.replace(/\s/g, '')); + }); + + it('should render agent activity for expired unenroll', () => { + const mockActionStatuses = [ + { + actionId: 'action4', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNENROLL', + nbAgentsActioned: 3, + status: 'EXPIRED', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + 'Agent unenrollment expired' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Expired on Sep 14, 2022 10:00 AM'.replace(/\s/g, '')); + }); + + it('should render agent activity for cancelled upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action5', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 3, + status: 'CANCELLED', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + cancellationTime: '2022-09-15T11:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + 'Agent upgrade cancelled' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Cancelled on Sep 15, 2022 11:00 AM'.replace(/\s/g, '')); + }); + + it('should render agent activity for failed reassign', () => { + const mockActionStatuses = [ + { + actionId: 'action7', + nbAgentsActionCreated: 1, + nbAgentsAck: 0, + type: 'POLICY_REASSIGN', + nbAgentsActioned: 1, + status: 'FAILED', + expiration: '2099-09-16T10:00:00.000Z', + newPolicyId: 'policy1', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 1, + completionTime: '2022-09-15T11:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '0 of 1 agent assigned to a new policy' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain( + 'A problem occurred during this operation. Started on Sep 15, 2022 10:00 AM.'.replace( + /\s/g, + '' + ) + ); + }); + + it('should render agent activity for unknown action', () => { + const mockActionStatuses = [ + { + actionId: 'action8', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNKNOWN', + nbAgentsActioned: 3, + status: 'COMPLETE', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + completionTime: '2022-09-15T12:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '0 of 3 agents actioned' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Completed Sep 15, 2022 12:00 PM'.replace(/\s/g, '')); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index 929eb4bbf54d1..dfa6623f4a90a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -176,6 +176,7 @@ export const AgentActivityFlyout: React.FunctionComponent<{ ) : null} {Object.keys(otherDays).map((day) => ( } actions={otherDays[day]} abortUpgrade={abortUpgrade} @@ -215,9 +216,13 @@ const ActivitySection: React.FunctionComponent<{ {actions.map((currentAction) => currentAction.type === 'UPGRADE' && currentAction.status === 'IN_PROGRESS' ? ( - + ) : ( - + ) )} @@ -272,7 +277,7 @@ const inProgressTitle = (action: ActionStatus) => ( defaultMessage="{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}" values={{ nbAgents: - action.nbAgentsAck === action.nbAgentsActioned + action.nbAgentsAck >= action.nbAgentsActioned ? action.nbAgentsAck : action.nbAgentsAck === 0 ? action.nbAgentsActioned @@ -438,14 +443,19 @@ const ActivityItem: React.FunctionComponent<{ action: ActionStatus }> = ({ actio {displayByStatus[action.status].icon} - + {displayByStatus[action.status].title} - {displayByStatus[action.status].description} + + {displayByStatus[action.status].description} + @@ -486,7 +496,7 @@ export const UpgradeInProgressActivityItem: React.FunctionComponent<{ {isScheduled ? : } - + {isScheduled && action.startTime ? ( - +

{isScheduled && action.startTime ? ( <> @@ -541,7 +551,7 @@ export const UpgradeInProgressActivityItem: React.FunctionComponent<{ size="s" onClick={onClickAbortUpgrade} isLoading={isAborting} - data-test-subj="currentBulkUpgrade.abortBtn" + data-test-subj="abortBtn" > bucket.key === action.actionId ); - const nbAgentsAck = matchingBucket?.doc_count ?? 0; + const nbAgentsAck = (matchingBucket?.agent_count as any)?.value ?? 0; const completionTime = (matchingBucket?.max_timestamp as any)?.value_as_string; const nbAgentsActioned = action.nbAgentsActioned || action.nbAgentsActionCreated; const complete = nbAgentsAck >= nbAgentsActioned; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx index 3b413cf67e4d5..c5032a1eb9b72 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx @@ -9,6 +9,7 @@ import type { EuiBasicTableColumn, EuiTableActionsColumnType } from '@elastic/eu import { EuiBadge, EuiLink, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo } from 'react'; +import moment from 'moment'; import { IntegrationsPopover } from '../../../../components/rules/related_integrations/integrations_popover'; import { DEFAULT_RELATIVE_DATE_THRESHOLD, @@ -387,7 +388,7 @@ export const useMonitoringColumns = ({ hasPermissions }: ColumnsProps): TableCol ), render: (value: DurationMetric | undefined) => ( - {value != null ? value.toFixed() : getEmptyTagValue()} + {value != null ? moment.duration(value, 'seconds').humanize() : getEmptyTagValue()} ), sortable: !!isInMemorySorting, diff --git a/yarn.lock b/yarn.lock index 374d3119e018e..23369504cad0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4284,6 +4284,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.10.1.tgz#57b5cc6c7b4e55d8642c93d06401fb1af4839899" integrity sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ== +"@octokit/openapi-types@^13.4.0": + version "13.4.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-13.4.0.tgz#06fe8fda93bf21bdd397fe7ef8805249efda6c06" + integrity sha512-2mVzW0X1+HDO3jF80/+QFZNzJiTefELKbhMu6yaBYbp/1gSMkVDm4rT472gJljTokWUlXaaE63m7WrWENhMDLw== + "@octokit/plugin-paginate-rest@^1.1.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc" @@ -4291,12 +4296,12 @@ dependencies: "@octokit/types" "^2.0.1" -"@octokit/plugin-paginate-rest@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-3.0.0.tgz#df779de686aeb21b5e776e4318defc33b0418566" - integrity sha512-fvw0Q5IXnn60D32sKeLIxgXCEZ7BTSAjJd8cFAE6QU5qUp0xo7LjFUjjX1J5D7HgN355CN4EXE4+Q1/96JaNUA== +"@octokit/plugin-paginate-rest@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-4.1.0.tgz#670ac9ac369448c69a2371bfcd7e2b37d95534f2" + integrity sha512-2O5K5fpajYG5g62wjzHR7/cWYaCA88CextAW3vFP+yoIHD0KEdlVMHfM5/i5LyV+JMmqiYW7w5qfg46FR+McNw== dependencies: - "@octokit/types" "^6.39.0" + "@octokit/types" "^7.1.1" "@octokit/plugin-request-log@^1.0.0", "@octokit/plugin-request-log@^1.0.4": version "1.0.4" @@ -4411,17 +4416,17 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/rest@^19.0.3": - version "19.0.3" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.3.tgz#b9a4e8dc8d53e030d611c053153ee6045f080f02" - integrity sha512-5arkTsnnRT7/sbI4fqgSJ35KiFaN7zQm0uQiQtivNQLI8RQx8EHwJCajcTUwmaCMNDg7tdCvqAnc7uvHHPxrtQ== +"@octokit/rest@^19.0.4": + version "19.0.4" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.4.tgz#fd8bed1cefffa486e9ae46a9dc608ce81bcfcbdd" + integrity sha512-LwG668+6lE8zlSYOfwPj4FxWdv/qFXYBpv79TWIQEpBLKA9D/IMcWsF/U9RGpA3YqMVDiTxpgVpEW3zTFfPFTA== dependencies: "@octokit/core" "^4.0.0" - "@octokit/plugin-paginate-rest" "^3.0.0" + "@octokit/plugin-paginate-rest" "^4.0.0" "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^6.0.0" -"@octokit/types@6.40.0", "@octokit/types@^6.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0": +"@octokit/types@6.40.0", "@octokit/types@^6.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.16.1": version "6.40.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.40.0.tgz#f2e665196d419e19bb4265603cf904a820505d0e" integrity sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw== @@ -4442,6 +4447,13 @@ dependencies: "@types/node" ">= 8" +"@octokit/types@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-7.1.1.tgz#a30fd6ca3279d59d532fa75583d65d93b7588e6d" + integrity sha512-Dx6cNTORyVaKY0Yeb9MbHksk79L8GXsihbG6PtWqTpkyA2TY1qBWE26EQXVG3dHwY9Femdd/WEeRUEiD0+H3TQ== + dependencies: + "@octokit/openapi-types" "^13.4.0" + "@openpgp/web-stream-tools@^0.0.10": version "0.0.10" resolved "https://registry.yarnpkg.com/@openpgp/web-stream-tools/-/web-stream-tools-0.0.10.tgz#4496390da9715c9bfc581ad144f9fb8a36a37775" @@ -10478,18 +10490,18 @@ babel-runtime@6.x, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" -backport@^8.9.2: - version "8.9.2" - resolved "https://registry.yarnpkg.com/backport/-/backport-8.9.2.tgz#cf0ec69428f9e86c20e1898dd77e8f6c12bf5afa" - integrity sha512-0ghVAwSssE0mamADGnsOybWn7RroKLSf9r4uU1IpAlxxa2zkRecfnGuSfEa5L1tQjh7lJwI/2i01JR6154r+qg== +backport@^8.9.4: + version "8.9.4" + resolved "https://registry.yarnpkg.com/backport/-/backport-8.9.4.tgz#2bbe58fd766ebda6c760852d029630a277098a54" + integrity sha512-REMiogdMQ+TOLQoEABttcCevbxJ14xlCMkHn7es0ZTeCleHz6T2bl93w/Fe+JIttuyZ0e8oPQW2DVe1feTG1pw== dependencies: - "@octokit/rest" "^19.0.3" + "@octokit/rest" "^19.0.4" axios "^0.27.2" dedent "^0.7.0" del "^6.1.1" - dotenv "^16.0.1" + dotenv "^16.0.2" find-up "^5.0.0" - graphql "^16.5.0" + graphql "^16.6.0" graphql-tag "^2.12.6" inquirer "^8.2.3" lodash "^4.17.21" @@ -10499,9 +10511,9 @@ backport@^8.9.2: strip-json-comments "^3.1.1" terminal-link "^2.1.1" utility-types "^3.10.0" - winston "^3.8.1" + winston "^3.8.2" yargs "^17.5.1" - yargs-parser "^21.0.1" + yargs-parser "^21.1.1" bail@^1.0.0: version "1.0.2" @@ -13642,10 +13654,10 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" - integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== +dotenv@^16.0.2: + version "16.0.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" + integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== dotenv@^8.0.0: version "8.2.0" @@ -16136,10 +16148,10 @@ graphql-tag@^2.12.6: dependencies: tslib "^2.1.0" -graphql@^16.5.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" - integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== +graphql@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" + integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== growly@^1.3.0: version "1.3.0" @@ -28716,7 +28728,7 @@ winston-transport@^4.5.0: readable-stream "^3.6.0" triple-beam "^1.3.0" -winston@^3.3.3, winston@^3.8.1: +winston@^3.3.3: version "3.8.1" resolved "https://registry.yarnpkg.com/winston/-/winston-3.8.1.tgz#76f15b3478cde170b780234e0c4cf805c5a7fb57" integrity sha512-r+6YAiCR4uI3N8eQNOg8k3P3PqwAm20cLKlzVD9E66Ch39+LZC+VH1UKf9JemQj2B3QoUHfKD7Poewn0Pr3Y1w== @@ -28732,6 +28744,23 @@ winston@^3.3.3, winston@^3.8.1: triple-beam "^1.3.0" winston-transport "^4.5.0" +winston@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.8.2.tgz#56e16b34022eb4cff2638196d9646d7430fdad50" + integrity sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew== + dependencies: + "@colors/colors" "1.5.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.5.0" + wkt-parser@^1.2.4: version "1.3.2" resolved "https://registry.yarnpkg.com/wkt-parser/-/wkt-parser-1.3.2.tgz#deeff04a21edc5b170a60da418e9ed1d1ab0e219" @@ -29000,11 +29029,16 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^21.0.0, yargs-parser@^21.0.1: +yargs-parser@^21.0.0: version "21.0.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"