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
5 changes: 5 additions & 0 deletions .changeset/page-migrate-service-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/app': patch
---

chore: migrate Service Map to shared `PageLayout` with a sticky toolbar (source, sampling, time range) and no duplicate page title.
202 changes: 104 additions & 98 deletions packages/app/src/DBServiceMapPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import Head from 'next/head';
import { parseAsInteger, useQueryState } from 'nuqs';
import { useForm, useWatch } from 'react-hook-form';
import { SourceKind, TTraceSource } from '@hyperdx/common-utils/dist/types';
import { Box, Button, Group, Modal, Slider, Text } from '@mantine/core';
import { Button, Group, Modal, Slider, Text } from '@mantine/core';
import { IconConnection } from '@tabler/icons-react';

import EmptyState from '@/components/EmptyState';
import { PageLayout } from '@/components/PageLayout';
import { IS_LOCAL_MODE } from '@/config';
import { withAppNav } from '@/layout';

Expand Down Expand Up @@ -114,113 +115,118 @@ function DBServiceMapPage() {
[brandName],
);

const sourceSelect = source ? (
<SourceSelectControlled
control={control}
name="source"
size="xs"
allowedSourceKinds={[SourceKind.Trace]}
sourceSchemaPreview={
<SourceSchemaPreview source={source} variant="text" />
}
/>
) : null;

const headerActions = (
<Group gap="sm" wrap="nowrap">
<Text bg="inherit" size="sm">
Sampling {samplingLabel}
</Text>
<div style={{ minWidth: '200px' }}>
<Slider
label={null}
min={0}
max={SAMPLING_FACTORS.length - 1}
value={SAMPLING_FACTORS.findIndex(
factor => factor.value === samplingFactor,
)}
onChange={v => setSamplingFactor(SAMPLING_FACTORS[v].value)}
showLabelOnHover={false}
/>
</div>
<TimePicker
inputValue={displayedTimeInputValue}
setInputValue={setDisplayedTimeInputValue}
onSearch={onSearch}
/>
</Group>
);

if (!isLoading && !hasTraceSources) {
return (
<Box
p="sm"
className="bg-body"
style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}
>
<>
{head}
<Text size="xl" mb="md">
Service Map
</Text>
{IS_LOCAL_MODE && (
<Modal
size="xl"
opened={isCreateSourceModalOpen}
onClose={() => setIsCreateSourceModalOpen(false)}
title="Configure New Trace Source"
>
<TableSourceForm
isNew
defaultName="My Trace Source"
onCreate={() => setIsCreateSourceModalOpen(false)}
/>
</Modal>
)}
<EmptyState
style={{ flex: 1 }}
icon={<IconConnection size={32} />}
title="No trace sources configured"
description="The Service Map visualizes relationships between your services using trace data. Configure a trace source to get started."
maw={600}
>
{IS_LOCAL_MODE ? (
<Button
variant="primary"
size="sm"
mt="sm"
onClick={() => setIsCreateSourceModalOpen(true)}
>
Create Trace Source
</Button>
) : (
<Button
component="a"
href="/team"
variant="primary"
size="sm"
mt="sm"
>
Go to Team Settings
</Button>
)}
</EmptyState>
</Box>
<PageLayout
data-testid="service-map-page"
fillViewport
content={
<>
{IS_LOCAL_MODE && (
<Modal
size="xl"
opened={isCreateSourceModalOpen}
onClose={() => setIsCreateSourceModalOpen(false)}
title="Configure New Trace Source"
>
<TableSourceForm
isNew
defaultName="My Trace Source"
onCreate={() => setIsCreateSourceModalOpen(false)}
/>
</Modal>
)}
<EmptyState
style={{ flex: 1, margin: 'var(--mantine-spacing-sm)' }}
icon={<IconConnection size={32} />}
title="No trace sources configured"
description="The Service Map visualizes relationships between your services using trace data. Configure a trace source to get started."
maw={600}
>
{IS_LOCAL_MODE ? (
<Button
variant="primary"
size="sm"
mt="sm"
onClick={() => setIsCreateSourceModalOpen(true)}
>
Create Trace Source
</Button>
) : (
<Button
component="a"
href="/team"
variant="primary"
size="sm"
mt="sm"
>
Go to Team Settings
</Button>
)}
</EmptyState>
</>
}
/>
</>
);
}

return source ? (
<Box
data-testid="service-map-page"
p="sm"
className="bg-body"
style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}
>
<>
{head}
<Group mb="md" justify="space-between">
<Group>
<Text size="xl">Service Map</Text>
<SourceSelectControlled
control={control}
name="source"
size="xs"
allowedSourceKinds={[SourceKind.Trace]}
sourceSchemaPreview={
<SourceSchemaPreview source={source} variant="text" />
}
/>
</Group>
<Group justify="flex-end">
<Text bg="inherit" size="sm">
Sampling {samplingLabel}
</Text>
<div style={{ minWidth: '200px' }}>
<Slider
label={null}
min={0}
max={SAMPLING_FACTORS.length - 1}
value={SAMPLING_FACTORS.findIndex(
factor => factor.value === samplingFactor,
)}
onChange={v => setSamplingFactor(SAMPLING_FACTORS[v].value)}
showLabelOnHover={false}
/>
</div>
<TimePicker
inputValue={displayedTimeInputValue}
setInputValue={setDisplayedTimeInputValue}
onSearch={onSearch}
<PageLayout
data-testid="service-map-page"
leading={sourceSelect}
actions={headerActions}
fillViewport
content={
<ServiceMap
traceTableSource={source}
dateRange={searchedTimeRange}
samplingFactor={samplingFactor}
/>
</Group>
</Group>
<ServiceMap
traceTableSource={source}
dateRange={searchedTimeRange}
samplingFactor={samplingFactor}
}
/>
</Box>
</>
) : null;
}

Expand Down
Loading