diff --git a/package.json b/package.json index b35646ba..44d0fe15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netdata/charts", - "version": "6.10.6", + "version": "6.10.7", "description": "Netdata frontend SDK and chart utilities", "main": "dist/index.js", "module": "dist/es6/index.js", @@ -80,6 +80,7 @@ "svgo-loader": "^4.0.0" }, "dependencies": { + "@tanstack/react-virtual": "^3.13.18", "d3": "^7.9.0", "d3-scale": "^4.0.2", "dygraphs": "^2.2.1", diff --git a/src/chartLibraries/dygraph/index.js b/src/chartLibraries/dygraph/index.js index 0a9b57ca..946f4a97 100644 --- a/src/chartLibraries/dygraph/index.js +++ b/src/chartLibraries/dygraph/index.js @@ -308,10 +308,12 @@ export default (sdk, chart) => { const makeChartTypeOptions = () => { const { labels } = chart.getPayload() - const { chartType, includeZero, enabledXAxis, enabledYAxis, yAxisLabelWidth } = + const { chartType, includeZero, enabledXAxis, enabledYAxis, yAxisLabelWidth, stepPlot } = chart.getAttributes() - const plotter = plotterByChartType[chartType] || plotterByChartType.default + const plotter = stepPlot + ? plotterByChartType.default + : plotterByChartType[chartType] || plotterByChartType.default const { strokeWidth, @@ -346,6 +348,7 @@ export default (sdk, chart) => { (forceIncludeZero && dimensionIds.length > 1 && selectedLegendDimensions.length > 1), stackedGraphNaNFill: "none", plotter, + stepPlot, errorBars, axes: { x: enabledXAxis diff --git a/src/chartLibraries/dygraph/index.test.js b/src/chartLibraries/dygraph/index.test.js index 75e7a733..1e77798a 100644 --- a/src/chartLibraries/dygraph/index.test.js +++ b/src/chartLibraries/dygraph/index.test.js @@ -219,6 +219,50 @@ describe("dygraphChart", () => { expect(instance.getChartHeight()).toBe(400) }) + it("enables dygraph stepPlot when the stepPlot attribute is set", () => { + const { sdk, chart } = makeTestChart({ + attributes: { theme: "dark", chartType: "line", stepPlot: true }, + }) + + chart.getPayload = () => ({ data: [[1617946860000, 1]], labels: ["time", "value"] }) + chart.getDateWindow = () => [1617946860000, 1617947750000] + chart.formatXAxis = x => x.toString() + chart.getThemeAttribute = () => "#333" + chart.getUnitSign = () => "" + + const instance = dygraphChart(sdk, chart) + instance.mount(document.createElement("div")) + + const Dygraph = require("dygraphs") + expect(Dygraph).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Array), + expect.objectContaining({ stepPlot: true }) + ) + }) + + it("does not enable dygraph stepPlot by default", () => { + const { sdk, chart } = makeTestChart({ + attributes: { theme: "dark", chartType: "line" }, + }) + + chart.getPayload = () => ({ data: [[1617946860000, 10]], labels: ["time", "value"] }) + chart.getDateWindow = () => [1617946860000, 1617947750000] + chart.formatXAxis = x => x.toString() + chart.getThemeAttribute = () => "#333" + chart.getUnitSign = () => "" + + const instance = dygraphChart(sdk, chart) + instance.mount(document.createElement("div")) + + const Dygraph = require("dygraphs") + expect(Dygraph).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Array), + expect.objectContaining({ stepPlot: false }) + ) + }) + it("returns axis range from dygraph", () => { const { sdk, chart } = makeTestChart() diff --git a/src/components/header/index.js b/src/components/header/index.js index d641fdab..83243931 100644 --- a/src/components/header/index.js +++ b/src/components/header/index.js @@ -30,6 +30,7 @@ const Header = ({ hasFilters }) => { const isMinimal = useIsMinimal() const leftHeaderElements = useAttributeValue("leftHeaderElements") const hasToolbox = useAttributeValue("hasToolbox") + const hideTitle = useAttributeValue("hideTitle") const focused = useAttributeValue("focused") return ( @@ -40,7 +41,7 @@ const Header = ({ hasFilters }) => { {arr[index + 1] ? : null} ))} - + {!hideTitle && <Title />} {isMinimal && hasFilters && <FilterToolbox opacity={focused ? 1 : 0.7} />} {hasToolbox && <Toolbox />} </Container> diff --git a/src/components/header/index.test.js b/src/components/header/index.test.js new file mode 100644 index 00000000..349628cd --- /dev/null +++ b/src/components/header/index.test.js @@ -0,0 +1,23 @@ +import React from "react" +import { screen } from "@testing-library/react" +import "@testing-library/jest-dom" +import { renderWithChart } from "@jest/testUtilities" +import Header from "./index" + +describe("Header component", () => { + it("renders the title by default", () => { + renderWithChart(<Header />, { + attributes: { title: "CPU Usage" }, + }) + + expect(screen.getByText("CPU Usage")).toBeInTheDocument() + }) + + it("hides the title when hideTitle is true", () => { + renderWithChart(<Header />, { + attributes: { title: "CPU Usage", hideTitle: true }, + }) + + expect(screen.queryByText("CPU Usage")).not.toBeInTheDocument() + }) +}) diff --git a/src/components/helpers/shortener.js b/src/components/helpers/shortener.js index 7c72d872..db782425 100644 --- a/src/components/helpers/shortener.js +++ b/src/components/helpers/shortener.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react" import { mergeRefs } from "@netdata/netdata-ui" -import shorten from "@/helpers/shorten" +import { shortenToWidth } from "@/helpers/shorten" import Tooltip from "@/components/tooltip" const Shortener = ({ text, Component = "div", noTooltip, ref: forwardedRef, ...rest }) => { @@ -12,14 +12,12 @@ const Shortener = ({ text, Component = "div", noTooltip, ref: forwardedRef, ...r if (!ref) return const containerWidth = ref.offsetWidth - let round = 0 + const contentWidth = ref.scrollWidth - while (ref.scrollWidth > containerWidth) { - ref.textContent = shorten(ref.textContent, round) - round = round + 1 - } + const next = shortenToWidth(text, contentWidth, containerWidth) - if (ref.textContent !== text) { + if (next !== text) { + ref.textContent = next setShortenText(text) } }, [text, ref]) diff --git a/src/components/line/footer/index.js b/src/components/line/footer/index.js index 688e46f8..7b7b0cc5 100644 --- a/src/components/line/footer/index.js +++ b/src/components/line/footer/index.js @@ -36,7 +36,7 @@ const Footer = () => { {!showingInfo && legend && ( <> {isHeatmap && <HeatmapColors />} - <Flex alignItems="center" padding={isMinimal ? [2] : [0]}> + <Flex alignItems="center" height={isMinimal ? "20px" : "36px"}> {!isMinimal && !isHeatmap && <DimensionSort />} <Legend padding={isHeatmap ? [0, 2] : undefined} /> </Flex> diff --git a/src/components/line/legend/index.js b/src/components/line/legend/index.js index 54b97d7e..99f7c358 100644 --- a/src/components/line/legend/index.js +++ b/src/components/line/legend/index.js @@ -1,7 +1,8 @@ -import React, { memo, useRef, useEffect } from "react" +import React, { useRef, useEffect, useState } from "react" import styled from "styled-components" import { debounce } from "throttle-debounce" -import { Flex, useNavigationArrows } from "@netdata/netdata-ui" +import { useVirtualizer } from "@tanstack/react-virtual" +import { Box, Flex } from "@netdata/netdata-ui" import navLeft from "@netdata/netdata-ui/dist/components/icon/assets/nav_left.svg" import navRight from "@netdata/netdata-ui/dist/components/icon/assets/nav_right.svg" import { @@ -27,18 +28,11 @@ const Container = styled(Flex).attrs(props => ({ overflow-x: overlay; overflow-y: hidden; - ::-webkit-scrollbar { - height: 6px; + &::-webkit-scrollbar { + height: 0; } ` -const getPositions = el => { - if (!el) return { x: 0 } - return { - x: el.scrollLeft, - } -} - const skeletonDimensions = Array.from(Array(5)) const SkeletonDimensions = () => ( @@ -49,23 +43,34 @@ const SkeletonDimensions = () => ( </Fragment> ) -const Dimensions = memo(({ lastItemRef }) => { - const dimensionIds = useDimensionIds() +const VirtualItem = styled(Flex).attrs({ + alignItems: "center", + padding: [0, 0.5, 0, 0], +})` + position: absolute; + top: 0; + left: 0; + height: 100%; +` - if (!dimensionIds) return null +const VirtualDimensions = ({ virtualizer, dimensionIds }) => ( + <Box position="relative" flex={false} height="100%" width={`${virtualizer.getTotalSize()}px`}> + {virtualizer.getVirtualItems().map(virtualItem => { + const id = dimensionIds[virtualItem.index] - return ( - <Fragment> - {dimensionIds.map((id, index) => ( - <Dimension - {...(index === dimensionIds.length - 1 && { ref: lastItemRef })} + return ( + <VirtualItem key={id} - id={id} - /> - ))} - </Fragment> - ) -}) + data-index={virtualItem.index} + ref={virtualizer.measureElement} + style={{ transform: `translateX(${virtualItem.start}px)` }} + > + <Dimension id={id} /> + </VirtualItem> + ) + })} + </Box> +) const Legend = props => { const dimensionIds = useDimensionIds() @@ -75,14 +80,27 @@ const Legend = props => { const active = useAttributeValue("active") const legendRef = useRef(null) - const lastItemRef = useRef() - const [arrowLeft, arrowRight, onScroll] = useNavigationArrows( - legendRef, - lastItemRef, - dimensionIds.length, - true - ) + const [arrowLeft, setArrowLeft] = useState(false) + const [arrowRight, setArrowRight] = useState(false) + + const virtualizer = useVirtualizer({ + horizontal: true, + count: dimensionIds?.length || 0, + getScrollElement: () => legendRef.current, + estimateSize: () => 200, + overscan: 5, + }) + + const totalSize = virtualizer.getTotalSize() + + const updateArrows = () => { + const el = legendRef.current + if (!el) return + + setArrowLeft(el.scrollLeft > 20) + setArrowRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 20) + } useEffect(() => { if (legendRef.current && active) { @@ -90,11 +108,15 @@ const Legend = props => { } }, [legendRef.current, active]) + useEffect(updateArrows, [totalSize]) + useEffect(() => { const scroll = () => { - const { x } = getPositions(legendRef.current) - chart.updateAttribute("legendScroll", x) - onScroll() + const el = legendRef.current + if (!el) return + + chart.updateAttribute("legendScroll", el.scrollLeft) + updateArrows() } scroll() @@ -134,7 +156,7 @@ const Legend = props => { } return ( - <Flex overflow="hidden" position="relative"> + <Flex overflow="hidden" position="relative" height="100%"> {arrowLeft && ( <Flex data-testid="filterTray-arrowLeft" @@ -151,7 +173,9 @@ const Legend = props => { </Flex> )} <Container ref={legendRef} {...props} data-track={chart.track("legend")}> - {!initialLoading && !empty && <Dimensions lastItemRef={lastItemRef} />} + {!initialLoading && !empty && dimensionIds && ( + <VirtualDimensions virtualizer={virtualizer} dimensionIds={dimensionIds} /> + )} {initialLoading && <SkeletonDimensions />} {!initialLoading && empty && <EmptyDimension />} </Container> diff --git a/src/components/line/overlays/types.js b/src/components/line/overlays/types.js index f8f8f97b..3bb5781a 100644 --- a/src/components/line/overlays/types.js +++ b/src/components/line/overlays/types.js @@ -66,13 +66,13 @@ const LatestTimeOverlay = props => ( ) const AnnotationOverlay = ({ id, ...rest }) => ( - <Container id={id} align={alignment.elementRight} top="25px" right={-5} {...rest}> + <Container id={id} align={alignment.elementRight} top="60px" right={-5} {...rest}> <Annotation id={id} /> </Container> ) const DraftAnnotationOverlay = ({ id, ...rest }) => ( - <Container id={id} align={alignment.elementRight} top="25px" right={-5} {...rest}> + <Container id={id} align={alignment.elementRight} top="60px" right={-5} {...rest}> <DraftAnnotation /> </Container> ) diff --git a/src/components/title/index.js b/src/components/title/index.js index bd8982b3..87057dc2 100644 --- a/src/components/title/index.js +++ b/src/components/title/index.js @@ -18,6 +18,8 @@ export const Title = props => { const name = useName() const isMinimal = useIsMinimal() const contextScope = useAttributeValue("contextScope") + const hideName = useAttributeValue("hideName") + const hideUnits = useAttributeValue("hideUnits") return ( <Flex @@ -31,7 +33,7 @@ export const Title = props => { <TextSmall color="textDescription" truncate> {title} </TextSmall> - {!!name && (!isMinimal || !title) && ( + {!!name && !hideName && (!isMinimal || !title) && ( <CopyToClipboard text={contextScope && contextScope.length ? contextScope.join(", ") : name} > @@ -41,7 +43,7 @@ export const Title = props => { </TextSmall> </CopyToClipboard> )} - {!!units && !isMinimal && ( + {!!units && !hideUnits && !isMinimal && ( <TextWithTooltip color="textLite" whiteSpace="nowrap" diff --git a/src/components/title/index.test.js b/src/components/title/index.test.js index 6a094d4c..5d9f53ed 100644 --- a/src/components/title/index.test.js +++ b/src/components/title/index.test.js @@ -160,4 +160,33 @@ describe("Title component", () => { // TextSmall component should have truncate prop expect(titleElement).toBeInTheDocument() }) + + it("hides the context name when hideName is true", () => { + renderWithChart(<Title />, { + attributes: { + title: "CPU Usage", + name: "system.cpu", + isMinimal: false, + hideName: true, + }, + }) + + expect(screen.getByText("CPU Usage")).toBeInTheDocument() + expect(screen.queryByText(/system\.cpu/)).not.toBeInTheDocument() + }) + + it("hides the units when hideUnits is true", () => { + renderWithChart(<Title />, { + attributes: { + title: "Memory Usage", + name: "system.memory", + isMinimal: false, + units: ["%"], + hideUnits: true, + }, + }) + + expect(screen.getByText("Memory Usage")).toBeInTheDocument() + expect(screen.queryByText(/\[.*\]/)).not.toBeInTheDocument() + }) }) diff --git a/src/helpers/shorten/index.js b/src/helpers/shorten/index.js index c062dcf2..3ed63a7d 100644 --- a/src/helpers/shorten/index.js +++ b/src/helpers/shorten/index.js @@ -44,11 +44,25 @@ export const shortForLength = (string, maxLength = 30) => { let round = 0 while (string.length > maxLength) { - string = shorten(string, round) + const next = shorten(string, round) round = round + 1 + + if (next === string) { + if (round > 3) break + } else { + string = next + } } return string } +export const shortenToWidth = (text, contentWidth, containerWidth) => { + if (!text || contentWidth <= 0 || contentWidth <= containerWidth) return text + + const targetChars = Math.max(3, Math.floor(text.length * (containerWidth / contentWidth))) + + return shortForLength(text, targetChars) +} + export default shorten diff --git a/src/helpers/shorten/index.test.js b/src/helpers/shorten/index.test.js index 9e3a1c71..0e9d817e 100644 --- a/src/helpers/shorten/index.test.js +++ b/src/helpers/shorten/index.test.js @@ -1,4 +1,4 @@ -import shorten, { shortForLength } from "." +import shorten, { shortForLength, shortenToWidth } from "." describe("shorten", () => { it("returns string as-is for non-string inputs", () => { @@ -84,4 +84,41 @@ describe("shortForLength", () => { expect(result.length).toBeLessThanOrEqual(10) expect(result).toContain("...") }) + + it("terminates instead of hanging for maxLength below the ellipsis floor", () => { + expect(shortForLength("systemd-journald-eu-west-1b", 0)).toBe("...") + expect(shortForLength("systemd-journald-eu-west-1b", 1)).toBe("...") + expect(shortForLength("systemd-journald-eu-west-1b", 2)).toBe("...") + }) +}) + +describe("shortenToWidth", () => { + it("returns text as-is for non-string inputs", () => { + expect(shortenToWidth(null, 100, 50)).toBeNull() + expect(shortenToWidth(undefined, 100, 50)).toBeUndefined() + expect(shortenToWidth("", 100, 50)).toBe("") + }) + + it("returns text unchanged when it already fits", () => { + expect(shortenToWidth("nginx", 40, 100)).toBe("nginx") + expect(shortenToWidth("nginx", 100, 100)).toBe("nginx") + }) + + it("returns text unchanged when content has no measurable width", () => { + expect(shortenToWidth("systemd-journald-eu-west-1b", 0, 0)).toBe("systemd-journald-eu-west-1b") + }) + + it("shortens text that overflows its container", () => { + const text = "systemd-journald-audit.service@cgroup_pod_1234567890-eu-west-1b" + const result = shortenToWidth(text, 600, 150) + + expect(result.length).toBeLessThan(text.length) + expect(result).toContain("...") + }) + + it("never produces output shorter than the ellipsis floor", () => { + const result = shortenToWidth("systemd-journald-eu-west-1b", 600, 1) + + expect(result).toBe("...") + }) }) diff --git a/src/helpers/stepped.js b/src/helpers/stepped.js new file mode 100644 index 00000000..d4d70b61 --- /dev/null +++ b/src/helpers/stepped.js @@ -0,0 +1,6 @@ +export const stateUnits = new Set(["state", "{state}", "status", "{status}"]) + +export const isStateUnits = units => { + const list = (Array.isArray(units) ? units : [units]).filter(Boolean) + return list.length > 0 && list.every(unit => stateUnits.has(unit)) +} diff --git a/src/helpers/stepped.test.js b/src/helpers/stepped.test.js new file mode 100644 index 00000000..8ca5f2ef --- /dev/null +++ b/src/helpers/stepped.test.js @@ -0,0 +1,32 @@ +import { isStateUnits } from "./stepped" + +describe("isStateUnits", () => { + it("returns true for state units", () => { + expect(isStateUnits(["{state}"])).toBe(true) + expect(isStateUnits(["state"])).toBe(true) + }) + + it("returns true for status units", () => { + expect(isStateUnits(["{status}"])).toBe(true) + expect(isStateUnits(["status"])).toBe(true) + }) + + it("accepts a plain string", () => { + expect(isStateUnits("state")).toBe(true) + }) + + it("returns false for non-state units", () => { + expect(isStateUnits(["bytes/s"])).toBe(false) + expect(isStateUnits(["%"])).toBe(false) + }) + + it("returns false when mixed with non-state units", () => { + expect(isStateUnits(["state", "bytes/s"])).toBe(false) + }) + + it("returns false for empty or missing units", () => { + expect(isStateUnits([])).toBe(false) + expect(isStateUnits([""])).toBe(false) + expect(isStateUnits(undefined)).toBe(false) + }) +}) diff --git a/src/sdk/initialAttributes.js b/src/sdk/initialAttributes.js index 0952021f..0d43fad9 100644 --- a/src/sdk/initialAttributes.js +++ b/src/sdk/initialAttributes.js @@ -64,6 +64,7 @@ export default { active: false, sparkline: false, chartType: "", + stepPlot: false, selectedLegendDimensions: [], contextItems: [], @@ -137,6 +138,9 @@ export default { enabledXAxis: true, hasToolbox: true, + hideTitle: false, + hideName: false, + hideUnits: false, hasHoverPopover: true, expandable: true, showAnnotations: true, diff --git a/src/sdk/makeChart/camelizePayload.js b/src/sdk/makeChart/camelizePayload.js index c9208b13..4b733e70 100644 --- a/src/sdk/makeChart/camelizePayload.js +++ b/src/sdk/makeChart/camelizePayload.js @@ -1,4 +1,5 @@ import { heatmapOrChartType } from "@/helpers/heatmap" +import { isStateUnits } from "@/helpers/stepped" import { getAlias } from "@/helpers/units" import normalizeSelectedInstances from "@/helpers/normalizeSelectedInstances" @@ -231,15 +232,18 @@ export default (payload, chart) => { contextsArray ) + const aliasedUnits = Array.isArray(units) ? units.map(getAlias) : [getAlias(units)] + const details = { viewDimensions: { ...viewDimensions, contexts: dimContexts, grouped, }, - units: Array.isArray(units) ? units.map(getAlias) : [getAlias(units)], + units: aliasedUnits, unitsStsByContext, chartType: heatmapOrChartType(viewDimensions.ids, chartType), + stepPlot: isStateUnits(aliasedUnits), title, tiers, perTier, diff --git a/src/sdk/makeChart/camelizePayload.test.js b/src/sdk/makeChart/camelizePayload.test.js index 41a2d448..cbd83bf8 100644 --- a/src/sdk/makeChart/camelizePayload.test.js +++ b/src/sdk/makeChart/camelizePayload.test.js @@ -50,6 +50,27 @@ describe("camelizePayload", () => { expect(result.title).toBe("Test Chart") expect(result.chartType).toBe("line") expect(result.units).toEqual(["By"]) + expect(result.stepPlot).toBe(false) + }) + + it("derives stepPlot for state units", () => { + const payload = { + view: { + title: "Connection State", + chart_type: "line", + units: "state", + }, + result: { + data: [], + labels: [], + point: { value: 0 }, + }, + } + + const result = camelizePayload(payload) + + expect(result.stepPlot).toBe(true) + expect(result.chartType).toBe("line") }) it("handles summary data", () => { diff --git a/src/sdk/makeChart/filters/getAggregateMethod.js b/src/sdk/makeChart/filters/getAggregateMethod.js index 93841ecb..349c27ba 100644 --- a/src/sdk/makeChart/filters/getAggregateMethod.js +++ b/src/sdk/makeChart/filters/getAggregateMethod.js @@ -1,3 +1,5 @@ +import { stateUnits } from "@/helpers/stepped" + const averageUnits = new Set([ "%", "percentage", @@ -52,8 +54,6 @@ const averageUnits = new Set([ "[degF]", ]) -const sumUnits = new Set(["state", "{state}", "status", "{status}"]) - const averageRegex = /(%|\/operation|\/run| run|\/request)/ export default chart => { @@ -66,7 +66,7 @@ export default chart => { let lowerUnit = unit.toLowerCase() if (averageUnits.has(unit) || averageRegex.test(lowerUnit)) return "avg" - if (sumUnits.has(unit) || sumUnits.has(lowerUnit)) return "sum" + if (stateUnits.has(unit) || stateUnits.has(lowerUnit)) return "sum" return "avg" } diff --git a/src/sdk/makeChart/filters/getInitialAttributes.js b/src/sdk/makeChart/filters/getInitialAttributes.js index 900935dc..0ecdb035 100644 --- a/src/sdk/makeChart/filters/getInitialAttributes.js +++ b/src/sdk/makeChart/filters/getInitialAttributes.js @@ -1,11 +1,6 @@ import getAggregateMethod from "./getAggregateMethod" import getDimensions from "./getDimensions" -export const stackedAggregations = { - avg: true, - sum: true, -} - export default chart => { const dimensionIds = chart.getAttribute("dimensionIds") const aggregationMethodAttr = chart.getAttribute("aggregationMethod") diff --git a/src/sdk/makeChart/filters/getInitialAttributes.test.js b/src/sdk/makeChart/filters/getInitialAttributes.test.js index 5ce9028f..d605917c 100644 --- a/src/sdk/makeChart/filters/getInitialAttributes.test.js +++ b/src/sdk/makeChart/filters/getInitialAttributes.test.js @@ -1,4 +1,4 @@ -import getInitialAttributes, { stackedAggregations } from "./getInitialAttributes" +import getInitialAttributes from "./getInitialAttributes" import { makeTestChart } from "@jest/testUtilities" describe("getInitialAttributes", () => { @@ -17,13 +17,6 @@ describe("getInitialAttributes", () => { chart = testChart.chart }) - it("exports stackedAggregations constant", () => { - expect(stackedAggregations).toEqual({ - avg: true, - sum: true, - }) - }) - it("returns correct initial attributes with no aggregationMethod", () => { const result = getInitialAttributes(chart) diff --git a/src/sdk/makeChart/filters/makeControllers.js b/src/sdk/makeChart/filters/makeControllers.js index 92da6b0b..1d7f5b23 100644 --- a/src/sdk/makeChart/filters/makeControllers.js +++ b/src/sdk/makeChart/filters/makeControllers.js @@ -1,36 +1,16 @@ import deepEqual from "@/helpers/deepEqual" import pristine, { pristineKey } from "@/sdk/pristine" -import getInitialFilterAttributes, { stackedAggregations } from "./getInitialAttributes" +import getInitialFilterAttributes from "./getInitialAttributes" import { isHeatmap } from "@/helpers/heatmap" import makeLog from "@/sdk/makeLog" export default chart => { - const chartType = chart.getAttribute("chartType") - let prevChartType = chartType - const log = ({ value, ...rest }) => makeLog(chart)({ ...rest, value: value && typeof value !== "string" ? JSON.stringify(value) : value, }) - const onGroupChange = groupBy => { - chart.updateAttribute("selectedLegendDimensions", []) - if (chart.getAttribute("selectedChartType")) return - - if (groupBy.length > 1 || groupBy[0] !== "dimension") { - prevChartType = prevChartType || chart.getAttribute("chartType") - const aggregationMethod = chart.getAttribute("aggregationMethod") - return chart.updateAttribute( - "chartType", - stackedAggregations[aggregationMethod] ? "stacked" : chartType - ) - } else { - chart.updateAttributes({ chartType: prevChartType, processing: true }) - } - prevChartType = chartType - } - const allowedGroupByValues = { node: true, instance: true, @@ -85,8 +65,9 @@ export default chart => { const changed = baseUpdateGroupBy(selected, {}) if (!changed) return + chart.updateAttribute("selectedLegendDimensions", []) chart.updateAttributes(getInitialFilterAttributes(chart)) - chart.fetch({ processing: true }).then(() => onGroupChange(chart.getAttribute("groupBy"))) + chart.fetch({ processing: true }) log({ chartAction: "chart-groupby-change",