Skip to content

Commit 1dc1c82

Browse files
authored
feat: team setting to disable fetching field metadata (#1034)
Closes HDX-2068 Allows a team to disable fetching field metadata across the app. Fetching is enabled by default. Here's the setting: <img width="1115" height="346" alt="image" src="https://github.com/user-attachments/assets/100e5ae9-6946-4215-a418-295294154452" /> And here's what it looks like when the setting is disabled: <img width="1627" height="829" alt="image" src="https://github.com/user-attachments/assets/0d020901-4b68-4239-baf3-f1c040074400" /> Notice that autocomplete is doing nothing and filters are not loaded
1 parent eed38e8 commit 1dc1c82

File tree

8 files changed

+239
-72
lines changed

8 files changed

+239
-72
lines changed

.changeset/blue-months-switch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hyperdx/api": patch
3+
"@hyperdx/app": patch
4+
---
5+
6+
feat: add team setting to disable field metadata queries in app

packages/api/src/controllers/team.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ export function setTeamSearchRowLimit(
7878
return Team.findByIdAndUpdate(teamId, { searchRowLimit }, { new: true });
7979
}
8080

81+
export function setTeamFieldMetadataDisabled(
82+
teamId: ObjectId,
83+
fieldMetadataDisabled: boolean,
84+
) {
85+
return Team.findByIdAndUpdate(
86+
teamId,
87+
{ fieldMetadataDisabled },
88+
{ new: true },
89+
);
90+
}
91+
8192
export async function getTags(teamId: ObjectId) {
8293
const [dashboardTags, savedSearchTags] = await Promise.all([
8394
Dashboard.aggregate([

packages/api/src/models/team.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface ITeam {
77
_id: ObjectId;
88
name: string;
99
searchRowLimit?: number;
10+
fieldMetadataDisabled?: boolean;
1011
allowedAuthMethods?: 'password'[];
1112
apiKey: string;
1213
hookId: string;
@@ -20,6 +21,7 @@ export default mongoose.model<ITeam>(
2021
{
2122
name: String,
2223
searchRowLimit: Number,
24+
fieldMetadataDisabled: Boolean,
2325
allowedAuthMethods: [String],
2426
hookId: {
2527
type: String,

packages/api/src/routers/api/team.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getTags,
1010
getTeam,
1111
rotateTeamApiKey,
12+
setTeamFieldMetadataDisabled,
1213
setTeamName,
1314
setTeamSearchRowLimit,
1415
} from '@/controllers/team';
@@ -110,6 +111,31 @@ router.patch(
110111
},
111112
);
112113

114+
router.patch(
115+
'/field-metadata',
116+
validateRequest({
117+
body: z.object({
118+
fieldMetadataDisabled: z.boolean(),
119+
}),
120+
}),
121+
async (req, res, next) => {
122+
try {
123+
const teamId = req.user?.team;
124+
if (teamId == null) {
125+
throw new Error(`User ${req.user?._id} not associated with a team`);
126+
}
127+
const { fieldMetadataDisabled } = req.body;
128+
const team = await setTeamFieldMetadataDisabled(
129+
teamId,
130+
fieldMetadataDisabled,
131+
);
132+
res.json({ fieldMetadataDisabled: team?.fieldMetadataDisabled });
133+
} catch (e) {
134+
next(e);
135+
}
136+
},
137+
);
138+
113139
router.post(
114140
'/invitation',
115141
validateRequest({

packages/app/src/TeamPage.tsx

Lines changed: 180 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ import {
3030
} from '@mantine/core';
3131
import { useDisclosure } from '@mantine/hooks';
3232
import { notifications } from '@mantine/notifications';
33+
import { UseQueryResult } from '@tanstack/react-query';
3334
import CodeMirror, { placeholder } from '@uiw/react-codemirror';
3435

3536
import { ConnectionForm } from '@/components/ConnectionForm';
37+
import SelectControlled from '@/components/SelectControlled';
3638
import { TableSourceForm } from '@/components/SourceForm';
3739
import { IS_LOCAL_MODE } from '@/config';
3840

@@ -1060,11 +1062,11 @@ function TeamNameSection() {
10601062
);
10611063
}
10621064

1063-
function TeamQueryConfigSection() {
1064-
const setSearchRowLimit = api.useSetTeamSearchRowLimit();
1065+
function SearchRowLimitForm() {
10651066
const { data: me, refetch: refetchMe } = api.useMe();
1067+
const setSearchRowLimit = api.useSetTeamSearchRowLimit();
10661068
const hasAdminAccess = true;
1067-
const [isEditingQueryLimits, setIsEditingQueryLimits] = useState(false);
1069+
const [isEditing, setIsEditing] = useState(false);
10681070
const searchRowLimit = me?.team.searchRowLimit ?? DEFAULT_SEARCH_ROW_LIMIT;
10691071
const form = useForm<{ searchRowLimit: number }>({
10701072
defaultValues: {
@@ -1090,7 +1092,7 @@ function TeamQueryConfigSection() {
10901092
message: 'Updated Search Row Limit',
10911093
});
10921094
refetchMe();
1093-
setIsEditingQueryLimits(false);
1095+
setIsEditing(false);
10941096
},
10951097
},
10961098
);
@@ -1104,6 +1106,178 @@ function TeamQueryConfigSection() {
11041106
[refetchMe, setSearchRowLimit, me?.team?.searchRowLimit],
11051107
);
11061108

1109+
return (
1110+
<Stack gap="xs" mb="md">
1111+
<InputLabel c="gray.3" size="md">
1112+
Search Row Limit
1113+
</InputLabel>
1114+
{isEditing && hasAdminAccess ? (
1115+
<form onSubmit={form.handleSubmit(onSubmit)}>
1116+
<Group>
1117+
<TextInput
1118+
size="xs"
1119+
type="number"
1120+
placeholder={searchRowLimit}
1121+
required
1122+
readOnly={!isEditing}
1123+
error={form.formState.errors.searchRowLimit?.message}
1124+
{...form.register('searchRowLimit', {
1125+
required: true,
1126+
})}
1127+
miw={300}
1128+
min={1}
1129+
max={100000}
1130+
autoFocus
1131+
onKeyDown={e => {
1132+
if (e.key === 'Escape') {
1133+
setIsEditing(false);
1134+
}
1135+
}}
1136+
/>
1137+
<Button
1138+
type="submit"
1139+
size="xs"
1140+
variant="light"
1141+
color="green"
1142+
loading={setSearchRowLimit.isPending}
1143+
>
1144+
Save
1145+
</Button>
1146+
<Button
1147+
type="button"
1148+
size="xs"
1149+
variant="default"
1150+
disabled={setSearchRowLimit.isPending}
1151+
onClick={() => {
1152+
setIsEditing(false);
1153+
}}
1154+
>
1155+
Cancel
1156+
</Button>
1157+
</Group>
1158+
</form>
1159+
) : (
1160+
<Group>
1161+
<Text className="text-white">{searchRowLimit}</Text>
1162+
{hasAdminAccess && (
1163+
<Button
1164+
size="xs"
1165+
variant="default"
1166+
leftSection={<i className="bi bi-pencil text-slate-300" />}
1167+
onClick={() => setIsEditing(true)}
1168+
>
1169+
Change
1170+
</Button>
1171+
)}
1172+
</Group>
1173+
)}
1174+
</Stack>
1175+
);
1176+
}
1177+
1178+
function FieldMetadataForm() {
1179+
const { data: me, refetch: refetchMe } = api.useMe();
1180+
const setFieldMetadataDisabled = api.useSetFieldMetadataDisabled();
1181+
const hasAdminAccess = true;
1182+
const [isEditing, setIsEditing] = useState(false);
1183+
const fieldMetadataDisabled = me?.team.fieldMetadataDisabled ?? false;
1184+
const fieldMetadata = fieldMetadataDisabled ? 'Disabled' : 'Enabled';
1185+
const form = useForm<{ fieldMetadata: string }>({
1186+
defaultValues: {
1187+
fieldMetadata: fieldMetadata,
1188+
},
1189+
});
1190+
1191+
const onSubmit: SubmitHandler<{ fieldMetadata: string }> = useCallback(
1192+
async values => {
1193+
try {
1194+
setFieldMetadataDisabled.mutate(
1195+
{
1196+
fieldMetadataDisabled: values.fieldMetadata === 'Disabled',
1197+
},
1198+
{
1199+
onError: e => {
1200+
notifications.show({
1201+
color: 'red',
1202+
message: 'Failed to update Field Metadata Queries',
1203+
});
1204+
},
1205+
onSuccess: () => {
1206+
notifications.show({
1207+
color: 'green',
1208+
message: `${values.fieldMetadata} Field Metadata queries`,
1209+
});
1210+
refetchMe();
1211+
setIsEditing(false);
1212+
},
1213+
},
1214+
);
1215+
} catch (e) {
1216+
notifications.show({
1217+
color: 'red',
1218+
message: e.message,
1219+
});
1220+
}
1221+
},
1222+
[refetchMe, setFieldMetadataDisabled, me?.team?.searchRowLimit],
1223+
);
1224+
1225+
return (
1226+
<Stack gap="xs">
1227+
<InputLabel c="gray.3" size="md">
1228+
Field Metadata Queries
1229+
</InputLabel>
1230+
{isEditing && hasAdminAccess ? (
1231+
<form onSubmit={form.handleSubmit(onSubmit)}>
1232+
<Group>
1233+
<SelectControlled
1234+
control={form.control}
1235+
name="fieldMetadata"
1236+
value={fieldMetadata}
1237+
data={['Enabled', 'Disabled']}
1238+
/>
1239+
<Button
1240+
type="submit"
1241+
size="xs"
1242+
variant="light"
1243+
color="green"
1244+
loading={setFieldMetadataDisabled.isPending}
1245+
>
1246+
Save
1247+
</Button>
1248+
<Button
1249+
type="button"
1250+
size="xs"
1251+
variant="default"
1252+
disabled={setFieldMetadataDisabled.isPending}
1253+
onClick={() => {
1254+
setIsEditing(false);
1255+
}}
1256+
>
1257+
Cancel
1258+
</Button>
1259+
</Group>
1260+
</form>
1261+
) : (
1262+
<Group>
1263+
<Text className="text-white">{fieldMetadata}</Text>
1264+
{hasAdminAccess && (
1265+
<Button
1266+
size="xs"
1267+
variant="default"
1268+
leftSection={<i className="bi bi-pencil text-slate-300" />}
1269+
onClick={() => setIsEditing(true)}
1270+
>
1271+
Change
1272+
</Button>
1273+
)}
1274+
</Group>
1275+
)}
1276+
</Stack>
1277+
);
1278+
}
1279+
1280+
function TeamQueryConfigSection() {
11071281
return (
11081282
<Box id="team_name">
11091283
<Text size="md" c="gray.4">
@@ -1112,69 +1286,8 @@ function TeamQueryConfigSection() {
11121286
<Divider my="md" />
11131287
<Card>
11141288
<Stack>
1115-
<InputLabel c="gray.3" size="md">
1116-
Search Row Limit
1117-
</InputLabel>
1118-
{isEditingQueryLimits && hasAdminAccess ? (
1119-
<form onSubmit={form.handleSubmit(onSubmit)}>
1120-
<Group>
1121-
<TextInput
1122-
size="xs"
1123-
type="number"
1124-
placeholder={searchRowLimit}
1125-
required
1126-
readOnly={!isEditingQueryLimits}
1127-
error={form.formState.errors.searchRowLimit?.message}
1128-
{...form.register('searchRowLimit', {
1129-
required: true,
1130-
})}
1131-
miw={300}
1132-
min={1}
1133-
max={100000}
1134-
autoFocus
1135-
onKeyDown={e => {
1136-
if (e.key === 'Escape') {
1137-
setIsEditingQueryLimits(false);
1138-
}
1139-
}}
1140-
/>
1141-
<Button
1142-
type="submit"
1143-
size="xs"
1144-
variant="light"
1145-
color="green"
1146-
loading={setSearchRowLimit.isPending}
1147-
>
1148-
Save
1149-
</Button>
1150-
<Button
1151-
type="button"
1152-
size="xs"
1153-
variant="default"
1154-
disabled={setSearchRowLimit.isPending}
1155-
onClick={() => {
1156-
setIsEditingQueryLimits(false);
1157-
}}
1158-
>
1159-
Cancel
1160-
</Button>
1161-
</Group>
1162-
</form>
1163-
) : (
1164-
<Group>
1165-
<Text className="text-white">{searchRowLimit}</Text>
1166-
{hasAdminAccess && (
1167-
<Button
1168-
size="xs"
1169-
variant="default"
1170-
leftSection={<i className="bi bi-pencil text-slate-300" />}
1171-
onClick={() => setIsEditingQueryLimits(true)}
1172-
>
1173-
Change
1174-
</Button>
1175-
)}
1176-
</Group>
1177-
)}
1289+
<SearchRowLimitForm />
1290+
<FieldMetadataForm />
11781291
</Stack>
11791292
</Card>
11801293
</Box>

packages/app/src/api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,15 @@ const api = {
271271
}).json(),
272272
});
273273
},
274+
useSetFieldMetadataDisabled() {
275+
return useMutation<any, HTTPError, { fieldMetadataDisabled: boolean }>({
276+
mutationFn: async ({ fieldMetadataDisabled }) =>
277+
hdxServer(`team/field-metadata`, {
278+
method: 'PATCH',
279+
json: { fieldMetadataDisabled },
280+
}).json(),
281+
});
282+
},
274283
useTags() {
275284
return useQuery({
276285
queryKey: [`team/tags`],

packages/app/src/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ export const IS_OSS = process.env.NEXT_PUBLIC_IS_OSS ?? 'true' === 'true';
2727
export const IS_LOCAL_MODE = //true;
2828
// @ts-ignore
2929
(process.env.NEXT_PUBLIC_IS_LOCAL_MODE ?? 'false') === 'true';
30-
export const IS_METADATA_FIELD_FETCH_DISABLED =
31-
HDX_DISABLE_METADATA_FIELD_FETCH === 'true';
3230

3331
// Features in development
3432
export const IS_K8S_DASHBOARD_ENABLED = true;

0 commit comments

Comments
 (0)