[ILM] Moved error and loading notices for data allocation (#85154)
* moved error and loading notices for data allocation field to below description * removed test code * expect controls to be showing, only render notice after network request has finished * added loading spinner for field inputs Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
cb29438b0d
commit
f6c149f4f2
|
@ -556,6 +556,7 @@ describe('edit policy', () => {
|
|||
await setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy();
|
||||
expect(findTestSubject(rendered, 'warm-dataTierAllocationControls').exists()).toBeTruthy();
|
||||
expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy();
|
||||
expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy();
|
||||
});
|
||||
|
@ -684,6 +685,7 @@ describe('edit policy', () => {
|
|||
await setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy();
|
||||
expect(findTestSubject(rendered, 'cold-dataTierAllocationControls').exists()).toBeTruthy();
|
||||
expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy();
|
||||
expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy();
|
||||
});
|
||||
|
|
|
@ -142,7 +142,7 @@ const getSelectOptions = (phase: PhaseWithAllocation, disableDataTierOption: boo
|
|||
].filter(Boolean) as SelectOptions[];
|
||||
|
||||
export const DataTierAllocation: FunctionComponent<SharedProps> = (props) => {
|
||||
const { phase, hasNodeAttributes, disableDataTierOption } = props;
|
||||
const { phase, hasNodeAttributes, disableDataTierOption, isLoading } = props;
|
||||
|
||||
const dataTierAllocationTypePath = `_meta.${phase}.dataTierAllocationType`;
|
||||
|
||||
|
@ -170,6 +170,7 @@ export const DataTierAllocation: FunctionComponent<SharedProps> = (props) => {
|
|||
<SuperSelectField
|
||||
field={field}
|
||||
euiFieldProps={{
|
||||
isLoading,
|
||||
hasDividers: true,
|
||||
'data-test-subj': 'dataTierSelect',
|
||||
options: getSelectOptions(phase, disableDataTierOption),
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { NodesDataProvider } from './node_data_provider';
|
||||
|
||||
export { NodeAllocation } from './node_allocation';
|
||||
|
||||
export { NodeAttrsDetails } from './node_attrs_details';
|
||||
|
@ -17,3 +15,5 @@ export { DefaultAllocationNotice } from './default_allocation_notice';
|
|||
export { NoNodeAttributesWarning } from './no_node_attributes_warning';
|
||||
|
||||
export { CloudDataTierCallout } from './cloud_data_tier_callout';
|
||||
|
||||
export { LoadingError } from './loading_error';
|
||||
|
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCallOut, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
onResendRequest: () => void;
|
||||
statusCode?: string | number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const LoadingError: FunctionComponent<Props> = ({
|
||||
statusCode,
|
||||
message,
|
||||
onResendRequest,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.nodeAttributesLoadingFailedTitle"
|
||||
defaultMessage="Unable to load node data"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
>
|
||||
<p>
|
||||
{message} ({statusCode})
|
||||
</p>
|
||||
<EuiButton onClick={onResendRequest} iconType="refresh" color="danger">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.nodeAttributesReloadButton"
|
||||
defaultMessage="Try again"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -37,7 +37,7 @@ const i18nTexts = {
|
|||
),
|
||||
};
|
||||
|
||||
export const NodeAllocation: FunctionComponent<SharedProps> = ({ phase, nodes }) => {
|
||||
export const NodeAllocation: FunctionComponent<SharedProps> = ({ phase, nodes, isLoading }) => {
|
||||
const allocationNodeAttributePath = `_meta.${phase}.allocationNodeAttribute`;
|
||||
|
||||
const [formData] = useFormData({
|
||||
|
@ -98,6 +98,7 @@ export const NodeAllocation: FunctionComponent<SharedProps> = ({ phase, nodes })
|
|||
nodeOptions
|
||||
),
|
||||
hasNoInitialSelection: false,
|
||||
isLoading,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,70 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiCallOut, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { ListNodesRouteResponse } from '../../../../../../../../../common/types';
|
||||
import { useLoadNodes } from '../../../../../../../services/api';
|
||||
|
||||
interface Props {
|
||||
children: (data: ListNodesRouteResponse) => JSX.Element;
|
||||
}
|
||||
|
||||
export const NodesDataProvider = ({ children }: Props): JSX.Element => {
|
||||
const { isLoading, data, error, resendRequest } = useLoadNodes();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const renderError = () => {
|
||||
if (error) {
|
||||
const { statusCode, message } = error;
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
style={{ maxWidth: 400 }}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.nodeAttributesLoadingFailedTitle"
|
||||
defaultMessage="Unable to load node attributes"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
>
|
||||
<p>
|
||||
{message} ({statusCode})
|
||||
</p>
|
||||
<EuiButton onClick={resendRequest} iconType="refresh" color="danger">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.nodeAttributesReloadButton"
|
||||
defaultMessage="Try again"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderError()}
|
||||
{/* `data` will always be defined because we use an initial value when loading */}
|
||||
{children(data!)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -19,4 +19,8 @@ export interface SharedProps {
|
|||
* detected.
|
||||
*/
|
||||
disableDataTierOption: boolean;
|
||||
/**
|
||||
* A flag to indicate whether input fields should be showing a loading spinner
|
||||
*/
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
import { get } from 'lodash';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiDescribedFormGroup, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiDescribedFormGroup, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
import { useKibana, useFormData } from '../../../../../../../shared_imports';
|
||||
|
||||
|
@ -18,14 +17,16 @@ import { getAvailableNodeRoleForPhase } from '../../../../../../lib/data_tiers';
|
|||
|
||||
import { isNodeRoleFirstPreference } from '../../../../../../lib';
|
||||
|
||||
import { useLoadNodes } from '../../../../../../services/api';
|
||||
|
||||
import { DataTierAllocationType } from '../../../../types';
|
||||
|
||||
import {
|
||||
DataTierAllocation,
|
||||
DefaultAllocationNotice,
|
||||
NoNodeAttributesWarning,
|
||||
NodesDataProvider,
|
||||
CloudDataTierCallout,
|
||||
LoadingError,
|
||||
} from './components';
|
||||
|
||||
import './_data_tier_allocation.scss';
|
||||
|
@ -53,85 +54,101 @@ export const DataTierAllocationField: FunctionComponent<Props> = ({ phase, descr
|
|||
const [formData] = useFormData({ watch: dataTierAllocationTypePath });
|
||||
const allocationType: DataTierAllocationType = get(formData, dataTierAllocationTypePath);
|
||||
|
||||
return (
|
||||
<NodesDataProvider>
|
||||
{({ nodesByRoles, nodesByAttributes, isUsingDeprecatedDataRoleConfig }) => {
|
||||
const hasDataNodeRoles = Object.keys(nodesByRoles).some((nodeRole) =>
|
||||
// match any of the "data_" roles, including data_content.
|
||||
nodeRole.trim().startsWith('data_')
|
||||
);
|
||||
const hasNodeAttrs = Boolean(Object.keys(nodesByAttributes ?? {}).length);
|
||||
const isCloudEnabled = cloud?.isCloudEnabled ?? false;
|
||||
const { data, resendRequest, error, isLoading } = useLoadNodes();
|
||||
|
||||
const renderNotice = () => {
|
||||
switch (allocationType) {
|
||||
case 'node_roles':
|
||||
if (isCloudEnabled && phase === 'cold') {
|
||||
const isUsingNodeRolesAllocation =
|
||||
!isUsingDeprecatedDataRoleConfig && hasDataNodeRoles;
|
||||
const hasNoNodesWithNodeRole = !nodesByRoles.data_cold?.length;
|
||||
const { nodesByRoles, nodesByAttributes, isUsingDeprecatedDataRoleConfig } = data!;
|
||||
|
||||
if (isUsingNodeRolesAllocation && hasNoNodesWithNodeRole) {
|
||||
// Tell cloud users they can deploy nodes on cloud.
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<CloudDataTierCallout />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
const hasDataNodeRoles = Object.keys(nodesByRoles).some((nodeRole) =>
|
||||
// match any of the "data_" roles, including data_content.
|
||||
nodeRole.trim().startsWith('data_')
|
||||
);
|
||||
const hasNodeAttrs = Boolean(Object.keys(nodesByAttributes ?? {}).length);
|
||||
const isCloudEnabled = cloud?.isCloudEnabled ?? false;
|
||||
|
||||
const allocationNodeRole = getAvailableNodeRoleForPhase(phase, nodesByRoles);
|
||||
if (
|
||||
allocationNodeRole === 'none' ||
|
||||
!isNodeRoleFirstPreference(phase, allocationNodeRole)
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<DefaultAllocationNotice phase={phase} targetNodeRole={allocationNodeRole} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'node_attrs':
|
||||
if (!hasNodeAttrs) {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<NoNodeAttributesWarning phase={phase} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
const renderNotice = () => {
|
||||
switch (allocationType) {
|
||||
case 'node_roles':
|
||||
if (isCloudEnabled && phase === 'cold') {
|
||||
const isUsingNodeRolesAllocation = !isUsingDeprecatedDataRoleConfig && hasDataNodeRoles;
|
||||
const hasNoNodesWithNodeRole = !nodesByRoles.data_cold?.length;
|
||||
|
||||
if (isUsingNodeRolesAllocation && hasNoNodesWithNodeRole) {
|
||||
// Tell cloud users they can deploy nodes on cloud.
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<CloudDataTierCallout />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={<h3>{i18nTexts.title}</h3>}
|
||||
description={description}
|
||||
fullWidth
|
||||
>
|
||||
<div className="ilmDataTierAllocationField">
|
||||
<DataTierAllocation
|
||||
hasNodeAttributes={hasNodeAttrs}
|
||||
phase={phase}
|
||||
nodes={nodesByAttributes}
|
||||
disableDataTierOption={Boolean(
|
||||
isCloudEnabled && !hasDataNodeRoles && isUsingDeprecatedDataRoleConfig
|
||||
)}
|
||||
const allocationNodeRole = getAvailableNodeRoleForPhase(phase, nodesByRoles);
|
||||
if (
|
||||
allocationNodeRole === 'none' ||
|
||||
!isNodeRoleFirstPreference(phase, allocationNodeRole)
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<DefaultAllocationNotice phase={phase} targetNodeRole={allocationNodeRole} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'node_attrs':
|
||||
if (!hasNodeAttrs) {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<NoNodeAttributesWarning phase={phase} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={<h3>{i18nTexts.title}</h3>}
|
||||
description={
|
||||
<>
|
||||
{description}
|
||||
{isLoading ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</>
|
||||
) : (
|
||||
error && (
|
||||
<LoadingError
|
||||
onResendRequest={resendRequest}
|
||||
message={error.message}
|
||||
statusCode={error.statusCode}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<div className="ilmDataTierAllocationField">
|
||||
<DataTierAllocation
|
||||
hasNodeAttributes={hasNodeAttrs}
|
||||
phase={phase}
|
||||
nodes={nodesByAttributes}
|
||||
disableDataTierOption={Boolean(
|
||||
isCloudEnabled && !hasDataNodeRoles && isUsingDeprecatedDataRoleConfig
|
||||
)}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
{/* Data tier related warnings and call-to-action notices */}
|
||||
{renderNotice()}
|
||||
</div>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
}}
|
||||
</NodesDataProvider>
|
||||
{/* Data tier related warnings and call-to-action notices */}
|
||||
{!isLoading && renderNotice()}
|
||||
</div>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue