Skip to content

Commit 30ded9f

Browse files
refactor(app): split ServicesDashboardPage into smaller sibling files (#2626)
## Summary `ServicesDashboardPage.tsx` had grown to **1,759 lines** in a single file — the page shell, three tab views, an endpoint latency chart, a service selector, and shared filter/query helpers all interleaved. This splits it into a `ServicesDashboardPage/` directory of smaller, cohesive files so the tabs and helpers can be read and edited in isolation. **This is a pure file-move refactor: no behavior, logic, JSX, or effects changed.** Code was relocated verbatim and relative imports rewired. ## Line count | | Lines | |---|---:| | Before — `ServicesDashboardPage.tsx` (monolith) | **1,759** | | After — largest file (`HttpTab.tsx`) | **699** | ## New structure ``` packages/app/src/ServicesDashboardPage/ index.ts (3) barrel — re-exports the original public API only types.ts (12) AppliedConfig type helpers.ts (78) pickSourceConfigFields, buildInFilterCondition, getScopedFilters, MAX_NUM_SERIES ServicesDashboardPage.tsx (446) page shell, ServiceSelectControlled, dynamic wrapper HttpTab.tsx (699) HttpTab + EndpointLatencyChart (colocated) DatabaseTab.tsx (517) DatabaseTab ErrorsTab.tsx (76) ErrorsTab __tests__/ServicesDashboardPage.test.tsx (171) new smoke test ``` ## Drop-in barrel `index.ts` re-exports exactly the original public API — the default page (with `getLayout`), `EndpointLatencyChart`, and `buildInFilterCondition` — so every external consumer keeps importing `@/ServicesDashboardPage` unchanged: - `pages/services.tsx` (default export) - `components/ServiceDashboardEndpointSidePanel.tsx` (`EndpointLatencyChart`) - `src/__tests__/ServicesDashboardPage.test.ts` (`buildInFilterCondition`) ## Decisions - **`EndpointLatencyChart` colocated with `HttpTab`** rather than its own file — `HttpTab` renders it directly, so splitting them would introduce a cross-file cycle. The barrel still re-exports it for the side panel. - **Relative imports rewritten to `@/` absolute** in the moved files (they now sit one level deeper); sibling imports within the directory use `./`. - **One essential smoke test added** — the existing `buildInFilterCondition` unit test does not render the component, so a minimal "it mounts" test was added (heavy children/hooks stubbed). No per-sub-component tests. ## Verification - `npx tsc --noEmit` — clean - `make ci-lint` — clean across all 5 projects - Unit tests — 4 passed (existing `buildInFilterCondition` + new smoke test) - E2E `services-dashboard.spec.ts` — **3 passed** (page load, HTTP tab charts, Lucene filtering) ## Follow-ups (intentionally not done here) - Each tab independently calls `useSource` + `useServiceDashboardExpressions` with identical args; could be lifted to a shared hook later (behavior-adjacent, out of scope for a pure move). - Two pre-existing lint warnings (`as [Date, Date]` assertion; a setter-naming warning) were carried over verbatim.
1 parent 2f76920 commit 30ded9f

9 files changed

Lines changed: 2018 additions & 1759 deletions

File tree

‎packages/app/src/ServicesDashboardPage.tsx‎

Lines changed: 0 additions & 1759 deletions
This file was deleted.

‎packages/app/src/ServicesDashboardPage/DatabaseTab.tsx‎

Lines changed: 517 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
DisplayType,
3+
isLogSource,
4+
isTraceSource,
5+
SourceKind,
6+
} from '@hyperdx/common-utils/dist/types';
7+
import { Grid } from '@mantine/core';
8+
9+
import { INTEGER_NUMBER_FORMAT } from '@/ChartUtils';
10+
import { ChartBox } from '@/components/ChartBox';
11+
import { DBTimeChart } from '@/components/DBTimeChart';
12+
import { getStoredLanguage } from '@/components/SearchInput/SearchWhereInput';
13+
import { useServiceDashboardExpressions } from '@/serviceDashboard';
14+
import { useSource } from '@/source';
15+
16+
import { getScopedFilters, pickSourceConfigFields } from './helpers';
17+
import { AppliedConfig } from './types';
18+
19+
// Errors Tab
20+
function ErrorsTab({
21+
searchedTimeRange,
22+
appliedConfig,
23+
}: {
24+
searchedTimeRange: [Date, Date];
25+
appliedConfig: AppliedConfig;
26+
}) {
27+
const { data: source } = useSource({
28+
id: appliedConfig.source,
29+
kinds: [SourceKind.Trace],
30+
});
31+
const { expressions } = useServiceDashboardExpressions({ source });
32+
33+
return (
34+
<Grid mt="md" grow={false} w="100%" maw="100%">
35+
<Grid.Col span={12}>
36+
<ChartBox style={{ height: 350 }}>
37+
{source && expressions && (
38+
<DBTimeChart
39+
title="Error Events per Service"
40+
sourceId={source.id}
41+
config={{
42+
source: source.id,
43+
...pickSourceConfigFields(source),
44+
where: appliedConfig.where || '',
45+
whereLanguage:
46+
(appliedConfig.whereLanguage ?? getStoredLanguage()) || 'sql',
47+
displayType: DisplayType.StackedBar,
48+
select: [
49+
{
50+
valueExpression: '',
51+
aggFn: 'count',
52+
},
53+
],
54+
numberFormat: INTEGER_NUMBER_FORMAT,
55+
filters: [
56+
{
57+
type: 'sql',
58+
condition: expressions.isError,
59+
},
60+
...getScopedFilters({ appliedConfig, expressions }),
61+
],
62+
groupBy:
63+
(isLogSource(source) || isTraceSource(source)
64+
? source.serviceNameExpression
65+
: undefined) || expressions.service,
66+
dateRange: searchedTimeRange,
67+
}}
68+
/>
69+
)}
70+
</ChartBox>
71+
</Grid.Col>
72+
</Grid>
73+
);
74+
}
75+
76+
export default ErrorsTab;

0 commit comments

Comments
 (0)