Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b11002a
[BUGFIX] add double backslash behind special charachters when uisng r…
shahrokni Jul 2, 2026
f7d2333
Prepare release v0.54.0-beta.11 (#192)
shahrokni Jul 2, 2026
89f81f5
[IGNORE] bump the plugin runtime dependencies (#193)
rickardsjp Jul 2, 2026
0145604
[BREAKINGCHANGE] move PanelEditorValues and panel editor schemas out …
adrianSepiol Jul 6, 2026
9668451
Bump golangci/golangci-lint-action in the actions group (#196)
dependabot[bot] Jul 7, 2026
452c208
[BUGFIX] fix timeseries tooltip hover for stacked and grouped charts …
ibakshay Jul 7, 2026
180780e
[ENHANCEMENT] EChart: extract timezone-aware axis label formatter (#190)
andreasgerstmayr Jul 9, 2026
5e546ba
[BUGFIX] fix race condition with initial query plugin spec (#199)
andreasgerstmayr Jul 16, 2026
a12a5db
[ignore] update perses/spec to beta.9 (#200)
Nexucis Jul 16, 2026
9bd2cb0
Release v0.54.0-rc.0 (#201)
Nexucis Jul 16, 2026
472a289
[IGNORE] fix vulnerable dependencies (#203)
jgbernalp Jul 20, 2026
100357f
Release v0.54.0-rc.1 (#205)
Nexucis Jul 21, 2026
88594e0
Release v0.54.0 (#207)
Nexucis Jul 29, 2026
40f4486
[ENHANCEMENT] include dashboard tags into v1alpha2 CR export (#209)
jgbernalp Jul 30, 2026
ba74808
[BUGFIX] fallback to versionless plugin export (#211)
jgbernalp Aug 3, 2026
0e737ba
[FEATURE] create @perses-dev/design-token package
zhuje Jul 7, 2026
fd71bf8
[IGNORE] address comments perses/shared pr #198
zhuje Jul 8, 2026
f3158c4
Merge branch 'ui-customization' into ui-customization-jenny
zhuje Aug 5, 2026
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 .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
enable_go_cache: false
enable_npm: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v9.2.1
uses: golangci/golangci-lint-action@v9.3.0
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v2.12.2
Expand Down
4 changes: 2 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@perses-dev/client",
"version": "0.54.0-beta.10",
"version": "0.54.0",
"description": "Functions as an API client or Data fetching Layer for interacting with a backend service",
"license": "Apache-2.0",
"homepage": "https://github.com/perses/perses/blob/main/README.md",
Expand All @@ -15,7 +15,7 @@
"main": "dist/cjs/index.js",
"types": "dist/index.d.ts",
"dependencies": {
"@perses-dev/spec": "0.2.0-beta.6",
"@perses-dev/spec": "0.2.0",
"zod": "^3.21.4"
},
"scripts": {
Expand Down
7 changes: 3 additions & 4 deletions components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@perses-dev/components",
"version": "0.54.0-beta.10",
"version": "0.54.0",
"description": "Common UI components used across Perses features",
"license": "Apache-2.0",
"homepage": "https://github.com/perses/perses/blob/main/README.md",
Expand Down Expand Up @@ -34,8 +34,8 @@
"@date-fns/tz": "^1.4.1",
"@fontsource/inter": "^5.0.0",
"@mui/x-date-pickers": "^7.23.1",
"@perses-dev/spec": "0.2.0-beta.6",
"@perses-dev/client": "0.54.0-beta.10",
"@perses-dev/spec": "0.2.0",
"@perses-dev/client": "0.54.0",
"numbro": "^2.3.6",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-table": "^8.20.5",
Expand All @@ -49,7 +49,6 @@
"notistack": "^3.0.2",
"react-colorful": "^5.6.1",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.51.3",
"react-virtuoso": "^4.12.2"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions components/src/EChart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
// limitations under the License.

export * from './EChart';
export * from './timezone-formatter';
79 changes: 79 additions & 0 deletions components/src/EChart/timezone-formatter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { createTimezoneAwareAxisFormatter } from './timezone-formatter';

// Mock formatWithTimeZone since it's from @perses-dev/components
jest.mock('@perses-dev/components', () => ({
formatWithTimeZone: jest.fn((date: Date, format: string, timeZone: string) => {
// Simple mock that returns format pattern with timezone
return `${format}[${timeZone}]`;
}),
}));

describe('createTimezoneAwareAxisFormatter', () => {
const testTimestamp = 1640995200000; // 2022-01-01 00:00:00 UTC
const timeZone = 'America/New_York';

it('should format for ranges > 5 years with year format', () => {
const formatter = createTimezoneAwareAxisFormatter(6 * 365 * 24 * 60 * 60 * 1000, timeZone);
const result = formatter(testTimestamp);
expect(result).toBe('yyyy[America/New_York]');
});

it('should format for ranges > 6 months with month-year format', () => {
const formatter = createTimezoneAwareAxisFormatter(3 * 365 * 24 * 60 * 60 * 1000, timeZone);
const result = formatter(testTimestamp);
expect(result).toBe('MMM yyyy[America/New_York]');
});

it('should format for ranges between 10 days and 6 months with day-month format', () => {
const formatter = createTimezoneAwareAxisFormatter(30 * 24 * 60 * 60 * 1000, timeZone); // 30 days
const result = formatter(testTimestamp);
expect(result).toBe('dd.MM[America/New_York]');
});

it('should format for ranges between 2-10 days with day-month-time format', () => {
const formatter = createTimezoneAwareAxisFormatter(5 * 24 * 60 * 60 * 1000, timeZone); // 5 days
const result = formatter(testTimestamp);
expect(result).toBe('dd.MM HH:mm[America/New_York]');
});

it('should format for ranges <= 2 days with time format', () => {
const formatter = createTimezoneAwareAxisFormatter(6 * 60 * 60 * 1000, timeZone); // 6 hours
const result = formatter(testTimestamp);
expect(result).toBe('HH:mm[America/New_York]');
});

it('should handle different timezones', () => {
const formatter = createTimezoneAwareAxisFormatter(6 * 60 * 60 * 1000, 'Europe/Prague');
const result = formatter(testTimestamp);
expect(result).toBe('HH:mm[Europe/Prague]');
});

it('should handle edge case at exactly 5 years', () => {
const fiveYears = 5 * 365 * 24 * 60 * 60 * 1000;
const formatter = createTimezoneAwareAxisFormatter(fiveYears, timeZone);
const result = formatter(testTimestamp);
// Should use MMM yyyy format (not > 5 years)
expect(result).toBe('MMM yyyy[America/New_York]');
});

it('should handle edge case at exactly 2 days', () => {
const twoDays = 2 * 24 * 60 * 60 * 1000;
const formatter = createTimezoneAwareAxisFormatter(twoDays, timeZone);
const result = formatter(testTimestamp);
// Should use HH:mm format (not > 2 days)
expect(result).toBe('HH:mm[America/New_York]');
});
});
50 changes: 50 additions & 0 deletions components/src/EChart/timezone-formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { formatWithTimeZone } from '@perses-dev/components';

const DAY_MS = 1000 * 60 * 60 * 24;
const MONTH_MS = DAY_MS * 30;
const YEAR_MS = DAY_MS * 365;

/**
* Creates a timezone-aware axis formatter function for different time ranges
*/
export function createTimezoneAwareAxisFormatter(rangeMs: number, timeZone: string) {
return function (value: number): string {
const timeStamp = new Date(Number(value));

// more than 5 years
if (rangeMs > YEAR_MS * 5) {
return formatWithTimeZone(timeStamp, 'yyyy', timeZone);
}

// more than 6 months
if (rangeMs > MONTH_MS * 6) {
return formatWithTimeZone(timeStamp, 'MMM yyyy', timeZone);
}

// more than 10 days
if (rangeMs > DAY_MS * 10) {
return formatWithTimeZone(timeStamp, 'dd.MM', timeZone);
}

// more than 2 days
if (rangeMs > DAY_MS * 2) {
return formatWithTimeZone(timeStamp, 'dd.MM HH:mm', timeZone);
}

// less or equal 2 days
return formatWithTimeZone(timeStamp, 'HH:mm', timeZone);
};
}
1 change: 0 additions & 1 deletion components/src/LinksEditor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@
// See the License for the specific language governing permissions and
// limitations under the License.

export * from './LinksEditor';
export * from './LinkEditorForm';
35 changes: 29 additions & 6 deletions components/src/TimeSeriesTooltip/TimeChartTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { memo, MutableRefObject, useRef, useState } from 'react';
import { memo, MutableRefObject, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { Box, Portal, Stack } from '@mui/material';
import { ECharts as EChartsInstance } from 'echarts/core';
import { TimeSeries } from '@perses-dev/spec';
Expand Down Expand Up @@ -57,26 +57,49 @@ export const TimeChartTooltip = memo(function TimeChartTooltip({
}: TimeChartTooltipProps) {
const [showAllSeries, setShowAllSeries] = useState(false);
const transform = useRef<string | undefined>();
const tooltipElementRef = useRef<HTMLDivElement | null>(null);

const mousePos = useMousePosition();
const { height, width, ref: tooltipRef } = useResizeObserver();

const isTooltipPinned = pinnedPos !== null && enablePinning;

// Stable callback — prevents the ResizeObserver from disconnecting/reconnecting on every render.
const setTooltipRef = useCallback(
(node: HTMLDivElement | null): void => {
tooltipElementRef.current = node;
tooltipRef(node);
},
[tooltipRef]
);

const containerElement = containerId ? document.querySelector(containerId) : undefined;

// Synchronously reposition after every render to prevent one-frame viewport overflow.
useLayoutEffect(() => {
if (mousePos === null) return;
const node = tooltipElementRef.current;
if (!node) return;
const rect = node.getBoundingClientRect();
if (rect.height === 0 || rect.width === 0) return;
const nextTransform = assembleTransform(mousePos, pinnedPos, rect.height, rect.width, containerElement);
if (nextTransform && nextTransform !== transform.current) {
transform.current = nextTransform;
node.style.transform = nextTransform;
}
});

if (mousePos === null || mousePos.target === null || data === null) return null;

// Ensure user is hovering over a chart before checking for nearby series.
if (pinnedPos === null && (mousePos.target as HTMLElement).tagName !== 'CANVAS') return null;

const chart = chartRef.current;

const containerElement = containerId ? document.querySelector(containerId) : undefined;
// if tooltip is attached to a container, set max height to the height of the container so tooltip does not get cut off
// Cap height to container so the tooltip is not cut off.
const maxHeight = containerElement ? containerElement.getBoundingClientRect().height : undefined;

transform.current = assembleTransform(mousePos, pinnedPos, height ?? 0, width ?? 0, containerElement);

// Get series nearby the cursor and pass into tooltip content children.
const nearbySeries = getNearbySeriesData({
mousePos,
data,
Expand All @@ -96,7 +119,7 @@ export const TimeChartTooltip = memo(function TimeChartTooltip({
return (
<Portal container={containerElement}>
<Box
ref={tooltipRef}
ref={setTooltipRef}
sx={(theme) => getTooltipStyles(theme, pinnedPos, maxHeight)}
style={{
transform: transform.current,
Expand Down
2 changes: 1 addition & 1 deletion components/src/TimeSeriesTooltip/TooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import { ReactElement, useMemo } from 'react';
import { Box } from '@mui/material';
import { NearbySeriesArray } from './nearby-series';
import { NearbySeriesArray } from './types';
import { SeriesInfo } from './SeriesInfo';

export interface TooltipContentProps {
Expand Down
2 changes: 1 addition & 1 deletion components/src/TimeSeriesTooltip/TooltipHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Pin from 'mdi-material-ui/Pin';
import PinOutline from 'mdi-material-ui/PinOutline';
import { memo, ReactElement } from 'react';
import { useTimeZone } from '../context/TimeZoneProvider';
import { NearbySeriesArray } from './nearby-series';
import { NearbySeriesArray } from './types';
import {
TOOLTIP_BG_COLOR_FALLBACK,
TOOLTIP_MAX_WIDTH,
Expand Down
1 change: 1 addition & 0 deletions components/src/TimeSeriesTooltip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export * from './TooltipContent';
export * from './TooltipHeader';
export * from './nearby-series';
export * from './tooltip-model';
export * from './types';
export * from './utils';
Loading
Loading