From 850de038dd39c5561c8c31b48b8f17dd7e8a73d1 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Wed, 20 Jan 2021 00:10:50 -0500 Subject: [PATCH 1/5] Increase number of default tooltip items to 8 --- .../CostOverviewByProductChart.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 0b482bd908..b615086b53 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import { useTheme, Box } from '@material-ui/core'; @@ -62,6 +62,7 @@ export const CostOverviewByProductChart = ({ const styles = useStyles(theme); const lastCompleteBillingDate = useLastCompleteBillingDate(); const { duration } = useFilters(mapFiltersToProps); + const [isExpanded, setExpanded] = useState(false); if (!costsByProduct) { return null; @@ -79,23 +80,27 @@ export const CostOverviewByProductChart = ({ lastCompleteBillingDate, ); const currentPeriodTotal = totalCost - previousPeriodTotal; + const otherProducts: string[] = []; + const productsByDate = costsByProduct.reduce((prodByDate, product) => { const productTotal = aggregationSum(product.aggregation); // Group products with less than 10% of the total cost into "Other" category - // when there we have >= 5 products. + // when we have >= 8 products. const isOtherProduct = - costsByProduct.length >= 5 && + costsByProduct.length >= 8 && productTotal < totalCost * LOW_COST_THRESHOLD; - const productName = isOtherProduct ? 'Other' : product.id; - const updatedProdByDate = { ...prodByDate }; + const updatedProdByDate = { ...prodByDate }; + if (isOtherProduct) { + otherProducts.push(product.id); + } product.aggregation.forEach(curAggregation => { const productCostsForDate = updatedProdByDate[curAggregation.date] || {}; updatedProdByDate[curAggregation.date] = { ...productCostsForDate, - [productName]: - (productCostsForDate[productName] || 0) + curAggregation.amount, + [product.id]: + (productCostsForDate[product.id] || 0) + curAggregation.amount, }; }); From b4980305412ae370b7d41a129a3634a6ca79e52f Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Wed, 20 Jan 2021 00:10:58 -0500 Subject: [PATCH 2/5] Add expand functionality --- .../CostOverviewByProductChart.tsx | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index b615086b53..5e6fcd233e 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -109,36 +109,48 @@ export const CostOverviewByProductChart = ({ const chartData: Record[] = Object.keys(productsByDate).map( date => { + const costsForDate = Object.keys(productsByDate[date]).reduce( + (dateCosts, product) => { + // Group costs for products that belong to 'Other' in the chart. + const cost = productsByDate[date][product]; + const productCost = otherProducts.includes(product) + ? { Other: (dateCosts.Other || 0) + cost } + : { [product]: cost }; + return { ...dateCosts, ...productCost }; + }, + {} as Record, + ); return { - ...productsByDate[date], + ...costsForDate, date: Date.parse(date), }; }, ); + const sortedProducts = costsByProduct.sort( + (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), + ); + const renderAreas = () => { - const productGroupNames = new Set( - Object.values(productsByDate) - .map(d => Object.keys(d)) - .flat(), - ); - const sortedProducts = costsByProduct + const separatedProducts = sortedProducts // Check that product is a separate group and hasn't been added to 'Other' .filter( - product => product.id !== 'Other' && productGroupNames.has(product.id), - ) - .sort( - (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), + product => + product.id !== 'Other' && !otherProducts.includes(product.id), ) .map(product => product.id); // Keep 'Other' category at the bottom of the stack - return ['Other', ...sortedProducts].map((product, i) => ( + return ['Other', ...separatedProducts].map((product, i) => ( setExpanded(true)} + style={{ + cursor: product === 'Other' && !isExpanded ? 'pointer' : null, + }} /> )); }; @@ -149,18 +161,43 @@ export const CostOverviewByProductChart = ({ }) => { if (isInvalid({ label, payload })) return null; - const title = dayjs(label).utc().format(DEFAULT_DATE_FORMAT); + const dateTitle = dayjs(label).utc().format(DEFAULT_DATE_FORMAT); const items = payload.map(p => ({ label: p.dataKey as string, value: formatGraphValue(p.value as number), fill: p.fill!, })); + const otherGroupItem = items.find(item => item.label === 'Other'); + const expandedOtherItems = sortedProducts + .filter(product => otherProducts.includes(product.id)) + .map(product => ({ + label: product.id, + value: formatGraphValue(productsByDate[dateTitle][product.id]), + fill: + otherGroupItem?.fill || + theme.palette.dataViz[theme.palette.dataViz.length - 1], + })); + const expandedTooltipItems = expandedOtherItems + .reverse() + .map((item, index) => ( + + )); return ( - - {items.reverse().map((item, index) => ( - - ))} + + {items + .filter(item => item.label !== 'Other') + .reverse() + .map((item, index) => ( + + ))} + {!isExpanded ? ( + + ) : null} + {isExpanded ? expandedTooltipItems : null} ); }; From 8b7ef9f8b77b36fec5dde4adbcfa937bd299e7b8 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Wed, 20 Jan 2021 00:12:52 -0500 Subject: [PATCH 3/5] Add changeset --- .changeset/cost-insights-curvy-dingos-live.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-curvy-dingos-live.md diff --git a/.changeset/cost-insights-curvy-dingos-live.md b/.changeset/cost-insights-curvy-dingos-live.md new file mode 100644 index 0000000000..f15d3b0492 --- /dev/null +++ b/.changeset/cost-insights-curvy-dingos-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Allow expand functionality to top panel product chart tooltip. From 649250e479309acdd842e288b792bfcacdd42e57 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Thu, 21 Jan 2021 16:54:04 -0500 Subject: [PATCH 4/5] Update chart itself on expand click --- .../CostOverviewByProductChart.tsx | 101 +++++++++--------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 5e6fcd233e..e6a40a9595 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -16,7 +16,8 @@ import React, { useState } from 'react'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; -import { useTheme, Box } from '@material-ui/core'; +import { useTheme, Box, Typography } from '@material-ui/core'; +import { default as FullScreenIcon } from '@material-ui/icons/Fullscreen'; import { AreaChart, ContentRenderer, @@ -59,7 +60,7 @@ export const CostOverviewByProductChart = ({ costsByProduct, }: CostOverviewByProductChartProps) => { const theme = useTheme(); - const styles = useStyles(theme); + const classes = useStyles(theme); const lastCompleteBillingDate = useLastCompleteBillingDate(); const { duration } = useFilters(mapFiltersToProps); const [isExpanded, setExpanded] = useState(false); @@ -113,9 +114,10 @@ export const CostOverviewByProductChart = ({ (dateCosts, product) => { // Group costs for products that belong to 'Other' in the chart. const cost = productsByDate[date][product]; - const productCost = otherProducts.includes(product) - ? { Other: (dateCosts.Other || 0) + cost } - : { [product]: cost }; + const productCost = + !isExpanded && otherProducts.includes(product) + ? { Other: (dateCosts.Other || 0) + cost } + : { [product]: cost }; return { ...dateCosts, ...productCost }; }, {} as Record, @@ -140,19 +142,31 @@ export const CostOverviewByProductChart = ({ ) .map(product => product.id); // Keep 'Other' category at the bottom of the stack - return ['Other', ...separatedProducts].map((product, i) => ( - setExpanded(true)} - style={{ - cursor: product === 'Other' && !isExpanded ? 'pointer' : null, - }} - /> - )); + const productsToDisplay = isExpanded + ? sortedProducts.map(product => product.id) + : ['Other', ...separatedProducts]; + + return productsToDisplay.map((product, i) => { + // Logic to handle case where there are more products than data viz colors. + const productColor = + theme.palette.dataViz[ + (productsToDisplay.length - 1 - i) % + (theme.palette.dataViz.length - 1) + ]; + return ( + setExpanded(true)} + style={{ + cursor: product === 'Other' && !isExpanded ? 'pointer' : null, + }} + /> + ); + }); }; const tooltipRenderer: ContentRenderer = ({ @@ -167,37 +181,18 @@ export const CostOverviewByProductChart = ({ value: formatGraphValue(p.value as number), fill: p.fill!, })); - - const otherGroupItem = items.find(item => item.label === 'Other'); - const expandedOtherItems = sortedProducts - .filter(product => otherProducts.includes(product.id)) - .map(product => ({ - label: product.id, - value: formatGraphValue(productsByDate[dateTitle][product.id]), - fill: - otherGroupItem?.fill || - theme.palette.dataViz[theme.palette.dataViz.length - 1], - })); - const expandedTooltipItems = expandedOtherItems - .reverse() - .map((item, index) => ( - - )); + const expandText = ( + + + Click to expand + + ); return ( - {items - .filter(item => item.label !== 'Other') - .reverse() - .map((item, index) => ( - - ))} - {!isExpanded ? ( - - ) : null} - {isExpanded ? expandedTooltipItems : null} + {items.reverse().map((item, index) => ( + + ))} + {!isExpanded ? expandText : null} ); }; @@ -218,8 +213,8 @@ export const CostOverviewByProductChart = ({ /> - + 0, 'dataMax']} - tick={{ fill: styles.axis.fill }} + tick={{ fill: classes.axis.fill }} tickFormatter={formatGraphValue} - width={styles.yAxis.width} + width={classes.yAxis.width} /> {renderAreas()} From 0fb1a35492148f8cc18e2185f131b4ef362de98c Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Fri, 22 Jan 2021 10:14:30 -0500 Subject: [PATCH 5/5] Add divider in tooltip --- .../CostOverviewByProductChart.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index e6a40a9595..b562434c5e 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -16,7 +16,13 @@ import React, { useState } from 'react'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; -import { useTheme, Box, Typography } from '@material-ui/core'; +import { + useTheme, + Box, + Typography, + Divider, + emphasize, +} from '@material-ui/core'; import { default as FullScreenIcon } from '@material-ui/icons/Fullscreen'; import { AreaChart, @@ -182,9 +188,17 @@ export const CostOverviewByProductChart = ({ fill: p.fill!, })); const expandText = ( - - - Click to expand + + + + + Click to expand + ); return (