[Fleet] Changes to agent upgrade modal to allow for rolling upgrades (#132421)

* [Fleet] Changes to agent upgrade modal to allow for rolling upgrades

* Update the onSubmit logic and handle case with single agent

* Fix check

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Add option to upgrade immediately; minor fixes

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* Add callout in modal for 400 errors

* Linter fixes

* Fix i18n error

* Address code review comments

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cristina Amico 2022-05-20 12:24:38 +02:00 committed by GitHub
parent 473141f58b
commit aa4c389ed2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 184 additions and 51 deletions

View file

@ -89,6 +89,7 @@ export interface PostBulkAgentUpgradeRequest {
agents: string[] | string; agents: string[] | string;
source_uri?: string; source_uri?: string;
version: string; version: string;
rollout_duration_seconds?: number;
}; };
} }

View file

@ -70,7 +70,6 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
<AgentUpgradeAgentModal <AgentUpgradeAgentModal
agents={[agent]} agents={[agent]}
agentCount={1} agentCount={1}
version={kibanaVersion}
onClose={() => { onClose={() => {
setIsUpgradeModalOpen(false); setIsUpgradeModalOpen(false);
refreshAgent(); refreshAgent();

View file

@ -24,7 +24,6 @@ import {
AgentUnenrollAgentModal, AgentUnenrollAgentModal,
AgentUpgradeAgentModal, AgentUpgradeAgentModal,
} from '../../components'; } from '../../components';
import { useKibanaVersion } from '../../../../hooks';
import type { SelectionMode } from './types'; import type { SelectionMode } from './types';
@ -48,11 +47,10 @@ export const AgentBulkActions: React.FunctionComponent<Props> = ({
selectedAgents, selectedAgents,
refreshAgents, refreshAgents,
}) => { }) => {
const kibanaVersion = useKibanaVersion();
// Bulk actions menu states // Bulk actions menu states
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false); const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const closeMenu = () => setIsMenuOpen(false); const closeMenu = () => setIsMenuOpen(false);
const openMenu = () => setIsMenuOpen(true); const onClickMenu = () => setIsMenuOpen(!isMenuOpen);
// Actions states // Actions states
const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState<boolean>(false); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState<boolean>(false);
@ -150,7 +148,6 @@ export const AgentBulkActions: React.FunctionComponent<Props> = ({
{isUpgradeModalOpen && ( {isUpgradeModalOpen && (
<EuiPortal> <EuiPortal>
<AgentUpgradeAgentModal <AgentUpgradeAgentModal
version={kibanaVersion}
agents={agents} agents={agents}
agentCount={agentCount} agentCount={agentCount}
onClose={() => { onClose={() => {
@ -172,7 +169,7 @@ export const AgentBulkActions: React.FunctionComponent<Props> = ({
fill fill
iconType="arrowDown" iconType="arrowDown"
iconSide="right" iconSide="right"
onClick={openMenu} onClick={onClickMenu}
data-test-subj="agentBulkActionsButton" data-test-subj="agentBulkActionsButton"
> >
<FormattedMessage <FormattedMessage

View file

@ -505,7 +505,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
fetchData(); fetchData();
refreshUpgrades(); refreshUpgrades();
}} }}
version={kibanaVersion}
/> />
</EuiPortal> </EuiPortal>
)} )}

View file

@ -0,0 +1,32 @@
/*
* 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.
*/
// Available versions for the upgrade of the Elastic Agent
// These versions are only intended to be used as a fallback
// in the event that the updated versions cannot be retrieved from the endpoint
export const FALLBACK_VERSIONS = [
'8.2.0',
'8.1.3',
'8.1.2',
'8.1.1',
'8.1.0',
'8.0.1',
'8.0.0',
'7.9.3',
'7.9.2',
'7.9.1',
'7.9.0',
'7.8.1',
'7.8.0',
'7.17.3',
'7.17.2',
'7.17.1',
'7.17.0',
];
export const MAINTAINANCE_VALUES = [1, 2, 4, 8, 12, 24, 48];

View file

@ -7,34 +7,89 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { EuiConfirmModal, EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import {
EuiConfirmModal,
EuiComboBox,
EuiFormRow,
EuiSpacer,
EuiToolTip,
EuiIcon,
EuiFlexGroup,
EuiFlexItem,
EuiCallOut,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
import type { Agent } from '../../../../types'; import type { Agent } from '../../../../types';
import { import {
sendPostAgentUpgrade, sendPostAgentUpgrade,
sendPostBulkAgentUpgrade, sendPostBulkAgentUpgrade,
useStartServices, useStartServices,
useKibanaVersion,
} from '../../../../hooks'; } from '../../../../hooks';
import { FALLBACK_VERSIONS, MAINTAINANCE_VALUES } from './constants';
interface Props { interface Props {
onClose: () => void; onClose: () => void;
agents: Agent[] | string; agents: Agent[] | string;
agentCount: number; agentCount: number;
version: string;
} }
const getVersion = (version: Array<EuiComboBoxOptionOption<string>>) => version[0].value as string;
export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
onClose, onClose,
agents, agents,
agentCount, agentCount,
version,
}) => { }) => {
const { notifications } = useStartServices(); const { notifications } = useStartServices();
const kibanaVersion = useKibanaVersion();
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<string | undefined>();
const isSingleAgent = Array.isArray(agents) && agents.length === 1; const isSingleAgent = Array.isArray(agents) && agents.length === 1;
const isSmallBatch = Array.isArray(agents) && agents.length > 1 && agents.length <= 10;
const isAllAgents = agents === ''; const isAllAgents = agents === '';
const fallbackVersions = [kibanaVersion].concat(FALLBACK_VERSIONS);
const fallbackOptions: Array<EuiComboBoxOptionOption<string>> = fallbackVersions.map(
(option) => ({
label: option,
value: option,
})
);
const maintainanceWindows = isSmallBatch ? [0].concat(MAINTAINANCE_VALUES) : MAINTAINANCE_VALUES;
const maintainanceOptions: Array<EuiComboBoxOptionOption<number>> = maintainanceWindows.map(
(option) => ({
label:
option === 0
? i18n.translate('xpack.fleet.upgradeAgents.noMaintainanceWindowOption', {
defaultMessage: 'Immediately',
})
: i18n.translate('xpack.fleet.upgradeAgents.hourLabel', {
defaultMessage: '{option} {count, plural, one {hour} other {hours}}',
values: { option, count: option === 1 },
}),
value: option === 0 ? 0 : option * 3600,
})
);
const [selectedVersion, setSelectedVersion] = useState([fallbackOptions[0]]);
const [selectedMantainanceWindow, setSelectedMantainanceWindow] = useState([
maintainanceOptions[0],
]);
async function onSubmit() { async function onSubmit() {
const version = getVersion(selectedVersion);
const rolloutOptions =
selectedMantainanceWindow.length > 0 && (selectedMantainanceWindow[0]?.value as number) > 0
? {
rollout_duration_seconds: selectedMantainanceWindow[0].value,
}
: {};
try { try {
setIsSubmitting(true); setIsSubmitting(true);
const { data, error } = isSingleAgent const { data, error } = isSingleAgent
@ -42,10 +97,14 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
version, version,
}) })
: await sendPostBulkAgentUpgrade({ : await sendPostBulkAgentUpgrade({
agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents,
version, version,
agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents,
...rolloutOptions,
}); });
if (error) { if (error) {
if (error?.statusCode === 400) {
setErrors(error?.message);
}
throw error; throw error;
} }
@ -114,39 +173,20 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
<EuiConfirmModal <EuiConfirmModal
data-test-subj="agentUpgradeModal" data-test-subj="agentUpgradeModal"
title={ title={
<EuiFlexGroup alignItems="center" gutterSize="s"> <>
<EuiFlexItem grow={false}> {isSingleAgent ? (
{isSingleAgent ? ( <FormattedMessage
<FormattedMessage id="xpack.fleet.upgradeAgents.upgradeSingleTitle"
id="xpack.fleet.upgradeAgents.upgradeSingleTitle" defaultMessage="Upgrade agent to latest version"
defaultMessage="Upgrade agent to latest version"
/>
) : (
<FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeMultipleTitle"
defaultMessage="Upgrade {count, plural, one {agent} other {{count} agents} =true {all selected agents}} to latest version"
values={{ count: isAllAgents || agentCount }}
/>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBetaBadge
iconType="beaker"
label={
<FormattedMessage
id="xpack.fleet.upgradeAgents.experimentalLabel"
defaultMessage="Experimental"
/>
}
tooltipContent={
<FormattedMessage
id="xpack.fleet.upgradeAgents.experimentalLabelTooltip"
defaultMessage="Upgrade agent might change or be removed in a future release and is not subject to the support SLA."
/>
}
/> />
</EuiFlexItem> ) : (
</EuiFlexGroup> <FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeMultipleTitle"
defaultMessage="Upgrade {count, plural, one {agent} other {{count} agents} =true {all selected agents}} to latest version"
values={{ count: isAllAgents || agentCount }}
/>
)}
</>
} }
onCancel={onClose} onCancel={onClose}
onConfirm={onSubmit} onConfirm={onSubmit}
@ -179,17 +219,88 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
defaultMessage="This action will upgrade the agent running on '{hostName}' to version {version}. This action can not be undone. Are you sure you wish to continue?" defaultMessage="This action will upgrade the agent running on '{hostName}' to version {version}. This action can not be undone. Are you sure you wish to continue?"
values={{ values={{
hostName: ((agents[0] as Agent).local_metadata.host as any).hostname, hostName: ((agents[0] as Agent).local_metadata.host as any).hostname,
version, version: getVersion(selectedVersion),
}} }}
/> />
) : ( ) : (
<FormattedMessage <FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeMultipleDescription" id="xpack.fleet.upgradeAgents.upgradeMultipleDescription"
defaultMessage="This action will upgrade multiple agents to version {version}. This action can not be undone. Are you sure you wish to continue?" defaultMessage="This action will upgrade multiple agents to version {version}. This action can not be undone. Are you sure you wish to continue?"
values={{ version }} values={{ version: getVersion(selectedVersion) }}
/> />
)} )}
</p> </p>
<EuiSpacer size="m" />
<EuiFormRow
label={i18n.translate('xpack.fleet.upgradeAgents.chooseVersionLabel', {
defaultMessage: 'Upgrade version',
})}
fullWidth
>
<EuiComboBox
data-test-subj="agentUpgradeModal.VersionCombobox"
fullWidth
singleSelection={{ asPlainText: true }}
options={fallbackOptions}
selectedOptions={selectedVersion}
onChange={(selected: Array<EuiComboBoxOptionOption<string>>) => {
setSelectedVersion(selected);
}}
/>
</EuiFormRow>
<EuiSpacer size="m" />
{!isSingleAgent ? (
<EuiFormRow
label={
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
{i18n.translate('xpack.fleet.upgradeAgents.maintainanceAvailableLabel', {
defaultMessage: 'Maintainance window available',
})}
</EuiFlexItem>
<EuiSpacer size="xs" />
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
content={i18n.translate(
'xpack.fleet.upgradeAgents.maintainanceAvailableTooltip',
{
defaultMessage:
'Defines the duration of time available to perform the upgrade. The agent upgrades are spread uniformly across this duration in order to avoid exhausting network resources.',
}
)}
>
<EuiIcon type="iInCircle" title="TooltipIcon" />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
}
fullWidth
>
<EuiComboBox
data-test-subj="agentUpgradeModal.MaintainanceCombobox"
fullWidth
singleSelection={{ asPlainText: true }}
options={maintainanceOptions}
selectedOptions={selectedMantainanceWindow}
onChange={(selected: Array<EuiComboBoxOptionOption<number>>) => {
setSelectedMantainanceWindow(selected);
}}
/>
</EuiFormRow>
) : null}
{errors ? (
<>
<EuiCallOut
color="danger"
title={i18n.translate('xpack.fleet.upgradeAgents.warningCallout', {
defaultMessage:
'Error upgrading the selected {count, plural, one {agent} other {{count} agents}}',
values: { count: isSingleAgent },
})}
/>
</>
) : null}
</EuiConfirmModal> </EuiConfirmModal>
); );
}; };

View file

@ -13071,8 +13071,6 @@
"xpack.fleet.upgradeAgents.cancelButtonLabel": "Annuler", "xpack.fleet.upgradeAgents.cancelButtonLabel": "Annuler",
"xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "Mettre à niveau {count, plural, one {l'agent} other {{count} agents} =true {tous les agents sélectionnés}}", "xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "Mettre à niveau {count, plural, one {l'agent} other {{count} agents} =true {tous les agents sélectionnés}}",
"xpack.fleet.upgradeAgents.confirmSingleButtonLabel": "Mettre à niveau l'agent", "xpack.fleet.upgradeAgents.confirmSingleButtonLabel": "Mettre à niveau l'agent",
"xpack.fleet.upgradeAgents.experimentalLabel": "Expérimental",
"xpack.fleet.upgradeAgents.experimentalLabelTooltip": "Une modification ou une suppression de la mise à niveau de l'agent peut intervenir dans une version ultérieure. La mise à niveau n'est pas soumise à l'accord de niveau de service du support technique.",
"xpack.fleet.upgradeAgents.fatalErrorNotificationTitle": "Erreur lors de la mise à niveau de {count, plural, one {l'agent} other {{count} agents} =true {tous les agents sélectionnés}}", "xpack.fleet.upgradeAgents.fatalErrorNotificationTitle": "Erreur lors de la mise à niveau de {count, plural, one {l'agent} other {{count} agents} =true {tous les agents sélectionnés}}",
"xpack.fleet.upgradeAgents.successMultiNotificationTitle": "{isMixed, select, true {{success} agents sur {total}} other {{isAllAgents, select, true {Tous les agents sélectionnés} other {{success}} }}} mis à niveau", "xpack.fleet.upgradeAgents.successMultiNotificationTitle": "{isMixed, select, true {{success} agents sur {total}} other {{isAllAgents, select, true {Tous les agents sélectionnés} other {{success}} }}} mis à niveau",
"xpack.fleet.upgradeAgents.successSingleNotificationTitle": "{count} agent mis à niveau", "xpack.fleet.upgradeAgents.successSingleNotificationTitle": "{count} agent mis à niveau",

View file

@ -13178,8 +13178,6 @@
"xpack.fleet.upgradeAgents.cancelButtonLabel": "キャンセル", "xpack.fleet.upgradeAgents.cancelButtonLabel": "キャンセル",
"xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "{count, plural, other {{count}個のエージェント} =true {すべての選択されたエージェント}}をアップグレード", "xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "{count, plural, other {{count}個のエージェント} =true {すべての選択されたエージェント}}をアップグレード",
"xpack.fleet.upgradeAgents.confirmSingleButtonLabel": "エージェントをアップグレード", "xpack.fleet.upgradeAgents.confirmSingleButtonLabel": "エージェントをアップグレード",
"xpack.fleet.upgradeAgents.experimentalLabel": "実験的",
"xpack.fleet.upgradeAgents.experimentalLabelTooltip": "アップグレードエージェントは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。",
"xpack.fleet.upgradeAgents.fatalErrorNotificationTitle": "{count, plural, other {{count}個のエージェント} =true {すべての選択されたエージェント}}のアップグレードエラー", "xpack.fleet.upgradeAgents.fatalErrorNotificationTitle": "{count, plural, other {{count}個のエージェント} =true {すべての選択されたエージェント}}のアップグレードエラー",
"xpack.fleet.upgradeAgents.successMultiNotificationTitle": "{isMixed, select, true {{success}/{total}個の} other {{isAllAgents, select, true {すべての選択された} other {{success}} }}}エージェントをアップグレードしました", "xpack.fleet.upgradeAgents.successMultiNotificationTitle": "{isMixed, select, true {{success}/{total}個の} other {{isAllAgents, select, true {すべての選択された} other {{success}} }}}エージェントをアップグレードしました",
"xpack.fleet.upgradeAgents.successSingleNotificationTitle": "{count}個のエージェントをアップグレードしました", "xpack.fleet.upgradeAgents.successSingleNotificationTitle": "{count}個のエージェントをアップグレードしました",

View file

@ -13202,8 +13202,6 @@
"xpack.fleet.upgradeAgents.cancelButtonLabel": "取消", "xpack.fleet.upgradeAgents.cancelButtonLabel": "取消",
"xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "升级{count, plural, one {代理} other { {count} 个代理} =true {所有选定代理}}", "xpack.fleet.upgradeAgents.confirmMultipleButtonLabel": "升级{count, plural, one {代理} other { {count} 个代理} =true {所有选定代理}}",
"xpack.fleet.upgradeAgents.confirmSingleButtonLabel": "升级代理", "xpack.fleet.upgradeAgents.confirmSingleButtonLabel": "升级代理",
"xpack.fleet.upgradeAgents.experimentalLabel": "实验性",
"xpack.fleet.upgradeAgents.experimentalLabelTooltip": "在未来的版本中可能会更改或移除升级代理,其不受支持 SLA 的约束。",
"xpack.fleet.upgradeAgents.fatalErrorNotificationTitle": "升级{count, plural, one {代理} other { {count} 个代理} =true {所有选定代理}}时出错", "xpack.fleet.upgradeAgents.fatalErrorNotificationTitle": "升级{count, plural, one {代理} other { {count} 个代理} =true {所有选定代理}}时出错",
"xpack.fleet.upgradeAgents.successMultiNotificationTitle": "已升级{isMixed, select, true { {success} 个(共 {total} 个)} other {{isAllAgents, select, true {所有选定} other { {success} 个} }}}代理", "xpack.fleet.upgradeAgents.successMultiNotificationTitle": "已升级{isMixed, select, true { {success} 个(共 {total} 个)} other {{isAllAgents, select, true {所有选定} other { {success} 个} }}}代理",
"xpack.fleet.upgradeAgents.successSingleNotificationTitle": "已升级 {count} 个代理", "xpack.fleet.upgradeAgents.successSingleNotificationTitle": "已升级 {count} 个代理",