Просмотр исходного кода

drilldown wip

Signed-off-by: Thomas Evans <tevans3@icloud.com>
jjarrett21 2 лет назад
Родитель
Сommit
7c57eee842
5 измененных файлов с 395 добавлено и 164 удалено
  1. 2 1
      ui/src/CloudCost/CloudCost.js
  2. 8 1
      ui/src/CloudCost/tokens.js
  3. 26 5
      ui/src/CloudCostReports.js
  4. 42 0
      ui/src/services/cloudCostTop.js
  5. 317 157
      ui/src/util.js

+ 2 - 1
ui/src/CloudCost/CloudCost.js

@@ -21,6 +21,7 @@ const CloudCost = ({
   totalData = {},
   graphData = [],
   currency = "USD",
+  drilldown,
 }) => {
   const useStyles = makeStyles({
     noResults: {
@@ -169,7 +170,7 @@ const CloudCost = ({
               </TableRow>
               {pageRows.map((row, key) => {
                 return (
-                  <TableRow key={key}>
+                  <TableRow key={key} onClick={() => drilldown(row)}>
                     <TableCell align="left">{row.name}</TableCell>
                     <TableCell align="right">
                       {round(row.kubernetesPercent * 100) + "%"}

+ 8 - 1
ui/src/CloudCost/tokens.js

@@ -29,4 +29,11 @@ const costMetricOptions = [
   { name: "Amortized Cost", value: "AmortizedCost" },
 ];
 
-export { windowOptions, aggregationOptions, costMetricOptions };
+const aggMap = {
+  invoiceEntityID: "Invoice Entity",
+  provider: "Provider",
+  service: "Service",
+  accountID: "Account",
+};
+
+export { windowOptions, aggregationOptions, costMetricOptions, aggMap };

+ 26 - 5
ui/src/CloudCostReports.js

@@ -14,11 +14,13 @@ import CloudCostEditControls from "./CloudCost/Controls/CloudCostEditControls";
 import Subtitle from "./components/Subtitle";
 import Warnings from "./components/Warnings";
 import CloudCostService from "./services/cloudCost";
+import CloudCostTopService from "./services/CloudCostTop";
 
 import {
   windowOptions,
   costMetricOptions,
   aggregationOptions,
+  aggMap,
 } from "./CloudCost/tokens";
 import { currencyCodes } from "./constants/currencyCodes";
 import CloudCost from "./CloudCost/CloudCost";
@@ -48,6 +50,7 @@ const CloudCostReports = () => {
   const [costMetric, setCostMetric] = React.useState(
     costMetricOptions[0].value
   );
+  const [filters, setFilters] = React.useState([]);
   const [currency, setCurrency] = React.useState("USD");
   // page and settings state
   const [init, setInit] = React.useState(false);
@@ -99,10 +102,11 @@ const CloudCostReports = () => {
     setLoading(true);
     setErrors([]);
     try {
-      const resp = await CloudCostService.fetchCloudCostData(
+      const resp = await CloudCostTopService.fetchCloudCostData(
         window,
         aggregateBy,
-        costMetric
+        costMetric,
+        filters
       );
       if (resp.data) {
         setCloudCostData(resp.data);
@@ -120,7 +124,7 @@ const CloudCostReports = () => {
             },
           ]);
         }
-        setCloudCostData([]);
+        // setCloudCostData([]);
       }
     } catch (err) {
       if (err.message.indexOf("404") === 0) {
@@ -144,11 +148,27 @@ const CloudCostReports = () => {
           },
         ]);
       }
-      setCloudCostData([]);
+      // setCloudCostData([]);
     }
     setLoading(false);
   }
 
+  function drilldown(row) {
+    const nameParts = row.name.split("/");
+    const nextAgg = aggregateBy.includes("service") ? "item" : "service";
+    const aggToString = [aggregateBy];
+    const newFilters = aggToString.map((property, i) => {
+      const value = nameParts[i];
+      return {
+        property,
+        value,
+        name: aggMap[property] || property,
+      };
+    });
+    setFilters(newFilters);
+    setAggregateBy(nextAgg);
+  }
+
   React.useEffect(() => {
     setWindow(searchParams.get("window") || "7d");
     setAggregateBy(searchParams.get("agg") || "service");
@@ -169,7 +189,7 @@ const CloudCostReports = () => {
   React.useEffect(() => {
     setFetch(!fetch);
     setTitle(generateTitle({ window, aggregateBy, costMetric }));
-  }, [window, aggregateBy, costMetric]);
+  }, [window, aggregateBy, costMetric, filters]);
 
   return (
     <Page active="cloud.html">
@@ -244,6 +264,7 @@ const CloudCostReports = () => {
               currency={currency}
               graphData={cloudCostData.graphData}
               totalData={cloudCostData.tableTotal}
+              drilldown={drilldown}
             />
           )}
         </Paper>

+ 42 - 0
ui/src/services/cloudCostTop.js

@@ -0,0 +1,42 @@
+import axios from "axios";
+import { getCloudFilters, formatSampleItemsForGraph } from "../util";
+
+class CloudCostTopService {
+  BASE_URL = process.env.BASE_URL || "{PLACEHOLDER_BASE_URL}";
+
+  async fetchCloudCostData(window, aggregate, costMetric, filters) {
+    if (this.BASE_URL.includes("PLACEHOLDER_BASE_URL")) {
+      this.BASE_URL = `http://localhost:9090/model`;
+    }
+
+    const params = {
+      window,
+      aggregate,
+      costMetric,
+      filters,
+    };
+
+    if (aggregate.includes("Item")) {
+      console.log("here");
+      console.log(aggregate);
+      const resp = await fetch(
+        `${
+          this.BASE_URL
+        }/model/cloudCost/top?window=${window}&costMetric=${costMetric}${getCloudFilters(
+          filters
+        )}`
+      );
+      const result_2 = await Promise.resolve(resp.data.json());
+      const whatData = formatSampleItemsForGraph(result_2);
+      console.log(whatData);
+      return formatSampleItemsForGraph(result_2);
+    }
+
+    const result = await axios.get(`${this.BASE_URL}/model/cloudCost/view`, {
+      params,
+    });
+    return result.data;
+  }
+}
+
+export default new CloudCostTopService();

+ 317 - 157
ui/src/util.js

@@ -1,102 +1,108 @@
-import { forEach, get, round } from 'lodash'
+import { forEach, get, round } from "lodash";
 
 // rangeToCumulative takes an AllocationSetRange (type: array[AllocationSet])
 // and accumulates the values into a single AllocationSet (type: object)
 export function rangeToCumulative(allocationSetRange, aggregateBy) {
   if (allocationSetRange.length === 0) {
-    return null
+    return null;
   }
 
-  const result = {}
+  const result = {};
 
   forEach(allocationSetRange, (allocSet) => {
     forEach(allocSet, (alloc) => {
       if (result[alloc.name] === undefined) {
-        const hrs = get(alloc, 'minutes', 0) / 60.0
+        const hrs = get(alloc, "minutes", 0) / 60.0;
 
         result[alloc.name] = {
           name: alloc.name,
           [aggregateBy]: alloc.name,
-          cpuCost: get(alloc, 'cpuCost', 0),
-          gpuCost: get(alloc, 'gpuCost', 0),
-          ramCost: get(alloc, 'ramCost', 0),
-          pvCost: get(alloc, 'pvCost', 0),
-          networkCost: get(alloc, 'networkCost', 0),
-          sharedCost: get(alloc, 'sharedCost', 0),
-          externalCost: get(alloc, 'externalCost', 0),
-          totalCost: get(alloc, 'totalCost', 0),
-          cpuUseCoreHrs: get(alloc, 'cpuCoreUsageAverage', 0) * hrs,
-          cpuReqCoreHrs: get(alloc, 'cpuCoreRequestAverage', 0) * hrs,
-          ramUseByteHrs: get(alloc, 'ramByteUsageAverage', 0) * hrs,
-          ramReqByteHrs: get(alloc, 'ramByteRequestAverage', 0) * hrs,
-          cpuEfficiency: get(alloc, 'cpuEfficiency', 0),
-          ramEfficiency: get(alloc, 'ramEfficiency', 0),
-          totalEfficiency: get(alloc, 'totalEfficiency', 0),
-        }
+          cpuCost: get(alloc, "cpuCost", 0),
+          gpuCost: get(alloc, "gpuCost", 0),
+          ramCost: get(alloc, "ramCost", 0),
+          pvCost: get(alloc, "pvCost", 0),
+          networkCost: get(alloc, "networkCost", 0),
+          sharedCost: get(alloc, "sharedCost", 0),
+          externalCost: get(alloc, "externalCost", 0),
+          totalCost: get(alloc, "totalCost", 0),
+          cpuUseCoreHrs: get(alloc, "cpuCoreUsageAverage", 0) * hrs,
+          cpuReqCoreHrs: get(alloc, "cpuCoreRequestAverage", 0) * hrs,
+          ramUseByteHrs: get(alloc, "ramByteUsageAverage", 0) * hrs,
+          ramReqByteHrs: get(alloc, "ramByteRequestAverage", 0) * hrs,
+          cpuEfficiency: get(alloc, "cpuEfficiency", 0),
+          ramEfficiency: get(alloc, "ramEfficiency", 0),
+          totalEfficiency: get(alloc, "totalEfficiency", 0),
+        };
       } else {
-        const hrs = get(alloc, 'minutes', 0) / 60.0
-
-        result[alloc.name].cpuCost += get(alloc, 'cpuCost', 0)
-        result[alloc.name].gpuCost += get(alloc, 'gpuCost', 0)
-        result[alloc.name].ramCost += get(alloc, 'ramCost', 0)
-        result[alloc.name].pvCost += get(alloc, 'pvCost', 0)
-        result[alloc.name].networkCost += get(alloc, 'networkCost', 0)
-        result[alloc.name].sharedCost += get(alloc, 'sharedCost', 0)
-        result[alloc.name].externalCost += get(alloc, 'externalCost', 0)
-        result[alloc.name].totalCost += get(alloc, 'totalCost', 0)
-        result[alloc.name].cpuUseCoreHrs += get(alloc, 'cpuCoreUsageAverage', 0) * hrs
-        result[alloc.name].cpuReqCoreHrs += get(alloc, 'cpuCoreRequestAverage', 0) * hrs
-        result[alloc.name].ramUseByteHrs += get(alloc, 'ramByteUsageAverage', 0) * hrs
-        result[alloc.name].ramReqByteHrs += get(alloc, 'ramByteRequestAverage', 0) * hrs
+        const hrs = get(alloc, "minutes", 0) / 60.0;
+
+        result[alloc.name].cpuCost += get(alloc, "cpuCost", 0);
+        result[alloc.name].gpuCost += get(alloc, "gpuCost", 0);
+        result[alloc.name].ramCost += get(alloc, "ramCost", 0);
+        result[alloc.name].pvCost += get(alloc, "pvCost", 0);
+        result[alloc.name].networkCost += get(alloc, "networkCost", 0);
+        result[alloc.name].sharedCost += get(alloc, "sharedCost", 0);
+        result[alloc.name].externalCost += get(alloc, "externalCost", 0);
+        result[alloc.name].totalCost += get(alloc, "totalCost", 0);
+        result[alloc.name].cpuUseCoreHrs +=
+          get(alloc, "cpuCoreUsageAverage", 0) * hrs;
+        result[alloc.name].cpuReqCoreHrs +=
+          get(alloc, "cpuCoreRequestAverage", 0) * hrs;
+        result[alloc.name].ramUseByteHrs +=
+          get(alloc, "ramByteUsageAverage", 0) * hrs;
+        result[alloc.name].ramReqByteHrs +=
+          get(alloc, "ramByteRequestAverage", 0) * hrs;
       }
-    })
-  })
+    });
+  });
 
   // If the range is of length > 1 (i.e. it is not just a single set) then
   // compute efficiency for each result after accumulating.
   if (allocationSetRange.length > 1) {
     forEach(result, (alloc, name) => {
       // If we can't compute total efficiency, it defaults to 0.0
-      let totalEfficiency = 0.0
+      let totalEfficiency = 0.0;
 
       // CPU efficiency is defined as (usage/request). If request == 0.0 but
       // usage > 0, then efficiency gets set to 1.0.
-      let cpuEfficiency = 0.0
+      let cpuEfficiency = 0.0;
       if (alloc.cpuReqCoreHrs > 0) {
-        cpuEfficiency = alloc.cpuUseCoreHrs / alloc.cpuReqCoreHrs
+        cpuEfficiency = alloc.cpuUseCoreHrs / alloc.cpuReqCoreHrs;
       } else if (alloc.cpuUseCoreHrs > 0) {
-        cpuEfficiency = 1.0
+        cpuEfficiency = 1.0;
       }
 
       // RAM efficiency is defined as (usage/request). If request == 0.0 but
       // usage > 0, then efficiency gets set to 1.0.
-      let ramEfficiency = 0.0
+      let ramEfficiency = 0.0;
       if (alloc.ramReqByteHrs > 0) {
-        ramEfficiency = alloc.ramUseByteHrs / alloc.ramReqByteHrs
+        ramEfficiency = alloc.ramUseByteHrs / alloc.ramReqByteHrs;
       } else if (alloc.ramUseByteHrs > 0) {
-        ramEfficiency = 1.0
+        ramEfficiency = 1.0;
       }
 
       // Compute efficiency as the cost-weighted average of CPU and RAM
       // efficiency
-      if ((alloc.cpuCost + alloc.ramCost) > 0.0) {
-        totalEfficiency = ((alloc.cpuCost*cpuEfficiency)+(alloc.ramCost*ramEfficiency)) / (alloc.cpuCost + alloc.ramCost)
+      if (alloc.cpuCost + alloc.ramCost > 0.0) {
+        totalEfficiency =
+          (alloc.cpuCost * cpuEfficiency + alloc.ramCost * ramEfficiency) /
+          (alloc.cpuCost + alloc.ramCost);
       }
 
-      result[name].cpuEfficiency = cpuEfficiency
-      result[name].ramEfficiency = ramEfficiency
-      result[name].totalEfficiency = totalEfficiency
-    })
+      result[name].cpuEfficiency = cpuEfficiency;
+      result[name].ramEfficiency = ramEfficiency;
+      result[name].totalEfficiency = totalEfficiency;
+    });
   }
 
-  return result
+  return result;
 }
 
 // cumulativeToTotals adds each entry in the given AllocationSet (type: object)
 // and returns a single Allocation (type: object) representing the totals
 export function cumulativeToTotals(allocationSet) {
   let totals = {
-    name: 'Totals',
+    name: "Totals",
     cpuCost: 0,
     gpuCost: 0,
     ramCost: 0,
@@ -108,177 +114,223 @@ export function cumulativeToTotals(allocationSet) {
     cpuEfficiency: 0,
     ramEfficiency: 0,
     totalEfficiency: 0,
-  }
+  };
 
   // Use these for computing efficiency. As such, idle will not factor into
   // these numbers, including CPU and RAM cost.
-  let cpuReqCoreHrs = 0
-  let cpuUseCoreHrs = 0
-  let ramReqByteHrs = 0
-  let ramUseByteHrs = 0
-  let cpuCost = 0
-  let ramCost = 0
+  let cpuReqCoreHrs = 0;
+  let cpuUseCoreHrs = 0;
+  let ramReqByteHrs = 0;
+  let ramUseByteHrs = 0;
+  let cpuCost = 0;
+  let ramCost = 0;
 
   forEach(allocationSet, (alloc, name) => {
     // Accumulate efficiency-related fields
     if (name !== "__idle__") {
-      cpuReqCoreHrs += get(alloc, 'cpuReqCoreHrs', 0.0)
-      cpuUseCoreHrs += get(alloc, 'cpuUseCoreHrs', 0.0)
-      ramReqByteHrs += get(alloc, 'ramReqByteHrs', 0.0)
-      ramUseByteHrs += get(alloc, 'ramUseByteHrs', 0.0)
-      cpuCost += get(alloc, 'cpuCost', 0.0)
-      ramCost += get(alloc, 'ramCost', 0.0)
+      cpuReqCoreHrs += get(alloc, "cpuReqCoreHrs", 0.0);
+      cpuUseCoreHrs += get(alloc, "cpuUseCoreHrs", 0.0);
+      ramReqByteHrs += get(alloc, "ramReqByteHrs", 0.0);
+      ramUseByteHrs += get(alloc, "ramUseByteHrs", 0.0);
+      cpuCost += get(alloc, "cpuCost", 0.0);
+      ramCost += get(alloc, "ramCost", 0.0);
     }
 
     // Sum cumulative fields
-    totals.cpuCost += get(alloc, 'cpuCost', 0)
-    totals.gpuCost += get(alloc, 'gpuCost', 0)
-    totals.ramCost += get(alloc, 'ramCost', 0)
-    totals.pvCost += get(alloc, 'pvCost', 0)
-    totals.networkCost += get(alloc, 'networkCost', 0)
-    totals.sharedCost += get(alloc, 'sharedCost', 0)
-    totals.externalCost += get(alloc, 'externalCost', 0)
-    totals.totalCost += get(alloc, 'totalCost', 0)
-  })
+    totals.cpuCost += get(alloc, "cpuCost", 0);
+    totals.gpuCost += get(alloc, "gpuCost", 0);
+    totals.ramCost += get(alloc, "ramCost", 0);
+    totals.pvCost += get(alloc, "pvCost", 0);
+    totals.networkCost += get(alloc, "networkCost", 0);
+    totals.sharedCost += get(alloc, "sharedCost", 0);
+    totals.externalCost += get(alloc, "externalCost", 0);
+    totals.totalCost += get(alloc, "totalCost", 0);
+  });
 
   // Compute efficiency
   if (cpuReqCoreHrs > 0) {
-    totals.cpuEfficiency = cpuUseCoreHrs / cpuReqCoreHrs
+    totals.cpuEfficiency = cpuUseCoreHrs / cpuReqCoreHrs;
   } else if (cpuUseCoreHrs > 0) {
-    totals.cpuEfficiency = 1.0
+    totals.cpuEfficiency = 1.0;
   }
 
   if (ramReqByteHrs > 0) {
-    totals.ramEfficiency = ramUseByteHrs / ramReqByteHrs
+    totals.ramEfficiency = ramUseByteHrs / ramReqByteHrs;
   } else if (ramUseByteHrs > 0) {
-    totals.ramEfficiency = 1.0
+    totals.ramEfficiency = 1.0;
   }
 
-  if ((cpuCost + ramCost) > 0) {
-    totals.totalEfficiency = ((cpuCost*totals.cpuEfficiency) + (ramCost*totals.ramEfficiency)) / (cpuCost + ramCost)
+  if (cpuCost + ramCost > 0) {
+    totals.totalEfficiency =
+      (cpuCost * totals.cpuEfficiency + ramCost * totals.ramEfficiency) /
+      (cpuCost + ramCost);
   }
 
-  totals.cpuReqCoreHrs = cpuReqCoreHrs
-  totals.cpuUseCoreHrs = cpuUseCoreHrs
-  totals.ramReqByteHrs = ramReqByteHrs
-  totals.ramUseByteHrs = ramUseByteHrs
+  totals.cpuReqCoreHrs = cpuReqCoreHrs;
+  totals.cpuUseCoreHrs = cpuUseCoreHrs;
+  totals.ramReqByteHrs = ramReqByteHrs;
+  totals.ramUseByteHrs = ramUseByteHrs;
 
-  return totals
+  return totals;
 }
 
 export function toVerboseTimeRange(window) {
-  const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
-
-  const start = new Date()
-  start.setUTCHours(0, 0, 0, 0)
-
-  const end = new Date()
-  end.setUTCHours(0, 0, 0, 0)
+  const months = [
+    "January",
+    "February",
+    "March",
+    "April",
+    "May",
+    "June",
+    "July",
+    "August",
+    "September",
+    "October",
+    "November",
+    "December",
+  ];
+
+  const start = new Date();
+  start.setUTCHours(0, 0, 0, 0);
+
+  const end = new Date();
+  end.setUTCHours(0, 0, 0, 0);
 
   switch (window) {
-    case 'today':
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}`
-    case 'yesterday':
-      start.setUTCDate(start.getUTCDate()-1)
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}`
-    case 'week':
-      start.setUTCDate(start.getUTCDate()-start.getUTCDay())
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} until now`
-    case 'month':
-      start.setUTCDate(1)
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} until now`
-    case 'lastweek':
-      start.setUTCDate(start.getUTCDate()-(start.getUTCDay()+7))
-      end.setUTCDate(end.getUTCDate()-(end.getUTCDay()+1))
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through ${end.getUTCDate()} ${months[end.getUTCMonth()]} ${end.getUTCFullYear()}`
-    case 'lastmonth':
-      end.setUTCDate(1)
-      end.setUTCDate(end.getUTCDate()-1)
-      start.setUTCDate(1)
-      start.setUTCDate(start.getUTCDate()-1)
-      start.setUTCDate(1)
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through ${end.getUTCDate()} ${months[end.getUTCMonth()]} ${end.getUTCFullYear()}`
-    case '6d':
-        start.setUTCDate(start.getUTCDate()-6)
-        return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
-    case '29d':
-      start.setUTCDate(start.getUTCDate()-29)
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
-    case '59d':
-      start.setUTCDate(start.getUTCDate()-59)
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
-    case '89d':
-      start.setUTCDate(start.getUTCDate()-89)
-      return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
+    case "today":
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()}`;
+    case "yesterday":
+      start.setUTCDate(start.getUTCDate() - 1);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()}`;
+    case "week":
+      start.setUTCDate(start.getUTCDate() - start.getUTCDay());
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} until now`;
+    case "month":
+      start.setUTCDate(1);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} until now`;
+    case "lastweek":
+      start.setUTCDate(start.getUTCDate() - (start.getUTCDay() + 7));
+      end.setUTCDate(end.getUTCDate() - (end.getUTCDay() + 1));
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
+        months[end.getUTCMonth()]
+      } ${end.getUTCFullYear()}`;
+    case "lastmonth":
+      end.setUTCDate(1);
+      end.setUTCDate(end.getUTCDate() - 1);
+      start.setUTCDate(1);
+      start.setUTCDate(start.getUTCDate() - 1);
+      start.setUTCDate(1);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
+        months[end.getUTCMonth()]
+      } ${end.getUTCFullYear()}`;
+    case "6d":
+      start.setUTCDate(start.getUTCDate() - 6);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} through now`;
+    case "29d":
+      start.setUTCDate(start.getUTCDate() - 29);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} through now`;
+    case "59d":
+      start.setUTCDate(start.getUTCDate() - 59);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} through now`;
+    case "89d":
+      start.setUTCDate(start.getUTCDate() - 89);
+      return `${start.getUTCDate()} ${
+        months[start.getUTCMonth()]
+      } ${start.getUTCFullYear()} through now`;
   }
 
-  const splitDates = window.split(",")
+  const splitDates = window.split(",");
   if (checkCustomWindow(window) && splitDates.length > 1) {
-    let s = splitDates[0].split(/\D+/).slice(0, 3)
-    let e = splitDates[1].split(/\D+/).slice(0, 3)
+    let s = splitDates[0].split(/\D+/).slice(0, 3);
+    let e = splitDates[1].split(/\D+/).slice(0, 3);
     if (s.length === 3 && e.length === 3) {
-      start.setUTCFullYear(s[0], s[1]-1, s[2])
-      end.setUTCFullYear(e[0], e[1]-1, e[2])
+      start.setUTCFullYear(s[0], s[1] - 1, s[2]);
+      end.setUTCFullYear(e[0], e[1] - 1, e[2]);
       if (start === end) {
-        return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}`
+        return `${start.getUTCDate()} ${
+          months[start.getUTCMonth()]
+        } ${start.getUTCFullYear()}`;
       } else {
-        return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through ${end.getUTCDate()} ${months[end.getUTCMonth()]} ${end.getUTCFullYear()}`
+        return `${start.getUTCDate()} ${
+          months[start.getUTCMonth()]
+        } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
+          months[end.getUTCMonth()]
+        } ${end.getUTCFullYear()}`;
       }
     }
   }
-  return null
+  return null;
 }
 
 export function bytesToString(bytes) {
-  const ei = Math.pow(1024, 6)
+  const ei = Math.pow(1024, 6);
   if (bytes >= ei) {
-    return `${round(bytes/ei, 1)} EiB`
+    return `${round(bytes / ei, 1)} EiB`;
   }
-  const pi = Math.pow(1024, 5)
+  const pi = Math.pow(1024, 5);
   if (bytes >= pi) {
-    return `${round(bytes/pi, 1)} PiB`
+    return `${round(bytes / pi, 1)} PiB`;
   }
-  const ti = Math.pow(1024, 4)
+  const ti = Math.pow(1024, 4);
   if (bytes >= ti) {
-    return `${round(bytes/ti, 1)} TiB`
+    return `${round(bytes / ti, 1)} TiB`;
   }
-  const gi = Math.pow(1024, 3)
+  const gi = Math.pow(1024, 3);
   if (bytes >= gi) {
-    return `${round(bytes/gi, 1)} GiB`
+    return `${round(bytes / gi, 1)} GiB`;
   }
-  const mi = Math.pow(1024, 2)
+  const mi = Math.pow(1024, 2);
   if (bytes >= mi) {
-    return `${round(bytes/mi, 1)} MiB`
+    return `${round(bytes / mi, 1)} MiB`;
   }
-  const ki = Math.pow(1024, 1)
+  const ki = Math.pow(1024, 1);
   if (bytes >= ki) {
-    return `${round(bytes/ki, 1)} KiB`
+    return `${round(bytes / ki, 1)} KiB`;
   }
 
-  return `${round(bytes, 1)} B`
+  return `${round(bytes, 1)} B`;
 }
 
-const currencyLocale = "en-US"
+const currencyLocale = "en-US";
 
 export function toCurrency(amount, currency, precision) {
   if (typeof amount !== "number") {
-    console.warn(`Tried to convert "${amount}" to currency, but it is not a number`)
-    return ""
+    console.warn(
+      `Tried to convert "${amount}" to currency, but it is not a number`
+    );
+    return "";
   }
 
   if (currency === undefined || currency === "") {
-    currency = "USD"
+    currency = "USD";
   }
 
   const opts = {
     style: "currency",
     currency: currency,
-
-  }
+  };
 
   if (typeof precision === "number") {
-    opts.minimumFractionDigits = precision
-    opts.maximumFractionDigits = precision
+    opts.minimumFractionDigits = precision;
+    opts.maximumFractionDigits = precision;
   }
 
   return amount.toLocaleString(currencyLocale, opts);
@@ -286,11 +338,119 @@ export function toCurrency(amount, currency, precision) {
 
 export function checkCustomWindow(window) {
   // Example ISO interval string: 2020-12-02T00:00:00Z,2020-12-03T23:59:59Z
-  const customDateRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z,\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
-  return customDateRegex.test(window)
+  const customDateRegex =
+    /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z,\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
+  return customDateRegex.test(window);
 }
 
+export function getCloudFilters(filters) {
+  const filterNamesMap = {
+    "invoice entity": "filterInvoiceEntityIDs",
+    provider: "filterProviders",
+    providerids: "filterProviderIDs",
+    service: "filterServices",
+    account: "filterAccountIDs",
+  };
+  const params = new URLSearchParams();
+  const labelFilters = [];
+
+  for (let filter of filters) {
+    const mapped = filterNamesMap[filter.property.toLowerCase()];
+
+    if (mapped) {
+      params.set(mapped, filter.value);
+    } else if (filter.property === "Labels") {
+      labelFilters.push(filter.value);
+    } else if (filter.property.startsWith(":")) {
+      labelFilters.push(`${filter.property.slice(6)}:${filter.value}`);
+    }
+  }
+  if (labelFilters.length) {
+    params.set("filterLabels", labelFilters.join(","));
+  }
+
+  return `&${params.toString()}`;
+}
 
+export function formatSampleItemsForGraph({ data }) {
+  const graphData = data.sets.map(({ cloudCosts, window: { end, start } }) => {
+    return {
+      end,
+      items: Object.entries(cloudCosts).map(([name, item]) => ({
+        name,
+        value: item.netCost.cost,
+      })),
+      start,
+    };
+  });
+  const accumulator = {};
+  data.sets.forEach(({ cloudCosts, window }) => {
+    Object.entries(cloudCosts).forEach(
+      ([name, { amortizedNetCost, netCost, properties }]) => {
+        accumulator[name] ||= {
+          cost: 0,
+          netCost: 0,
+          start: "",
+          end: "",
+          providerID: "",
+          labelName: "",
+          kubernetesCost: 0,
+          get kubernetesPercent() {
+            return this.kubernetesCost / this.cost;
+          },
+        };
+        accumulator[name].cost += amortizedNetCost.cost;
+        accumulator[name].kubernetesCost +=
+          amortizedNetCost.cost * amortizedNetCost.kubernetesPercent;
+        accumulator[name].netCost += netCost.cost;
+        accumulator[name].start = window.start;
+        accumulator[name].end = window.end;
+        accumulator[name].providerID = properties.providerID;
+        accumulator[name].labelName = properties.labels?.name;
+      }
+    );
+  });
+  const tableRows = Object.entries(accumulator).map(
+    ([
+      name,
+      {
+        cost,
+        netCost,
+        start,
+        end,
+        providerID,
+        kubernetesCost,
+        kubernetesPercent,
+        labelName,
+      },
+    ]) => ({
+      cost,
+      name,
+      kubernetesCost,
+      get kubernetesPercent() {
+        return this.kubernetesCost / this.cost;
+      },
+      netCost,
+      start,
+      end,
+      providerID,
+      labelName,
+    })
+  );
+  const tableTotal = tableRows.reduce((tr1, tr2) => ({
+    ...tr1,
+    cost: tr2.cost,
+    name: "",
+    kubernetesCost:
+      tr1.kubernetesCost * tr1.cost + tr2.kubernetesCost * tr2.cost,
+    netCost: tr1.netCost + tr2.netCost,
+    start: "",
+    end: "",
+    labelName: "",
+    providerID: "",
+  }));
+  return { graphData, tableRows, tableTotal };
+}
 
 export default {
   rangeToCumulative,
@@ -299,4 +459,4 @@ export default {
   bytesToString,
   toCurrency,
   checkCustomWindow,
-}
+};