Skip to content

Commit

Permalink
feat(pci.savings-plan): view percentage
Browse files Browse the repository at this point in the history
ref: TAPC-1779
Signed-off-by: Eric Ciccotti <[email protected]>
  • Loading branch information
Eric Ciccotti committed Jan 31, 2025
1 parent ebbcb15 commit 1edad29
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

"dashboard_kpis_non_discounted_name": "Montant de facture non remisé",
"dashboard_kpis_non_discounted_tooltip": "Montant de la facture dépassant vos configurations Savings Plans (sur la période sélectionnée pour la ressource sélectionnée)",

"dashboard_kpis_not_available": "Non disponible",
"dashboard_select_label_resource": "Service",
"dashboard_select_label_flavor": "Gamme d'instance",
"dashboard_select_label_model": "Modèle",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const savingsPlanConsumptionMocked: SavingsPlanConsumption = {
'c63901e0-d16d-485d-b856-907eb26170fc',
'fef7a43c-4a4b-42c4-8b3c-31b7e7679fd7',
],
utilization: '100%',
usage: '100%',
},
{
savings_plans_ids: null,
Expand All @@ -49,15 +49,15 @@ export const savingsPlanConsumptionMocked: SavingsPlanConsumption = {
'c63901e0-d16d-485d-b856-907eb26170fc',
'fef7a43c-4a4b-42c4-8b3c-31b7e7679fd7',
],
utilization: '100%',
usage: '100%',
},
{
savings_plans_ids: null,
begin: '2025-01-17T07:00:00.000Z',
coverage: '100%',
end: '2025-01-17T08:54:59.912Z',
resource_ids: null,
utilization: '100%',
usage: '100%',
},
],
subscriptions: [
Expand Down Expand Up @@ -129,7 +129,7 @@ export const savingsPlanConsumptionMocked: SavingsPlanConsumption = {
'c76cda57-abf7-4c87-ac49-2e71fee6dc27',
'96cd7052-33ea-4cec-85c1-11a1a4f78887',
],
utilization: '100%',
usage: '100%',
},
{
savings_plans_ids: null,
Expand All @@ -146,15 +146,15 @@ export const savingsPlanConsumptionMocked: SavingsPlanConsumption = {
'c76cda57-abf7-4c87-ac49-2e71fee6dc27',
'96cd7052-33ea-4cec-85c1-11a1a4f78887',
],
utilization: '100%',
usage: '100%',
},
{
savings_plans_ids: null,
begin: '2025-01-17T07:00:00.000Z',
coverage: '100%',
end: '2025-01-17T08:54:59.912Z',
resource_ids: null,
utilization: '100%',
usage: '100%',
},
],
subscriptions: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
OdsIcon,
OdsSkeleton,
} from '@ovhcloud/ods-components/react';
import React from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { calculateAverageUsage } from '@/utils/kpi/utils';
import { SavingsPlanConsumption } from '@/types/savingsPlanConsumption.type';

const Kpi = ({
title,
Expand All @@ -16,11 +18,12 @@ const Kpi = ({
isLoading,
}: {
title: string;
value: number;
value: number | null;
tooltip: string;
index: number;
isLoading: boolean;
}) => {
const { t } = useTranslation('dashboard');
if (isLoading)
return (
<div className="h-16 w-[200px] mx-2 flex flex-col justify-center">
Expand All @@ -46,14 +49,25 @@ const Kpi = ({
</OdsText>
</OdsTooltip>
</OdsText>
<span className="text-[20px] font-bold mb-0 text-[#0050D7]">{value}</span>
<span className="text-[20px] font-bold mb-0 text-[#0050D7]">
{value !== null ? value.toFixed(2) : t('dashboard_kpis_not_available')}
</span>
</div>
);
};

const Kpis = ({ isLoading }: Readonly<{ isLoading: boolean }>) => {
const Kpis = ({
isLoading,
consumption,
}: Readonly<{
isLoading: boolean;
consumption: SavingsPlanConsumption;
}>) => {
const { t } = useTranslation('dashboard');
const data = [
const computedUsagePercent = useMemo(() => {
return calculateAverageUsage(consumption);
}, [consumption]);
const kpiData = [
{
title: t('dashboard_kpis_active_plans_name'),
tooltip: t('dashboard_kpis_active_plans_tooltip'),
Expand All @@ -62,18 +76,18 @@ const Kpis = ({ isLoading }: Readonly<{ isLoading: boolean }>) => {
{
title: t('dashboard_kpis_usage_percent_name'),
tooltip: t('dashboard_kpis_usage_percent_tooltip'),
value: 12,
value: computedUsagePercent,
},
{
title: t('dashboard_kpis_coverage_percent_name'),
tooltip: t('dashboard_kpis_coverage_percent_tooltip'),
value: 5,
value: 10,
},
];

return (
<div className="flex flex-row gap-4 items-center mt-7">
{data.map((item, index) => (
{kpiData.map((item, index) => (
<React.Fragment key={item.title}>
<Kpi
title={item.title}
Expand All @@ -82,7 +96,7 @@ const Kpis = ({ isLoading }: Readonly<{ isLoading: boolean }>) => {
index={index}
isLoading={isLoading}
/>
{index < data.length - 1 && (
{index < kpiData.length - 1 && (
<div className="h-16 w-px bg-gray-300 mx-2"></div>
)}
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const getSavingsPlanConsumption = async (
projectId: string,
): Promise<SavingsPlanConsumption> => {
const response = await fetch(
// TODO: replace with the correct url when it will be available
`https://localhost:8080/uservice/gateway/rating/plans/${projectId}?flavor=b3-8`,
// TODO: replace with the correct url when it will be available, we use proxy instead
`http://localhost:8080/proxy`,
// `http://localhost:8080/uservice/gateway/rating/plans/${projectId}?flavor=b3-16`,
);
return response.json();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,30 @@ import { getBigestActiveSavingsPlan } from '@/utils/savingsPlan';
const Dashboard: React.FC = () => {
const { projectId } = useParams();
const navigate = useNavigate();
const { data: savingsPlan, isLoading, isPending } = useSavingsPlan();
const { environment } = useContext(ShellContext);
const locale = environment.getUserLocale();
const { data: consumption } = useSavingsPlanConsumption();
const {
data: savingsPlan,
isLoading: isLoadingSavingsPlan,
isPending: isPendingSavingsPlan,
} = useSavingsPlan();

const {
data: consumption,
isLoading: isLoadingConsumption,
} = useSavingsPlanConsumption();

const { t } = useTranslation(['dashboard']);

useEffect(() => {
if (!isLoading && !isPending && savingsPlan?.length === 0) {
if (
!isLoadingSavingsPlan &&
!isPendingSavingsPlan &&
savingsPlan?.length === 0
) {
navigate(`/pci/projects/${projectId}/savings-plan/onboarding`);
}
}, [isLoading, isPending, savingsPlan]);
}, [isLoadingSavingsPlan, isPendingSavingsPlan, savingsPlan]);

const currentPlan = getBigestActiveSavingsPlan(savingsPlan);

Expand All @@ -43,10 +56,10 @@ const Dashboard: React.FC = () => {
defaultFilter={currentPlan}
savingsPlan={savingsPlan}
locale={locale}
isLoading={isLoading}
isLoading={isLoadingConsumption}
/>
<Kpis isLoading={isLoading} />
<ConsumptionDatagrid isLoading={isLoading} />
<Kpis isLoading={isLoadingConsumption} consumption={consumption} />
<ConsumptionDatagrid isLoading={isLoadingConsumption} />
</>
);
};
Expand Down
46 changes: 46 additions & 0 deletions packages/manager/apps/pci-savings-plan/src/utils/kpi/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest';
import { calculateAverageUsage } from './utils';
import { SavingsPlanConsumption } from '@/types/savingsPlanConsumption.type';

describe('calculateAverageUsage', () => {
const testCases = [
{
description: 'should return null when no periods exist in any flavor',
input: { flavors: [{ periods: [] }, { periods: [] }] },
expected: null,
},
{
description:
'should correctly calculate the average utilization across multiple periods',
input: {
flavors: [
{ periods: [{ utilization: '40%' }, { utilization: '60%' }] },
{ periods: [{ utilization: '80%' }] },
],
},
expected: 60.0,
},
{
description: 'should handle a single period correctly',
input: { flavors: [{ periods: [{ utilization: '75%' }] }] },
expected: 75.0,
},
{
description: 'should ignore invalid values and calculate correctly',
input: {
flavors: [
{ periods: [{ utilization: '50%' }, { utilization: 'N/A' }] },
],
},
expected: 50.0,
},
];

testCases.forEach(({ description, input, expected }) => {
it(description, () => {
expect(calculateAverageUsage(input as SavingsPlanConsumption)).toBe(
expected,
);
});
});
});
25 changes: 25 additions & 0 deletions packages/manager/apps/pci-savings-plan/src/utils/kpi/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SavingsPlanConsumption } from '@/types/savingsPlanConsumption.type';

export const calculateAverageUsage = (
consumption: SavingsPlanConsumption | null,
): number | null => {
if (!consumption.flavors.some(({ periods }) => periods.length > 0)) {
return null;
}

const utilizationValues = consumption.flavors.flatMap(({ periods }) =>
periods
.map(({ utilization }) => parseFloat(utilization.replace('%', '')))
.filter((value) => !Number.isNaN(value)),
);

if (!utilizationValues.length) {
return null;
}

const averageUtilization =
utilizationValues.reduce((sum, value) => sum + value, 0) /
utilizationValues.length;

return averageUtilization;
};

0 comments on commit 1edad29

Please sign in to comment.