From 3fdb1ad0726c1d7245d71dfe4dc5c8ca57e2a994 Mon Sep 17 00:00:00 2001 From: Kav91 Date: Wed, 16 Aug 2023 00:55:25 +1000 Subject: [PATCH] feat: product chart group by --- src/components/MaturityElementList/index.js | 7 +- .../ReportView/maturityContainer.js | 135 +++++++--- src/components/ScoreCard/index.js | 35 +-- src/components/ScoreCard/summary-card.js | 30 +-- src/components/ScoreCharts/index.js | 235 ++++++++++++------ src/components/ScoreList/index.js | 19 +- 6 files changed, 302 insertions(+), 159 deletions(-) diff --git a/src/components/MaturityElementList/index.js b/src/components/MaturityElementList/index.js index 3828ae4..a5ebe99 100644 --- a/src/components/MaturityElementList/index.js +++ b/src/components/MaturityElementList/index.js @@ -10,6 +10,7 @@ export default function MaturityElementList({ elements = [], isUserDefault, view, + groupBy, }) { return useMemo(() => { if (elements.length === 0) return
; @@ -20,6 +21,7 @@ export default function MaturityElementList({
{elements.map((score, idx) => ( ); case 'table': - return ; + return ; case 'charts': - return ; + return ; case 'summary': return (
{elements.map((score, idx) => ( h.document.runAt === selected ) || [selected]; const { accountSummaries } = selectedHistory?.document || {}; - const scoredCollection = (accountSummaries || []).map((a) => { - const elementScores = []; - Object.keys(rules).forEach((key) => { - const value = a[key]; + let scoredCollection = []; - if (value !== undefined && value !== null) { - const payload = { - name: key, - status: percentageToStatus(value), - score: `${Math.round(value)}%`, - }; + if (groupBy === 'account') { + scoredCollection = (accountSummaries || []).map((a) => { + const elementScores = []; + + Object.keys(rules).forEach((key) => { + const value = a[key]; + + if (value !== undefined && value !== null) { + const payload = { + name: key, + status: percentageToStatus(value), + score: `${Math.round(value)}%`, + }; + + elementScores.push(payload); + } + }); + + const payload = { + title: a.name, + subtitle: a.id, + rollUpScore: Math.round((a.totalScore / a.maxScore) * 100), + rollUpStatus: STATUSES.UNKNOWN, + elementListLabel: 'Products', + elementScores, + }; - elementScores.push(payload); - } + payload.rollUpStatus = percentageToStatus(payload.rollUpScore); + + return payload; }); + } else if (groupBy === 'product') { + scoredCollection = Object.keys(rules) + .filter((product) => + accountSummaries.find( + (a) => a[product] !== null && a[product] !== undefined + ) + ) + .map((product) => { + const elementScores = []; + let totalScore = 0; + + accountSummaries.forEach((account) => { + const value = account[product]; + totalScore += value; + + if (value !== undefined && value !== null) { + const payload = { + name: account.name, + id: account.id, + status: percentageToStatus(value), + score: `${Math.round(value)}%`, + }; - const payload = { - title: a.name, - subtitle: a.id, - rollUpScore: Math.round((a.totalScore / a.maxScore) * 100), - rollUpStatus: STATUSES.UNKNOWN, - elementListLabel: 'Products', - elementScores, - }; + elementScores.push(payload); + } + }); + + const payload = { + title: product, + // subtitle: account.id, + rollUpScore: Math.round( + (totalScore / (accountSummaries.length * 100)) * 100 + ), + rollUpStatus: STATUSES.UNKNOWN, + elementListLabel: 'Products', + elementScores, + }; - payload.rollUpStatus = percentageToStatus(payload.rollUpScore); + payload.rollUpStatus = percentageToStatus(payload.rollUpScore); - return payload; - }); + return payload; + }); + } return useMemo(() => { return ( @@ -84,14 +132,45 @@ export default function MaturityContainer(props) { SegmentedControlItem.ICON_TYPE.DATAVIZ__DATAVIZ__LINE_CHART } /> + {groupBy === 'account' && ( + + )} + +     + { + if (value === 'product' && view === 'table') { + setView('summary'); + } + + setGroupBy(value); + }} + > - + + ); - }, [history, selected, view]); + }, [history, selected, view, groupBy]); } diff --git a/src/components/ScoreCard/index.js b/src/components/ScoreCard/index.js index d6c625e..2d682e9 100644 --- a/src/components/ScoreCard/index.js +++ b/src/components/ScoreCard/index.js @@ -17,25 +17,31 @@ const ScoreCard = ({ elementScores, isUserDefault, view, + groupBy, }) => { return useMemo(() => { return (
- navigation.openStackedNerdlet({ - id: 'score-details-nerdlet', - urlState: { - isUserDefault, - accountName: title, - accountId: subtitle, - accountPercentage: rollUpScore, - historyId, - selectedAccountId, - entitySearchQuery, - }, - }) + onClick={ + groupBy === 'account' + ? () => + /* eslint-disable */ + navigation.openStackedNerdlet({ + id: 'score-details-nerdlet', + urlState: { + isUserDefault, + accountName: title, + accountId: subtitle, + accountPercentage: rollUpScore, + historyId, + selectedAccountId, + entitySearchQuery, + }, + }) + : undefined + /* eslint-enable */ } > @@ -51,11 +57,12 @@ const ScoreCard = ({ rollUpStatus={rollUpStatus} elementListLabel={elementListLabel} elementScores={elementScores} + groupBy={groupBy} /> )} {view === DISPLAY_MODES.NAVIGATOR && ( - + )}
); diff --git a/src/components/ScoreCard/summary-card.js b/src/components/ScoreCard/summary-card.js index aa4e5ff..c0bb134 100644 --- a/src/components/ScoreCard/summary-card.js +++ b/src/components/ScoreCard/summary-card.js @@ -1,17 +1,16 @@ import React, { useMemo } from 'react'; -import PropTypes from 'prop-types'; import { HeadingText } from 'nr1'; import { ProgressBar } from '@newrelic/nr-labs-components'; -import { STATUSES } from '../../constants'; import ScoreList from '../ScoreList'; -const SummaryCard = ({ +export default function SummaryCard({ + // groupBy, elementScores, rollUpStatus, rollUpScore, maxScore, elementListLabel, -}) => { +}) { return useMemo(() => { const elementSliceIndex = elementScores.length > 8 @@ -56,25 +55,4 @@ const SummaryCard = ({ ); }, [rollUpScore, maxScore, rollUpStatus, elementListLabel, elementScores]); -}; - -SummaryCard.PropTypes = { - /* the roll up score label */ - rollUpScore: PropTypes.number, - /* the total possible score */ - maxScore: PropTypes.number, - /* the overall status taking into account the scores for each element */ - rollUpStatus: PropTypes.oneOf(Object.values(STATUSES)), - /* the title assigned to the list of scored elements */ - elementListLabel: PropTypes.string, - /* the individual scored elements that contribute to the aggregate score */ - elementScores: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string, - status: PropTypes.oneOf(Object.values(STATUSES)), - score: PropTypes.string, - }) - ), -}; - -export default SummaryCard; +} diff --git a/src/components/ScoreCharts/index.js b/src/components/ScoreCharts/index.js index 648d657..80d383f 100644 --- a/src/components/ScoreCharts/index.js +++ b/src/components/ScoreCharts/index.js @@ -3,7 +3,8 @@ import rules, { productColors } from '../../rules'; import DataContext from '../../context/data'; import { Grid, GridItem, LineChart, HeadingText } from 'nr1'; -export default function ScoreCharts() { +export default function ScoreCharts(props) { + const { groupBy } = props; const { view, reportHistory, userViewHistory } = useContext(DataContext); const history = view?.page === 'DefaultView' @@ -11,102 +12,192 @@ export default function ScoreCharts() { : reportHistory.filter((r) => r.document.reportId === view?.props?.id); const selected = view?.props?.selected || view?.props?.document?.selected; + const accounts = + view?.props?.accounts || view?.props?.document?.accounts || []; - const accountProductChartData = ( - view?.props?.accounts || - view?.props?.document?.accounts || - [] - ).map((a) => { - const productLineData = []; - const chartData = {}; - let showHistoryMarker = false; - - Object.keys(rules).forEach((key) => { - const series = { - metadata: { - id: key, - name: key, - color: - productColors[key] || - `#${Math.floor(Math.random() * 16777215).toString(16)}`, - viz: 'main', - units_data: { - x: 'TIMESTAMP', - y: 'COUNT', + // standardize the colors used if in product grouping + const accountColors = {}; + accounts.forEach((account) => { + accountColors[account.id] = `#${Math.floor( + Math.random() * 16777215 + ).toString(16)}`; + }); + + let chartData = []; + + if (groupBy === 'product') { + chartData = Object.keys(rules) + .filter((product) => + history.find((h) => + h.document.accountSummaries.find( + (a) => a[product] !== null && a[product] !== undefined + ) + ) + ) + .map((product) => { + const lineData = []; + const chartData = {}; + let showHistoryMarker = false; + + accounts.forEach((accountId) => { + const series = { + metadata: { + id: accountId, + name: accountId, + color: + accountColors[accountId] || + `#${Math.floor(Math.random() * 16777215).toString(16)}`, + viz: 'main', + units_data: { + x: 'TIMESTAMP', + y: 'COUNT', + }, + }, + data: [], + }; + + history.forEach((h) => { + const { document } = h; + const { accountSummaries, runAt } = document; + const data = { x: runAt, y: null }; + + const summary = accountSummaries.find((a) => a.id === accountId); + + if (summary) { + if (summary[product] !== null && summary[product] !== undefined) { + chartData.productName = product; + data.y += summary[product]; + } + } + + // data.y = data.y / accountSummaries.length; + + if (data.y) { + series.data.push(data); + } + }); + + if (series.data.length > 0) { + lineData.push(series); + } + + if (series.data.length > 1) { + showHistoryMarker = true; + } + }); + + if (showHistoryMarker) { + lineData.push({ + metadata: { + id: `History Marker`, + name: 'History Marker', + color: '#000000', + viz: 'event', + }, + data: [ + { + x0: selected, + x1: selected + 1, + }, + ], + }); + } + chartData.lineData = lineData; + return chartData; + }); + } else if (groupBy === 'account') { + chartData = accounts.map((a) => { + const lineData = []; + const chartData = {}; + let showHistoryMarker = false; + + Object.keys(rules).forEach((key) => { + const series = { + metadata: { + id: key, + name: key, + color: + productColors[key] || + `#${Math.floor(Math.random() * 16777215).toString(16)}`, + viz: 'main', + units_data: { + x: 'TIMESTAMP', + y: 'COUNT', + }, }, - }, - data: [], - }; - - history.forEach((h) => { - const { document } = h; - const { accountSummaries, runAt } = document; - const data = { x: runAt, y: null }; - - accountSummaries.forEach((s) => { - if (s[key] !== null && s[key] !== undefined && s.id === a) { - chartData.accountName = s.name; - chartData.accountId = s.id; - data.y += s[key]; + data: [], + }; + + history.forEach((h) => { + const { document } = h; + const { accountSummaries, runAt } = document; + const data = { x: runAt, y: null }; + + accountSummaries.forEach((s) => { + if (s[key] !== null && s[key] !== undefined && s.id === a) { + chartData.accountName = s.name; + chartData.accountId = s.id; + data.y += s[key]; + } + }); + + data.y = data.y / accountSummaries.length; + + if (data.y) { + series.data.push(data); } }); - data.y = data.y / accountSummaries.length; + if (series.data.length > 0) { + lineData.push(series); + } - if (data.y) { - series.data.push(data); + if (series.data.length > 1) { + showHistoryMarker = true; } }); - if (series.data.length > 0) { - productLineData.push(series); - } - - if (series.data.length > 1) { - showHistoryMarker = true; + // inject history marker + // more than 1 series should be present in any product category otherwise it will highlight the entire chart + if (showHistoryMarker) { + lineData.push({ + metadata: { + id: `History Marker`, + name: 'History Marker', + color: '#000000', + viz: 'event', + }, + data: [ + { + x0: selected, + x1: selected + 1, + }, + ], + }); } + chartData.lineData = lineData; + return chartData; }); - - // inject history marker - // more than 1 series should be present in any product category otherwise it will highlight the entire chart - if (showHistoryMarker) { - productLineData.push({ - metadata: { - id: `History Marker`, - name: 'History Marker', - color: '#000000', - viz: 'event', - }, - data: [ - { - x0: selected, - x1: selected + 1, - }, - ], - }); - } - chartData.productLineData = productLineData; - return chartData; - }); + } return useMemo(() => { return (
- {accountProductChartData.map((a) => { + {chartData.map((a) => { return ( - {a.accountName} - + {a.accountName || a.productName} + ); })}
); - }, [view, selected]); + }, [view, selected, chartData]); } diff --git a/src/components/ScoreList/index.js b/src/components/ScoreList/index.js index 60b8d07..b8ec36a 100644 --- a/src/components/ScoreList/index.js +++ b/src/components/ScoreList/index.js @@ -1,10 +1,8 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Tooltip } from 'nr1'; import Score from '../Score'; -import { STATUSES } from '../../constants'; -const ScoreList = ({ idxBase, scores = [] }) => { +export default function ScoreList({ idxBase, scores = [] }) { return (
{scores.map((score, i) => ( @@ -18,17 +16,4 @@ const ScoreList = ({ idxBase, scores = [] }) => { ))}
); -}; - -ScoreList.propTypes = { - idxBase: PropTypes.number, - scores: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string, - status: PropTypes.oneOf(Object.values(STATUSES)), - score: PropTypes.string, - }) - ), -}; - -export default ScoreList; +}