Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release-4.9] Bug 2066771: Enhance Insights widget empty states #11218

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -4,16 +4,19 @@
"important": "important",
"critical": "critical",
"Insights Advisor identifies and prioritizes risks to security, performance, availability, and stability of your clusters.": "Insights Advisor identifies and prioritizes risks to security, performance, availability, and stability of your clusters.",
"Temporary unavailable.": "Temporary unavailable.",
"Disabled or waiting for results.": "Disabled or waiting for results.",
"Temporarily unavailable.": "Temporarily unavailable.",
"Disabled.": "Disabled.",
"Waiting for results.": "Waiting for results.",
"Total issue": "Total issue",
"Total issue_plural": "Total issues",
"Total Risk": "Total Risk",
"Fixable issues": "Fixable issues",
"View all in OpenShift Cluster Manager": "View all in OpenShift Cluster Manager",
"Go to OpenShift Cluster Manager": "Go to OpenShift Cluster Manager",
"View all recommendations in Insights Advisor": "View all recommendations in Insights Advisor",
"View more in Insights Advisor": "View more in Insights Advisor",
"More about Insights": "More about Insights",
"Not available": "Not available",
"Disabled": "Disabled",
"Issues pending": "Issues pending",
"{{issuesNumber}} issue found": "{{issuesNumber}} issue found",
"{{issuesNumber}} issues found": "{{issuesNumber}} issues found",
"Insights": "Insights",
Expand Down
Expand Up @@ -10,8 +10,9 @@ import {
legendColorScale,
riskSorting,
mapMetrics,
isWaitingOrDisabled as _isWaitingOrDisabled,
isError as _isError,
isWaiting,
isDisabled,
isError,
} from './mappers';
import './style.scss';

Expand All @@ -21,16 +22,20 @@ const DataComponent: React.FC<DataComponentProps> = ({ x, y, datum }) => {
};

export const InsightsPopup: React.FC<PrometheusHealthPopupProps> = ({ responses, k8sResult }) => {
const [
{ response: metricsResponse, error: metricsError },
{ response: operatorStatusResponse, error: operatorStatusError },
] = responses;
const { t } = useTranslation();
const metrics = mapMetrics(responses[0].response);
const metrics = mapMetrics(metricsResponse);
const clusterID = (k8sResult as K8sResourceKind)?.data?.spec?.clusterID || '';
const riskEntries = Object.entries(metrics).sort(
([k1], [k2]) => riskSorting[k1] - riskSorting[k2],
);
const numberOfIssues = Object.values(metrics).reduce((acc, cur) => acc + cur, 0);

const isWaitingOrDisabled = _isWaitingOrDisabled(metrics);
const isError = _isError(metrics);
const waiting = isWaiting(metrics) || !metricsResponse || !operatorStatusResponse;
const error = isError(metrics) || metricsError || operatorStatusError;
const disabled = isDisabled(operatorStatusResponse);

const riskKeys = {
// t('insights-plugin~low')
Expand All @@ -50,18 +55,14 @@ export const InsightsPopup: React.FC<PrometheusHealthPopupProps> = ({ responses,
'insights-plugin~Insights Advisor identifies and prioritizes risks to security, performance, availability, and stability of your clusters.',
)}
</p>
{isError && (
<div className="co-status-popup__section">
{t('insights-plugin~Temporary unavailable.')}
</div>
)}
{isWaitingOrDisabled && (
<div className="co-status-popup__section">
{t('insights-plugin~Disabled or waiting for results.')}
</div>
)}
<div className="co-status-popup__section">
{!isWaitingOrDisabled && !isError && (
{error ? (
<p className="text-muted">{t('insights-plugin~Temporarily unavailable.')}</p>
) : disabled ? (
<p className="text-muted">{t('insights-plugin~Disabled.')}</p>
) : waiting ? (
<p className="text-muted">{t('insights-plugin~Waiting for results.')}</p>
) : (
<div>
<div>
<ChartDonut
data={riskEntries.map(([k, v]) => ({
Expand Down Expand Up @@ -99,35 +100,32 @@ export const InsightsPopup: React.FC<PrometheusHealthPopupProps> = ({ responses,
}}
/>
</div>
)}
</div>
<div className="co-status-popup__section">
{!isWaitingOrDisabled && !isError && clusterID && (
<>
<h6 className="pf-c-title pf-m-md">{t('insights-plugin~Fixable issues')}</h6>
{clusterID ? (
<>
<h6 className="pf-c-title pf-m-md">{t('insights-plugin~Fixable issues')}</h6>
<div>
<ExternalLink
href={`https://console.redhat.com/openshift/insights/advisor/clusters/${clusterID}`}
text={t('insights-plugin~View all recommendations in Insights Advisor')}
/>
</div>
</>
) : (
<div>
<ExternalLink
href={`https://console.redhat.com/openshift/details/${clusterID}#insights`}
text={t('insights-plugin~View all in OpenShift Cluster Manager')}
href={`https://console.redhat.com/openshift/insights/advisor`}
text={t('insights-plugin~View more in Insights Advisor')}
/>
</div>
</>
)}
{!isWaitingOrDisabled && !isError && !clusterID && (
<div>
<ExternalLink
href={`https://console.redhat.com/openshift/`}
text={t('insights-plugin~Go to OpenShift Cluster Manager')}
/>
</div>
)}
{(isWaitingOrDisabled || isError) && (
<ExternalLink
href={`${openshiftHelpBase}support/remote_health_monitoring/using-insights-to-identify-issues-with-your-cluster.html`}
text={t('insights-plugin~More about Insights')}
/>
)}
</div>
)}
</div>
)}
{(waiting || disabled || error) && (
<ExternalLink
href={`${openshiftHelpBase}support/remote_health_monitoring/using-insights-to-identify-issues-with-your-cluster.html`}
text={t('insights-plugin~More about Insights')}
/>
)}
</div>
);
};
Expand Down
Expand Up @@ -57,7 +57,9 @@ export const mapMetrics = (response: PrometheusResponse): Metrics => {
// An error occurred while requesting Insights results (e.g. IO is turned off)
export const isError = (values: Metrics) => _.isEmpty(values);

/* Insights Operator is disabled (e.g. pull-secret is removed) or has been
just initialized and waiting for the first results. */
export const isWaitingOrDisabled = (values: Metrics) =>
// Insights Operator has been just initialized and waiting for the first results
export const isWaiting = (values: Metrics) =>
Object.values(values).some((cur: number) => cur === -1);

// Insights Operator is disabled by removing the pull secret
export const isDisabled = (response) => !!parseInt(response?.data?.result?.[0]?.value?.[1], 10);
Expand Up @@ -3,31 +3,56 @@ import * as _ from 'lodash';
import { PrometheusResponse } from '@console/internal/components/graphs';
import { PrometheusHealthHandler, SubsystemHealth } from '@console/plugin-sdk';
import { HealthState } from '@console/shared/src/components/dashboard/status-card/states';
import { mapMetrics, isError, isWaitingOrDisabled } from './mappers';
import { mapMetrics, isError, isWaiting, isDisabled } from './mappers';

export const getClusterInsightsComponentStatus = (
response: PrometheusResponse,
error,
const getClusterInsightsComponentStatus = (
responses: {
response: PrometheusResponse;
error: any;
}[],
): SubsystemHealth => {
if (error) {
const [
{ response: metricsResponse, error: metricsError },
{ response: operatorStatusResponse, error: operatorStatusError },
] = responses;
const operatorDisabled = isDisabled(operatorStatusResponse);

// Unexpected error
if (metricsError || operatorStatusError) {
return {
state: HealthState.NOT_AVAILABLE,
message: i18next.t('insights-plugin~Not available'),
};
}
if (!response) {
return { state: HealthState.LOADING };
// Insights Operator is disabled
if (operatorDisabled) {
return {
state: HealthState.WARNING,
message: i18next.t('insights-plugin~Disabled'),
};
}
const values = mapMetrics(response);

// Initializing Insights Operator
if (!metricsResponse || !operatorStatusResponse) {
return {
state: HealthState.PROGRESS,
message: i18next.t('insights-plugin~Issues pending'),
};
}
const values = mapMetrics(metricsResponse);
// Malformed metrics values
if (isError(values)) {
return { state: HealthState.ERROR, message: i18next.t('insights-plugin~Not available') };
return {
state: HealthState.NOT_AVAILABLE,
message: i18next.t('insights-plugin~Not available'),
};
}

if (!isError(values) && isWaitingOrDisabled(values)) {
return { state: HealthState.UNKNOWN, message: i18next.t('insights-plugin~Not available') };
// Waiting for the first results
if (!isError(values) && isWaiting(values)) {
return {
state: HealthState.PROGRESS,
message: i18next.t('insights-plugin~Issues pending'),
};
}

// Insights Operator has sent rules results
const issuesNumber = Object.values(values).reduce((acc, cur) => acc + cur, 0);
const issueStr =
Expand All @@ -44,13 +69,11 @@ export const getClusterInsightsComponentStatus = (
};

export const getClusterInsightsStatus: PrometheusHealthHandler = (responses, t, cluster) => {
const componentHealth = getClusterInsightsComponentStatus(
responses[0].response,
responses[0].error,
);
if (componentHealth.state === HealthState.LOADING || !_.get(cluster, 'loaded')) {
return { state: HealthState.LOADING };
// Extra state for not yet loaded cluster
if (!_.get(cluster, 'loaded')) {
return {
state: HealthState.LOADING,
};
}

return componentHealth;
return getClusterInsightsComponentStatus(responses);
};
4 changes: 2 additions & 2 deletions frontend/packages/insights-plugin/src/plugin.tsx
Expand Up @@ -12,8 +12,8 @@ const plugin: Plugin<ConsumedExtensions> = [
// t('insights-plugin~Insights')
title: '%insights-plugin~Insights%',
queries: [
"health_statuses_insights{metric=~'low|moderate|important|critical'}",
'insightsclient_request_send_total',
'health_statuses_insights{metric=~"low|moderate|important|critical"}',
'cluster_operator_conditions{name="insights", condition="Disabled"}',
],
healthHandler: getClusterInsightsStatus,
additionalResource: {
Expand Down