Skip to content

Commit c8cc8e5

Browse files
authored
feat: Include alert tags in the tags API response (#3092)
## Summary This PR updates the team/tags endpoint to return tags from Alert documents, in addition to Dashboards and Saved Searches (existing behavior). Now that Alerts can be tagged independently of dashboards and saved searches, this is necessary so that if a tag is use exclusively by an alert, that tag appears in the suggested tags for other alerts. There is no existing index on tags in Dashboards or Saved Searches, so this update is consistent with that for alerts. ### Screenshots or video <img width="289" height="364" alt="Screenshot 2026-09-08 at 9 50 51 AM" src="https://github.com/user-attachments/assets/211e67aa-6c04-431d-ba32-abdbb36dcc82" /> ### How to test locally 1. Create an alert and add a new tag to it 2. Create another alert and notice that the tag added to the first alert is present in the tag dropdown ### References - Linear Issue: Closes HDX-5287 - Related PRs:
1 parent 78a33ba commit c8cc8e5

3 files changed

Lines changed: 47 additions & 18 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/api": patch
3+
---
4+
5+
feat: Include alert tags in the tags API response

‎packages/api/src/controllers/team.ts‎

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
44

55
import * as config from '@/config';
66
import type { ObjectId } from '@/models';
7+
import Alert from '@/models/alert';
78
import Dashboard from '@/models/dashboard';
89
import { SavedSearch } from '@/models/savedSearch';
910
import Team, { type ITeam, type TeamDocument } from '@/models/team';
@@ -120,23 +121,17 @@ export function updateTeamClickhouseSettings(
120121
}
121122

122123
export async function getTags(teamId: ObjectId) {
123-
const [dashboardTags, savedSearchTags] = await Promise.all([
124-
Dashboard.aggregate([
125-
{ $match: { team: teamId } },
126-
{ $unwind: '$tags' },
127-
{ $group: { _id: '$tags' } },
128-
]),
129-
SavedSearch.aggregate([
130-
{ $match: { team: teamId } },
131-
{ $unwind: '$tags' },
132-
{ $group: { _id: '$tags' } },
133-
]),
124+
const distinctTagsPipeline: mongoose.PipelineStage[] = [
125+
{ $match: { team: teamId } },
126+
{ $unwind: '$tags' },
127+
{ $group: { _id: '$tags' } },
128+
];
129+
130+
const tagGroups = await Promise.all([
131+
Dashboard.aggregate<{ _id: string }>(distinctTagsPipeline),
132+
SavedSearch.aggregate<{ _id: string }>(distinctTagsPipeline),
133+
Alert.aggregate<{ _id: string }>(distinctTagsPipeline),
134134
]);
135135

136-
return [
137-
...new Set([
138-
...dashboardTags.map(t => t._id),
139-
...savedSearchTags.map(t => t._id),
140-
]),
141-
];
136+
return [...new Set(tagGroups.flat().map(t => t._id))];
142137
}

‎packages/api/src/routers/api/__tests__/team.int.test.ts‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,37 @@ describe('team router', () => {
9696
tags: ['test', 'test2'],
9797
})
9898
.expect(200);
99+
100+
await Alert.create({
101+
team: team.id,
102+
source: AlertSource.SAVED_SEARCH,
103+
savedSearch: new mongoose.Types.ObjectId(),
104+
threshold: 10,
105+
thresholdType: AlertThresholdType.ABOVE,
106+
interval: '5m',
107+
channel: {
108+
type: 'webhook',
109+
webhookId: new mongoose.Types.ObjectId().toString(),
110+
},
111+
tags: ['test2', 'test3'],
112+
});
113+
114+
await Alert.create({
115+
team: new mongoose.Types.ObjectId(),
116+
source: AlertSource.SAVED_SEARCH,
117+
savedSearch: new mongoose.Types.ObjectId(),
118+
threshold: 10,
119+
thresholdType: AlertThresholdType.ABOVE,
120+
interval: '5m',
121+
channel: {
122+
type: 'webhook',
123+
webhookId: new mongoose.Types.ObjectId().toString(),
124+
},
125+
tags: ['other-team'],
126+
});
127+
99128
const resp = await agent.get('/team/tags').expect(200);
100-
expect(resp.body.data).toStrictEqual(['test', 'test2']);
129+
expect(resp.body.data.sort()).toStrictEqual(['test', 'test2', 'test3']);
101130
});
102131

103132
it('GET /team/members', async () => {

0 commit comments

Comments
 (0)