diff --git a/.changeset/slow-queens-visit.md b/.changeset/slow-queens-visit.md index 2081215287..2df948e477 100644 --- a/.changeset/slow-queens-visit.md +++ b/.changeset/slow-queens-visit.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-opencost': major +'@backstage/plugin-opencost': minor --- New OpenCost plugin provides an port of the latest OpenCost UI to Backstage with updated dependencies. The plugin's README covers installation and configuration diff --git a/plugins/opencost/README.md b/plugins/opencost/README.md index d034f1b3dd..39c5e7ee2e 100644 --- a/plugins/opencost/README.md +++ b/plugins/opencost/README.md @@ -15,18 +15,18 @@ All of the code was originally ported from https://github.com/opencost/opencost/ ``` 2. Add the `OpenCostPage` to your `packages/app/src/App.tsx`: -```tsx -import { OpenCostPage } from '@backstage/plugin-opencost'; -``` + ```tsx + import { OpenCostPage } from '@backstage/plugin-opencost'; + ``` and -```tsx - - … - } /> - -``` + ```tsx + + … + } /> + + ``` 3. Add link to OpenCost to your sidebar @@ -74,6 +74,6 @@ opencost: - Allow for user-provided default reports and/or disabling controls - Support multiple hard-coded reports - clean up deprecation warnings and upgrade to all the latest React components -- Fork(?) to support Kubecost, which could provide Alerts and Recommendations, similar to the Cost Explorer plugin +- Fork(?) to support `Kubecost`, which could provide Alerts and Recommendations, similar to the Cost Explorer plugin ![Screenshot](screenshot.png) diff --git a/plugins/opencost/config.d.ts b/plugins/opencost/config.d.ts index 0ff11bd1ed..0070bec7b7 100644 --- a/plugins/opencost/config.d.ts +++ b/plugins/opencost/config.d.ts @@ -15,13 +15,14 @@ */ export interface Config { - /** Configurations for the OpenCost plugin - * opencost config + /** + * Configurations for the OpenCost plugin + * * @visibility frontend */ opencost?: { /** - * The ppbase URL for the OpenCost API + * The base URL for the OpenCost API * @visibility frontend */ baseUrl: string; diff --git a/plugins/opencost/package.json b/plugins/opencost/package.json index e5de3defa8..2dde15362b 100644 --- a/plugins/opencost/package.json +++ b/plugins/opencost/package.json @@ -31,10 +31,11 @@ "@material-ui/lab": "^4.0.0-alpha.60", "@material-ui/pickers": "^3.3.10", "@material-ui/styles": "^4.11.5", + "axios": "^1.4.0", "date-fns": "^2.30.0", "lodash": "^4.17.21", "react-use": "^17.2.4", - "recharts": "^2.7.0" + "recharts": "^2.5.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0", @@ -48,7 +49,6 @@ "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", - "@types/node": "*", "cross-fetch": "^3.1.5", "msw": "^1.0.0" }, diff --git a/plugins/opencost/src/components/AllocationChart/RangeChart.js b/plugins/opencost/src/components/AllocationChart/RangeChart.js index d95981f42a..b1ce5a34ee 100644 --- a/plugins/opencost/src/components/AllocationChart/RangeChart.js +++ b/plugins/opencost/src/components/AllocationChart/RangeChart.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import React from 'react'; import { BarChart, diff --git a/plugins/opencost/src/components/AllocationChart/SummaryChart.js b/plugins/opencost/src/components/AllocationChart/SummaryChart.js index 9ac59cdbca..53c0842845 100644 --- a/plugins/opencost/src/components/AllocationChart/SummaryChart.js +++ b/plugins/opencost/src/components/AllocationChart/SummaryChart.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import React from 'react'; import { ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'; import { primary, greyscale, browns } from '../../constants/colors'; diff --git a/plugins/opencost/src/components/AllocationChart/index.js b/plugins/opencost/src/components/AllocationChart/index.js index c08c5513a8..9fec3e296f 100644 --- a/plugins/opencost/src/components/AllocationChart/index.js +++ b/plugins/opencost/src/components/AllocationChart/index.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import React from 'react'; import { isArray, filter, map, reduce, reverse, sortBy } from 'lodash'; diff --git a/plugins/opencost/src/components/Controls/Download.js b/plugins/opencost/src/components/Controls/Download.js index 527940924a..727188e5c1 100644 --- a/plugins/opencost/src/components/Controls/Download.js +++ b/plugins/opencost/src/components/Controls/Download.js @@ -13,84 +13,89 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React from 'react' -import { get, forEach, reverse, round, sortBy } from 'lodash' -import ExportIcon from '@material-ui/icons/GetApp' -import IconButton from '@material-ui/core/IconButton' -import Tooltip from '@material-ui/core/Tooltip' +import React from 'react'; +import { get, forEach, reverse, round, sortBy } from 'lodash'; +import ExportIcon from '@material-ui/icons/GetApp'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; const columns = [ { - head: "Name", - prop: "name", + head: 'Name', + prop: 'name', currency: false, - }, { - head: "CPU", - prop: "cpuCost", + }, + { + head: 'CPU', + prop: 'cpuCost', currency: true, - }, { - head: "GPU", - prop: "gpuCost", + }, + { + head: 'GPU', + prop: 'gpuCost', currency: true, - }, { - head: "RAM", - prop: "ramCost", + }, + { + head: 'RAM', + prop: 'ramCost', currency: true, - }, { - head: "PV", - prop: "pvCost", + }, + { + head: 'PV', + prop: 'pvCost', currency: true, - }, { - head: "Network", - prop: "networkCost", + }, + { + head: 'Network', + prop: 'networkCost', currency: true, - }, { - head: "Shared", - prop: "sharedCost", + }, + { + head: 'Shared', + prop: 'sharedCost', currency: true, - }, { - head: "Total", - prop: "totalCost", + }, + { + head: 'Total', + prop: 'totalCost', currency: true, - } -] + }, +]; -const toCSVLine = (datum) => { - const cols = [] +const toCSVLine = datum => { + const cols = []; forEach(columns, c => { if (c.currency) { - cols.push(round(get(datum, c.prop, 0.0), 2)) + cols.push(round(get(datum, c.prop, 0.0), 2)); } else { - cols.push(`"${get(datum, c.prop, "")}"`) + cols.push(`"${get(datum, c.prop, '')}"`); } - }) + }); - return cols.join(',') -} + return cols.join(','); +}; -const DownloadControl = ({ - cumulativeData, - title, -}) => { +const DownloadControl = ({ cumulativeData, title }) => { // downloadReport downloads a CSV of the cumulative allocation data function downloadReport() { // Build CSV - const head = columns.map(c => c.head).join(',') - const body = reverse(sortBy(cumulativeData, 'totalCost')).map(toCSVLine).join('\r\n') - const csv = `${head}\r\n${body}` + const head = columns.map(c => c.head).join(','); + const body = reverse(sortBy(cumulativeData, 'totalCost')) + .map(toCSVLine) + .join('\r\n'); + const csv = `${head}\r\n${body}`; // Create download link - const a = document.createElement("a") - a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" })) - const filename = title.toLocaleLowerCase('en-US').replace(/\s/gi, '-') - a.setAttribute("download", `${filename}-${Date.now()}.csv`) + const a = document.createElement('a'); + a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' })); + const filename = title.toLocaleLowerCase('en-US').replace(/\s/gi, '-'); + a.setAttribute('download', `${filename}-${Date.now()}.csv`); // Click the link - document.body.appendChild(a) - a.click() - document.body.removeChild(a) + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); } return ( @@ -99,7 +104,7 @@ const DownloadControl = ({ - ) -} + ); +}; -export default React.memo(DownloadControl) +export default React.memo(DownloadControl); diff --git a/plugins/opencost/src/components/Controls/Edit.js b/plugins/opencost/src/components/Controls/Edit.js index a1b3fe1fc7..f02f8cc111 100644 --- a/plugins/opencost/src/components/Controls/Edit.js +++ b/plugins/opencost/src/components/Controls/Edit.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { makeStyles } from '@material-ui/styles'; import FormControl from '@material-ui/core/FormControl'; import InputLabel from '@material-ui/core/InputLabel'; diff --git a/plugins/opencost/src/components/Controls/index.js b/plugins/opencost/src/components/Controls/index.js index f5ea994e33..c63833a6ab 100644 --- a/plugins/opencost/src/components/Controls/index.js +++ b/plugins/opencost/src/components/Controls/index.js @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React from 'react' -import DownloadControl from './Download' -import EditControl from './Edit' +import React from 'react'; +import DownloadControl from './Download'; +import EditControl from './Edit'; const Controls = ({ windowOptions, @@ -34,7 +33,6 @@ const Controls = ({ currencyOptions, setCurrency, }) => { - return (
- +
- ) -} + ); +}; -export default React.memo(Controls) +export default React.memo(Controls); diff --git a/plugins/opencost/src/components/OpenCostPage/OpenCostPage.test.tsx b/plugins/opencost/src/components/OpenCostPage/OpenCostPage.test.tsx index de6565246c..ea10906f85 100644 --- a/plugins/opencost/src/components/OpenCostPage/OpenCostPage.test.tsx +++ b/plugins/opencost/src/components/OpenCostPage/OpenCostPage.test.tsx @@ -37,6 +37,8 @@ describe('OpenCostPage', () => { it('should render', async () => { await renderInTestApp(); - expect(screen.getByText('Open source Kubernetes cloud cost monitoring')).toBeInTheDocument(); + expect( + screen.getByText('Open source Kubernetes cloud cost monitoring'), + ).toBeInTheDocument(); }); }); diff --git a/plugins/opencost/src/components/OpenCostPage/OpenCostPage.tsx b/plugins/opencost/src/components/OpenCostPage/OpenCostPage.tsx index 88ef390c49..19a383e7b6 100644 --- a/plugins/opencost/src/components/OpenCostPage/OpenCostPage.tsx +++ b/plugins/opencost/src/components/OpenCostPage/OpenCostPage.tsx @@ -38,10 +38,10 @@ export const OpenCostPage = () => ( /> - + - + diff --git a/plugins/opencost/src/components/OpenCostReport/OpenCostReport.tsx b/plugins/opencost/src/components/OpenCostReport/OpenCostReport.tsx index 1fb57d78db..8a4cad49c8 100644 --- a/plugins/opencost/src/components/OpenCostReport/OpenCostReport.tsx +++ b/plugins/opencost/src/components/OpenCostReport/OpenCostReport.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - // code ported from https://github.com/opencost/opencost/blob/develop/ui/src/Reports.js import React, { useEffect, useState } from 'react'; diff --git a/plugins/opencost/src/components/Page.js b/plugins/opencost/src/components/Page.js index 9d5af515d0..6346b6287b 100644 --- a/plugins/opencost/src/components/Page.js +++ b/plugins/opencost/src/components/Page.js @@ -14,8 +14,8 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/styles' -import React from 'react' +import { makeStyles } from '@material-ui/styles'; +import React from 'react'; const useStyles = makeStyles({ wrapper: { @@ -29,21 +29,19 @@ const useStyles = makeStyles({ display: 'flex', flexFlow: 'column', flexGrow: 1, - } -}) + }, +}); const Page = props => { - const classes = useStyles() + const classes = useStyles(); return (
-
- {props.children} -
+
{props.children}
- ) -} + ); +}; -export default Page +export default Page; diff --git a/plugins/opencost/src/components/SelectWindow.js b/plugins/opencost/src/components/SelectWindow.js index a8eb00f03c..87ecfe093e 100644 --- a/plugins/opencost/src/components/SelectWindow.js +++ b/plugins/opencost/src/components/SelectWindow.js @@ -14,19 +14,22 @@ * limitations under the License. */ -import React, { useEffect, useState } from 'react' -import { makeStyles } from '@material-ui/styles' -import { endOfDay, startOfDay } from 'date-fns' -import { MuiPickersUtilsProvider, KeyboardDatePicker } from '@material-ui/pickers' -import Button from '@material-ui/core/Button' -import DateFnsUtils from '@date-io/date-fns' -import FormControl from '@material-ui/core/FormControl' -import Link from '@material-ui/core/Link' -import Popover from '@material-ui/core/Popover' -import TextField from '@material-ui/core/TextField' -import Typography from '@material-ui/core/Typography' -import { isValid } from 'date-fns' -import { find, get } from 'lodash' +import React, { useEffect, useState } from 'react'; +import { makeStyles } from '@material-ui/styles'; +import { endOfDay, startOfDay } from 'date-fns'; +import { + MuiPickersUtilsProvider, + KeyboardDatePicker, +} from '@material-ui/pickers'; +import Button from '@material-ui/core/Button'; +import DateFnsUtils from '@date-io/date-fns'; +import FormControl from '@material-ui/core/FormControl'; +import Link from '@material-ui/core/Link'; +import Popover from '@material-ui/core/Popover'; +import TextField from '@material-ui/core/TextField'; +import Typography from '@material-ui/core/Typography'; +import { isValid } from 'date-fns'; +import { find, get } from 'lodash'; const useStyles = makeStyles({ dateContainer: { @@ -45,96 +48,99 @@ const useStyles = makeStyles({ margin: 8, width: 120, }, -}) +}); const SelectWindow = ({ windowOptions, window, setWindow }) => { - const classes = useStyles() - const [anchorEl, setAnchorEl] = useState(null) + const classes = useStyles(); + const [anchorEl, setAnchorEl] = useState(null); - const [startDate, setStartDate] = useState(null) - const [endDate, setEndDate] = useState(null) - const [intervalString, setIntervalString] = useState(null) + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + const [intervalString, setIntervalString] = useState(null); - const handleClick = (event) => { - setAnchorEl(event.currentTarget) + const handleClick = event => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleStartDateChange = date => { + if (isValid(date)) { + setStartDate(startOfDay(date)); } + }; - const handleClose = () => { - setAnchorEl(null) + const handleEndDateChange = date => { + if (isValid(date)) { + setEndDate(endOfDay(date)); } + }; - const handleStartDateChange = (date) => { - if (isValid(date)) { - setStartDate(startOfDay(date)) - } + const handleSubmitPresetDates = dateString => { + setWindow(dateString); + setStartDate(null); + setEndDate(null); + handleClose(); + }; + + const handleSubmitCustomDates = () => { + if (intervalString !== null) { + setWindow(intervalString); + handleClose(); } + }; - const handleEndDateChange = (date) => { - if (isValid(date)) { - setEndDate(endOfDay(date)) - } + useEffect(() => { + if (startDate !== null && endDate !== null) { + // Note: getTimezoneOffset() is calculated based on current system locale, NOT date object + const adjustedStartDate = new Date( + startDate - startDate.getTimezoneOffset() * 60000, + ); + const adjustedEndDate = new Date( + endDate - endDate.getTimezoneOffset() * 60000, + ); + setIntervalString( + `${adjustedStartDate.toISOString().split('.')[0]}Z` + + `,${adjustedEndDate.toISOString().split('.')[0]}Z`, + ); } + }, [startDate, endDate]); - const handleSubmitPresetDates = (dateString) => { - setWindow(dateString) - setStartDate(null) - setEndDate(null) - handleClose() - } + const open = Boolean(anchorEl); + const id = open ? 'date-range-popover' : undefined; - const handleSubmitCustomDates = () => { - if (intervalString !== null) { - setWindow(intervalString) - handleClose() - } - } - - useEffect(() => { - if (startDate !== null && endDate !== null) { - // Note: getTimezoneOffset() is calculated based on current system locale, NOT date object - const adjustedStartDate = new Date(startDate - startDate.getTimezoneOffset() * 60000) - const adjustedEndDate = new Date(endDate - endDate.getTimezoneOffset() * 60000) - setIntervalString( - `${adjustedStartDate.toISOString().split('.')[0] }Z` - + `,${ - adjustedEndDate.toISOString().split('.')[0] }Z` - ) - } - }, [startDate, endDate]) - - const open = Boolean(anchorEl) - const id = open ? 'date-range-popover' : undefined - - return ( - <> - - + + handleClick(e)} inputProps={{ readOnly: true, style: { cursor: 'pointer' }, }} - /> - - -
-
+ /> + + +
+
{ }} /> -
- -
-
-
- {windowOptions.map(opt => - ( + +
+
+
+ {windowOptions.map(opt => ( + handleSubmitPresetDates(opt.value)} > {opt.name} - ) - )} -
+ + ))}
- - - ) - } +
+
+ + ); +}; - export default React.memo(SelectWindow) +export default React.memo(SelectWindow); diff --git a/plugins/opencost/src/components/Subtitle.js b/plugins/opencost/src/components/Subtitle.js index 1c7affb8b7..63b3df61ff 100644 --- a/plugins/opencost/src/components/Subtitle.js +++ b/plugins/opencost/src/components/Subtitle.js @@ -14,12 +14,12 @@ * limitations under the License. */ -import React from 'react' -import { makeStyles } from '@material-ui/styles' -import { upperFirst } from 'lodash' -import Breadcrumbs from '@material-ui/core/Breadcrumbs' -import NavigateNextIcon from '@material-ui/icons/NavigateNext' -import Typography from '@material-ui/core/Typography' +import React from 'react'; +import { makeStyles } from '@material-ui/styles'; +import { upperFirst } from 'lodash'; +import Breadcrumbs from '@material-ui/core/Breadcrumbs'; +import NavigateNextIcon from '@material-ui/icons/NavigateNext'; +import Typography from '@material-ui/core/Typography'; import { toVerboseTimeRange } from '../util'; const useStyles = makeStyles({ @@ -29,14 +29,14 @@ const useStyles = makeStyles({ }, }, link: { - cursor: "pointer", + cursor: 'pointer', }, -}) +}); const Subtitle = ({ report }) => { - const classes = useStyles() + const classes = useStyles(); - const { aggregateBy, window } = report + const { aggregateBy, window } = report; return (
@@ -45,13 +45,15 @@ const Subtitle = ({ report }) => { aria-label="breadcrumb" > {aggregateBy && aggregateBy.length > 0 ? ( - {toVerboseTimeRange(window)} by {upperFirst(aggregateBy)} + + {toVerboseTimeRange(window)} by {upperFirst(aggregateBy)} + ) : ( {toVerboseTimeRange(window)} )}
- ) -} + ); +}; -export default React.memo(Subtitle) +export default React.memo(Subtitle); diff --git a/plugins/opencost/src/components/Warnings.js b/plugins/opencost/src/components/Warnings.js index 5cb3ccfdd6..ae1047ebc2 100644 --- a/plugins/opencost/src/components/Warnings.js +++ b/plugins/opencost/src/components/Warnings.js @@ -14,24 +14,24 @@ * limitations under the License. */ -import React from 'react' -import { makeStyles } from '@material-ui/styles' -import List from '@material-ui/core/List' -import ListItem from '@material-ui/core/ListItem' -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemText from '@material-ui/core/ListItemText' -import Paper from '@material-ui/core/Paper' -import WarningIcon from '@material-ui/icons/Warning' +import React from 'react'; +import { makeStyles } from '@material-ui/styles'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Paper from '@material-ui/core/Paper'; +import WarningIcon from '@material-ui/icons/Warning'; const useStyles = makeStyles({ root: {}, -}) +}); -const Warnings = ({warnings}) => { - const classes = useStyles() +const Warnings = ({ warnings }) => { + const classes = useStyles(); if (!warnings || warnings.length === 0) { - return null + return null; } return ( @@ -47,7 +47,7 @@ const Warnings = ({warnings}) => { ))} - ) -} + ); +}; -export default Warnings +export default Warnings; diff --git a/plugins/opencost/src/constants/colors.js b/plugins/opencost/src/constants/colors.js index 4c66549ad0..8e0289541b 100644 --- a/plugins/opencost/src/constants/colors.js +++ b/plugins/opencost/src/constants/colors.js @@ -1,15 +1,31 @@ -import blue from '@material-ui/core/colors/blue' -import brown from '@material-ui/core/colors/brown' -import cyan from '@material-ui/core/colors/cyan' -import deepOrange from '@material-ui/core/colors/deepOrange' -import deepPurple from '@material-ui/core/colors/deepPurple' -import green from '@material-ui/core/colors/green' -import grey from '@material-ui/core/colors/grey' -import indigo from '@material-ui/core/colors/indigo' -import orange from '@material-ui/core/colors/orange' -import red from '@material-ui/core/colors/red' -import teal from '@material-ui/core/colors/teal' -import yellow from '@material-ui/core/colors/yellow' +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import blue from '@material-ui/core/colors/blue'; +import brown from '@material-ui/core/colors/brown'; +import cyan from '@material-ui/core/colors/cyan'; +import deepOrange from '@material-ui/core/colors/deepOrange'; +import deepPurple from '@material-ui/core/colors/deepPurple'; +import green from '@material-ui/core/colors/green'; +import grey from '@material-ui/core/colors/grey'; +import indigo from '@material-ui/core/colors/indigo'; +import orange from '@material-ui/core/colors/orange'; +import red from '@material-ui/core/colors/red'; +import teal from '@material-ui/core/colors/teal'; +import yellow from '@material-ui/core/colors/yellow'; export const primary = [ blue[500], @@ -22,7 +38,7 @@ export const primary = [ indigo[500], deepOrange[500], deepPurple[500], -] +]; export const greyscale = [ grey[300], @@ -31,8 +47,6 @@ export const greyscale = [ grey[500], grey[100], grey[600], -] +]; -export const browns = [ - brown[500], -] +export const browns = [brown[500]]; diff --git a/plugins/opencost/src/constants/currencyCodes.js b/plugins/opencost/src/constants/currencyCodes.js index 219384c24b..a325df54d0 100644 --- a/plugins/opencost/src/constants/currencyCodes.js +++ b/plugins/opencost/src/constants/currencyCodes.js @@ -1 +1,196 @@ -export const currencyCodes = ["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYR", "BZD", "CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LVL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "USN", "USS", "UYI", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XFU", "XOF", "XPD", "XPF", "XPT", "XTS", "XXX", "YER", "ZAR", "ZMW"]; +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const currencyCodes = [ + 'AED', + 'AFN', + 'ALL', + 'AMD', + 'ANG', + 'AOA', + 'ARS', + 'AUD', + 'AWG', + 'AZN', + 'BAM', + 'BBD', + 'BDT', + 'BGN', + 'BHD', + 'BIF', + 'BMD', + 'BND', + 'BOB', + 'BOV', + 'BRL', + 'BSD', + 'BTN', + 'BWP', + 'BYR', + 'BZD', + 'CAD', + 'CDF', + 'CHE', + 'CHF', + 'CHW', + 'CLF', + 'CLP', + 'CNY', + 'COP', + 'COU', + 'CRC', + 'CUC', + 'CUP', + 'CVE', + 'CZK', + 'DJF', + 'DKK', + 'DOP', + 'DZD', + 'EGP', + 'ERN', + 'ETB', + 'EUR', + 'FJD', + 'FKP', + 'GBP', + 'GEL', + 'GHS', + 'GIP', + 'GMD', + 'GNF', + 'GTQ', + 'GYD', + 'HKD', + 'HNL', + 'HRK', + 'HTG', + 'HUF', + 'IDR', + 'ILS', + 'INR', + 'IQD', + 'IRR', + 'ISK', + 'JMD', + 'JOD', + 'JPY', + 'KES', + 'KGS', + 'KHR', + 'KMF', + 'KPW', + 'KRW', + 'KWD', + 'KYD', + 'KZT', + 'LAK', + 'LBP', + 'LKR', + 'LRD', + 'LSL', + 'LTL', + 'LVL', + 'LYD', + 'MAD', + 'MDL', + 'MGA', + 'MKD', + 'MMK', + 'MNT', + 'MOP', + 'MRO', + 'MUR', + 'MVR', + 'MWK', + 'MXN', + 'MXV', + 'MYR', + 'MZN', + 'NAD', + 'NGN', + 'NIO', + 'NOK', + 'NPR', + 'NZD', + 'OMR', + 'PAB', + 'PEN', + 'PGK', + 'PHP', + 'PKR', + 'PLN', + 'PYG', + 'QAR', + 'RON', + 'RSD', + 'RUB', + 'RWF', + 'SAR', + 'SBD', + 'SCR', + 'SDG', + 'SEK', + 'SGD', + 'SHP', + 'SLL', + 'SOS', + 'SRD', + 'SSP', + 'STD', + 'SYP', + 'SZL', + 'THB', + 'TJS', + 'TMT', + 'TND', + 'TOP', + 'TRY', + 'TTD', + 'TWD', + 'TZS', + 'UAH', + 'UGX', + 'USD', + 'USN', + 'USS', + 'UYI', + 'UYU', + 'UZS', + 'VEF', + 'VND', + 'VUV', + 'WST', + 'XAF', + 'XAG', + 'XAU', + 'XBA', + 'XBB', + 'XBC', + 'XBD', + 'XCD', + 'XDR', + 'XFU', + 'XOF', + 'XPD', + 'XPF', + 'XPT', + 'XTS', + 'XXX', + 'YER', + 'ZAR', + 'ZMW', +]; diff --git a/plugins/opencost/src/index.ts b/plugins/opencost/src/index.ts index 0391378aef..32ec0d0091 100644 --- a/plugins/opencost/src/index.ts +++ b/plugins/opencost/src/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { openCostPlugin, OpenCostPage } from './plugin'; diff --git a/plugins/opencost/src/plugin.test.ts b/plugins/opencost/src/plugin.test.ts index a505cbe130..6d6d20c603 100644 --- a/plugins/opencost/src/plugin.test.ts +++ b/plugins/opencost/src/plugin.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { openCostPlugin } from './plugin'; describe('opencost', () => { diff --git a/plugins/opencost/src/plugin.ts b/plugins/opencost/src/plugin.ts index edaa04d595..9eaf8f63f5 100644 --- a/plugins/opencost/src/plugin.ts +++ b/plugins/opencost/src/plugin.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createPlugin, createRoutableExtension, diff --git a/plugins/opencost/src/routes.ts b/plugins/opencost/src/routes.ts index b7ace600e2..5f1f9e4527 100644 --- a/plugins/opencost/src/routes.ts +++ b/plugins/opencost/src/routes.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ diff --git a/plugins/opencost/src/services/allocation.js b/plugins/opencost/src/services/allocation.js index 8b94c4945e..e7fba8feae 100644 --- a/plugins/opencost/src/services/allocation.js +++ b/plugins/opencost/src/services/allocation.js @@ -1,8 +1,23 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import axios from 'axios'; class AllocationService { async fetchAllocation(baseUrl, win, aggregate, options) { - const { accumulate, } = options; + const { accumulate } = options; const params = { window: win, aggregate: aggregate, @@ -12,10 +27,8 @@ class AllocationService { params.accumulate = accumulate; } - console.log(`${baseUrl}/allocation/compute`, { params }); const result = await axios.get(`${baseUrl}/allocation/compute`, { params }); - console.log(result.data); return result.data; } } diff --git a/plugins/opencost/src/setupTests.ts b/plugins/opencost/src/setupTests.ts deleted file mode 100644 index 73dd8dce47..0000000000 --- a/plugins/opencost/src/setupTests.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import '@testing-library/jest-dom'; -import 'cross-fetch/polyfill'; diff --git a/plugins/opencost/src/util.js b/plugins/opencost/src/util.js index 8e4bfcb54d..d2c8fa7562 100644 --- a/plugins/opencost/src/util.js +++ b/plugins/opencost/src/util.js @@ -1,18 +1,34 @@ -import { forEach, get, round } from 'lodash' +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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) => { + 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, @@ -32,70 +48,76 @@ export function rangeToCumulative(allocationSetRange, aggregateBy) { cpuEfficiency: get(alloc, 'cpuEfficiency', 0), ramEfficiency: get(alloc, 'ramEfficiency', 0), totalEfficiency: get(alloc, 'totalEfficiency', 0), - } + }; } else { - const hrs = get(alloc, 'minutes', 0) / 60.0 + 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 + 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 = { + const totals = { name: 'Totals', cpuCost: 0, gpuCost: 0, @@ -108,177 +130,225 @@ 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) + 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); } // 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 months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; - const start = new Date() - start.setUTCHours(0, 0, 0, 0) + const start = new Date(); + start.setUTCHours(0, 0, 0, 0); - const end = new Date() - end.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()}` + return `${start.getUTCDate()} ${ + months[start.getUTCMonth()] + } ${start.getUTCFullYear()}`; case 'yesterday': - start.setUTCDate(start.getUTCDate()-1) - return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}` + 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` + 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` + 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()}` + 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()}` + 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` + 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` + 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` + 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` + start.setUTCDate(start.getUTCDate() - 89); + return `${start.getUTCDate()} ${ + months[start.getUTCMonth()] + } ${start.getUTCFullYear()} through now`; + // no default } - 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) + const s = splitDates[0].split(/\D+/).slice(0, 3); + const 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()}` - } 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()}`; } + 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 "" + if (typeof amount !== 'number') { + Console.warn( + `Tried to convert "${amount}" to currency, but it is not a number`, + ); + return ''; } - if (currency === undefined || currency === "") { - currency = "USD" + let ctype = currency; + + if (currency === undefined || currency === '') { + ctype = 'USD'; } const opts = { - style: "currency", - currency: currency, + style: 'currency', + currency: ctype, + }; - } - - if (typeof precision === "number") { - opts.minimumFractionDigits = precision - opts.maximumFractionDigits = precision + if (typeof precision === 'number') { + opts.minimumFractionDigits = precision; + opts.maximumFractionDigits = precision; } return amount.toLocaleString(currencyLocale, opts); @@ -286,12 +356,11 @@ 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 default { rangeToCumulative, cumulativeToTotals, @@ -299,4 +368,4 @@ export default { bytesToString, toCurrency, checkCustomWindow, -} +}; diff --git a/yarn.lock b/yarn.lock index 3bb3052421..4e837003a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7482,6 +7482,39 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-opencost@workspace:plugins/opencost": + version: 0.0.0-use.local + resolution: "@backstage/plugin-opencost@workspace:plugins/opencost" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/dev-utils": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@date-io/date-fns": ^2.16.0 + "@material-ui/core": ^4.9.13 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": ^4.0.0-alpha.60 + "@material-ui/pickers": ^3.3.10 + "@material-ui/styles": ^4.11.5 + "@testing-library/jest-dom": ^5.10.1 + "@testing-library/react": ^12.1.3 + "@testing-library/user-event": ^14.0.0 + axios: ^1.4.0 + cross-fetch: ^3.1.5 + date-fns: ^2.30.0 + lodash: ^4.17.21 + msw: ^1.0.0 + react-use: ^17.2.4 + recharts: ^2.5.0 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + react-router-dom: "*" + languageName: unknown + linkType: soft + "@backstage/plugin-org-react@workspace:plugins/org-react": version: 0.0.0-use.local resolution: "@backstage/plugin-org-react@workspace:plugins/org-react" @@ -9948,6 +9981,13 @@ __metadata: languageName: node linkType: hard +"@date-io/core@npm:^2.17.0": + version: 2.17.0 + resolution: "@date-io/core@npm:2.17.0" + checksum: 008dfc79eb54256805113d76feca82fe0b08a245ecbfb2d53809e6a129dc201f9dbd053c8ad63512203ab1a13ff7f76de0edc31829588ef507d53307974c29a8 + languageName: node + linkType: hard + "@date-io/date-fns@npm:^1.3.13": version: 1.3.13 resolution: "@date-io/date-fns@npm:1.3.13" @@ -9959,6 +9999,20 @@ __metadata: languageName: node linkType: hard +"@date-io/date-fns@npm:^2.16.0": + version: 2.17.0 + resolution: "@date-io/date-fns@npm:2.17.0" + dependencies: + "@date-io/core": ^2.17.0 + peerDependencies: + date-fns: ^2.0.0 + peerDependenciesMeta: + date-fns: + optional: true + checksum: bfab634c985a98dd44fdfc5f982b0348da2886c9ad79a8d71fd7bfa05b740af7f5cf340508cd7ce51d67c141fb8766998c8efdf50653252c68ec078471cd76e6 + languageName: node + linkType: hard + "@date-io/luxon@npm:1.x, @date-io/luxon@npm:^1.3.13": version: 1.3.13 resolution: "@date-io/luxon@npm:1.3.13"