Skip to content

Commit ea18c5e

Browse files
committed
feat: more bar
1 parent 1e8ef9c commit ea18c5e

5 files changed

Lines changed: 134 additions & 4 deletions

File tree

src/components/Configs/Changes/ConfigChangesSwimlane.tsx

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { ConfigChange } from "@flanksource-ui/api/types/configs";
2+
import { parseDateMath } from "@flanksource-ui/ui/Dates/TimeRangePicker/parseDateMath";
3+
import useTimeRangeParams from "@flanksource-ui/ui/Dates/TimeRangePicker/useTimeRangeParams";
4+
import {
5+
TimeRangeOption,
6+
displayTimeFormat,
7+
timeRangeOptionsToAbsolute
8+
} from "@flanksource-ui/ui/Dates/TimeRangePicker/rangeOptions";
29
import dayjs from "dayjs";
310
import { useCallback, useMemo, useRef, useState } from "react";
411
import ConfigChangesSwimlaneLegend from "./Swimlane/Legend";
12+
import {
13+
MORE_TIME_RANGE_BAR_WIDTH,
14+
MoreTimeRangeBar
15+
} from "./Swimlane/MoreTimeRangeHandle";
516
import {
617
EmptySwimlaneState,
718
GroupParentRow,
@@ -23,6 +34,64 @@ import {
2334
useResizableColumn
2435
} from "./Swimlane/Utils";
2536

37+
const GRAPH_RANGE_PRESETS = [
38+
{ display: "5 minutes", range: "now-5m", ms: 5 * 60 * 1000 },
39+
{ display: "15 minutes", range: "now-15m", ms: 15 * 60 * 1000 },
40+
{ display: "30 minutes", range: "now-30m", ms: 30 * 60 * 1000 },
41+
{ display: "1 hour", range: "now-1h", ms: 60 * 60 * 1000 },
42+
{ display: "2 hours", range: "now-2h", ms: 2 * 60 * 60 * 1000 },
43+
{ display: "3 hours", range: "now-3h", ms: 3 * 60 * 60 * 1000 },
44+
{ display: "6 hours", range: "now-6h", ms: 6 * 60 * 60 * 1000 },
45+
{ display: "12 hours", range: "now-12h", ms: 12 * 60 * 60 * 1000 },
46+
{ display: "24 hours", range: "now-24h", ms: 24 * 60 * 60 * 1000 },
47+
{ display: "2 days", range: "now-2d", ms: 2 * 24 * 60 * 60 * 1000 },
48+
{ display: "7 days", range: "now-7d", ms: 7 * 24 * 60 * 60 * 1000 }
49+
];
50+
51+
const GRAPH_DEFAULT_RANGE: TimeRangeOption = {
52+
type: "relative",
53+
display: "2 hours",
54+
range: "now-2h"
55+
};
56+
57+
function resolveRangeDate(value: string, roundUp = false) {
58+
if (value === "now") {
59+
return dayjs();
60+
}
61+
if (value.startsWith("now")) {
62+
return dayjs(parseDateMath(value, roundUp));
63+
}
64+
return dayjs(value);
65+
}
66+
67+
function getIncreasedTimeRange(range?: TimeRangeOption): TimeRangeOption {
68+
const currentRange = range ?? GRAPH_DEFAULT_RANGE;
69+
const { from, to } = timeRangeOptionsToAbsolute(currentRange);
70+
const fromDate = resolveRangeDate(from);
71+
const toDate = resolveRangeDate(to, true);
72+
const durationMs = Math.max(0, toDate.diff(fromDate));
73+
const nextPreset =
74+
GRAPH_RANGE_PRESETS.find((preset) => preset.ms > durationMs + 1000) ??
75+
GRAPH_RANGE_PRESETS[GRAPH_RANGE_PRESETS.length - 1]!;
76+
77+
if (currentRange.type === "relative" && to === "now") {
78+
return {
79+
type: "relative",
80+
display: nextPreset.display,
81+
range: nextPreset.range
82+
};
83+
}
84+
85+
return {
86+
type: "absolute",
87+
display: "Custom",
88+
from: toDate
89+
.subtract(nextPreset.ms, "millisecond")
90+
.format(displayTimeFormat),
91+
to: toDate.format(displayTimeFormat)
92+
};
93+
}
94+
2695
type ConfigChangesSwimlaneProps = {
2796
changes: ConfigChange[];
2897
isLoading?: boolean;
@@ -39,6 +108,11 @@ export default function ConfigChangesSwimlane({
39108
);
40109
const containerRef = useRef<HTMLDivElement | null>(null);
41110
const markersRef = useRef<HTMLDivElement | null>(null);
111+
const { setTimeRangeParams, getTimeRangeFromUrl } = useTimeRangeParams();
112+
const timeRangeValue = getTimeRangeFromUrl();
113+
const increaseTimeRange = useCallback(() => {
114+
setTimeRangeParams(getIncreasedTimeRange(timeRangeValue));
115+
}, [setTimeRangeParams, timeRangeValue]);
42116
const containerWidth = useContainerWidth(containerRef);
43117
const initialColumnWidth = Math.min(
44118
MAX_COLUMN_WIDTH,
@@ -49,7 +123,7 @@ export default function ConfigChangesSwimlane({
49123
const markersWidth = useContainerWidth(markersRef);
50124
const numBuckets = Math.max(1, Math.floor(markersWidth / BUCKET_MIN_PX));
51125

52-
const { groups, min, max, ticks } = useMemo(() => {
126+
const { groups, min, max, ticks, hasPreRangeChanges } = useMemo(() => {
53127
const grouped = new Map<string, ConfigChange[]>();
54128
for (const c of changes) {
55129
const key = c.config_id ?? c.config?.id ?? c.id;
@@ -102,7 +176,8 @@ export default function ConfigChangesSwimlane({
102176
groups: groupRowsByPath(rows),
103177
min,
104178
max,
105-
ticks: generateTimeTicks(min, max)
179+
ticks: generateTimeTicks(min, max),
180+
hasPreRangeChanges: rows.some((row) => row.preRangeBadge)
106181
};
107182
}, [changes, numBuckets]);
108183

@@ -118,6 +193,10 @@ export default function ConfigChangesSwimlane({
118193
if (isLoading) return <LoadingSwimlaneState />;
119194
if (changes.length === 0) return <EmptySwimlaneState />;
120195

196+
const timelineOffsetWidth = hasPreRangeChanges
197+
? MORE_TIME_RANGE_BAR_WIDTH
198+
: 0;
199+
121200
let rowIdx = 0;
122201

123202
const renderGroup = (
@@ -135,6 +214,7 @@ export default function ConfigChangesSwimlane({
135214
numBuckets={numBuckets}
136215
onItemClicked={onItemClicked}
137216
onResizeMouseDown={onResizeMouseDown}
217+
timelineOffsetWidth={timelineOffsetWidth}
138218
min={min}
139219
max={max}
140220
indentLevel={indentLevel}
@@ -156,6 +236,7 @@ export default function ConfigChangesSwimlane({
156236
numBuckets={numBuckets}
157237
onItemClicked={onItemClicked}
158238
onResizeMouseDown={onResizeMouseDown}
239+
timelineOffsetWidth={timelineOffsetWidth}
159240
min={min}
160241
max={max}
161242
indentLevel={indentLevel}
@@ -174,13 +255,18 @@ export default function ConfigChangesSwimlane({
174255
<TimeAxisHeader
175256
columnWidth={columnWidth}
176257
markersRef={markersRef}
258+
timelineOffsetWidth={timelineOffsetWidth}
177259
ticks={ticks}
178260
min={min}
179261
max={max}
180262
/>
181263

182264
<ConfigChangesSwimlaneLegend changes={changes} />
183265

266+
{hasPreRangeChanges && (
267+
<MoreTimeRangeBar left={columnWidth} onClick={increaseTimeRange} />
268+
)}
269+
184270
<div className="flex-1">{groups.map((group) => renderGroup(group))}</div>
185271
</div>
186272
);

src/components/Configs/Changes/Swimlane/GroupParentRow.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function GroupParentRow({
1919
numBuckets,
2020
onItemClicked,
2121
onResizeMouseDown,
22+
timelineOffsetWidth = 0,
2223
min,
2324
max,
2425
indentLevel = 0
@@ -30,6 +31,7 @@ export function GroupParentRow({
3031
numBuckets: number;
3132
onItemClicked: (change: ConfigChange) => void;
3233
onResizeMouseDown: (e: React.MouseEvent) => void;
34+
timelineOffsetWidth?: number;
3335
min: number;
3436
max: number;
3537
indentLevel?: number;
@@ -110,7 +112,10 @@ export function GroupParentRow({
110112
<ResizeHandle onMouseDown={onResizeMouseDown} />
111113
</div>
112114
{collapsed && (
113-
<div className="flex flex-1 flex-row items-stretch border-l border-gray-200">
115+
<div
116+
className="flex flex-1 flex-row items-stretch border-l border-gray-200"
117+
style={{ paddingLeft: timelineOffsetWidth }}
118+
>
114119
<BucketCells
115120
buckets={mergedBuckets}
116121
numBuckets={numBuckets}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const MORE_TIME_RANGE_BAR_WIDTH = 16;
2+
3+
export function MoreTimeRangeBar({
4+
left,
5+
onClick
6+
}: {
7+
left: number;
8+
onClick: () => void;
9+
}) {
10+
return (
11+
<div className="sticky top-0 z-30 h-0">
12+
<button
13+
type="button"
14+
title="Increase time range"
15+
aria-label="Increase time range"
16+
onClick={(e) => {
17+
e.stopPropagation();
18+
onClick();
19+
}}
20+
className="absolute top-0 flex h-screen items-center justify-center border-x border-gray-200 bg-gray-50 text-[9px] font-medium uppercase tracking-wide text-gray-400 hover:bg-blue-50 hover:text-blue-600"
21+
style={{ left, width: MORE_TIME_RANGE_BAR_WIDTH }}
22+
>
23+
<span className="[writing-mode:vertical-rl]">More</span>
24+
</button>
25+
</div>
26+
);
27+
}

src/components/Configs/Changes/Swimlane/SwimlaneRow.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function SwimlaneRow({
1212
numBuckets,
1313
onItemClicked,
1414
onResizeMouseDown,
15+
timelineOffsetWidth = 0,
1516
min,
1617
max,
1718
indentLevel = 0,
@@ -22,6 +23,7 @@ export function SwimlaneRow({
2223
numBuckets: number;
2324
onItemClicked: (change: ConfigChange) => void;
2425
onResizeMouseDown: (e: React.MouseEvent) => void;
26+
timelineOffsetWidth?: number;
2527
min: number;
2628
max: number;
2729
indentLevel?: number;
@@ -47,15 +49,22 @@ export function SwimlaneRow({
4749
<span className="truncate">{row.name}</span>
4850
</ConfigsTypeIcon>
4951
</div>
52+
5053
<span className="h-4 w-px shrink-0 bg-gray-200" />
54+
5155
<SeverityBadges
5256
severity={row.severity}
5357
changes={allChanges}
5458
onExpand={onItemClicked}
5559
/>
60+
5661
<ResizeHandle onMouseDown={onResizeMouseDown} />
5762
</div>
58-
<div className="flex flex-1 flex-row items-stretch border-l border-gray-200">
63+
64+
<div
65+
className="flex flex-1 flex-row items-stretch border-l border-gray-200"
66+
style={{ paddingLeft: timelineOffsetWidth }}
67+
>
5968
{row.preRangeBadge && (
6069
<span className="flex items-center px-1 text-[10px] text-gray-400">
6170
{row.preRangeBadge}

src/components/Configs/Changes/Swimlane/TimeAxisHeader.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { calcPercent } from "./Utils";
55
export function TimeAxisHeader({
66
columnWidth,
77
markersRef,
8+
timelineOffsetWidth = 0,
89
ticks,
910
min,
1011
max
1112
}: {
1213
columnWidth: number;
1314
markersRef: React.MutableRefObject<HTMLDivElement | null>;
15+
timelineOffsetWidth?: number;
1416
ticks: number[];
1517
min: number;
1618
max: number;
@@ -20,6 +22,7 @@ export function TimeAxisHeader({
2022
<div className="shrink-0" style={{ width: columnWidth }} />
2123
<div
2224
className="relative flex-1 border-l border-gray-200 px-2 py-1"
25+
style={{ paddingLeft: timelineOffsetWidth }}
2326
ref={(el) => {
2427
markersRef.current = el;
2528
}}

0 commit comments

Comments
 (0)