@electrolux-oss/plugin-infrawallet 1.1.0-20251122142002-7ac6f9a → 1.1.0-20251202133021-aade8f1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/functions.esm.js +3 -1
- package/dist/api/functions.esm.js.map +1 -1
- package/dist/components/Budgets/Budgets.esm.js +130 -69
- package/dist/components/Budgets/Budgets.esm.js.map +1 -1
- package/dist/hooks/useInfraWalletLuceneParams.esm.js +9 -1
- package/dist/hooks/useInfraWalletLuceneParams.esm.js.map +1 -1
- package/package.json +1 -1
|
@@ -205,6 +205,7 @@ const calculateBudgetAnalytics = (monthlyCosts, annualBudget) => {
|
|
|
205
205
|
const daysIntoCurrentMonth = moment().date();
|
|
206
206
|
const daysInCurrentMonth = moment().daysInMonth();
|
|
207
207
|
let yearToDateSpent = 0;
|
|
208
|
+
let projectedCurrentMonthCost = 0;
|
|
208
209
|
const monthlySpending = [];
|
|
209
210
|
for (let month = 1; month <= currentMonth; month++) {
|
|
210
211
|
const monthKey = `${currentYear}-${month.toString().padStart(2, "0")}`;
|
|
@@ -213,7 +214,7 @@ const calculateBudgetAnalytics = (monthlyCosts, annualBudget) => {
|
|
|
213
214
|
yearToDateSpent += monthCost;
|
|
214
215
|
monthlySpending.push(monthCost);
|
|
215
216
|
} else if (month === currentMonth) {
|
|
216
|
-
|
|
217
|
+
projectedCurrentMonthCost = monthCost / daysIntoCurrentMonth * daysInCurrentMonth;
|
|
217
218
|
yearToDateSpent += monthCost;
|
|
218
219
|
monthlySpending.push(projectedCurrentMonthCost);
|
|
219
220
|
}
|
|
@@ -243,6 +244,7 @@ const calculateBudgetAnalytics = (monthlyCosts, annualBudget) => {
|
|
|
243
244
|
yearToDateSpent,
|
|
244
245
|
monthlyRunRate,
|
|
245
246
|
projectedAnnualSpending,
|
|
247
|
+
projectedCurrentMonthCost,
|
|
246
248
|
budgetHealthStatus,
|
|
247
249
|
budgetUtilizationPercent,
|
|
248
250
|
targetMonthlySpending,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"functions.esm.js","sources":["../../src/api/functions.ts"],"sourcesContent":["import { format, parse, subDays, subMonths } from 'date-fns';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { Filters, Report, Tag } from './types';\n\nexport const mergeCostReports = (reports: Report[], threshold?: number): Report[] => {\n const totalCosts: { id: string; total: number }[] = [];\n reports.forEach(report => {\n let total = 0;\n Object.values(report.reports).forEach(v => {\n total += v as number;\n });\n totalCosts.push({ id: report.id, total: total });\n });\n totalCosts.sort((a, b) => b.total - a.total);\n const idsToBeKept = totalCosts.slice(0, threshold).map(v => v.id);\n\n const mergedReports = reduce(\n reports,\n (accumulator: { [key: string]: Report }, report) => {\n let keyName = 'Others';\n if (idsToBeKept.includes(report.id)) {\n keyName = report.id;\n }\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n reports: {},\n };\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {},\n );\n\n return Object.values(mergedReports).sort((a, b) => a.id.localeCompare(b.id));\n};\n\nexport const filterCostReports = (reports: Report[], filters: Filters): Report[] => {\n const filteredReports = reports.filter(report => {\n let match = true;\n Object.keys(filters).forEach(key => {\n if (filters[key].length > 0) {\n const reportValue = report[key] as string | undefined;\n\n // If the report doesn't have this field at all, it doesn't match\n if (reportValue === undefined) {\n match = false;\n return;\n }\n\n // Check if the report value matches any of the filter values\n const valueMatches = filters[key].some(filterValue => {\n // For exact matches\n if (reportValue === filterValue) {\n return true;\n }\n\n // For account field: match with or without account ID suffix\n // e.g., \"AWS/aws-staging-mock\" should match \"AWS/aws-staging-mock (012345678901)\"\n if (key === 'account') {\n // Extract the base account name (everything before the parentheses)\n const baseAccountName = reportValue.split(' (')[0];\n const filterAccountName = filterValue.split(' (')[0];\n if (baseAccountName === filterAccountName) {\n return true;\n }\n }\n\n // For service field: handle provider prefix matching\n // e.g., \"AWS/Lambda\" in filter should match \"AWS/Lambda\" in report\n if (key === 'service' && reportValue.includes('/') && filterValue.includes('/')) {\n return reportValue === filterValue;\n }\n\n return false;\n });\n\n if (!valueMatches) {\n match = false;\n }\n }\n });\n return match;\n });\n\n return filteredReports;\n};\n\nexport const aggregateCostReports = (reports: Report[], aggregatedBy?: string): Report[] => {\n const aggregatedReports: { [key: string]: Report } = reduce(\n reports,\n (accumulator, report) => {\n let keyName: string = 'no value';\n if (aggregatedBy && aggregatedBy in report) {\n keyName = report[aggregatedBy] as string;\n } else if (aggregatedBy === 'none') {\n keyName = 'Total';\n }\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n provider: report.provider,\n reports: {},\n } as {\n id: string;\n reports: { [key: string]: number };\n [key: string]: any;\n };\n\n if (aggregatedBy !== undefined) {\n accumulator[keyName][aggregatedBy] = keyName;\n }\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {} as { [key: string]: Report },\n );\n return Object.values(aggregatedReports);\n};\n\nexport const getReportKeyAndValues = (reports: Report[] | undefined): { [key: string]: string[] } => {\n const excludedKeys = ['id', 'reports'];\n const keyValueSets: { [key: string]: Set<string> } = {};\n reports?.forEach(report => {\n Object.keys(report).forEach(key => {\n if (!excludedKeys.includes(key)) {\n if (keyValueSets[key] === undefined) {\n keyValueSets[key] = new Set<string>();\n }\n\n keyValueSets[key].add(report[key] as string);\n }\n });\n });\n\n const keyValues: { [key: string]: string[] } = {};\n Object.keys(keyValueSets).forEach((key: string) => {\n keyValues[key] = Array.from(keyValueSets[key]);\n keyValues[key].sort((a, b) => a.localeCompare(b));\n });\n return keyValues;\n};\n\nexport const extractProvider = (input: string): string | undefined => {\n let provider = undefined;\n if (input && input.indexOf('/') !== -1) {\n provider = input.split('/')[0];\n }\n\n return provider;\n};\n\nexport const extractAccountInfo = (input: string): { accountName: string; accountId?: string } => {\n // try to match format: accountName (accountId), e.g. aws-dev (123456789012)\n const regex = /^(.*?)\\s*\\(([^)]+)\\)$/;\n const match = input.match(regex);\n\n if (match) {\n const accountName = match[1];\n const accountId = match[2];\n return { accountName: accountName, accountId: accountId };\n }\n\n return { accountName: input };\n};\n\n// check if targetTag exists in tags\nexport function tagExists(tags: Tag[], targetTag: Tag): boolean {\n return tags.some(\n tag => tag.provider === targetTag.provider && tag.key === targetTag.key && tag.value === targetTag.value,\n );\n}\n\n// convert Tag array to (provider1:key1=value1 OR provider2:key2=value2) format\nexport const tagsToString = (tags: Tag[]): string => {\n if (tags.length === 0) {\n return '()';\n }\n\n const keyValuePairs = tags.map(tag => `${tag.provider}:${tag.key}=${tag.value}`);\n return `(${keyValuePairs.join(' OR ')})`;\n};\n\nexport const getAllReportTags = (reports: Report[]): string[] => {\n const tags = new Set<string>();\n const reservedKeys = ['id', 'account', 'service', 'category', 'provider', 'reports'];\n reports.forEach(report => {\n Object.keys(report).forEach(key => {\n if (reservedKeys.indexOf(key) === -1) {\n tags.add(key);\n }\n });\n });\n return Array.from(tags);\n};\n\nexport const getPreviousMonth = (month: string): string => {\n const date = parse(month, 'yyyy-MM', new Date());\n const previousMonth = subMonths(date, 1);\n return format(previousMonth, 'yyyy-MM');\n};\n\nexport const getPreviousDay = (day: string): string => {\n const date = parse(day, 'yyyy-MM-dd', new Date());\n const previousDay = subDays(date, 1);\n return format(previousDay, 'yyyy-MM-dd');\n};\n\nexport const getPeriodStrings = (granularity: string, startTime: Date, endTime: Date): string[] => {\n const result: string[] = [];\n const current = moment(startTime);\n\n while (current.isSameOrBefore(endTime) && current.isSameOrBefore(moment())) {\n if (granularity === 'monthly') {\n result.push(current.format('YYYY-MM'));\n current.add(1, 'months');\n } else {\n result.push(current.format('YYYY-MM-DD'));\n current.add(1, 'days');\n }\n }\n\n return result;\n};\n\nexport const formatCurrency = (number: number, currency?: string): string => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n notation: 'compact',\n }).format(number);\n};\n\nexport interface BudgetAnalytics {\n yearToDateSpent: number;\n monthlyRunRate: number;\n projectedAnnualSpending: number;\n budgetHealthStatus: 'healthy' | 'warning' | 'critical';\n budgetUtilizationPercent: number;\n targetMonthlySpending: number;\n monthsRemaining: number;\n averageMonthlySpending: number;\n spendingVelocity: number;\n confidenceRange: {\n low: number;\n high: number;\n };\n}\n\nexport const calculateBudgetAnalytics = (\n monthlyCosts: Record<string, number>,\n annualBudget: number,\n): BudgetAnalytics => {\n const currentMonth = moment().month() + 1;\n const currentYear = moment().year();\n const daysIntoCurrentMonth = moment().date();\n const daysInCurrentMonth = moment().daysInMonth();\n\n let yearToDateSpent = 0;\n const monthlySpending: number[] = [];\n\n for (let month = 1; month <= currentMonth; month++) {\n const monthKey = `${currentYear}-${month.toString().padStart(2, '0')}`;\n const monthCost = monthlyCosts[monthKey] || 0;\n\n if (month < currentMonth) {\n yearToDateSpent += monthCost;\n monthlySpending.push(monthCost);\n } else if (month === currentMonth) {\n const projectedCurrentMonthCost = (monthCost / daysIntoCurrentMonth) * daysInCurrentMonth;\n yearToDateSpent += monthCost;\n monthlySpending.push(projectedCurrentMonthCost);\n }\n }\n\n const monthsRemaining = 12 - currentMonth + (1 - daysIntoCurrentMonth / daysInCurrentMonth);\n const averageMonthlySpending =\n monthlySpending.length > 0 ? monthlySpending.reduce((sum, cost) => sum + cost, 0) / monthlySpending.length : 0;\n\n const monthlyRunRate = monthlySpending.length > 0 ? monthlySpending[monthlySpending.length - 1] || 0 : 0;\n\n const projectedAnnualSpending = yearToDateSpent + averageMonthlySpending * monthsRemaining;\n\n const budgetUtilizationPercent = annualBudget > 0 ? (yearToDateSpent / annualBudget) * 100 : 0;\n const expectedUtilizationPercent = ((currentMonth - 1 + daysIntoCurrentMonth / daysInCurrentMonth) / 12) * 100;\n\n let budgetHealthStatus: 'healthy' | 'warning' | 'critical' = 'healthy';\n if (budgetUtilizationPercent > expectedUtilizationPercent + 20) {\n budgetHealthStatus = 'critical';\n } else if (budgetUtilizationPercent > expectedUtilizationPercent + 10) {\n budgetHealthStatus = 'warning';\n }\n\n const targetMonthlySpending = monthsRemaining > 0 ? (annualBudget - yearToDateSpent) / monthsRemaining : 0;\n\n const spendingVariance =\n monthlySpending.length > 1\n ? Math.sqrt(\n monthlySpending.reduce((sum, cost) => sum + Math.pow(cost - averageMonthlySpending, 2), 0) /\n (monthlySpending.length - 1),\n )\n : 0;\n\n const spendingVelocity =\n monthlySpending.length >= 2\n ? ((monthlySpending[monthlySpending.length - 1] - monthlySpending[monthlySpending.length - 2]) /\n monthlySpending[monthlySpending.length - 2]) *\n 100\n : 0;\n\n const confidenceRange = {\n low: Math.max(0, projectedAnnualSpending - spendingVariance * 2 * Math.sqrt(monthsRemaining)),\n high: projectedAnnualSpending + spendingVariance * 2 * Math.sqrt(monthsRemaining),\n };\n\n return {\n yearToDateSpent,\n monthlyRunRate,\n projectedAnnualSpending,\n budgetHealthStatus,\n budgetUtilizationPercent,\n targetMonthlySpending,\n monthsRemaining,\n averageMonthlySpending,\n spendingVelocity,\n confidenceRange,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"functions.esm.js","sources":["../../src/api/functions.ts"],"sourcesContent":["import { format, parse, subDays, subMonths } from 'date-fns';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { Filters, Report, Tag } from './types';\n\nexport const mergeCostReports = (reports: Report[], threshold?: number): Report[] => {\n const totalCosts: { id: string; total: number }[] = [];\n reports.forEach(report => {\n let total = 0;\n Object.values(report.reports).forEach(v => {\n total += v as number;\n });\n totalCosts.push({ id: report.id, total: total });\n });\n totalCosts.sort((a, b) => b.total - a.total);\n const idsToBeKept = totalCosts.slice(0, threshold).map(v => v.id);\n\n const mergedReports = reduce(\n reports,\n (accumulator: { [key: string]: Report }, report) => {\n let keyName = 'Others';\n if (idsToBeKept.includes(report.id)) {\n keyName = report.id;\n }\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n reports: {},\n };\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {},\n );\n\n return Object.values(mergedReports).sort((a, b) => a.id.localeCompare(b.id));\n};\n\nexport const filterCostReports = (reports: Report[], filters: Filters): Report[] => {\n const filteredReports = reports.filter(report => {\n let match = true;\n Object.keys(filters).forEach(key => {\n if (filters[key].length > 0) {\n const reportValue = report[key] as string | undefined;\n\n // If the report doesn't have this field at all, it doesn't match\n if (reportValue === undefined) {\n match = false;\n return;\n }\n\n // Check if the report value matches any of the filter values\n const valueMatches = filters[key].some(filterValue => {\n // For exact matches\n if (reportValue === filterValue) {\n return true;\n }\n\n // For account field: match with or without account ID suffix\n // e.g., \"AWS/aws-staging-mock\" should match \"AWS/aws-staging-mock (012345678901)\"\n if (key === 'account') {\n // Extract the base account name (everything before the parentheses)\n const baseAccountName = reportValue.split(' (')[0];\n const filterAccountName = filterValue.split(' (')[0];\n if (baseAccountName === filterAccountName) {\n return true;\n }\n }\n\n // For service field: handle provider prefix matching\n // e.g., \"AWS/Lambda\" in filter should match \"AWS/Lambda\" in report\n if (key === 'service' && reportValue.includes('/') && filterValue.includes('/')) {\n return reportValue === filterValue;\n }\n\n return false;\n });\n\n if (!valueMatches) {\n match = false;\n }\n }\n });\n return match;\n });\n\n return filteredReports;\n};\n\nexport const aggregateCostReports = (reports: Report[], aggregatedBy?: string): Report[] => {\n const aggregatedReports: { [key: string]: Report } = reduce(\n reports,\n (accumulator, report) => {\n let keyName: string = 'no value';\n if (aggregatedBy && aggregatedBy in report) {\n keyName = report[aggregatedBy] as string;\n } else if (aggregatedBy === 'none') {\n keyName = 'Total';\n }\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n provider: report.provider,\n reports: {},\n } as {\n id: string;\n reports: { [key: string]: number };\n [key: string]: any;\n };\n\n if (aggregatedBy !== undefined) {\n accumulator[keyName][aggregatedBy] = keyName;\n }\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {} as { [key: string]: Report },\n );\n return Object.values(aggregatedReports);\n};\n\nexport const getReportKeyAndValues = (reports: Report[] | undefined): { [key: string]: string[] } => {\n const excludedKeys = ['id', 'reports'];\n const keyValueSets: { [key: string]: Set<string> } = {};\n reports?.forEach(report => {\n Object.keys(report).forEach(key => {\n if (!excludedKeys.includes(key)) {\n if (keyValueSets[key] === undefined) {\n keyValueSets[key] = new Set<string>();\n }\n\n keyValueSets[key].add(report[key] as string);\n }\n });\n });\n\n const keyValues: { [key: string]: string[] } = {};\n Object.keys(keyValueSets).forEach((key: string) => {\n keyValues[key] = Array.from(keyValueSets[key]);\n keyValues[key].sort((a, b) => a.localeCompare(b));\n });\n return keyValues;\n};\n\nexport const extractProvider = (input: string): string | undefined => {\n let provider = undefined;\n if (input && input.indexOf('/') !== -1) {\n provider = input.split('/')[0];\n }\n\n return provider;\n};\n\nexport const extractAccountInfo = (input: string): { accountName: string; accountId?: string } => {\n // try to match format: accountName (accountId), e.g. aws-dev (123456789012)\n const regex = /^(.*?)\\s*\\(([^)]+)\\)$/;\n const match = input.match(regex);\n\n if (match) {\n const accountName = match[1];\n const accountId = match[2];\n return { accountName: accountName, accountId: accountId };\n }\n\n return { accountName: input };\n};\n\n// check if targetTag exists in tags\nexport function tagExists(tags: Tag[], targetTag: Tag): boolean {\n return tags.some(\n tag => tag.provider === targetTag.provider && tag.key === targetTag.key && tag.value === targetTag.value,\n );\n}\n\n// convert Tag array to (provider1:key1=value1 OR provider2:key2=value2) format\nexport const tagsToString = (tags: Tag[]): string => {\n if (tags.length === 0) {\n return '()';\n }\n\n const keyValuePairs = tags.map(tag => `${tag.provider}:${tag.key}=${tag.value}`);\n return `(${keyValuePairs.join(' OR ')})`;\n};\n\nexport const getAllReportTags = (reports: Report[]): string[] => {\n const tags = new Set<string>();\n const reservedKeys = ['id', 'account', 'service', 'category', 'provider', 'reports'];\n reports.forEach(report => {\n Object.keys(report).forEach(key => {\n if (reservedKeys.indexOf(key) === -1) {\n tags.add(key);\n }\n });\n });\n return Array.from(tags);\n};\n\nexport const getPreviousMonth = (month: string): string => {\n const date = parse(month, 'yyyy-MM', new Date());\n const previousMonth = subMonths(date, 1);\n return format(previousMonth, 'yyyy-MM');\n};\n\nexport const getPreviousDay = (day: string): string => {\n const date = parse(day, 'yyyy-MM-dd', new Date());\n const previousDay = subDays(date, 1);\n return format(previousDay, 'yyyy-MM-dd');\n};\n\nexport const getPeriodStrings = (granularity: string, startTime: Date, endTime: Date): string[] => {\n const result: string[] = [];\n const current = moment(startTime);\n\n while (current.isSameOrBefore(endTime) && current.isSameOrBefore(moment())) {\n if (granularity === 'monthly') {\n result.push(current.format('YYYY-MM'));\n current.add(1, 'months');\n } else {\n result.push(current.format('YYYY-MM-DD'));\n current.add(1, 'days');\n }\n }\n\n return result;\n};\n\nexport const formatCurrency = (number: number, currency?: string): string => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n notation: 'compact',\n }).format(number);\n};\n\nexport interface BudgetAnalytics {\n yearToDateSpent: number;\n monthlyRunRate: number;\n projectedAnnualSpending: number;\n projectedCurrentMonthCost: number;\n budgetHealthStatus: 'healthy' | 'warning' | 'critical';\n budgetUtilizationPercent: number;\n targetMonthlySpending: number;\n monthsRemaining: number;\n averageMonthlySpending: number;\n spendingVelocity: number;\n confidenceRange: {\n low: number;\n high: number;\n };\n}\n\nexport const calculateBudgetAnalytics = (\n monthlyCosts: Record<string, number>,\n annualBudget: number,\n): BudgetAnalytics => {\n const currentMonth = moment().month() + 1;\n const currentYear = moment().year();\n const daysIntoCurrentMonth = moment().date();\n const daysInCurrentMonth = moment().daysInMonth();\n\n let yearToDateSpent = 0;\n let projectedCurrentMonthCost = 0;\n const monthlySpending: number[] = [];\n\n for (let month = 1; month <= currentMonth; month++) {\n const monthKey = `${currentYear}-${month.toString().padStart(2, '0')}`;\n const monthCost = monthlyCosts[monthKey] || 0;\n\n if (month < currentMonth) {\n yearToDateSpent += monthCost;\n monthlySpending.push(monthCost);\n } else if (month === currentMonth) {\n projectedCurrentMonthCost = (monthCost / daysIntoCurrentMonth) * daysInCurrentMonth;\n yearToDateSpent += monthCost;\n monthlySpending.push(projectedCurrentMonthCost);\n }\n }\n\n const monthsRemaining = 12 - currentMonth + (1 - daysIntoCurrentMonth / daysInCurrentMonth);\n const averageMonthlySpending =\n monthlySpending.length > 0 ? monthlySpending.reduce((sum, cost) => sum + cost, 0) / monthlySpending.length : 0;\n\n const monthlyRunRate = monthlySpending.length > 0 ? monthlySpending[monthlySpending.length - 1] || 0 : 0;\n\n const projectedAnnualSpending = yearToDateSpent + averageMonthlySpending * monthsRemaining;\n\n const budgetUtilizationPercent = annualBudget > 0 ? (yearToDateSpent / annualBudget) * 100 : 0;\n const expectedUtilizationPercent = ((currentMonth - 1 + daysIntoCurrentMonth / daysInCurrentMonth) / 12) * 100;\n\n let budgetHealthStatus: 'healthy' | 'warning' | 'critical' = 'healthy';\n if (budgetUtilizationPercent > expectedUtilizationPercent + 20) {\n budgetHealthStatus = 'critical';\n } else if (budgetUtilizationPercent > expectedUtilizationPercent + 10) {\n budgetHealthStatus = 'warning';\n }\n\n const targetMonthlySpending = monthsRemaining > 0 ? (annualBudget - yearToDateSpent) / monthsRemaining : 0;\n\n const spendingVariance =\n monthlySpending.length > 1\n ? Math.sqrt(\n monthlySpending.reduce((sum, cost) => sum + Math.pow(cost - averageMonthlySpending, 2), 0) /\n (monthlySpending.length - 1),\n )\n : 0;\n\n const spendingVelocity =\n monthlySpending.length >= 2\n ? ((monthlySpending[monthlySpending.length - 1] - monthlySpending[monthlySpending.length - 2]) /\n monthlySpending[monthlySpending.length - 2]) *\n 100\n : 0;\n\n const confidenceRange = {\n low: Math.max(0, projectedAnnualSpending - spendingVariance * 2 * Math.sqrt(monthsRemaining)),\n high: projectedAnnualSpending + spendingVariance * 2 * Math.sqrt(monthsRemaining),\n };\n\n return {\n yearToDateSpent,\n monthlyRunRate,\n projectedAnnualSpending,\n projectedCurrentMonthCost,\n budgetHealthStatus,\n budgetUtilizationPercent,\n targetMonthlySpending,\n monthsRemaining,\n averageMonthlySpending,\n spendingVelocity,\n confidenceRange,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ import Skeleton from '@mui/material/Skeleton';
|
|
|
19
19
|
import { useTheme } from '@mui/material/styles';
|
|
20
20
|
import Switch from '@mui/material/Switch';
|
|
21
21
|
import Typography from '@mui/material/Typography';
|
|
22
|
-
import { useDrawingArea,
|
|
22
|
+
import { useDrawingArea, ResponsiveChartContainer, ChartsGrid, ChartsAxisHighlight, LinePlot, BarPlot, MarkPlot, LineHighlightPlot, ChartsReferenceLine, ChartsXAxis, ChartsYAxis, ChartsTooltip } from '@mui/x-charts';
|
|
23
23
|
import { max } from 'lodash';
|
|
24
24
|
import moment from 'moment';
|
|
25
25
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
@@ -97,35 +97,34 @@ function getRecommendationColor(type) {
|
|
|
97
97
|
return "info";
|
|
98
98
|
}
|
|
99
99
|
function BudgetChart(props) {
|
|
100
|
-
const {
|
|
100
|
+
const { height } = useDrawingArea();
|
|
101
101
|
const theme = useTheme();
|
|
102
|
-
const { provider, monthlyCosts, view } = props;
|
|
103
|
-
const [annualBudget, setAnnualBudget] = useState(void 0);
|
|
104
|
-
const [openManageBudget, setOpenManageBudget] = useState(false);
|
|
105
|
-
const [refreshTrigger, setRefreshTrigger] = useState(false);
|
|
102
|
+
const { provider, monthlyCosts, view, budgets, setBudgets } = props;
|
|
106
103
|
const infraWalletApi = useApi(infraWalletApiRef);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const updatedBudget = response.data?.find((a) => a.provider.toLowerCase() === provider.toLowerCase());
|
|
112
|
-
setAnnualBudget(updatedBudget);
|
|
113
|
-
};
|
|
114
|
-
fetchBudget();
|
|
115
|
-
}, [refreshTrigger, provider, infraWalletApi]);
|
|
104
|
+
const annualBudget = budgets.find((b) => b.provider.toLowerCase() === provider.toLowerCase());
|
|
105
|
+
const annualBudgetAmount = annualBudget?.amount || 0;
|
|
106
|
+
const [openManageBudget, setOpenManageBudget] = useState(false);
|
|
107
|
+
const budgetAnalytics = calculateBudgetAnalytics(monthlyCosts, annualBudgetAmount);
|
|
116
108
|
const updateBudget = async (event) => {
|
|
117
109
|
event.preventDefault();
|
|
118
110
|
const formData = new FormData(event.currentTarget);
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const newAnnualBudget = {
|
|
111
|
+
const amount = Number(formData.get("amount") || 0);
|
|
112
|
+
const updated = {
|
|
122
113
|
id: annualBudget?.id,
|
|
123
114
|
provider,
|
|
124
|
-
name
|
|
125
|
-
amount
|
|
115
|
+
name: annualBudget?.name || `${provider} annual budget`,
|
|
116
|
+
amount
|
|
126
117
|
};
|
|
127
|
-
await infraWalletApi.updateBudget("default",
|
|
128
|
-
|
|
118
|
+
await infraWalletApi.updateBudget("default", updated);
|
|
119
|
+
setBudgets((prev) => {
|
|
120
|
+
const index = prev.findIndex((b) => b.provider.toLowerCase() === provider.toLowerCase());
|
|
121
|
+
if (index >= 0) {
|
|
122
|
+
const copy = [...prev];
|
|
123
|
+
copy[index] = { ...copy[index], ...updated };
|
|
124
|
+
return copy;
|
|
125
|
+
}
|
|
126
|
+
return [...prev, updated];
|
|
127
|
+
});
|
|
129
128
|
setOpenManageBudget(false);
|
|
130
129
|
};
|
|
131
130
|
const nonAccumulatedCosts = [];
|
|
@@ -147,25 +146,102 @@ function BudgetChart(props) {
|
|
|
147
146
|
accumulatedCosts.push(accumulatedCosts[accumulatedCosts.length - 1] + cost);
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
|
-
let budgetAmount =
|
|
149
|
+
let budgetAmount = annualBudgetAmount;
|
|
150
|
+
let chartSeries;
|
|
151
|
+
let yAxis;
|
|
151
152
|
if (view === "Monthly" /* MONTHLY */) {
|
|
152
153
|
budgetAmount = budgetAmount / 12;
|
|
154
|
+
const lastIndex = nonAccumulatedCosts.length - 1;
|
|
155
|
+
const lastActualCost = lastIndex >= 0 ? nonAccumulatedCosts[lastIndex] : 0;
|
|
156
|
+
const projectedCurrentMonthCost = budgetAnalytics.projectedCurrentMonthCost;
|
|
157
|
+
const projectedDelta = projectedCurrentMonthCost - lastActualCost;
|
|
158
|
+
const monthlyMax = max([...nonAccumulatedCosts, budgetAmount]) || 0;
|
|
159
|
+
chartSeries = [
|
|
160
|
+
{
|
|
161
|
+
id: "actual-spend",
|
|
162
|
+
yAxisKey: "spendAxis",
|
|
163
|
+
data: nonAccumulatedCosts,
|
|
164
|
+
type: "bar",
|
|
165
|
+
stack: "combined",
|
|
166
|
+
label: "Actual Spend",
|
|
167
|
+
color: colorList[0],
|
|
168
|
+
valueFormatter: (value) => formatCurrency(value || 0)
|
|
169
|
+
}
|
|
170
|
+
];
|
|
171
|
+
if (lastIndex >= 0 && projectedDelta > 0) {
|
|
172
|
+
const deltaData = nonAccumulatedCosts.map((_, i) => i === lastIndex ? projectedDelta : 0);
|
|
173
|
+
chartSeries.push({
|
|
174
|
+
id: "projected-delta",
|
|
175
|
+
yAxisKey: "deltaAxis",
|
|
176
|
+
data: deltaData,
|
|
177
|
+
type: "bar",
|
|
178
|
+
stack: "combined",
|
|
179
|
+
label: "Projected Delta",
|
|
180
|
+
color: theme.palette.warning.main,
|
|
181
|
+
valueFormatter: (value) => {
|
|
182
|
+
if (value === 0) return null;
|
|
183
|
+
return formatCurrency(value || 0);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
yAxis = [
|
|
188
|
+
{
|
|
189
|
+
id: "spendAxis",
|
|
190
|
+
min: 0,
|
|
191
|
+
max: monthlyMax,
|
|
192
|
+
valueFormatter: (value) => formatCurrency(value || 0),
|
|
193
|
+
colorMap: {
|
|
194
|
+
type: "piecewise",
|
|
195
|
+
thresholds: [budgetAmount > 0 ? budgetAmount : Number.MAX_SAFE_INTEGER],
|
|
196
|
+
colors: [colorList[0], theme.palette.error.main]
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "deltaAxis",
|
|
201
|
+
min: 0,
|
|
202
|
+
max: monthlyMax
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
} else {
|
|
206
|
+
chartSeries = [
|
|
207
|
+
{
|
|
208
|
+
id: "yearAxis",
|
|
209
|
+
yAxisKey: "spendAxis",
|
|
210
|
+
data: accumulatedCosts,
|
|
211
|
+
type: "line",
|
|
212
|
+
showMark: false,
|
|
213
|
+
valueFormatter: (value) => formatCurrency(value || 0)
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
yAxis = [
|
|
217
|
+
{
|
|
218
|
+
id: "spendAxis",
|
|
219
|
+
min: 0,
|
|
220
|
+
max: max([...accumulatedCosts, budgetAmount, budgetAnalytics.confidenceRange.high]),
|
|
221
|
+
valueFormatter: (value) => formatCurrency(value || 0),
|
|
222
|
+
colorMap: {
|
|
223
|
+
type: "piecewise",
|
|
224
|
+
thresholds: [budgetAmount > 0 ? budgetAmount : Number.MAX_SAFE_INTEGER],
|
|
225
|
+
colors: [colorList[0], theme.palette.error.main]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
];
|
|
153
229
|
}
|
|
154
|
-
return /* @__PURE__ */ React.createElement(Paper, { sx: { padding: 2 } }, /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 } }, /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(ProviderIcon, { provider }), /* @__PURE__ */ React.createElement(Typography, { variant: "h6", sx: { ml: 1, fontWeight: "bold" } }, provider)),
|
|
230
|
+
return /* @__PURE__ */ React.createElement(Paper, { sx: { padding: 2 } }, /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 } }, /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(ProviderIcon, { provider }), /* @__PURE__ */ React.createElement(Typography, { variant: "h6", sx: { ml: 1, fontWeight: "bold" } }, provider)), annualBudgetAmount > 0 && /* @__PURE__ */ React.createElement(
|
|
155
231
|
BudgetHealthIndicator,
|
|
156
232
|
{
|
|
157
233
|
status: budgetAnalytics.budgetHealthStatus,
|
|
158
234
|
utilizationPercent: budgetAnalytics.budgetUtilizationPercent
|
|
159
235
|
}
|
|
160
|
-
)),
|
|
236
|
+
)), annualBudgetAmount > 0 && /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 1, sx: { mb: 2 } }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(Card, { variant: "outlined", sx: { textAlign: "center" } }, /* @__PURE__ */ React.createElement(CardContent, { sx: { py: 1, "&:last-child": { pb: 1 } } }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "YTD Spent"), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", fontWeight: "bold" }, formatCurrency(budgetAnalytics.yearToDateSpent)), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "of ", formatCurrency(annualBudgetAmount))))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(Card, { variant: "outlined", sx: { textAlign: "center" } }, /* @__PURE__ */ React.createElement(CardContent, { sx: { py: 1, "&:last-child": { pb: 1 } } }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Projected Annual"), /* @__PURE__ */ React.createElement(
|
|
161
237
|
Typography,
|
|
162
238
|
{
|
|
163
239
|
variant: "body2",
|
|
164
240
|
fontWeight: "bold",
|
|
165
|
-
color: budgetAnalytics.projectedAnnualSpending >
|
|
241
|
+
color: budgetAnalytics.projectedAnnualSpending > annualBudgetAmount ? "error" : "inherit"
|
|
166
242
|
},
|
|
167
243
|
formatCurrency(budgetAnalytics.projectedAnnualSpending)
|
|
168
|
-
), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, budgetAnalytics.projectedAnnualSpending >
|
|
244
|
+
), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, budgetAnalytics.projectedAnnualSpending > annualBudgetAmount ? `+${formatCurrency(budgetAnalytics.projectedAnnualSpending - annualBudgetAmount)} over` : `${formatCurrency(annualBudgetAmount - budgetAnalytics.projectedAnnualSpending)} under`)))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(Card, { variant: "outlined", sx: { textAlign: "center" } }, /* @__PURE__ */ React.createElement(CardContent, { sx: { py: 1, "&:last-child": { pb: 1 } } }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Monthly Run Rate"), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", fontWeight: "bold" }, formatCurrency(budgetAnalytics.monthlyRunRate)), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: getSpendingVelocityColor(budgetAnalytics.spendingVelocity) }, getSpendingVelocityIcon(budgetAnalytics.spendingVelocity), Math.abs(budgetAnalytics.spendingVelocity).toFixed(1), "% MoM")))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(Card, { variant: "outlined", sx: { textAlign: "center" } }, /* @__PURE__ */ React.createElement(CardContent, { sx: { py: 1, "&:last-child": { pb: 1 } } }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Target Monthly"), /* @__PURE__ */ React.createElement(
|
|
169
245
|
Typography,
|
|
170
246
|
{
|
|
171
247
|
variant: "body2",
|
|
@@ -174,40 +250,17 @@ function BudgetChart(props) {
|
|
|
174
250
|
},
|
|
175
251
|
formatCurrency(budgetAnalytics.targetMonthlySpending)
|
|
176
252
|
), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, budgetAnalytics.monthsRemaining.toFixed(1), " months left"))))), /* @__PURE__ */ React.createElement(
|
|
177
|
-
|
|
253
|
+
ResponsiveChartContainer,
|
|
178
254
|
{
|
|
179
|
-
width: width + 20,
|
|
180
255
|
height,
|
|
181
|
-
series:
|
|
182
|
-
{
|
|
183
|
-
data: view === "Annual" /* ANNUAL */ ? accumulatedCosts : nonAccumulatedCosts,
|
|
184
|
-
type: view === "Annual" /* ANNUAL */ ? "line" : "bar",
|
|
185
|
-
valueFormatter: (value) => {
|
|
186
|
-
return formatCurrency(value || 0);
|
|
187
|
-
},
|
|
188
|
-
showMark: false
|
|
189
|
-
}
|
|
190
|
-
],
|
|
256
|
+
series: chartSeries,
|
|
191
257
|
xAxis: [
|
|
192
258
|
{
|
|
193
259
|
data: Object.entries(monthList).sort(([a], [b]) => a.localeCompare(b)).map(([_, value]) => value),
|
|
194
260
|
scaleType: "band"
|
|
195
261
|
}
|
|
196
262
|
],
|
|
197
|
-
yAxis
|
|
198
|
-
{
|
|
199
|
-
min: 0,
|
|
200
|
-
max: view === "Annual" /* ANNUAL */ ? max([...accumulatedCosts, budgetAmount, budgetAnalytics.confidenceRange.high]) : max([...nonAccumulatedCosts, budgetAmount]),
|
|
201
|
-
valueFormatter: (value) => {
|
|
202
|
-
return formatCurrency(value || 0);
|
|
203
|
-
},
|
|
204
|
-
colorMap: {
|
|
205
|
-
type: "piecewise",
|
|
206
|
-
thresholds: [budgetAmount > 0 ? budgetAmount : Number.MAX_SAFE_INTEGER],
|
|
207
|
-
colors: [colorList[0], theme.palette.error.main]
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
]
|
|
263
|
+
yAxis
|
|
211
264
|
},
|
|
212
265
|
/* @__PURE__ */ React.createElement(ChartsGrid, { horizontal: true }),
|
|
213
266
|
/* @__PURE__ */ React.createElement(ChartsAxisHighlight, { x: view === "Annual" /* ANNUAL */ ? "line" : "band" }),
|
|
@@ -230,7 +283,7 @@ function BudgetChart(props) {
|
|
|
230
283
|
labelStyle: { fill: theme.palette.error.main, fontSize: "0.9em" }
|
|
231
284
|
}
|
|
232
285
|
),
|
|
233
|
-
view === "Annual" /* ANNUAL */ &&
|
|
286
|
+
view === "Annual" /* ANNUAL */ && annualBudgetAmount > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
234
287
|
ChartsReferenceLine,
|
|
235
288
|
{
|
|
236
289
|
y: budgetAnalytics.projectedAnnualSpending,
|
|
@@ -283,23 +336,11 @@ function BudgetChart(props) {
|
|
|
283
336
|
name: "amount",
|
|
284
337
|
type: "number",
|
|
285
338
|
startAdornment: /* @__PURE__ */ React.createElement(InputAdornment, { position: "start" }, "$"),
|
|
286
|
-
defaultValue:
|
|
339
|
+
defaultValue: annualBudgetAmount
|
|
287
340
|
}
|
|
288
341
|
)))), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { type: "submit", variant: "contained" }, "Submit"), /* @__PURE__ */ React.createElement(Button, { onClick: () => setOpenManageBudget(false) }, "Cancel"))))));
|
|
289
342
|
}
|
|
290
|
-
function BudgetInsights({ reports }) {
|
|
291
|
-
const [budgets, setBudgets] = useState([]);
|
|
292
|
-
const infraWalletApi = useApi(infraWalletApiRef);
|
|
293
|
-
useEffect(() => {
|
|
294
|
-
const fetchBudgets = async () => {
|
|
295
|
-
try {
|
|
296
|
-
const response = await infraWalletApi.getBudgets("default");
|
|
297
|
-
setBudgets(response.data || []);
|
|
298
|
-
} catch (error) {
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
fetchBudgets();
|
|
302
|
-
}, [infraWalletApi]);
|
|
343
|
+
function BudgetInsights({ reports, budgets }) {
|
|
303
344
|
const insights = reports.map((report) => {
|
|
304
345
|
const budget = budgets.find((b) => b.provider.toLowerCase() === report.id.toLowerCase());
|
|
305
346
|
if (!budget?.amount) return null;
|
|
@@ -356,6 +397,7 @@ function BudgetInsights({ reports }) {
|
|
|
356
397
|
const Budgets = ({ providerErrorsSetter }) => {
|
|
357
398
|
const [reportsAggregatedAndMerged, setReportsAggregatedAndMerged] = useState(void 0);
|
|
358
399
|
const [budgetView, setBudgetView] = useState("Annual" /* ANNUAL */);
|
|
400
|
+
const [budgets, setBudgets] = useState([]);
|
|
359
401
|
const infraWalletApi = useApi(infraWalletApiRef);
|
|
360
402
|
const alertApi = useApi(alertApiRef);
|
|
361
403
|
const fetchCosts = useCallback(async () => {
|
|
@@ -373,13 +415,32 @@ const Budgets = ({ providerErrorsSetter }) => {
|
|
|
373
415
|
useEffect(() => {
|
|
374
416
|
fetchCosts();
|
|
375
417
|
}, [fetchCosts]);
|
|
418
|
+
useEffect(() => {
|
|
419
|
+
const fetchBudgets = async () => {
|
|
420
|
+
try {
|
|
421
|
+
const response = await infraWalletApi.getBudgets("default");
|
|
422
|
+
setBudgets(response.data || []);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
fetchBudgets();
|
|
427
|
+
}, [infraWalletApi]);
|
|
376
428
|
return /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, moment().year(), " ", budgetView, " Budgets"), /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Annual"), /* @__PURE__ */ React.createElement(
|
|
377
429
|
Switch,
|
|
378
430
|
{
|
|
379
431
|
size: "small",
|
|
380
432
|
onChange: (event) => setBudgetView(event.target.checked ? "Monthly" /* MONTHLY */ : "Annual" /* ANNUAL */)
|
|
381
433
|
}
|
|
382
|
-
), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Monthly")))), reportsAggregatedAndMerged && /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(BudgetInsights, { reports: reportsAggregatedAndMerged })), reportsAggregatedAndMerged !== void 0 ? reportsAggregatedAndMerged.map((report) => /* @__PURE__ */ React.createElement(Grid, { item: true, key: `${report.id}-grid`, xs: 4 }, /* @__PURE__ */ React.createElement(
|
|
434
|
+
), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Monthly")))), reportsAggregatedAndMerged && /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(BudgetInsights, { reports: reportsAggregatedAndMerged, budgets })), reportsAggregatedAndMerged !== void 0 ? reportsAggregatedAndMerged.map((report) => /* @__PURE__ */ React.createElement(Grid, { item: true, key: `${report.id}-grid`, xs: 4 }, /* @__PURE__ */ React.createElement(
|
|
435
|
+
BudgetChart,
|
|
436
|
+
{
|
|
437
|
+
provider: report.id,
|
|
438
|
+
monthlyCosts: report.reports,
|
|
439
|
+
view: budgetView,
|
|
440
|
+
budgets,
|
|
441
|
+
setBudgets
|
|
442
|
+
}
|
|
443
|
+
))) : /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
|
|
383
444
|
Paper,
|
|
384
445
|
{
|
|
385
446
|
sx: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Budgets.esm.js","sources":["../../../src/components/Budgets/Budgets.tsx"],"sourcesContent":["import { alertApiRef, useApi } from '@backstage/core-plugin-api';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Card from '@mui/material/Card';\nimport CardContent from '@mui/material/CardContent';\nimport Chip from '@mui/material/Chip';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormControl from '@mui/material/FormControl';\nimport Grid from '@mui/material/Grid';\nimport Input from '@mui/material/Input';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport InputLabel from '@mui/material/InputLabel';\nimport Paper from '@mui/material/Paper';\nimport Skeleton from '@mui/material/Skeleton';\nimport { useTheme } from '@mui/material/styles';\nimport Switch from '@mui/material/Switch';\nimport Typography from '@mui/material/Typography';\nimport {\n BarPlot,\n ChartContainer,\n ChartsAxisHighlight,\n ChartsGrid,\n ChartsReferenceLine,\n ChartsTooltip,\n ChartsXAxis,\n ChartsYAxis,\n LineHighlightPlot,\n LinePlot,\n MarkPlot,\n useDrawingArea,\n} from '@mui/x-charts';\nimport { max } from 'lodash';\nimport moment from 'moment';\nimport React, { FC, useCallback, useEffect, useState } from 'react';\nimport {\n aggregateCostReports,\n formatCurrency,\n mergeCostReports,\n calculateBudgetAnalytics,\n BudgetAnalytics,\n} from '../../api/functions';\nimport { infraWalletApiRef } from '../../api/InfraWalletApi';\nimport { Budget, Report } from '../../api/types';\nimport { colorList } from '../constants';\nimport { ProviderIcon } from '../ProviderIcon';\nimport { BudgetsProps } from '../types';\n\nconst enum BUDGET_VIEW {\n MONTHLY = 'Monthly',\n ANNUAL = 'Annual',\n}\n\nconst monthList = {\n '01': 'Jan',\n '02': 'Feb',\n '03': 'Mar',\n '04': 'Apr',\n '05': 'May',\n '06': 'Jun',\n '07': 'Jul',\n '08': 'Aug',\n '09': 'Sep',\n '10': 'Oct',\n '11': 'Nov',\n '12': 'Dec',\n};\n\ninterface BudgetHealthIndicatorProps {\n status: 'healthy' | 'warning' | 'critical';\n utilizationPercent: number;\n}\n\nfunction BudgetHealthIndicator({ status, utilizationPercent }: BudgetHealthIndicatorProps) {\n const getStatusColor = () => {\n switch (status) {\n case 'healthy':\n return '#4caf50';\n case 'warning':\n return '#ff9800';\n case 'critical':\n return '#f44336';\n default:\n return '#9e9e9e';\n }\n };\n\n const getStatusLabel = () => {\n switch (status) {\n case 'healthy':\n return 'On Track';\n case 'warning':\n return 'At Risk';\n case 'critical':\n return 'Over Budget';\n default:\n return 'Unknown';\n }\n };\n\n return (\n <Chip\n label={`${getStatusLabel()} (${utilizationPercent.toFixed(1)}%)`}\n size=\"small\"\n sx={{\n backgroundColor: getStatusColor(),\n color: 'white',\n fontWeight: 'bold',\n '& .MuiChip-label': { fontWeight: 'bold' },\n }}\n />\n );\n}\n\ninterface BudgetChartProps {\n provider: string;\n monthlyCosts: Record<string, number>;\n view: string;\n}\n\nfunction getSpendingVelocityColor(velocity: number) {\n if (velocity > 10) return 'error';\n if (velocity < -10) return 'success.main';\n return 'textSecondary';\n}\n\nfunction getSpendingVelocityIcon(velocity: number) {\n if (velocity > 0) return '↑';\n if (velocity < 0) return '↓';\n return '→';\n}\n\nfunction getRecommendationColor(type: string) {\n if (type === 'critical') return 'error';\n if (type === 'warning') return 'warning';\n return 'info';\n}\n\nfunction BudgetChart(props: Readonly<BudgetChartProps>) {\n const { width, height } = useDrawingArea();\n const theme = useTheme();\n const { provider, monthlyCosts, view } = props;\n\n const [annualBudget, setAnnualBudget] = useState<Budget | undefined>(undefined);\n const [openManageBudget, setOpenManageBudget] = useState(false);\n const [refreshTrigger, setRefreshTrigger] = useState(false);\n\n const infraWalletApi = useApi(infraWalletApiRef);\n\n const budgetAnalytics: BudgetAnalytics = calculateBudgetAnalytics(monthlyCosts, annualBudget?.amount || 0);\n\n useEffect(() => {\n const fetchBudget = async () => {\n const response = await infraWalletApi.getBudget('default', provider);\n const updatedBudget = response.data?.find(a => a.provider.toLowerCase() === provider.toLowerCase());\n setAnnualBudget(updatedBudget);\n };\n\n fetchBudget();\n }, [refreshTrigger, provider, infraWalletApi]);\n\n const updateBudget = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const formData = new FormData(event.currentTarget);\n const name = annualBudget?.name || `${provider} annual budget`;\n const amount = formData.get('amount');\n const newAnnualBudget: Budget = {\n id: annualBudget?.id,\n provider: provider,\n name: name,\n amount: amount ? Number(amount) : 0,\n };\n await infraWalletApi.updateBudget('default', newAnnualBudget);\n setRefreshTrigger(prev => !prev);\n setOpenManageBudget(false);\n };\n\n const nonAccumulatedCosts: number[] = [];\n const accumulatedCosts: number[] = [];\n for (const month of Object.keys(monthList).sort((a, b) => Number(a) - Number(b))) {\n const yearMonth = `${moment().year()}-${month}`;\n\n let cost;\n if (yearMonth in monthlyCosts) {\n cost = monthlyCosts[yearMonth];\n } else if (Number(month) < moment().month()) {\n cost = 0;\n } else {\n break;\n }\n\n nonAccumulatedCosts.push(cost);\n if (month === '01') {\n accumulatedCosts.push(cost);\n } else {\n accumulatedCosts.push(accumulatedCosts[accumulatedCosts.length - 1] + cost);\n }\n }\n\n let budgetAmount = annualBudget?.amount || 0;\n if (view === BUDGET_VIEW.MONTHLY) {\n budgetAmount = budgetAmount / 12;\n }\n\n return (\n <Paper sx={{ padding: 2 }}>\n {/* Budget Health Header */}\n <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>\n <Box sx={{ display: 'flex', alignItems: 'center' }}>\n <ProviderIcon provider={provider} />\n <Typography variant=\"h6\" sx={{ ml: 1, fontWeight: 'bold' }}>\n {provider}\n </Typography>\n </Box>\n {annualBudget?.amount && (\n <BudgetHealthIndicator\n status={budgetAnalytics.budgetHealthStatus}\n utilizationPercent={budgetAnalytics.budgetUtilizationPercent}\n />\n )}\n </Box>\n\n {/* Budget Metrics Cards */}\n {annualBudget?.amount && (\n <Grid container spacing={1} sx={{ mb: 2 }}>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n YTD Spent\n </Typography>\n <Typography variant=\"body2\" fontWeight=\"bold\">\n {formatCurrency(budgetAnalytics.yearToDateSpent)}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n of {formatCurrency(annualBudget.amount)}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Projected Annual\n </Typography>\n <Typography\n variant=\"body2\"\n fontWeight=\"bold\"\n color={budgetAnalytics.projectedAnnualSpending > (annualBudget?.amount || 0) ? 'error' : 'inherit'}\n >\n {formatCurrency(budgetAnalytics.projectedAnnualSpending)}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {budgetAnalytics.projectedAnnualSpending > (annualBudget?.amount || 0)\n ? `+${formatCurrency(budgetAnalytics.projectedAnnualSpending - annualBudget.amount)} over`\n : `${formatCurrency(annualBudget.amount - budgetAnalytics.projectedAnnualSpending)} under`}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Monthly Run Rate\n </Typography>\n <Typography variant=\"body2\" fontWeight=\"bold\">\n {formatCurrency(budgetAnalytics.monthlyRunRate)}\n </Typography>\n <Typography variant=\"caption\" color={getSpendingVelocityColor(budgetAnalytics.spendingVelocity)}>\n {getSpendingVelocityIcon(budgetAnalytics.spendingVelocity)}\n {Math.abs(budgetAnalytics.spendingVelocity).toFixed(1)}% MoM\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Target Monthly\n </Typography>\n <Typography\n variant=\"body2\"\n fontWeight=\"bold\"\n color={\n budgetAnalytics.targetMonthlySpending < budgetAnalytics.monthlyRunRate ? 'error' : 'success.main'\n }\n >\n {formatCurrency(budgetAnalytics.targetMonthlySpending)}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {budgetAnalytics.monthsRemaining.toFixed(1)} months left\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n </Grid>\n )}\n\n <ChartContainer\n width={width + 20}\n height={height}\n series={[\n {\n data: view === BUDGET_VIEW.ANNUAL ? accumulatedCosts : nonAccumulatedCosts,\n type: view === BUDGET_VIEW.ANNUAL ? 'line' : 'bar',\n valueFormatter: (value: number | null) => {\n return formatCurrency(value || 0);\n },\n showMark: false,\n },\n ]}\n xAxis={[\n {\n data: Object.entries(monthList)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([_, value]) => value),\n scaleType: 'band',\n },\n ]}\n yAxis={[\n {\n min: 0,\n max:\n view === BUDGET_VIEW.ANNUAL\n ? max([...accumulatedCosts, budgetAmount, budgetAnalytics.confidenceRange.high])\n : max([...nonAccumulatedCosts, budgetAmount]),\n valueFormatter: value => {\n return formatCurrency(value || 0);\n },\n colorMap: {\n type: 'piecewise',\n thresholds: [budgetAmount > 0 ? budgetAmount : Number.MAX_SAFE_INTEGER],\n colors: [colorList[0], theme.palette.error.main],\n },\n },\n ]}\n >\n <ChartsGrid horizontal />\n <ChartsAxisHighlight x={view === BUDGET_VIEW.ANNUAL ? 'line' : 'band'} />\n <LinePlot />\n <BarPlot />\n <MarkPlot />\n <LineHighlightPlot />\n <ChartsReferenceLine\n y={budgetAmount}\n label={budgetAmount ? formatCurrency(budgetAmount) : undefined}\n labelAlign=\"end\"\n lineStyle={{\n stroke: budgetAmount ? theme.palette.error.main : 'transparent',\n strokeDasharray: '5 5',\n strokeWidth: 1.5,\n strokeOpacity: 0.8,\n }}\n labelStyle={{ fill: theme.palette.error.main, fontSize: '0.9em' }}\n />\n {/* Add projection line for annual view */}\n {view === BUDGET_VIEW.ANNUAL && annualBudget?.amount && (\n <>\n <ChartsReferenceLine\n y={budgetAnalytics.projectedAnnualSpending}\n label={`Projected: ${formatCurrency(budgetAnalytics.projectedAnnualSpending)}`}\n labelAlign=\"start\"\n lineStyle={{\n stroke: theme.palette.warning.main,\n strokeDasharray: '3 3',\n strokeWidth: 1.5,\n strokeOpacity: 0.8,\n }}\n labelStyle={{ fill: theme.palette.warning.main, fontSize: '0.8em' }}\n />\n {/* Confidence Range - High */}\n <ChartsReferenceLine\n y={budgetAnalytics.confidenceRange.high}\n label={`High: ${formatCurrency(budgetAnalytics.confidenceRange.high)}`}\n labelAlign=\"start\"\n lineStyle={{\n stroke: theme.palette.grey[400],\n strokeDasharray: '2 2',\n strokeWidth: 1,\n strokeOpacity: 0.5,\n }}\n labelStyle={{ fill: theme.palette.grey[600], fontSize: '0.7em' }}\n />\n {/* Confidence Range - Low */}\n <ChartsReferenceLine\n y={budgetAnalytics.confidenceRange.low}\n label={`Low: ${formatCurrency(budgetAnalytics.confidenceRange.low)}`}\n labelAlign=\"start\"\n lineStyle={{\n stroke: theme.palette.grey[400],\n strokeDasharray: '2 2',\n strokeWidth: 1,\n strokeOpacity: 0.5,\n }}\n labelStyle={{ fill: theme.palette.grey[600], fontSize: '0.7em' }}\n />\n </>\n )}\n <ChartsXAxis />\n <ChartsYAxis />\n <ChartsTooltip />\n </ChartContainer>\n\n <Box sx={{ textAlign: 'center' }}>\n <Button onClick={() => setOpenManageBudget(true)}>Manage budget</Button>\n <Dialog fullWidth maxWidth=\"sm\" open={openManageBudget} onClose={() => setOpenManageBudget(false)}>\n <form onSubmit={updateBudget}>\n <DialogTitle>Manage Budget</DialogTitle>\n <DialogContent>\n <DialogContentText>Please enter your {props.provider} annual budget here.</DialogContentText>\n <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>\n <ProviderIcon provider={provider} />\n \n <FormControl variant=\"standard\">\n <InputLabel>Amount</InputLabel>\n <Input\n id=\"amount\"\n name=\"amount\"\n type=\"number\"\n startAdornment={<InputAdornment position=\"start\">$</InputAdornment>}\n defaultValue={annualBudget?.amount}\n />\n </FormControl>\n </Box>\n </DialogContent>\n <DialogActions>\n <Button type=\"submit\" variant=\"contained\">\n Submit\n </Button>\n <Button onClick={() => setOpenManageBudget(false)}>Cancel</Button>\n </DialogActions>\n </form>\n </Dialog>\n </Box>\n </Paper>\n );\n}\n\ninterface BudgetInsightsProps {\n reports: Report[];\n}\n\nfunction BudgetInsights({ reports }: BudgetInsightsProps) {\n const [budgets, setBudgets] = useState<Budget[]>([]);\n const infraWalletApi = useApi(infraWalletApiRef);\n\n useEffect(() => {\n const fetchBudgets = async () => {\n try {\n const response = await infraWalletApi.getBudgets('default');\n setBudgets(response.data || []);\n } catch (error) {\n // Failed to fetch budgets - silent error handling\n }\n };\n fetchBudgets();\n }, [infraWalletApi]);\n\n const insights = reports\n .map(report => {\n const budget = budgets.find(b => b.provider.toLowerCase() === report.id.toLowerCase());\n if (!budget?.amount) return null;\n\n const analytics = calculateBudgetAnalytics(report.reports, budget.amount);\n return { provider: report.id, budget, analytics };\n })\n .filter(Boolean);\n\n const totalBudget = budgets.reduce((sum, b) => sum + (b.amount || 0), 0);\n const totalProjected = insights.reduce((sum, i) => sum + (i?.analytics.projectedAnnualSpending || 0), 0);\n const overBudgetProviders = insights.filter(i => i?.analytics.budgetHealthStatus === 'critical');\n const atRiskProviders = insights.filter(i => i?.analytics.budgetHealthStatus === 'warning');\n\n const getRecommendations = () => {\n const recommendations = [];\n\n if (overBudgetProviders.length > 0) {\n recommendations.push({\n type: 'critical',\n message: `${overBudgetProviders.length} provider(s) are over budget: ${overBudgetProviders.map(p => p?.provider).join(', ')}`,\n });\n }\n\n if (atRiskProviders.length > 0) {\n recommendations.push({\n type: 'warning',\n message: `${atRiskProviders.length} provider(s) at risk: ${atRiskProviders.map(p => p?.provider).join(', ')}`,\n });\n }\n\n if (totalProjected > totalBudget) {\n recommendations.push({\n type: 'info',\n message: `Total projected spending (${formatCurrency(totalProjected)}) exceeds total budget (${formatCurrency(totalBudget)}) by ${formatCurrency(totalProjected - totalBudget)}`,\n });\n }\n\n const highVelocityProviders = insights.filter(i => i?.analytics && i.analytics.spendingVelocity > 20);\n if (highVelocityProviders.length > 0) {\n recommendations.push({\n type: 'warning',\n message: `High spending acceleration detected in: ${highVelocityProviders.map(p => p?.provider).join(', ')}`,\n });\n }\n\n return recommendations;\n };\n\n const recommendations = getRecommendations();\n\n if (insights.length === 0) return null;\n\n return (\n <Card sx={{ mb: 3 }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Budget Insights\n </Typography>\n\n {/* Overview Stats */}\n <Grid container spacing={2} sx={{ mb: 2 }}>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color=\"primary\">\n {insights.length}\n </Typography>\n <Typography variant=\"caption\">Providers</Typography>\n </Box>\n </Grid>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color={overBudgetProviders.length > 0 ? 'error' : 'success.main'}>\n {overBudgetProviders.length}\n </Typography>\n <Typography variant=\"caption\">Over Budget</Typography>\n </Box>\n </Grid>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color={atRiskProviders.length > 0 ? 'warning.main' : 'success.main'}>\n {atRiskProviders.length}\n </Typography>\n <Typography variant=\"caption\">At Risk</Typography>\n </Box>\n </Grid>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color={totalProjected > totalBudget ? 'error' : 'success.main'}>\n {((totalProjected / totalBudget) * 100).toFixed(0)}%\n </Typography>\n <Typography variant=\"caption\">Projected vs Budget</Typography>\n </Box>\n </Grid>\n </Grid>\n\n {/* Recommendations */}\n {recommendations.length > 0 && (\n <Box>\n <Typography variant=\"subtitle2\" gutterBottom>\n Recommendations\n </Typography>\n {recommendations.map(rec => (\n <Chip\n key={`${rec.type}-${rec.message}`}\n label={rec.message}\n size=\"small\"\n color={getRecommendationColor(rec.type)}\n variant=\"outlined\"\n sx={{ mr: 1, mb: 1, maxWidth: '100%' }}\n />\n ))}\n </Box>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport const Budgets: FC<BudgetsProps> = ({ providerErrorsSetter }) => {\n const [reportsAggregatedAndMerged, setReportsAggregatedAndMerged] = useState<Report[] | undefined>(undefined);\n const [budgetView, setBudgetView] = useState(BUDGET_VIEW.ANNUAL);\n\n const infraWalletApi = useApi(infraWalletApiRef);\n const alertApi = useApi(alertApiRef);\n\n const fetchCosts = useCallback(async () => {\n await infraWalletApi\n .getCostReports('', [], '', 'monthly', moment().startOf('y').toDate(), moment().endOf('d').toDate())\n .then(reportsResponse => {\n if (reportsResponse.data) {\n const aggregatedReports = aggregateCostReports(reportsResponse.data, 'provider');\n const aggregatedAndMergedReports = mergeCostReports(aggregatedReports);\n setReportsAggregatedAndMerged(aggregatedAndMergedReports);\n }\n if (reportsResponse.status === 207 && reportsResponse.errors) {\n providerErrorsSetter(reportsResponse.errors);\n }\n })\n .catch(e => alertApi.post({ message: `${e.message}`, severity: 'error' }));\n }, [alertApi, infraWalletApi, providerErrorsSetter]);\n\n useEffect(() => {\n fetchCosts();\n }, [fetchCosts]);\n\n return (\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n <Typography variant=\"h5\">\n {moment().year()} {budgetView} Budgets\n </Typography>\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Typography variant=\"body2\">Annual</Typography>\n <Switch\n size=\"small\"\n onChange={event => setBudgetView(event.target.checked ? BUDGET_VIEW.MONTHLY : BUDGET_VIEW.ANNUAL)}\n />\n <Typography variant=\"body2\">Monthly</Typography>\n </Box>\n </Box>\n </Grid>\n\n {/* Budget Insights Panel */}\n {reportsAggregatedAndMerged && (\n <Grid item xs={12}>\n <BudgetInsights reports={reportsAggregatedAndMerged} />\n </Grid>\n )}\n {reportsAggregatedAndMerged !== undefined ? (\n reportsAggregatedAndMerged.map(report => (\n <Grid item key={`${report.id}-grid`} xs={4}>\n <BudgetChart provider={report.id} monthlyCosts={report.reports} view={budgetView} />\n </Grid>\n ))\n ) : (\n <Grid item xs={12}>\n <Paper\n sx={{\n display: 'flex',\n flexDirection: 'column',\n height: 500,\n backgroundColor: 'transparent',\n boxShadow: 'none',\n }}\n >\n <div style={{ width: '60%', margin: 'auto' }}>\n <Skeleton />\n <Skeleton />\n <Skeleton />\n </div>\n </Paper>\n </Grid>\n )}\n </Grid>\n );\n};\n"],"names":["recommendations"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Budgets.esm.js","sources":["../../../src/components/Budgets/Budgets.tsx"],"sourcesContent":["import { alertApiRef, useApi } from '@backstage/core-plugin-api';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Card from '@mui/material/Card';\nimport CardContent from '@mui/material/CardContent';\nimport Chip from '@mui/material/Chip';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormControl from '@mui/material/FormControl';\nimport Grid from '@mui/material/Grid';\nimport Input from '@mui/material/Input';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport InputLabel from '@mui/material/InputLabel';\nimport Paper from '@mui/material/Paper';\nimport Skeleton from '@mui/material/Skeleton';\nimport { useTheme } from '@mui/material/styles';\nimport Switch from '@mui/material/Switch';\nimport Typography from '@mui/material/Typography';\nimport {\n BarPlot,\n ResponsiveChartContainer,\n ChartsAxisHighlight,\n ChartsGrid,\n ChartsReferenceLine,\n ChartsTooltip,\n ChartsXAxis,\n ChartsYAxis,\n LineHighlightPlot,\n LinePlot,\n MarkPlot,\n useDrawingArea,\n} from '@mui/x-charts';\nimport { max } from 'lodash';\nimport moment from 'moment';\nimport React, { FC, useCallback, useEffect, useState } from 'react';\nimport {\n aggregateCostReports,\n formatCurrency,\n mergeCostReports,\n calculateBudgetAnalytics,\n BudgetAnalytics,\n} from '../../api/functions';\nimport { infraWalletApiRef } from '../../api/InfraWalletApi';\nimport { Budget, Report } from '../../api/types';\nimport { colorList } from '../constants';\nimport { ProviderIcon } from '../ProviderIcon';\nimport { BudgetsProps } from '../types';\n\nconst enum BUDGET_VIEW {\n MONTHLY = 'Monthly',\n ANNUAL = 'Annual',\n}\n\nconst monthList = {\n '01': 'Jan',\n '02': 'Feb',\n '03': 'Mar',\n '04': 'Apr',\n '05': 'May',\n '06': 'Jun',\n '07': 'Jul',\n '08': 'Aug',\n '09': 'Sep',\n '10': 'Oct',\n '11': 'Nov',\n '12': 'Dec',\n};\n\ninterface BudgetHealthIndicatorProps {\n status: 'healthy' | 'warning' | 'critical';\n utilizationPercent: number;\n}\n\nfunction BudgetHealthIndicator({ status, utilizationPercent }: BudgetHealthIndicatorProps) {\n const getStatusColor = () => {\n switch (status) {\n case 'healthy':\n return '#4caf50';\n case 'warning':\n return '#ff9800';\n case 'critical':\n return '#f44336';\n default:\n return '#9e9e9e';\n }\n };\n\n const getStatusLabel = () => {\n switch (status) {\n case 'healthy':\n return 'On Track';\n case 'warning':\n return 'At Risk';\n case 'critical':\n return 'Over Budget';\n default:\n return 'Unknown';\n }\n };\n\n return (\n <Chip\n label={`${getStatusLabel()} (${utilizationPercent.toFixed(1)}%)`}\n size=\"small\"\n sx={{\n backgroundColor: getStatusColor(),\n color: 'white',\n fontWeight: 'bold',\n '& .MuiChip-label': { fontWeight: 'bold' },\n }}\n />\n );\n}\n\ninterface BudgetChartProps {\n provider: string;\n monthlyCosts: Record<string, number>;\n view: string;\n budgets: Budget[];\n setBudgets: React.Dispatch<React.SetStateAction<Budget[]>>;\n}\n\nfunction getSpendingVelocityColor(velocity: number) {\n if (velocity > 10) return 'error';\n if (velocity < -10) return 'success.main';\n return 'textSecondary';\n}\n\nfunction getSpendingVelocityIcon(velocity: number) {\n if (velocity > 0) return '↑';\n if (velocity < 0) return '↓';\n return '→';\n}\n\nfunction getRecommendationColor(type: string) {\n if (type === 'critical') return 'error';\n if (type === 'warning') return 'warning';\n return 'info';\n}\n\nfunction BudgetChart(props: Readonly<BudgetChartProps>) {\n const { height } = useDrawingArea();\n const theme = useTheme();\n const { provider, monthlyCosts, view, budgets, setBudgets } = props;\n const infraWalletApi = useApi(infraWalletApiRef);\n\n const annualBudget = budgets.find(b => b.provider.toLowerCase() === provider.toLowerCase());\n const annualBudgetAmount = annualBudget?.amount || 0;\n\n const [openManageBudget, setOpenManageBudget] = useState(false);\n\n const budgetAnalytics: BudgetAnalytics = calculateBudgetAnalytics(monthlyCosts, annualBudgetAmount);\n\n const updateBudget = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const formData = new FormData(event.currentTarget);\n const amount = Number(formData.get('amount') || 0);\n const updated: Budget = {\n id: annualBudget?.id,\n provider: provider,\n name: annualBudget?.name || `${provider} annual budget`,\n amount: amount,\n };\n await infraWalletApi.updateBudget('default', updated);\n setBudgets(prev => {\n const index = prev.findIndex(b => b.provider.toLowerCase() === provider.toLowerCase());\n if (index >= 0) {\n const copy = [...prev];\n copy[index] = { ...copy[index], ...updated };\n return copy;\n }\n return [...prev, updated];\n });\n setOpenManageBudget(false);\n };\n\n const nonAccumulatedCosts: number[] = [];\n const accumulatedCosts: number[] = [];\n for (const month of Object.keys(monthList).sort((a, b) => Number(a) - Number(b))) {\n const yearMonth = `${moment().year()}-${month}`;\n\n let cost;\n if (yearMonth in monthlyCosts) {\n cost = monthlyCosts[yearMonth];\n } else if (Number(month) < moment().month()) {\n cost = 0;\n } else {\n break;\n }\n\n nonAccumulatedCosts.push(cost);\n if (month === '01') {\n accumulatedCosts.push(cost);\n } else {\n accumulatedCosts.push(accumulatedCosts[accumulatedCosts.length - 1] + cost);\n }\n }\n\n let budgetAmount = annualBudgetAmount;\n\n let chartSeries: any[];\n let yAxis: any[];\n\n if (view === BUDGET_VIEW.MONTHLY) {\n budgetAmount = budgetAmount / 12;\n\n const lastIndex = nonAccumulatedCosts.length - 1;\n const lastActualCost = lastIndex >= 0 ? nonAccumulatedCosts[lastIndex] : 0;\n const projectedCurrentMonthCost = budgetAnalytics.projectedCurrentMonthCost;\n const projectedDelta = projectedCurrentMonthCost - lastActualCost;\n const monthlyMax = max([...nonAccumulatedCosts, budgetAmount]) || 0;\n\n chartSeries = [\n {\n id: 'actual-spend',\n yAxisKey: 'spendAxis',\n data: nonAccumulatedCosts,\n type: 'bar',\n stack: 'combined',\n label: 'Actual Spend',\n color: colorList[0],\n valueFormatter: (value: number | null) => formatCurrency(value || 0),\n },\n ];\n\n if (lastIndex >= 0 && projectedDelta > 0) {\n const deltaData = nonAccumulatedCosts.map((_, i) => (i === lastIndex ? projectedDelta : 0));\n\n chartSeries.push({\n id: 'projected-delta',\n yAxisKey: 'deltaAxis',\n data: deltaData,\n type: 'bar',\n stack: 'combined',\n label: 'Projected Delta',\n color: theme.palette.warning.main,\n valueFormatter: (value: number | null) => {\n if (value === 0) return null;\n return formatCurrency(value || 0);\n },\n });\n }\n\n yAxis = [\n {\n id: 'spendAxis',\n min: 0,\n max: monthlyMax,\n valueFormatter: (value: number | null) => formatCurrency(value || 0),\n colorMap: {\n type: 'piecewise',\n thresholds: [budgetAmount > 0 ? budgetAmount : Number.MAX_SAFE_INTEGER],\n colors: [colorList[0], theme.palette.error.main],\n },\n },\n {\n id: 'deltaAxis',\n min: 0,\n max: monthlyMax,\n },\n ];\n } else {\n chartSeries = [\n {\n id: 'yearAxis',\n yAxisKey: 'spendAxis',\n data: accumulatedCosts,\n type: 'line',\n showMark: false,\n valueFormatter: (value: number | null) => formatCurrency(value || 0),\n },\n ];\n\n yAxis = [\n {\n id: 'spendAxis',\n min: 0,\n max: max([...accumulatedCosts, budgetAmount, budgetAnalytics.confidenceRange.high]),\n valueFormatter: (value: number | null) => formatCurrency(value || 0),\n colorMap: {\n type: 'piecewise',\n thresholds: [budgetAmount > 0 ? budgetAmount : Number.MAX_SAFE_INTEGER],\n colors: [colorList[0], theme.palette.error.main],\n },\n },\n ];\n }\n\n return (\n <Paper sx={{ padding: 2 }}>\n {/* Budget Health Header */}\n <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>\n <Box sx={{ display: 'flex', alignItems: 'center' }}>\n <ProviderIcon provider={provider} />\n <Typography variant=\"h6\" sx={{ ml: 1, fontWeight: 'bold' }}>\n {provider}\n </Typography>\n </Box>\n {annualBudgetAmount > 0 && (\n <BudgetHealthIndicator\n status={budgetAnalytics.budgetHealthStatus}\n utilizationPercent={budgetAnalytics.budgetUtilizationPercent}\n />\n )}\n </Box>\n\n {/* Budget Metrics Cards */}\n {annualBudgetAmount > 0 && (\n <Grid container spacing={1} sx={{ mb: 2 }}>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n YTD Spent\n </Typography>\n <Typography variant=\"body2\" fontWeight=\"bold\">\n {formatCurrency(budgetAnalytics.yearToDateSpent)}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n of {formatCurrency(annualBudgetAmount)}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Projected Annual\n </Typography>\n <Typography\n variant=\"body2\"\n fontWeight=\"bold\"\n color={budgetAnalytics.projectedAnnualSpending > annualBudgetAmount ? 'error' : 'inherit'}\n >\n {formatCurrency(budgetAnalytics.projectedAnnualSpending)}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {budgetAnalytics.projectedAnnualSpending > annualBudgetAmount\n ? `+${formatCurrency(budgetAnalytics.projectedAnnualSpending - annualBudgetAmount)} over`\n : `${formatCurrency(annualBudgetAmount - budgetAnalytics.projectedAnnualSpending)} under`}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Monthly Run Rate\n </Typography>\n <Typography variant=\"body2\" fontWeight=\"bold\">\n {formatCurrency(budgetAnalytics.monthlyRunRate)}\n </Typography>\n <Typography variant=\"caption\" color={getSpendingVelocityColor(budgetAnalytics.spendingVelocity)}>\n {getSpendingVelocityIcon(budgetAnalytics.spendingVelocity)}\n {Math.abs(budgetAnalytics.spendingVelocity).toFixed(1)}% MoM\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n <Grid item xs={6}>\n <Card variant=\"outlined\" sx={{ textAlign: 'center' }}>\n <CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Target Monthly\n </Typography>\n <Typography\n variant=\"body2\"\n fontWeight=\"bold\"\n color={\n budgetAnalytics.targetMonthlySpending < budgetAnalytics.monthlyRunRate ? 'error' : 'success.main'\n }\n >\n {formatCurrency(budgetAnalytics.targetMonthlySpending)}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {budgetAnalytics.monthsRemaining.toFixed(1)} months left\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n </Grid>\n )}\n\n <ResponsiveChartContainer\n height={height}\n series={chartSeries}\n xAxis={[\n {\n data: Object.entries(monthList)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([_, value]) => value),\n scaleType: 'band',\n },\n ]}\n yAxis={yAxis}\n >\n <ChartsGrid horizontal />\n <ChartsAxisHighlight x={view === BUDGET_VIEW.ANNUAL ? 'line' : 'band'} />\n <LinePlot />\n <BarPlot />\n <MarkPlot />\n <LineHighlightPlot />\n <ChartsReferenceLine\n y={budgetAmount}\n label={budgetAmount ? formatCurrency(budgetAmount) : undefined}\n labelAlign=\"end\"\n lineStyle={{\n stroke: budgetAmount ? theme.palette.error.main : 'transparent',\n strokeDasharray: '5 5',\n strokeWidth: 1.5,\n strokeOpacity: 0.8,\n }}\n labelStyle={{ fill: theme.palette.error.main, fontSize: '0.9em' }}\n />\n {/* Add projection line for annual view */}\n {view === BUDGET_VIEW.ANNUAL && annualBudgetAmount > 0 && (\n <>\n <ChartsReferenceLine\n y={budgetAnalytics.projectedAnnualSpending}\n label={`Projected: ${formatCurrency(budgetAnalytics.projectedAnnualSpending)}`}\n labelAlign=\"start\"\n lineStyle={{\n stroke: theme.palette.warning.main,\n strokeDasharray: '3 3',\n strokeWidth: 1.5,\n strokeOpacity: 0.8,\n }}\n labelStyle={{ fill: theme.palette.warning.main, fontSize: '0.8em' }}\n />\n {/* Confidence Range - High */}\n <ChartsReferenceLine\n y={budgetAnalytics.confidenceRange.high}\n label={`High: ${formatCurrency(budgetAnalytics.confidenceRange.high)}`}\n labelAlign=\"start\"\n lineStyle={{\n stroke: theme.palette.grey[400],\n strokeDasharray: '2 2',\n strokeWidth: 1,\n strokeOpacity: 0.5,\n }}\n labelStyle={{ fill: theme.palette.grey[600], fontSize: '0.7em' }}\n />\n {/* Confidence Range - Low */}\n <ChartsReferenceLine\n y={budgetAnalytics.confidenceRange.low}\n label={`Low: ${formatCurrency(budgetAnalytics.confidenceRange.low)}`}\n labelAlign=\"start\"\n lineStyle={{\n stroke: theme.palette.grey[400],\n strokeDasharray: '2 2',\n strokeWidth: 1,\n strokeOpacity: 0.5,\n }}\n labelStyle={{ fill: theme.palette.grey[600], fontSize: '0.7em' }}\n />\n </>\n )}\n <ChartsXAxis />\n <ChartsYAxis />\n <ChartsTooltip />\n </ResponsiveChartContainer>\n\n <Box sx={{ textAlign: 'center' }}>\n <Button onClick={() => setOpenManageBudget(true)}>Manage budget</Button>\n <Dialog fullWidth maxWidth=\"sm\" open={openManageBudget} onClose={() => setOpenManageBudget(false)}>\n <form onSubmit={updateBudget}>\n <DialogTitle>Manage Budget</DialogTitle>\n <DialogContent>\n <DialogContentText>Please enter your {props.provider} annual budget here.</DialogContentText>\n <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>\n <ProviderIcon provider={provider} />\n \n <FormControl variant=\"standard\">\n <InputLabel>Amount</InputLabel>\n <Input\n id=\"amount\"\n name=\"amount\"\n type=\"number\"\n startAdornment={<InputAdornment position=\"start\">$</InputAdornment>}\n defaultValue={annualBudgetAmount}\n />\n </FormControl>\n </Box>\n </DialogContent>\n <DialogActions>\n <Button type=\"submit\" variant=\"contained\">\n Submit\n </Button>\n <Button onClick={() => setOpenManageBudget(false)}>Cancel</Button>\n </DialogActions>\n </form>\n </Dialog>\n </Box>\n </Paper>\n );\n}\n\ninterface BudgetInsightsProps {\n reports: Report[];\n budgets: Budget[];\n}\n\nfunction BudgetInsights({ reports, budgets }: BudgetInsightsProps) {\n const insights = reports\n .map(report => {\n const budget = budgets.find(b => b.provider.toLowerCase() === report.id.toLowerCase());\n if (!budget?.amount) return null;\n\n const analytics = calculateBudgetAnalytics(report.reports, budget.amount);\n return { provider: report.id, budget, analytics };\n })\n .filter(Boolean);\n\n const totalBudget = budgets.reduce((sum, b) => sum + (b.amount || 0), 0);\n const totalProjected = insights.reduce((sum, i) => sum + (i?.analytics.projectedAnnualSpending || 0), 0);\n const overBudgetProviders = insights.filter(i => i?.analytics.budgetHealthStatus === 'critical');\n const atRiskProviders = insights.filter(i => i?.analytics.budgetHealthStatus === 'warning');\n\n const getRecommendations = () => {\n const recommendations = [];\n\n if (overBudgetProviders.length > 0) {\n recommendations.push({\n type: 'critical',\n message: `${overBudgetProviders.length} provider(s) are over budget: ${overBudgetProviders.map(p => p?.provider).join(', ')}`,\n });\n }\n\n if (atRiskProviders.length > 0) {\n recommendations.push({\n type: 'warning',\n message: `${atRiskProviders.length} provider(s) at risk: ${atRiskProviders.map(p => p?.provider).join(', ')}`,\n });\n }\n\n if (totalProjected > totalBudget) {\n recommendations.push({\n type: 'info',\n message: `Total projected spending (${formatCurrency(totalProjected)}) exceeds total budget (${formatCurrency(totalBudget)}) by ${formatCurrency(totalProjected - totalBudget)}`,\n });\n }\n\n const highVelocityProviders = insights.filter(i => i?.analytics && i.analytics.spendingVelocity > 20);\n if (highVelocityProviders.length > 0) {\n recommendations.push({\n type: 'warning',\n message: `High spending acceleration detected in: ${highVelocityProviders.map(p => p?.provider).join(', ')}`,\n });\n }\n\n return recommendations;\n };\n\n const recommendations = getRecommendations();\n\n if (insights.length === 0) return null;\n\n return (\n <Card sx={{ mb: 3 }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Budget Insights\n </Typography>\n\n {/* Overview Stats */}\n <Grid container spacing={2} sx={{ mb: 2 }}>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color=\"primary\">\n {insights.length}\n </Typography>\n <Typography variant=\"caption\">Providers</Typography>\n </Box>\n </Grid>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color={overBudgetProviders.length > 0 ? 'error' : 'success.main'}>\n {overBudgetProviders.length}\n </Typography>\n <Typography variant=\"caption\">Over Budget</Typography>\n </Box>\n </Grid>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color={atRiskProviders.length > 0 ? 'warning.main' : 'success.main'}>\n {atRiskProviders.length}\n </Typography>\n <Typography variant=\"caption\">At Risk</Typography>\n </Box>\n </Grid>\n <Grid item xs={3}>\n <Box textAlign=\"center\">\n <Typography variant=\"h4\" color={totalProjected > totalBudget ? 'error' : 'success.main'}>\n {((totalProjected / totalBudget) * 100).toFixed(0)}%\n </Typography>\n <Typography variant=\"caption\">Projected vs Budget</Typography>\n </Box>\n </Grid>\n </Grid>\n\n {/* Recommendations */}\n {recommendations.length > 0 && (\n <Box>\n <Typography variant=\"subtitle2\" gutterBottom>\n Recommendations\n </Typography>\n {recommendations.map(rec => (\n <Chip\n key={`${rec.type}-${rec.message}`}\n label={rec.message}\n size=\"small\"\n color={getRecommendationColor(rec.type)}\n variant=\"outlined\"\n sx={{ mr: 1, mb: 1, maxWidth: '100%' }}\n />\n ))}\n </Box>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport const Budgets: FC<BudgetsProps> = ({ providerErrorsSetter }) => {\n const [reportsAggregatedAndMerged, setReportsAggregatedAndMerged] = useState<Report[] | undefined>(undefined);\n const [budgetView, setBudgetView] = useState(BUDGET_VIEW.ANNUAL);\n const [budgets, setBudgets] = useState<Budget[]>([]);\n\n const infraWalletApi = useApi(infraWalletApiRef);\n const alertApi = useApi(alertApiRef);\n\n const fetchCosts = useCallback(async () => {\n await infraWalletApi\n .getCostReports('', [], '', 'monthly', moment().startOf('y').toDate(), moment().endOf('d').toDate())\n .then(reportsResponse => {\n if (reportsResponse.data) {\n const aggregatedReports = aggregateCostReports(reportsResponse.data, 'provider');\n const aggregatedAndMergedReports = mergeCostReports(aggregatedReports);\n setReportsAggregatedAndMerged(aggregatedAndMergedReports);\n }\n if (reportsResponse.status === 207 && reportsResponse.errors) {\n providerErrorsSetter(reportsResponse.errors);\n }\n })\n .catch(e => alertApi.post({ message: `${e.message}`, severity: 'error' }));\n }, [alertApi, infraWalletApi, providerErrorsSetter]);\n\n useEffect(() => {\n fetchCosts();\n }, [fetchCosts]);\n\n useEffect(() => {\n const fetchBudgets = async () => {\n try {\n const response = await infraWalletApi.getBudgets('default');\n setBudgets(response.data || []);\n } catch (error) {\n // Failed to fetch budgets - silent error handling\n }\n };\n fetchBudgets();\n }, [infraWalletApi]);\n\n return (\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n <Typography variant=\"h5\">\n {moment().year()} {budgetView} Budgets\n </Typography>\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Typography variant=\"body2\">Annual</Typography>\n <Switch\n size=\"small\"\n onChange={event => setBudgetView(event.target.checked ? BUDGET_VIEW.MONTHLY : BUDGET_VIEW.ANNUAL)}\n />\n <Typography variant=\"body2\">Monthly</Typography>\n </Box>\n </Box>\n </Grid>\n\n {/* Budget Insights Panel */}\n {reportsAggregatedAndMerged && (\n <Grid item xs={12}>\n <BudgetInsights reports={reportsAggregatedAndMerged} budgets={budgets} />\n </Grid>\n )}\n {reportsAggregatedAndMerged !== undefined ? (\n reportsAggregatedAndMerged.map(report => (\n <Grid item key={`${report.id}-grid`} xs={4}>\n <BudgetChart\n provider={report.id}\n monthlyCosts={report.reports}\n view={budgetView}\n budgets={budgets}\n setBudgets={setBudgets}\n />\n </Grid>\n ))\n ) : (\n <Grid item xs={12}>\n <Paper\n sx={{\n display: 'flex',\n flexDirection: 'column',\n height: 500,\n backgroundColor: 'transparent',\n boxShadow: 'none',\n }}\n >\n <div style={{ width: '60%', margin: 'auto' }}>\n <Skeleton />\n <Skeleton />\n <Skeleton />\n </div>\n </Paper>\n </Grid>\n )}\n </Grid>\n );\n};\n"],"names":["recommendations"],"mappings":"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"}
|
|
@@ -59,6 +59,14 @@ const quoteLuceneValue = (value) => {
|
|
|
59
59
|
}
|
|
60
60
|
return value;
|
|
61
61
|
};
|
|
62
|
+
const quoteTag = (provider, key, value) => {
|
|
63
|
+
const specialChars = /[/()[\]{}\s:]/;
|
|
64
|
+
const tag = `${provider}.${key}=${value}`;
|
|
65
|
+
if (specialChars.test(key)) {
|
|
66
|
+
return `tag:"${tag}"`;
|
|
67
|
+
}
|
|
68
|
+
return `tag:${tag}`;
|
|
69
|
+
};
|
|
62
70
|
const filtersAndTagsToLucene = (filters, tags) => {
|
|
63
71
|
const queryParts = [];
|
|
64
72
|
for (const field of Object.keys(filters)) {
|
|
@@ -74,7 +82,7 @@ const filtersAndTagsToLucene = (filters, tags) => {
|
|
|
74
82
|
}
|
|
75
83
|
for (const tag of tags) {
|
|
76
84
|
if (tag.provider && tag.key && tag.value) {
|
|
77
|
-
queryParts.push(
|
|
85
|
+
queryParts.push(quoteTag(tag.provider, tag.key, tag.value));
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
return queryParts.join(" AND ");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInfraWalletLuceneParams.esm.js","sources":["../../src/hooks/useInfraWalletLuceneParams.ts"],"sourcesContent":["import { format, parse, startOfMonth } from 'date-fns';\nimport * as lucene from 'lucene';\nimport { useCallback, useEffect, useRef } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Filters, Tag } from '../api/types';\nimport { MonthRange } from '../components/types';\n\n/**\n * Parse tag value into Tag object\n * Supports two formats:\n * 1. provider.key=value (preferred)\n * 2. provider:key:value (fallback)\n */\nconst parseTag = (tagValue: string): Tag | null => {\n // Try provider.key=value format first\n if (tagValue.includes('.') && tagValue.includes('=')) {\n const [providerKey, value] = tagValue.split('=');\n const [provider, key] = providerKey.split('.');\n if (provider && key && value) {\n return { provider, key, value };\n }\n }\n\n // Fallback to provider:key:value format\n if (tagValue.includes(':')) {\n const tagParts = tagValue.split(':');\n if (tagParts.length >= 3) {\n const provider = tagParts[0];\n const key = tagParts[1];\n const value = tagParts.slice(2).join(':'); // In case value contains colons\n return { provider, key, value };\n }\n }\n\n return null;\n};\n\n/**\n * Add a filter value to the filters object, avoiding duplicates\n */\nconst addFilterValue = (filters: Filters, field: string, value: string): void => {\n if (!filters[field]) {\n filters[field] = [];\n }\n if (!filters[field].includes(value)) {\n filters[field].push(value);\n }\n};\n\n/**\n * Convert Lucene AST to Filters and Tags\n */\nconst astToFiltersAndTags = (ast: any): { filters: Filters; tags: Tag[] } => {\n const filters: Filters = {};\n const tags: Tag[] = [];\n\n const traverseAST = (node: any): void => {\n if (!node) return;\n\n // Process field:value pairs\n if (node.field && node.term) {\n if (node.field === 'tag') {\n const tag = parseTag(node.term);\n if (tag) {\n tags.push(tag);\n }\n } else {\n addFilterValue(filters, node.field, node.term);\n }\n }\n\n // Recursively process left and right nodes\n if (node.left) traverseAST(node.left);\n if (node.right) traverseAST(node.right);\n };\n\n traverseAST(ast);\n return { filters, tags };\n};\n\n/**\n * Escape and quote a value for Lucene query if it contains special characters\n */\nconst quoteLuceneValue = (value: string): string => {\n // Check if value contains special Lucene characters that require quoting\n const specialChars = /[/()[\\]{}\\s:]/;\n if (specialChars.test(value)) {\n // Escape any quotes in the value and wrap in quotes\n const escapedQuote = String.raw`\\\"`;\n return `\"${value.replaceAll('\"', escapedQuote)}\"`;\n }\n return value;\n};\n\n/**\n * Convert Filters and Tags to Lucene query string\n */\nconst filtersAndTagsToLucene = (filters: Filters, tags: Tag[]): string => {\n const queryParts: string[] = [];\n\n // Convert filters to query parts\n for (const field of Object.keys(filters)) {\n const values = filters[field];\n if (values && values.length > 0) {\n if (values.length === 1) {\n queryParts.push(`${field}:${quoteLuceneValue(values[0])}`);\n } else {\n const orValues = values.map(value => `${field}:${quoteLuceneValue(value)}`).join(' OR ');\n queryParts.push(`(${orValues})`);\n }\n }\n }\n\n // Convert tags to query parts\n for (const tag of tags) {\n if (tag.provider && tag.key && tag.value) {\n // Use the new format: tag:provider.key=value\n queryParts.push(`tag:${tag.provider}.${tag.key}=${tag.value}`);\n }\n }\n\n return queryParts.join(' AND ');\n};\n\n/**\n * Parse Lucene query string safely\n */\nconst parseLuceneQuery = (query: string): { filters: Filters; tags: Tag[] } => {\n if (!query.trim()) {\n return { filters: {}, tags: [] };\n }\n\n try {\n const ast = lucene.parse(query);\n return astToFiltersAndTags(ast);\n } catch {\n // Intentionally catch and ignore parse errors - invalid Lucene queries\n // should gracefully fall back to empty state rather than break the UI\n return { filters: {}, tags: [] };\n }\n};\n\n/**\n * Format date to YYYY-MM for URL\n */\nconst formatDateForUrl = (date: Date): string => {\n return format(date, 'yyyy-MM');\n};\n\n/**\n * Parse YYYY-MM from URL to Date\n */\nconst parseDateFromUrl = (dateStr: string): Date | null => {\n try {\n return startOfMonth(parse(dateStr, 'yyyy-MM', new Date()));\n } catch {\n return null;\n }\n};\n\nexport interface InfraWalletLuceneUrlState {\n filters: Filters;\n selectedTags: Tag[];\n monthRange: MonthRange;\n granularity: string;\n aggregatedBy: string;\n}\n\ninterface UseInfraWalletLuceneParamsOptions {\n defaultFilters?: Filters;\n defaultTags?: Tag[];\n defaultMonthRange?: MonthRange;\n defaultGranularity?: string;\n defaultAggregatedBy?: string;\n}\n\n/**\n * Custom hook to manage InfraWallet state in URL search params using Lucene query syntax\n * This enables deep linking and bookmarking of specific filter/view combinations\n */\nexport const useInfraWalletLuceneParams = (options: UseInfraWalletLuceneParamsOptions = {}) => {\n const [searchParams, setSearchParams] = useSearchParams();\n const isInitialMount = useRef(true);\n\n /**\n * Get initial state from URL or use defaults\n */\n const getInitialState = useCallback((): InfraWalletLuceneUrlState => {\n // Parse Lucene query (URL decode first)\n const queryParam = searchParams.get('q') || '';\n const decodedQuery = decodeURIComponent(queryParam);\n const { filters, tags } = parseLuceneQuery(decodedQuery);\n\n // Use parsed values or defaults\n const finalFilters = Object.keys(filters).length > 0 ? filters : options.defaultFilters || {};\n const selectedTags = tags.length > 0 ? tags : options.defaultTags || [];\n\n // Month Range\n const fromParam = searchParams.get('from');\n const toParam = searchParams.get('to');\n let monthRange = options.defaultMonthRange;\n if (fromParam && toParam) {\n const startMonth = parseDateFromUrl(fromParam);\n const endMonth = parseDateFromUrl(toParam);\n if (startMonth && endMonth) {\n monthRange = { startMonth, endMonth };\n }\n }\n\n // Ensure monthRange is never undefined - provide a default\n if (!monthRange) {\n const currentMonth = startOfMonth(new Date());\n monthRange = { startMonth: currentMonth, endMonth: currentMonth };\n }\n\n // Granularity\n const granularityParam = searchParams.get('granularity');\n const granularity = granularityParam || options.defaultGranularity || 'monthly';\n\n const groupByParam = searchParams.get('groupBy');\n const aggregatedBy = groupByParam || options.defaultAggregatedBy || 'none';\n\n return {\n filters: finalFilters,\n selectedTags,\n monthRange,\n granularity,\n aggregatedBy,\n };\n }, [searchParams, options]);\n\n /**\n * Update URL search params with current state\n */\n const updateUrlState = useCallback(\n (state: Partial<InfraWalletLuceneUrlState>) => {\n setSearchParams(\n prev => {\n const newParams = new URLSearchParams(prev);\n\n // Update Lucene query if filters or tags changed\n if (state.filters !== undefined || state.selectedTags !== undefined) {\n // Get current state to merge with new state\n const currentQuery = prev.get('q') || '';\n const decodedCurrentQuery = decodeURIComponent(currentQuery);\n const { filters: currentFilters, tags: currentTags } = parseLuceneQuery(decodedCurrentQuery);\n\n const finalFilters = state.filters ?? currentFilters;\n const finalTags = state.selectedTags ?? currentTags;\n\n const luceneQuery = filtersAndTagsToLucene(finalFilters, finalTags);\n if (luceneQuery) {\n // URL encode the query before setting it\n newParams.set('q', encodeURIComponent(luceneQuery));\n } else {\n newParams.delete('q');\n }\n }\n\n // Update month range\n if (state.monthRange !== undefined) {\n newParams.set('from', formatDateForUrl(state.monthRange.startMonth));\n newParams.set('to', formatDateForUrl(state.monthRange.endMonth));\n }\n\n // Update granularity\n if (state.granularity !== undefined) {\n newParams.set('granularity', state.granularity);\n }\n\n // Update aggregatedBy (groupBy)\n if (state.aggregatedBy !== undefined) {\n newParams.set('groupBy', state.aggregatedBy);\n }\n\n return newParams;\n },\n { replace: true },\n ); // Use replace to avoid creating excessive history entries\n },\n [setSearchParams],\n );\n\n useEffect(() => {\n isInitialMount.current = false;\n }, []);\n\n return {\n getInitialState,\n updateUrlState,\n isInitialMount: isInitialMount.current,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useInfraWalletLuceneParams.esm.js","sources":["../../src/hooks/useInfraWalletLuceneParams.ts"],"sourcesContent":["import { format, parse, startOfMonth } from 'date-fns';\nimport * as lucene from 'lucene';\nimport { useCallback, useEffect, useRef } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Filters, Tag } from '../api/types';\nimport { MonthRange } from '../components/types';\n\n/**\n * Parse tag value into Tag object\n * Supports two formats:\n * 1. provider.key=value (preferred)\n * 2. provider:key:value (fallback)\n */\nconst parseTag = (tagValue: string): Tag | null => {\n // Try provider.key=value format first\n if (tagValue.includes('.') && tagValue.includes('=')) {\n const [providerKey, value] = tagValue.split('=');\n const [provider, key] = providerKey.split('.');\n if (provider && key && value) {\n return { provider, key, value };\n }\n }\n\n // Fallback to provider:key:value format\n if (tagValue.includes(':')) {\n const tagParts = tagValue.split(':');\n if (tagParts.length >= 3) {\n const provider = tagParts[0];\n const key = tagParts[1];\n const value = tagParts.slice(2).join(':'); // In case value contains colons\n return { provider, key, value };\n }\n }\n\n return null;\n};\n\n/**\n * Add a filter value to the filters object, avoiding duplicates\n */\nconst addFilterValue = (filters: Filters, field: string, value: string): void => {\n if (!filters[field]) {\n filters[field] = [];\n }\n if (!filters[field].includes(value)) {\n filters[field].push(value);\n }\n};\n\n/**\n * Convert Lucene AST to Filters and Tags\n */\nconst astToFiltersAndTags = (ast: any): { filters: Filters; tags: Tag[] } => {\n const filters: Filters = {};\n const tags: Tag[] = [];\n\n const traverseAST = (node: any): void => {\n if (!node) return;\n\n // Process field:value pairs\n if (node.field && node.term) {\n if (node.field === 'tag') {\n const tag = parseTag(node.term);\n if (tag) {\n tags.push(tag);\n }\n } else {\n addFilterValue(filters, node.field, node.term);\n }\n }\n\n // Recursively process left and right nodes\n if (node.left) traverseAST(node.left);\n if (node.right) traverseAST(node.right);\n };\n\n traverseAST(ast);\n return { filters, tags };\n};\n\n/**\n * Escape and quote a value for Lucene query if it contains special characters\n */\nconst quoteLuceneValue = (value: string): string => {\n // Check if value contains special Lucene characters that require quoting\n const specialChars = /[/()[\\]{}\\s:]/;\n if (specialChars.test(value)) {\n // Escape any quotes in the value and wrap in quotes\n const escapedQuote = String.raw`\\\"`;\n return `\"${value.replaceAll('\"', escapedQuote)}\"`;\n }\n return value;\n};\n\n/**\n * Quote a tag for Lucene query if a key contains special characters\n */\nconst quoteTag = (provider: string, key: string, value: string): string => {\n const specialChars = /[/()[\\]{}\\s:]/;\n const tag = `${provider}.${key}=${value}`;\n\n // If tag has special character in the key, it needs to be quoted. E.g. tag:\"AWS.key:environment=staging\"\n if (specialChars.test(key)) {\n return `tag:\"${tag}\"`;\n }\n return `tag:${tag}`;\n};\n\n/**\n * Convert Filters and Tags to Lucene query string\n */\nconst filtersAndTagsToLucene = (filters: Filters, tags: Tag[]): string => {\n const queryParts: string[] = [];\n\n // Convert filters to query parts\n for (const field of Object.keys(filters)) {\n const values = filters[field];\n if (values && values.length > 0) {\n if (values.length === 1) {\n queryParts.push(`${field}:${quoteLuceneValue(values[0])}`);\n } else {\n const orValues = values.map(value => `${field}:${quoteLuceneValue(value)}`).join(' OR ');\n queryParts.push(`(${orValues})`);\n }\n }\n }\n\n // Convert tags to query parts\n for (const tag of tags) {\n if (tag.provider && tag.key && tag.value) {\n // Use the new format: tag:provider.key=value\n queryParts.push(quoteTag(tag.provider, tag.key, tag.value));\n }\n }\n\n return queryParts.join(' AND ');\n};\n\n/**\n * Parse Lucene query string safely\n */\nconst parseLuceneQuery = (query: string): { filters: Filters; tags: Tag[] } => {\n if (!query.trim()) {\n return { filters: {}, tags: [] };\n }\n\n try {\n const ast = lucene.parse(query);\n return astToFiltersAndTags(ast);\n } catch {\n // Intentionally catch and ignore parse errors - invalid Lucene queries\n // should gracefully fall back to empty state rather than break the UI\n return { filters: {}, tags: [] };\n }\n};\n\n/**\n * Format date to YYYY-MM for URL\n */\nconst formatDateForUrl = (date: Date): string => {\n return format(date, 'yyyy-MM');\n};\n\n/**\n * Parse YYYY-MM from URL to Date\n */\nconst parseDateFromUrl = (dateStr: string): Date | null => {\n try {\n return startOfMonth(parse(dateStr, 'yyyy-MM', new Date()));\n } catch {\n return null;\n }\n};\n\nexport interface InfraWalletLuceneUrlState {\n filters: Filters;\n selectedTags: Tag[];\n monthRange: MonthRange;\n granularity: string;\n aggregatedBy: string;\n}\n\ninterface UseInfraWalletLuceneParamsOptions {\n defaultFilters?: Filters;\n defaultTags?: Tag[];\n defaultMonthRange?: MonthRange;\n defaultGranularity?: string;\n defaultAggregatedBy?: string;\n}\n\n/**\n * Custom hook to manage InfraWallet state in URL search params using Lucene query syntax\n * This enables deep linking and bookmarking of specific filter/view combinations\n */\nexport const useInfraWalletLuceneParams = (options: UseInfraWalletLuceneParamsOptions = {}) => {\n const [searchParams, setSearchParams] = useSearchParams();\n const isInitialMount = useRef(true);\n\n /**\n * Get initial state from URL or use defaults\n */\n const getInitialState = useCallback((): InfraWalletLuceneUrlState => {\n // Parse Lucene query (URL decode first)\n const queryParam = searchParams.get('q') || '';\n const decodedQuery = decodeURIComponent(queryParam);\n const { filters, tags } = parseLuceneQuery(decodedQuery);\n\n // Use parsed values or defaults\n const finalFilters = Object.keys(filters).length > 0 ? filters : options.defaultFilters || {};\n const selectedTags = tags.length > 0 ? tags : options.defaultTags || [];\n\n // Month Range\n const fromParam = searchParams.get('from');\n const toParam = searchParams.get('to');\n let monthRange = options.defaultMonthRange;\n if (fromParam && toParam) {\n const startMonth = parseDateFromUrl(fromParam);\n const endMonth = parseDateFromUrl(toParam);\n if (startMonth && endMonth) {\n monthRange = { startMonth, endMonth };\n }\n }\n\n // Ensure monthRange is never undefined - provide a default\n if (!monthRange) {\n const currentMonth = startOfMonth(new Date());\n monthRange = { startMonth: currentMonth, endMonth: currentMonth };\n }\n\n // Granularity\n const granularityParam = searchParams.get('granularity');\n const granularity = granularityParam || options.defaultGranularity || 'monthly';\n\n const groupByParam = searchParams.get('groupBy');\n const aggregatedBy = groupByParam || options.defaultAggregatedBy || 'none';\n\n return {\n filters: finalFilters,\n selectedTags,\n monthRange,\n granularity,\n aggregatedBy,\n };\n }, [searchParams, options]);\n\n /**\n * Update URL search params with current state\n */\n const updateUrlState = useCallback(\n (state: Partial<InfraWalletLuceneUrlState>) => {\n setSearchParams(\n prev => {\n const newParams = new URLSearchParams(prev);\n\n // Update Lucene query if filters or tags changed\n if (state.filters !== undefined || state.selectedTags !== undefined) {\n // Get current state to merge with new state\n const currentQuery = prev.get('q') || '';\n const decodedCurrentQuery = decodeURIComponent(currentQuery);\n const { filters: currentFilters, tags: currentTags } = parseLuceneQuery(decodedCurrentQuery);\n\n const finalFilters = state.filters ?? currentFilters;\n const finalTags = state.selectedTags ?? currentTags;\n\n const luceneQuery = filtersAndTagsToLucene(finalFilters, finalTags);\n if (luceneQuery) {\n // URL encode the query before setting it\n newParams.set('q', encodeURIComponent(luceneQuery));\n } else {\n newParams.delete('q');\n }\n }\n\n // Update month range\n if (state.monthRange !== undefined) {\n newParams.set('from', formatDateForUrl(state.monthRange.startMonth));\n newParams.set('to', formatDateForUrl(state.monthRange.endMonth));\n }\n\n // Update granularity\n if (state.granularity !== undefined) {\n newParams.set('granularity', state.granularity);\n }\n\n // Update aggregatedBy (groupBy)\n if (state.aggregatedBy !== undefined) {\n newParams.set('groupBy', state.aggregatedBy);\n }\n\n return newParams;\n },\n { replace: true },\n ); // Use replace to avoid creating excessive history entries\n },\n [setSearchParams],\n );\n\n useEffect(() => {\n isInitialMount.current = false;\n }, []);\n\n return {\n getInitialState,\n updateUrlState,\n isInitialMount: isInitialMount.current,\n };\n};\n"],"names":[],"mappings":"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"}
|