Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@netdata/charts",
"version": "6.12.10",
"version": "6.12.11",
"description": "Netdata frontend SDK and chart utilities",
"main": "dist/index.js",
"module": "dist/es6/index.js",
Expand Down
21 changes: 12 additions & 9 deletions src/chartLibraries/dygraph/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ export default (sdk, chart) => {
hoverX.toggle(attributes.enabledHover)
navigation.toggle(attributes.enabledNavigation, attributes.navigation)

const onUnitsConversionChange = () =>
updateOptions({
...makeChartTypeOptions(),
digitsAfterDecimal:
chart.getAttribute("unitsConversionFractionDigits")[0] < 0
? 0
: chart.getAttribute("unitsConversionFractionDigits")[0],
})

listeners = [
chartUI.on("resize", () => dygraph.resize()),
chart.onAttributeChange("hoverX", dimensions => {
Expand Down Expand Up @@ -183,15 +192,9 @@ export default (sdk, chart) => {

updateOptions(makeChartTypeOptions())
}),
chart.onAttributeChange("unitsConversionPrefix", () => {
updateOptions({
...makeChartTypeOptions(),
digitsAfterDecimal:
chart.getAttribute("unitsConversionFractionDigits")[0] < 0
? 0
: chart.getAttribute("unitsConversionFractionDigits")[0],
})
}),
// scalable conversions move the prefix, conversable ones move the base
chart.onAttributeChange("unitsConversionPrefix", onUnitsConversionChange),
chart.onAttributeChange("unitsConversionBase", onUnitsConversionChange),
chart.onAttributeChange("selectedLegendDimensions", () => {
if (chart.getAttribute("processing")) return

Expand Down
28 changes: 28 additions & 0 deletions src/chartLibraries/dygraph/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,34 @@ describe("dygraphChart", () => {
expect(mockDygraph.updateOptions.mock.calls[1][1]).toBe(true)
})

it("redraws when a conversable conversion changes the base unit", () => {
const { sdk, chart } = makeTestChart({
attributes: {
highlighting: false,
panning: false,
processing: false,
},
})

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"))
instance.render()
mockDygraph.updateOptions.mockClear()

chart.updateAttribute("unitsConversionBase", ["[degF]"])

expect(mockDygraph.updateOptions).toHaveBeenCalled()
})

it("skips render when highlighting, panning, or processing", () => {
const { sdk, chart } = makeTestChart({
attributes: {
Expand Down
15 changes: 14 additions & 1 deletion src/components/filterToolbox/timeAggregation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Flex, TextMicro } from "@netdata/netdata-ui"
import { useAttributeValue, useChart } from "@/components/provider"
import Dropdown from "./dropdownSingleSelect"

const useMenuItems = (chart, perTier = []) => {
export const useMenuItems = (chart, perTier = []) => {
const [firstTier, ...restTiers] = perTier
return useMemo(
() =>
Expand Down Expand Up @@ -38,6 +38,19 @@ const useMenuItems = (chart, perTier = []) => {
short: "SUM()",
"data-track": chart.track("time-aggregation-sum"),
},
{
justDesc: true,
description:
"The function below does not combine values at all. Use it for metrics that represent a state rather than a quantity.",
},
{
value: "latest",
label: "Latest value",
description:
"Show the last database value in each point instead of combining them. Use it for states, statuses and boolean metrics, where a combined value such as 0.5 is meaningless. Requires a recent Netdata agent; older agents fall back to average.",
short: "LATEST()",
"data-track": chart.track("time-aggregation-latest"),
},
Array.isArray(restTiers) &&
typeof firstTier?.points !== "undefined" && {
justDesc: true,
Expand Down
40 changes: 40 additions & 0 deletions src/components/filterToolbox/timeAggregation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { renderHook } from "@testing-library/react"
import { useMenuItems } from "./timeAggregation"

const chart = { track: name => name }

describe("time aggregation menu items", () => {
it("offers latest with a LATEST() short label", () => {
const { result } = renderHook(() => useMenuItems(chart))
const latest = result.current.find(item => item.value === "latest")

expect(latest).toBeTruthy()
expect(latest.short).toBe("LATEST()")
expect(latest.label).toBe("Latest value")
})

it("precedes latest with a description-only separator", () => {
const { result } = renderHook(() => useMenuItems(chart))
const index = result.current.findIndex(item => item.value === "latest")

expect(index).toBeGreaterThan(0)
expect(result.current[index - 1].justDesc).toBe(true)
})

it("keeps the everyday functions ahead of latest", () => {
const { result } = renderHook(() => useMenuItems(chart))
const valueAt = value => result.current.findIndex(item => item.value === value)

expect(valueAt("min")).toBeLessThan(valueAt("latest"))
expect(valueAt("max")).toBeLessThan(valueAt("latest"))
expect(valueAt("average")).toBeLessThan(valueAt("latest"))
expect(valueAt("sum")).toBeLessThan(valueAt("latest"))
})

it("mentions the older-agent fallback in the description", () => {
const { result } = renderHook(() => useMenuItems(chart))
const latest = result.current.find(item => item.value === "latest")

expect(latest.description).toMatch(/fall back to average/i)
})
})
17 changes: 12 additions & 5 deletions src/components/provider/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,14 @@ export const useUnits = (key = "units") => {

const forceUpdate = useForceUpdate()

useImmediateListener(
() => chart.onAttributeChange(`${key}ConversionPrefix`, forceUpdate),
[chart, key]
)
useImmediateListener(() => {
const offListeners = [
chart.onAttributeChange(`${key}ConversionPrefix`, forceUpdate),
chart.onAttributeChange(`${key}ConversionBase`, forceUpdate),
]

return () => offListeners.forEach(off => off())
}, [chart, key])

return chart.getUnits(key)
}
Expand Down Expand Up @@ -367,6 +371,7 @@ export const useValueUnitAttributes = (
const units = useAttributeValue(unitsKey)
const desiredUnits = useAttributeValue("desiredUnits")
const secondsAsTime = useAttributeValue("secondsAsTime")
const temperature = useAttributeValue("temperature")
const viewDimensions = useAttributeValue("viewDimensions")

return useMemo(() => {
Expand All @@ -387,6 +392,7 @@ export const useValueUnitAttributes = (
units,
desiredUnits,
secondsAsTime,
temperature,
viewDimensions,
])
}
Expand Down Expand Up @@ -620,7 +626,8 @@ export const useValue = (
return unregister(
chart.onAttributeChange("hoverX", () => setState(getValue())),
chart.on("dimensionChanged", () => setState(getValue())),
chart.onAttributeChange(`${unitsKey}Conversion`, () => setState(getValue())),
chart.onAttributeChange(`${unitsKey}ConversionPrefix`, () => setState(getValue())),
chart.onAttributeChange(`${unitsKey}ConversionBase`, () => setState(getValue())),
chart.on("successFetch", () => setState(getValue()))
)
}, [chart, id, valueKey, period, unitsKey, abs, allowNull])
Expand Down
19 changes: 19 additions & 0 deletions src/components/provider/selectors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
useLatestDisplayValue,
useLatestDisplayValueWithUnit,
useLatestValue,
useValueWithUnit,
getValueByPeriod,
} from "./selectors"

Expand Down Expand Up @@ -417,4 +418,22 @@ describe("Chart Provider Selectors", () => {
expect(result.current.formatted.convertedUnit).toBe("KiB")
})
})

describe("useValueWithUnit", () => {
it("reconverts when the temperature preference changes", () => {
const { result, chart } = renderHookWithChart(
() => useValueWithUnit(100, { scaleByValue: true }),
{ attributes: { units: ["Cel"], desiredUnits: ["auto"] } }
)

expect(result.current.convertedUnit).toBe("°C")

act(() => {
chart.updateAttribute("temperature", "fahrenheit")
})

expect(result.current.convertedUnit).toBe("°F")
expect(result.current.convertedValue).toBe("212")
})
})
})
35 changes: 31 additions & 4 deletions src/components/toolbox/settings/numberFormat.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import React, { useMemo, useState, useEffect } from "react"
import { Flex, TextSmall, TextInput, Select } from "@netdata/netdata-ui"
import { useAttributeValue, useChart } from "@/components/provider"
import { getScales, getUnitConfig, isScalable } from "@/helpers/units"
import conversableUnits, { keys as conversableKeys } from "@/helpers/units/conversableUnits"
import conversableUnits, {
keys as conversableKeys,
isTemperatureUnit,
} from "@/helpers/units/conversableUnits"

const symbolOf = unit => getUnitConfig(unit).print_symbol || unit

// what "auto" resolves to right now, asked of the converter itself rather than restated here
const resolvePreferred = (chart, unit) => {
const target = (conversableKeys[unit] || []).find(key =>
conversableUnits[unit][key].check(chart, 0)
)

return symbolOf(target || unit)
}

const NumberFormat = () => {
const chart = useChart()
const units = useAttributeValue("units")
const desiredUnitsAttr = useAttributeValue("desiredUnits") || ["auto"]
const staticFractionDigitsAttr = useAttributeValue("staticFractionDigits")
const temperature = useAttributeValue("temperature")

const [selectedUnitIndex, setSelectedUnitIndex] = useState(0)

Expand All @@ -25,17 +40,28 @@ const NumberFormat = () => {
)

const scaleOptions = useMemo(() => {
if (isTemperatureUnit(selectedUnit)) {
return [
{ value: "auto", label: `Follow preference (${resolvePreferred(chart, selectedUnit)})` },
{ value: "original", label: symbolOf(selectedUnit) },
...(conversableKeys[selectedUnit] || []).map(key => ({
value: key,
label: symbolOf(key),
})),
]
}

const options = [
{ value: "auto", label: "Auto scale" },
{ value: "original", label: "No conversion" },
]

if (isScalable(selectedUnit)) {
if (isScalable(selectedUnit) || conversableUnits[selectedUnit]) {
if (conversableUnits[selectedUnit]) {
const scaleKeys =
conversableKeys[selectedUnit] || Object.keys(conversableUnits[selectedUnit])
scaleKeys.forEach(key => {
options.push({ value: key, label: key })
options.push({ value: key, label: symbolOf(key) })
})
} else {
const [scaleKeys] = getScales(selectedUnit)
Expand All @@ -50,7 +76,7 @@ const NumberFormat = () => {
}

return options
}, [selectedUnit])
}, [chart, selectedUnit, temperature])

useEffect(() => {
setDesiredUnits(desiredUnitsAttr[selectedUnitIndex] || "auto")
Expand All @@ -77,6 +103,7 @@ const NumberFormat = () => {
update({ staticFractionDigits: value })
}


return (
<Flex column gap={2}>
<TextSmall color="textNoFocus" strong>
Expand Down
27 changes: 27 additions & 0 deletions src/components/toolbox/settings/numberFormat.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,30 @@ it("labels scale options with the base unit, not the incoming prefixed unit", as
expect(screen.getByText("Mi By/s")).toBeInTheDocument()
expect(screen.queryByText("Ki KiBy/s")).not.toBeInTheDocument()
})

// temperature units render a Temperature select ahead of Scale
const openScale = async user => {
const selects = screen.getAllByRole("combobox")
await user.click(selects[selects.length - 1])
}

it("offers Fahrenheit for a Celsius chart, labelled by its symbol", async () => {
const { user } = renderWithChart(<NumberFormat />, {
attributes: { units: ["Cel"], desiredUnits: ["auto"] },
})

await openScale(user)

expect(screen.getByText("°F")).toBeInTheDocument()
expect(screen.queryByText("[degF]")).not.toBeInTheDocument()
})

it("offers Celsius for a Fahrenheit chart", async () => {
const { user } = renderWithChart(<NumberFormat />, {
attributes: { units: ["[degF]"], desiredUnits: ["auto"] },
})

await openScale(user)

expect(screen.getByText("°C")).toBeInTheDocument()
})
Loading