diff --git a/.changeset/tidy-invites-share.md b/.changeset/tidy-invites-share.md new file mode 100644 index 0000000000..dad537f8cc --- /dev/null +++ b/.changeset/tidy-invites-share.md @@ -0,0 +1,5 @@ +--- +'@hyperdx/api': patch +--- + +Apply configured default connections and sources when a user accepts a team invite. diff --git a/packages/api/src/controllers/__tests__/team.int.test.ts b/packages/api/src/controllers/__tests__/team.int.test.ts index 1330ae83ba..3856de9377 100644 --- a/packages/api/src/controllers/__tests__/team.int.test.ts +++ b/packages/api/src/controllers/__tests__/team.int.test.ts @@ -6,6 +6,7 @@ import { getTeamInviteUrl, } from '@/controllers/team'; import { clearDBCollections, closeDB, connectDB } from '@/fixtures'; +import Team from '@/models/team'; describe('getTeamInviteUrl', () => { it('builds the join-team URL for a token', () => { @@ -37,7 +38,12 @@ describe('team controller', () => { await team.save(); + const otherTeam = await Team.create({ name: 'Other Team' }); + expect(await getTeam(team._id)).toBeTruthy(); + expect((await getTeam(otherTeam._id))?._id.toString()).toBe( + otherTeam._id.toString(), + ); expect(await getTeamByApiKey('apiKey')).toBeTruthy(); }); }); diff --git a/packages/api/src/controllers/team.ts b/packages/api/src/controllers/team.ts index 965cbf40d9..bc3617f4da 100644 --- a/packages/api/src/controllers/team.ts +++ b/packages/api/src/controllers/team.ts @@ -78,7 +78,7 @@ export function getTeam(id: string | ObjectId, fields?: readonly string[]) { return LOCAL_APP_TEAM as any; } - return Team.findOne({}, fields); + return Team.findById(id, fields); } export function getTeamByApiKey(apiKey: string) { diff --git a/packages/api/src/routers/api/__tests__/team.int.test.ts b/packages/api/src/routers/api/__tests__/team.int.test.ts index c00a530ca1..f2b6c89bc1 100644 --- a/packages/api/src/routers/api/__tests__/team.int.test.ts +++ b/packages/api/src/routers/api/__tests__/team.int.test.ts @@ -2,11 +2,12 @@ import _ from 'lodash'; import { ObjectId } from 'mongodb'; import mongoose from 'mongoose'; -import { getLoggedInAgent, getServer } from '@/fixtures'; +import { getAgent, getLoggedInAgent, getServer } from '@/fixtures'; import Alert, { AlertSource, AlertThresholdType } from '@/models/alert'; import Team from '@/models/team'; import TeamInvite from '@/models/teamInvite'; import User from '@/models/user'; +import * as setupDefaults from '@/setupDefaults'; describe('team router', () => { const server = getServer(); @@ -16,6 +17,7 @@ describe('team router', () => { }); afterEach(async () => { + jest.restoreAllMocks(); await server.clearDBs(); }); @@ -153,6 +155,26 @@ describe('team router', () => { expect(resp.body.url).toContain(`/join-team?token=${teamInvite.token}`); }); + it('POST /team/setup/:token provisions defaults for the invited team', async () => { + const { team } = await getLoggedInAgent(server); + const invite = await TeamInvite.create({ + email: 'invited@example.com', + name: 'Invited User', + teamId: team._id, + token: 'invite_token', + }); + const setupTeamDefaults = jest + .spyOn(setupDefaults, 'setupTeamDefaults') + .mockResolvedValue(); + + await getAgent(server) + .post(`/team/setup/${invite.token}`) + .send({ password: 'TacoCat!2#4X' }) + .expect(303); + + expect(setupTeamDefaults).toHaveBeenCalledWith(team._id.toString()); + }); + it('POST /team/invitation with different case reuses existing invitation', async () => { const { agent } = await getLoggedInAgent(server); diff --git a/packages/api/src/routers/api/root.ts b/packages/api/src/routers/api/root.ts index 220ba6f27b..04a3efb52b 100644 --- a/packages/api/src/routers/api/root.ts +++ b/packages/api/src/routers/api/root.ts @@ -10,6 +10,10 @@ import { handleAuthError, redirectToDashboard } from '@/middleware/auth'; import TeamInvite from '@/models/teamInvite'; import User from '@/models/user'; // TODO -> do not import model directly import { setupTeamDefaults } from '@/setupDefaults'; +import { + setBusinessContext, + withOperationMetrics, +} from '@/utils/instrumentation'; import logger from '@/utils/logger'; import passport from '@/utils/passport'; import { isMongoConnected, mongoReadyStateName } from '@/utils/readiness'; @@ -188,6 +192,24 @@ router.post('/team/setup/:token', async (req, res, next) => { ); } + const teamId = teamInvite.teamId.toString(); + setBusinessContext({ + teamId, + userId: user._id.toString(), + email: user.email, + }); + + try { + await withOperationMetrics('team_defaults.setup', () => + setupTeamDefaults(teamId), + ); + } catch (error) { + logger.error( + { err: serializeError(error) }, + 'Failed to setup team defaults', + ); + } + await TeamInvite.findByIdAndRemove(teamInvite._id); req.login(user, err => {