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
6 changes: 6 additions & 0 deletions .changeset/quick-poems-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@hyperdx/api": patch
"@hyperdx/app": patch
---

feat(alerts): include tileId in Slack alert URLs
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@ describe('checkAlerts', () => {
expect(
buildAlertMessageTemplateHdxLink(alertProvider, defaultChartView),
).toMatchInlineSnapshot(
`"http://app:8080/dashboards/id-123?from=1679089083103&granularity=5+minute&to=1679093339103"`,
`"http://app:8080/dashboards/id-123?from=1679089083103&granularity=5+minute&to=1679093339103&highlightedTileId=test-tile-id"`,
);
});

Expand Down Expand Up @@ -2573,7 +2573,7 @@ describe('checkAlerts', () => {
{
text: {
text: [
`*<http://app:8080/dashboards/${dashboard._id}?from=1700170200000&granularity=5+minute&to=1700174700000 | 🚨 Alert for "Logs Count" in "My Dashboard" - 3 meets or exceeds 1>*`,
`*<http://app:8080/dashboards/${dashboard._id}?from=1700170200000&granularity=5+minute&to=1700174700000&highlightedTileId=17quud | 🚨 Alert for "Logs Count" in "My Dashboard" - 3 meets or exceeds 1>*`,
'',
'3 meets or exceeds 1',
'Time Range (UTC): [Nov 16 10:05:00 PM - Nov 16 10:10:00 PM)',
Expand Down Expand Up @@ -3540,7 +3540,7 @@ describe('checkAlerts', () => {
expect(fetchMock).toHaveBeenCalledWith('https://webhook.site/123', {
method: 'POST',
body: JSON.stringify({
text: `http://app:8080/dashboards/${dashboard.id}?from=1700170200000&granularity=5+minute&to=1700174700000 | 🚨 Alert for "Logs Count" in "My Dashboard" - 3 meets or exceeds 1`,
text: `http://app:8080/dashboards/${dashboard.id}?from=1700170200000&granularity=5+minute&to=1700174700000&highlightedTileId=17quud | 🚨 Alert for "Logs Count" in "My Dashboard" - 3 meets or exceeds 1`,
}),
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -5340,7 +5340,7 @@ describe('checkAlerts', () => {
{
text: {
text: [
`*<http://app:8080/dashboards/${dashboard._id}?from=1700170200000&granularity=5+minute&to=1700174700000 | 🚨 Alert for "CPU" in "My Dashboard" - 6 meets or exceeds 1>*`,
`*<http://app:8080/dashboards/${dashboard._id}?from=1700170200000&granularity=5+minute&to=1700174700000&highlightedTileId=17quud | 🚨 Alert for "CPU" in "My Dashboard" - 6 meets or exceeds 1>*`,
'',
'6 meets or exceeds 1',
'Time Range (UTC): [Nov 16 10:05:00 PM - Nov 16 10:10:00 PM)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,44 @@ describe('DefaultAlertProvider', () => {
expect(params.has('granularity')).toBe(true);
});

it('should include highlightedTileId when tileId is provided', () => {
const result = provider.buildChartLink({
dashboardId: 'dashboard-123',
startTime: new Date('2023-03-17T22:13:03.103Z'),
endTime: new Date('2023-03-17T22:13:59.103Z'),
granularity: '5m',
tileId: 'tile-abc-789',
});

const url = new URL(result);
expect(url.searchParams.get('highlightedTileId')).toBe('tile-abc-789');
});

it('should omit highlightedTileId when tileId is undefined', () => {
const result = provider.buildChartLink({
dashboardId: 'dashboard-123',
startTime: new Date('2023-03-17T22:13:03.103Z'),
endTime: new Date('2023-03-17T22:13:59.103Z'),
granularity: '5m',
});

const url = new URL(result);
expect(url.searchParams.has('highlightedTileId')).toBe(false);
});

it('should omit highlightedTileId when tileId is an empty string', () => {
const result = provider.buildChartLink({
dashboardId: 'dashboard-123',
startTime: new Date('2023-03-17T22:13:03.103Z'),
endTime: new Date('2023-03-17T22:13:59.103Z'),
granularity: '5m',
tileId: '',
});

const url = new URL(result);
expect(url.searchParams.has('highlightedTileId')).toBe(false);
});

it('should handle very close dates', () => {
const startTime = new Date('2023-03-17T22:13:03.103Z');
const endTime = new Date('2023-03-17T22:13:03.104Z'); // 1ms difference
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/tasks/checkAlerts/providers/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,13 @@ export default class DefaultAlertProvider implements AlertProvider {
endTime,
granularity,
startTime,
tileId,
}: {
dashboardId: string;
endTime: Date;
granularity: string;
startTime: Date;
tileId?: string;
}): string {
const url = new URL(`${config.FRONTEND_URL}/dashboards/${dashboardId}`);
// extend both start and end time by 7x granularity
Expand All @@ -333,6 +335,9 @@ export default class DefaultAlertProvider implements AlertProvider {
granularity: convertMsToGranularityString(ms(granularity)),
to,
});
if (tileId) {
queryParams.set('highlightedTileId', tileId);
}
url.search = queryParams.toString();
return url.toString();
}
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/tasks/checkAlerts/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface AlertProvider {
endTime: Date;
granularity: string;
startTime: Date;
tileId?: string;
}): string;

/**
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/tasks/checkAlerts/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ export const buildAlertMessageTemplateHdxLink = (
endTime,
granularity,
startTime,
tileId: alert.tileId,
});
}

Expand Down
26 changes: 26 additions & 0 deletions packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,32 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
[setUrlActiveTabs],
);

// When arriving via ?highlightedTileId, switch to the tile's tab if it
// isn't already active so the tile is in the DOM for the Tile-level
// scroll/highlight effect to take effect. Guard with a ref keyed on the
// highlighted id so a user manually switching tabs afterwards doesn't
// get reverted on the next render.
const handledHighlightRef = useRef<string | null>(null);
useEffect(() => {
if (!highlightedTileId || !dashboard) return;
if (handledHighlightRef.current === highlightedTileId) return;
handledHighlightRef.current = highlightedTileId;
const tile = dashboard.tiles.find(t => t.id === highlightedTileId);
if (!tile?.containerId || !tile.tabId) return;
const container = containers.find(c => c.id === tile.containerId);
if (!container || getActiveTabId(container) === tile.tabId) return;
setUrlActiveTabs(prev => ({
...(prev ?? {}),
[container.id]: tile.tabId!,
}));
}, [
highlightedTileId,
dashboard,
containers,
getActiveTabId,
setUrlActiveTabs,
]);

// Valid move targets: groups and individual tabs within groups
const moveTargetContainers = useMemo<MoveTarget[]>(() => {
const targets: MoveTarget[] = [];
Expand Down
Loading