Skip to content
Merged

Fixes #207

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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
7 changes: 5 additions & 2 deletions src/chartLibraries/dygraph/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -346,6 +348,7 @@ export default (sdk, chart) => {
(forceIncludeZero && dimensionIds.length > 1 && selectedLegendDimensions.length > 1),
stackedGraphNaNFill: "none",
plotter,
stepPlot,
errorBars,
axes: {
x: enabledXAxis
Expand Down
44 changes: 44 additions & 0 deletions src/chartLibraries/dygraph/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
3 changes: 2 additions & 1 deletion src/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -40,7 +41,7 @@ const Header = ({ hasFilters }) => {
{arr[index + 1] ? <Separator /> : null}
</Fragment>
))}
<Title />
{!hideTitle && <Title />}
{isMinimal && hasFilters && <FilterToolbox opacity={focused ? 1 : 0.7} />}
{hasToolbox && <Toolbox />}
</Container>
Expand Down
23 changes: 23 additions & 0 deletions src/components/header/index.test.js
Original file line number Diff line number Diff line change
@@ -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()
})
})
12 changes: 5 additions & 7 deletions src/components/helpers/shortener.js
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand All @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion src/components/line/footer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
98 changes: 61 additions & 37 deletions src/components/line/legend/index.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 = () => (
Expand All @@ -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()
Expand All @@ -75,26 +80,43 @@ 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) {
legendRef.current.scrollTo({ left: chart.getAttribute("legendScroll") })
}
}, [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()
Expand Down Expand Up @@ -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"
Expand All @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions src/components/line/overlays/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
)
Expand Down
6 changes: 4 additions & 2 deletions src/components/title/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}
>
Expand All @@ -41,7 +43,7 @@ export const Title = props => {
</TextSmall>
</CopyToClipboard>
)}
{!!units && !isMinimal && (
{!!units && !hideUnits && !isMinimal && (
<TextWithTooltip
color="textLite"
whiteSpace="nowrap"
Expand Down
29 changes: 29 additions & 0 deletions src/components/title/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
Loading