diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json
index e22462533..05f140401 100644
--- a/locales/en/plugin__odf-console.json
+++ b/locales/en/plugin__odf-console.json
@@ -559,14 +559,15 @@
"Select a namespace:": "Select a namespace:",
"Only showing PVCs that are being mounted on an active pod": "Only showing PVCs that are being mounted on an active pod",
"This card shows the requested capacity for different Kubernetes resources. The figures shown represent the usable storage, meaning that data replication is not taken into consideration.": "This card shows the requested capacity for different Kubernetes resources. The figures shown represent the usable storage, meaning that data replication is not taken into consideration.",
- "The {{estimationName}} is a rough estimation. The calculation is based on the data gathered on day to day basis.": "The {{estimationName}} is a rough estimation. The calculation is based on the data gathered on day to day basis.",
+ "<0><0>Understand terms0><1>Net storage consumption1><2>Indicates the daily net change in storage capacity.2><3>Average storage consumption 3><4>Refers to the amount of data used over a specified period. A positive average indicates how quickly the cluster is filling up, while a negative average indicates the rate at which the cluster is clearing up.4><5>Estimated days until full ** 5><6>Indicates the number of days remaining before a storage system reaches its maximum capacity based on current usage trends.6><7>Calculations for above metrics are based on the data gathered day to day basis. **This is only a rough estimation These calculations are based on the data gathered on day to day basis7>0>": "<0><0>Understand terms0><1>Net storage consumption1><2>Indicates the daily net change in storage capacity.2><3>Average storage consumption 3><4>Refers to the amount of data used over a specified period. A positive average indicates how quickly the cluster is filling up, while a negative average indicates the rate at which the cluster is clearing up.4><5>Estimated days until full ** 5><6>Indicates the number of days remaining before a storage system reaches its maximum capacity based on current usage trends.6><7>Calculations for above metrics are based on the data gathered day to day basis. **This is only a rough estimation These calculations are based on the data gathered on day to day basis7>0>",
"Consumption trend": "Consumption trend",
- "Storage consumption per day": "Storage consumption per day",
+ "Storage consumption": "Storage consumption",
"Over the past {{daysUp}} ": "Over the past {{daysUp}} ",
"day": "day",
- "Average consumption": "Average consumption",
+ "Net storage consumption": "Net storage consumption",
+ "Average storage consumption": "Average storage consumption",
"Estimated days until full": "Estimated days until full",
- "number of days left": "number of days left",
+ "Understanding these terms": "Understanding these terms",
"Internal": "Internal",
"Raw capacity is the absolute total disk space available to the array subsystem.": "Raw capacity is the absolute total disk space available to the array subsystem.",
"Active health checks": "Active health checks",
diff --git a/packages/ocs/dashboards/persistent-internal/capacity-trend-card/capacity-trend-card.tsx b/packages/ocs/dashboards/persistent-internal/capacity-trend-card/capacity-trend-card.tsx
index 613de700d..12ae5b34c 100644
--- a/packages/ocs/dashboards/persistent-internal/capacity-trend-card/capacity-trend-card.tsx
+++ b/packages/ocs/dashboards/persistent-internal/capacity-trend-card/capacity-trend-card.tsx
@@ -17,7 +17,7 @@ import {
import { ConfigMapModel } from '@odf/shared/models';
import { ConfigMapKind } from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
-import { humanizeBinaryBytes, parser } from '@odf/shared/utils';
+import { humanizeBinaryBytesWithNegatives, parser } from '@odf/shared/utils';
import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
import { TFunction } from 'i18next';
import { Trans } from 'react-i18next';
@@ -29,12 +29,14 @@ import {
CardTitle,
Flex,
FlexItem,
+ PopoverPosition,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import {
CAPACITY_TREND_QUERIES,
+ CEPH_CAPACITY_BREAKDOWN_QUERIES,
StorageDashboardQuery,
} from '../../../queries/ceph-storage';
import { ODFSystemParams } from '../../../types';
@@ -50,11 +52,38 @@ const calculateDaysUp = (timespan: number): number | null => {
return Math.ceil(daysPassed); // If days passed is half a day or more, round up to the nearest whole day
};
-const roughEstimationToolTip = (t: TFunction, estimationName: string) => {
+const understandingToolTip = (t: TFunction) => {
return (
- The {{ estimationName }} is a rough estimation. The calculation is based
- on the data gathered on day to day basis.
+
+ Understand terms
+ Net storage consumption
+
+
+ Indicates the daily net change in storage capacity.
+
+
+ Average storage consumption
+
+
+ Refers to the amount of data used over a specified period. A positive
+ average indicates how quickly the cluster is filling up, while a
+ negative average indicates the rate at which the cluster is clearing
+ up.
+
+ Estimated days until full **
+
+
+ Indicates the number of days remaining before a storage system reaches
+ its maximum capacity based on current usage trends.
+
+
+
+ Calculations for above metrics are based on the data gathered day to
+ day basis. **This is only a rough estimation These calculations are
+ based on the data gathered on day to day basis
+
+
);
};
@@ -115,9 +144,26 @@ const CapacityTrendCard: React.FC = () => {
basePath: usePrometheusBasePath(),
});
- const loadError = availableCapacityError || totalUtilError || uptimeError;
+ const [totalCapacity, totalCapacityError, totalCapacityLoading] =
+ useCustomPrometheusPoll({
+ query:
+ CEPH_CAPACITY_BREAKDOWN_QUERIES[
+ StorageDashboardQuery.CEPH_CAPACITY_TOTAL
+ ],
+ endpoint: 'api/v1/query' as any,
+ basePath: usePrometheusBasePath(),
+ });
+
+ const loadError =
+ availableCapacityError ||
+ totalUtilError ||
+ uptimeError ||
+ totalCapacityError;
const loading =
- availableCapacityLoading || totalUtilLoading || uptimeUtilLoading;
+ availableCapacityLoading ||
+ totalUtilLoading ||
+ uptimeUtilLoading ||
+ totalCapacityLoading;
let daysUp: number;
if (!loading && !loadError) {
@@ -125,18 +171,31 @@ const CapacityTrendCard: React.FC = () => {
}
const totalUtilMetric = parser(totalUtil);
+ const totalCapacityMetric = parser(totalCapacity);
const avgUtilMetric =
!!daysUp && !!totalUtilMetric
? totalUtilMetric / Math.ceil(daysUp) // do a ceil function, if it's a new cluster (dayUp < 1)
: 0;
- const avgUtilMetricByte = humanizeBinaryBytes(avgUtilMetric.toFixed(0));
+ const avgUtilMetricByte = humanizeBinaryBytesWithNegatives(
+ avgUtilMetric.toFixed(0)
+ );
const availableCapacityMetric = parser(availableCapacity);
- const daysLeft =
+ let daysLeft =
!!availableCapacityMetric && avgUtilMetric
? Math.floor(availableCapacityMetric / avgUtilMetric)
: 0;
+ if (daysLeft < 0) {
+ // Clean up negative average values, if a user empties a cluster the average value goes to negative we need to make
+ // sure that estimated days left takes into account the average rate of filling up the cluster.
+ // This will give better information about estimated days
+ const clusterCleanUpToZeroDays =
+ (totalCapacityMetric - availableCapacityMetric) / Math.abs(avgUtilMetric);
+ const clusterFillUpToMaxDays =
+ totalCapacityMetric / Math.abs(avgUtilMetric);
+ daysLeft = clusterCleanUpToZeroDays + clusterFillUpToMaxDays;
+ }
return (
@@ -149,7 +208,7 @@ const CapacityTrendCard: React.FC = () => {
- {t('Storage consumption per day')}
+ {t('Storage consumption')}
{t('Over the past {{daysUp}} ', {
@@ -160,12 +219,13 @@ const CapacityTrendCard: React.FC = () => {
{
: undefined
}
chartType="grouped-line"
- showLegend={false}
+ showLegend={true}
timespan={daysUp * 24 * 60 * 60 * 1000}
+ showHumanizedInLegend={true}
/>
-
+
+
+
- {t('Average consumption')}
+ {t('Average storage consumption')}
- {avgUtilMetricByte.string} / {t('day')}
+ {avgUtilMetricByte.string}
-
-
{t('Estimated days until full')}
-
- {roughEstimationToolTip(t, t('number of days left'))}
-
{daysLeft} {pluralize(daysUp, t('day'), t('days'), false)}
+
+
+ {understandingToolTip(t)}
+
+
)}
{loading && !loadError && (
diff --git a/packages/shared/src/dashboards/utilization-card/area-chart.tsx b/packages/shared/src/dashboards/utilization-card/area-chart.tsx
index 93ae49fe5..105bce8a4 100644
--- a/packages/shared/src/dashboards/utilization-card/area-chart.tsx
+++ b/packages/shared/src/dashboards/utilization-card/area-chart.tsx
@@ -41,6 +41,7 @@ export const AreaChart: React.FC = ({
tickCount = 4,
height = 200,
showLegend,
+ legendComponent,
}) => {
const [containerRef, width] = useRefWidth();
@@ -146,6 +147,7 @@ export const AreaChart: React.FC = ({
padding={{ top: 20, left: 70, bottom: 60, right: 0 }}
legendData={chartType && showLegend ? legendData : null}
legendPosition="bottom-left"
+ legendComponent={legendComponent}
>
{xAxis && (
@@ -194,4 +196,5 @@ export type AreaChartProps = {
tickCount?: number;
height?: number;
showLegend?: boolean;
+ legendComponent?: React.ReactElement;
};
diff --git a/packages/shared/src/dashboards/utilization-card/prometheus-utilization-item.tsx b/packages/shared/src/dashboards/utilization-card/prometheus-utilization-item.tsx
index 0111037ee..3aec4263e 100644
--- a/packages/shared/src/dashboards/utilization-card/prometheus-utilization-item.tsx
+++ b/packages/shared/src/dashboards/utilization-card/prometheus-utilization-item.tsx
@@ -44,6 +44,7 @@ export const PrometheusUtilizationItem: React.FC
CustomUtilizationSummary,
formatDate,
timespan,
+ showHumanizedInLegend,
}) => {
const { duration } = useUtilizationDuration();
const defaultBasePath = usePrometheusBasePath();
@@ -94,6 +95,7 @@ export const PrometheusUtilizationItem: React.FC
showLegend={showLegend}
CustomUtilizationSummary={CustomUtilizationSummary}
formatDate={formatDate}
+ showHumanizedInLegend={showHumanizedInLegend}
/>
);
};
@@ -175,4 +177,5 @@ type PrometheusUtilizationItemProps = PrometheusCommonProps & {
CustomUtilizationSummary?: React.FC;
formatDate?: (date: Date, showSeconds?: boolean) => string;
timespan?: number;
+ showHumanizedInLegend?: boolean;
};
diff --git a/packages/shared/src/dashboards/utilization-card/utilization-item.tsx b/packages/shared/src/dashboards/utilization-card/utilization-item.tsx
index bfb4239e2..8443497be 100644
--- a/packages/shared/src/dashboards/utilization-card/utilization-item.tsx
+++ b/packages/shared/src/dashboards/utilization-card/utilization-item.tsx
@@ -13,6 +13,7 @@ import {
import { ByteDataTypes } from '@openshift-console/dynamic-plugin-sdk/lib/api/internal-types';
import { global_danger_color_100 as dangerColor } from '@patternfly/react-tokens/dist/js/global_danger_color_100';
import { global_warning_color_100 as warningColor } from '@patternfly/react-tokens/dist/js/global_warning_color_100';
+import { ChartThemeColor, ChartLegend } from '@patternfly/react-charts';
import { useCustomTranslation } from '../../useCustomTranslationHook';
enum LIMIT_STATE {
@@ -59,6 +60,7 @@ export const UtilizationItem: React.FC = React.memo(
showLegend,
CustomUtilizationSummary,
formatDate,
+ showHumanizedInLegend,
}) => {
const { t } = useCustomTranslation();
const { data, chartStyle } = mapLimitsRequests({
@@ -92,6 +94,14 @@ export const UtilizationItem: React.FC = React.memo(
humanAvailable = humanizeValue(max - current).string;
}
+ let humanizedLegend: JSX.Element;
+ if (!!showHumanizedInLegend && current)
+ humanizedLegend = (
+
+ );
const chart = (
= React.memo(
mainDataName="usage"
showLegend={showLegend}
formatDate={formatDate}
+ legendComponent={humanizedLegend}
/>
);
@@ -277,4 +288,5 @@ type UtilizationItemProps = {
hideHorizontalBorder?: boolean;
CustomUtilizationSummary?: React.FC;
formatDate?: (date: Date, showSeconds?: boolean) => string;
+ showHumanizedInLegend?: boolean;
};
diff --git a/packages/shared/src/generic/FieldLevelHelp.tsx b/packages/shared/src/generic/FieldLevelHelp.tsx
index 54eb0ef05..1410e209a 100644
--- a/packages/shared/src/generic/FieldLevelHelp.tsx
+++ b/packages/shared/src/generic/FieldLevelHelp.tsx
@@ -5,7 +5,7 @@ import { useCustomTranslation } from '../useCustomTranslationHook';
import './field-level-help.scss';
export const FieldLevelHelp: React.FC = React.memo(
- ({ children, popoverHasAutoWidth, testId, position }) => {
+ ({ children, popoverHasAutoWidth, testId, position, buttonText }) => {
const { t } = useCustomTranslation();
if (React.Children.count(children) === 0) {
return null;
@@ -24,6 +24,7 @@ export const FieldLevelHelp: React.FC = React.memo(
className="odf-field-level-help"
data-test-id={testId || null}
>
+ {!!buttonText && buttonText + ' '}
@@ -38,4 +39,5 @@ type FieldLevelHelpProps = {
popoverHasAutoWidth?: PopoverProps['hasAutoWidth'];
testId?: string;
position?: PopoverProps['position'];
+ buttonText?: string;
};
diff --git a/packages/shared/src/utils/humanize.js b/packages/shared/src/utils/humanize.js
index 0f0d04e5c..db0dfa1e8 100644
--- a/packages/shared/src/utils/humanize.js
+++ b/packages/shared/src/utils/humanize.js
@@ -68,11 +68,14 @@ const convertBaseValueToUnits = (
unitArray,
divisor,
initialUnit,
- preferredUnit
+ preferredUnit,
+ humanizeNegative
) => {
const sliceIndex = initialUnit ? unitArray.indexOf(initialUnit) : 0;
const units_ = unitArray.slice(sliceIndex);
+ const shouldHumanizeNegative = humanizeNegative && value < 0;
+
if (preferredUnit || preferredUnit === '') {
const unitIndex = units_.indexOf(preferredUnit);
if (unitIndex !== -1) {
@@ -83,11 +86,18 @@ const convertBaseValueToUnits = (
}
}
+ if (shouldHumanizeNegative) {
+ value = Math.abs(value);
+ }
+
let unit = units_.shift();
while (value >= divisor && units_.length > 0) {
value = value / divisor;
unit = units_.shift();
}
+ if (shouldHumanizeNegative) {
+ value *= -1;
+ }
return { value, unit };
};
@@ -167,7 +177,8 @@ const humanize = (units.humanize = (
typeName,
useRound = false,
initialUnit,
- preferredUnit
+ preferredUnit,
+ humanizedNegative = false
) => {
const type = getType(typeName);
@@ -180,7 +191,8 @@ const humanize = (units.humanize = (
type.units,
type.divisor,
initialUnit,
- preferredUnit
+ preferredUnit,
+ humanizedNegative
);
if (useRound) {
@@ -190,7 +202,8 @@ const humanize = (units.humanize = (
type.units,
type.divisor,
converted.unit,
- preferredUnit
+ preferredUnit,
+ humanizedNegative
);
}
@@ -220,6 +233,11 @@ export const humanizeBinaryBytesWithoutB = (v, initialUnit, preferredUnit) =>
humanize(v, 'binaryBytesWithoutB', true, initialUnit, preferredUnit);
export const humanizeBinaryBytes = (v, initialUnit, preferredUnit) =>
humanize(v, 'binaryBytes', true, initialUnit, preferredUnit);
+export const humanizeBinaryBytesWithNegatives = (
+ v,
+ initialUnit,
+ preferredUnit
+) => humanize(v, 'binaryBytes', true, initialUnit, preferredUnit, true);
export const humanizeDecimalBytes = (v, initialUnit, preferredUnit) =>
humanize(v, 'decimalBytes', true, initialUnit, preferredUnit);
export const humanizeDecimalBytesPerSec = (v, initialUnit, preferredUnit) =>