Skip to content

Commit a2e1e18

Browse files
committed
feat: Add ability to disable data sources (fixes #1545)
- Add 'disabled' field to source schema (common-utils, api, app) - Move enable/disable toggle to expanded form header (top right) - Dim disabled sources in list view (50% opacity when closed) - Show full opacity when form is open for editing - Filter out disabled sources in search page, dashboards, sessions - Add changeset for release management - Smooth transition effects for visual feedback Based on PR feedback, form simplification will be addressed separately.
1 parent ebaebc1 commit a2e1e18

File tree

11 files changed

+122
-33
lines changed

11 files changed

+122
-33
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@hyperdx/common-utils": patch
3+
"@hyperdx/api": patch
4+
"@hyperdx/app": patch
5+
---
6+
7+
Add ability to disable data sources with improved UX
8+

packages/api/src/models/source.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export const Source = mongoose.model<ISource>(
4040
},
4141

4242
name: String,
43+
disabled: {
44+
type: Boolean,
45+
default: false,
46+
},
4347
displayedTimestampValueExpression: String,
4448
implicitColumnExpression: String,
4549
serviceNameExpression: String,

packages/app/src/DBSearchPage.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,22 @@ type SearchConfigFromSchema = z.infer<typeof SearchConfigSchema>;
171171

172172
// Helper function to get the default source id
173173
export function getDefaultSourceId(
174-
sources: { id: string }[] | undefined,
174+
sources: { id: string; disabled?: boolean }[] | undefined,
175175
lastSelectedSourceId: string | undefined,
176176
): string {
177177
if (!sources || sources.length === 0) return '';
178+
179+
// Filter out disabled sources
180+
const enabledSources = sources.filter(s => !s.disabled);
181+
if (enabledSources.length === 0) return '';
182+
178183
if (
179184
lastSelectedSourceId &&
180-
sources.some(s => s.id === lastSelectedSourceId)
185+
enabledSources.some(s => s.id === lastSelectedSourceId)
181186
) {
182187
return lastSelectedSourceId;
183188
}
184-
return sources[0].id;
189+
return enabledSources[0].id;
185190
}
186191

187192
function SourceEditMenu({

packages/app/src/KubernetesDashboardPage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,8 @@ const findSource = (
944944
s =>
945945
(kind === undefined || s.kind === kind) &&
946946
(id === undefined || s.id === id) &&
947-
(connection === undefined || s.connection === connection),
947+
(connection === undefined || s.connection === connection) &&
948+
!s.disabled,
948949
);
949950
};
950951

@@ -989,7 +990,8 @@ export const resolveSourceIds = (
989990
s =>
990991
s.kind === SourceKind.Log &&
991992
s.metricSourceId &&
992-
findSource(sources, { id: s.metricSourceId }),
993+
findSource(sources, { id: s.metricSourceId }) &&
994+
!s.disabled,
993995
);
994996

995997
if (logSourceWithMetricSource) {

packages/app/src/ServicesDashboardPage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1414,7 +1414,9 @@ function ServicesDashboardPage() {
14141414
const appliedConfigWithoutFilters = useMemo(() => {
14151415
if (!sources?.length) return appliedConfigParams;
14161416

1417-
const traceSources = sources?.filter(s => s.kind === SourceKind.Trace);
1417+
const traceSources = sources?.filter(
1418+
s => s.kind === SourceKind.Trace && !s.disabled,
1419+
);
14181420
const paramsSourceIdIsTraceSource = traceSources?.find(
14191421
s => s.id === appliedConfigParams.source,
14201422
);

packages/app/src/SessionsPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,9 @@ export default function SessionsPage() {
257257
// Auto-select the first session source when the page loads
258258
useEffect(() => {
259259
if (sources && sources.length > 0 && !appliedConfig.sessionSource) {
260-
// Find the first session source
260+
// Find the first enabled session source
261261
const sessionSource = sources.find(
262-
source => source.kind === SourceKind.Session,
262+
source => source.kind === SourceKind.Session && !source.disabled,
263263
);
264264
if (sessionSource) {
265265
setValue('source', sessionSource.id);

packages/app/src/components/SourceSelect.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ function SourceSelectControlledComponent({
6666
data
6767
?.filter(
6868
source =>
69-
!allowedSourceKinds || allowedSourceKinds.includes(source.kind),
69+
(!allowedSourceKinds || allowedSourceKinds.includes(source.kind)) &&
70+
!source.disabled,
7071
)
7172
.map(d => ({
7273
value: d.id,

packages/app/src/components/Sources/SourceForm.tsx

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
Select,
3131
Slider,
3232
Stack,
33+
Switch,
3334
Text,
3435
Tooltip,
3536
} from '@mantine/core';
@@ -1453,6 +1454,37 @@ export function TableSourceForm({
14531454
}) {
14541455
const { data: source } = useSource({ id: sourceId });
14551456
const { data: connections } = useConnections();
1457+
const updateSourceMutation = useUpdateSource();
1458+
1459+
const handleDisabledToggle = useCallback(
1460+
(newDisabledValue: boolean) => {
1461+
if (!source || isNew) return;
1462+
1463+
updateSourceMutation.mutate(
1464+
{
1465+
source: {
1466+
...source,
1467+
disabled: newDisabledValue,
1468+
},
1469+
},
1470+
{
1471+
onSuccess: () => {
1472+
notifications.show({
1473+
color: 'green',
1474+
message: `Source ${newDisabledValue ? 'disabled' : 'enabled'} successfully`,
1475+
});
1476+
},
1477+
onError: error => {
1478+
notifications.show({
1479+
color: 'red',
1480+
message: `Failed to ${newDisabledValue ? 'disable' : 'enable'} source - ${error.message}`,
1481+
});
1482+
},
1483+
},
1484+
);
1485+
},
1486+
[source, isNew, updateSourceMutation],
1487+
);
14561488

14571489
const { control, setValue, handleSubmit, resetField, setError, clearErrors } =
14581490
useForm<TSourceUnion>({
@@ -1824,7 +1856,27 @@ export function TableSourceForm({
18241856
}
18251857
>
18261858
<Stack gap="md" mb="md">
1827-
<Text mb="lg">Source Settings</Text>
1859+
<Flex justify="space-between" align="center" mb="lg">
1860+
<Text>Source Settings</Text>
1861+
{!isNew && (
1862+
<Controller
1863+
control={control}
1864+
name="disabled"
1865+
render={({ field: { value, onChange } }) => (
1866+
<Switch
1867+
size="sm"
1868+
checked={!value}
1869+
onChange={(event) => {
1870+
const newDisabledValue = !event.currentTarget.checked;
1871+
onChange(newDisabledValue);
1872+
handleDisabledToggle(newDisabledValue);
1873+
}}
1874+
label={value ? 'Disabled' : 'Enabled'}
1875+
/>
1876+
)}
1877+
/>
1878+
)}
1879+
</Flex>
18281880
<FormRow label={'Name'}>
18291881
<InputControlled
18301882
control={control}

packages/app/src/components/Sources/SourcesList.tsx

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -156,30 +156,43 @@ export function SourcesList({
156156

157157
{sources?.map((s, index) => (
158158
<React.Fragment key={s.id}>
159-
<Flex justify="space-between" align="center">
160-
<div>
161-
<Text size={textSize} fw={500}>
162-
{s.name}
163-
</Text>
164-
<Text size={subtextSize} c="dimmed" mt={4}>
165-
<Group gap="xs">
166-
{capitalizeFirstLetter(s.kind)}
167-
<Group gap={4}>
168-
<IconServer size={iconSize} />
169-
{connections?.find(c => c.id === s.connection)?.name}
170-
</Group>
171-
<Group gap={4}>
172-
{s.from && (
173-
<>
174-
<IconStack size={iconSize} />
175-
{s.from.databaseName}
176-
{s.kind === SourceKind.Metric ? '' : '.'}
177-
{s.from.tableName}
178-
</>
179-
)}
159+
<Flex
160+
justify="space-between"
161+
align="center"
162+
opacity={s.disabled ? 0.5 : 1}
163+
style={{
164+
transition: 'opacity 0.2s ease',
165+
}}
166+
>
167+
<div style={{ flex: 1 }}>
168+
<Flex align="center" gap="sm">
169+
<div>
170+
<Group gap="xs" align="center">
171+
<Text size={textSize} fw={500}>
172+
{s.name}
173+
</Text>
180174
</Group>
181-
</Group>
182-
</Text>
175+
<Text size={subtextSize} c="dimmed" mt={4}>
176+
<Group gap="xs">
177+
{capitalizeFirstLetter(s.kind)}
178+
<Group gap={4}>
179+
<IconServer size={iconSize} />
180+
{connections?.find(c => c.id === s.connection)?.name}
181+
</Group>
182+
<Group gap={4}>
183+
{s.from && (
184+
<>
185+
<IconStack size={iconSize} />
186+
{s.from.databaseName}
187+
{s.kind === SourceKind.Metric ? '' : '.'}
188+
{s.from.tableName}
189+
</>
190+
)}
191+
</Group>
192+
</Group>
193+
</Text>
194+
</div>
195+
</Flex>
183196
</div>
184197
<ActionIcon
185198
variant="secondary"

packages/app/src/source.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export function useUpdateSource() {
144144
return s;
145145
});
146146
});
147+
return source;
147148
} else {
148149
return await hdxServer(`sources/${source.id}`, {
149150
method: 'PUT',

0 commit comments

Comments
 (0)