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
11 changes: 11 additions & 0 deletions .changeset/sources-refactor-ui-variants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@hyperdx/app": minor
---

Refactor Sources components and add custom Mantine UI variants

- Move SourceForm to Sources/ subfolder with reusable SourcesList component
- Add primary, secondary, and danger button/action icon variants
- Improve Storybook with font switching and component stories
- Update ErrorBoundary styling with danger variant

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ e2e/cypress/results
**/playwright/.cache/
**/.auth/

# storybook
**/storybook-static/

# scripts
scripts/*.csv
**/venv
Expand Down
16 changes: 16 additions & 0 deletions agent_docs/code_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@
- Define TypeScript interfaces for props
- Use proper keys for lists, memoization for expensive computations

## Mantine UI Components

The project uses Mantine UI with **custom variants** defined in `packages/app/src/theme/mantineTheme.ts`:

### Custom Button Variants
- `variant="primary"` - Light green button for primary actions
- `variant="secondary"` - Default styled button for secondary actions
- `variant="danger"` - Light red button for destructive actions

### Custom ActionIcon Variants
- `variant="primary"` - Light green action icon
- `variant="secondary"` - Default styled action icon
- `variant="danger"` - Light red action icon for destructive actions

These are valid variants - do not replace them with standard Mantine variants like `variant="light" color="red"`.

## Refactoring

- Edit files directly - don't create `component-v2.tsx` copies
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"app:dev": "concurrently -k -n 'API,APP,ALERTS-TASK,COMMON-UTILS' -c 'green.bold,blue.bold,yellow.bold,magenta' 'nx run @hyperdx/api:dev' 'nx run @hyperdx/app:dev' 'nx run @hyperdx/api:dev-task check-alerts' 'nx run @hyperdx/common-utils:dev'",
"app:dev:local": "concurrently -k -n 'APP,COMMON-UTILS' -c 'blue.bold,magenta' 'nx run @hyperdx/app:dev:local' 'nx run @hyperdx/common-utils:dev'",
"app:lint": "nx run @hyperdx/app:ci:lint",
"app:storybook": "nx run @hyperdx/app:storybook",
"dev": "yarn build:common-utils && dotenvx run --convention=nextjs -- docker compose -f docker-compose.dev.yml up -d && yarn app:dev && docker compose -f docker-compose.dev.yml down",
"dev:local": "IS_LOCAL_APP_MODE='DANGEROUSLY_is_local_app_mode💀' yarn dev",
"dev:down": "docker compose -f docker-compose.dev.yml down",
Expand Down
9 changes: 9 additions & 0 deletions packages/app/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ const config: StorybookConfig = {
options: {},
},
staticDirs: ['./public'],
webpackFinal: async config => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
'next/router': require.resolve('next/router'),
};
}
return config;
},
};

export default config;
69 changes: 58 additions & 11 deletions packages/app/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { NextAdapter } from 'next-query-params';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { QueryClient, QueryClientProvider } from 'react-query';
import { QueryParamProvider } from 'use-query-params';
import type { Preview } from '@storybook/nextjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { ibmPlexMono, inter, roboto, robotoMono } from '../src/fonts';
import { meHandler } from '../src/mocks/handlers';
import { ThemeWrapper } from '../src/ThemeWrapper';

Expand All @@ -31,29 +32,75 @@ export const globalTypes = {
defaultValue: 'light',
toolbar: {
icon: 'mirror',
title: 'Theme',
items: [
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
],
},
},
font: {
name: 'Font',
description: 'App font family',
defaultValue: 'inter',
toolbar: {
icon: 'typography',
title: 'Font',
items: [
{ value: 'inter', title: 'Inter' },
{ value: 'roboto', title: 'Roboto' },
{ value: 'ibm-plex-mono', title: 'IBM Plex Mono' },
{ value: 'roboto-mono', title: 'Roboto Mono' },
],
},
},
};

initialize();

const queryClient = new QueryClient();
const fontMap = {
inter: inter,
roboto: roboto,
'ibm-plex-mono': ibmPlexMono,
'roboto-mono': robotoMono,
};

// Create a new QueryClient for each story to avoid cache pollution between stories
const createQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
staleTime: 0,
},
},
});

const preview: Preview = {
decorators: [
(Story, context) => (
<QueryClientProvider client={queryClient}>
<QueryParamProvider adapter={NextAdapter}>
<ThemeWrapper colorScheme={context.globals.theme || 'light'}>
<Story />
</ThemeWrapper>
</QueryParamProvider>
</QueryClientProvider>
),
(Story, context) => {
// Create a fresh QueryClient for each story render
const [queryClient] = React.useState(() => createQueryClient());

const selectedFont = context.globals.font || 'inter';
const font = fontMap[selectedFont as keyof typeof fontMap] || inter;
const fontFamily = font.style.fontFamily;

return (
<div className={font.className}>
<QueryClientProvider client={queryClient}>
<QueryParamProvider adapter={NextAdapter}>
<ThemeWrapper
colorScheme={context.globals.theme || 'light'}
fontFamily={fontFamily}
>
<Story />
</ThemeWrapper>
</QueryParamProvider>
</QueryClientProvider>
</div>
);
},
],
loaders: [mswLoader],
parameters: {
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/DBSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import { InputControlled } from '@/components/InputControlled';
import OnboardingModal from '@/components/OnboardingModal';
import SearchPageActionBar from '@/components/SearchPageActionBar';
import SearchTotalCountChart from '@/components/SearchTotalCountChart';
import { TableSourceForm } from '@/components/SourceForm';
import { TableSourceForm } from '@/components/Sources/SourceForm';
import { SourceSelectControlled } from '@/components/SourceSelect';
import { SQLInlineEditorControlled } from '@/components/SQLInlineEditor';
import { Tags } from '@/components/Tags';
Expand Down
113 changes: 14 additions & 99 deletions packages/app/src/TeamPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { Fragment, useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import Head from 'next/head';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { SubmitHandler, useForm, useWatch } from 'react-hook-form';
import { DEFAULT_METADATA_MAX_ROWS_TO_READ } from '@hyperdx/common-utils/dist/core/metadata';
import {
SourceKind,
TeamClickHouseSettings,
} from '@hyperdx/common-utils/dist/types';
import { TeamClickHouseSettings } from '@hyperdx/common-utils/dist/types';
import {
Box,
Button,
Expand All @@ -27,19 +24,15 @@ import {
import { notifications } from '@mantine/notifications';
import {
IconCheck,
IconChevronDown,
IconChevronUp,
IconClipboard,
IconDatabase,
IconHelpCircle,
IconPencil,
IconServer,
IconX,
} from '@tabler/icons-react';

import { ConnectionForm } from '@/components/ConnectionForm';
import SelectControlled from '@/components/SelectControlled';
import { TableSourceForm } from '@/components/SourceForm';
import { SourcesList } from '@/components/Sources/SourcesList';
import { IS_LOCAL_MODE } from '@/config';

import { PageHeader } from './components/PageHeader';
Expand All @@ -49,8 +42,6 @@ import api from './api';
import { useConnections } from './connection';
import { DEFAULT_QUERY_TIMEOUT, DEFAULT_SEARCH_ROW_LIMIT } from './defaults';
import { withAppNav } from './layout';
import { useSources } from './source';
import { capitalizeFirstLetter } from './utils';

function ConnectionsSection() {
const { data: connections } = useConnections();
Expand All @@ -64,7 +55,7 @@ function ConnectionsSection() {
<Box id="connections">
<Text size="md">Connections</Text>
<Divider my="md" />
<Card variant="muted">
<Card>
<Stack mb="md">
{connections?.map(c => (
<Box key={c.id}>
Expand Down Expand Up @@ -149,91 +140,15 @@ function ConnectionsSection() {
}

function SourcesSection() {
const { data: connections } = useConnections();
const { data: sources } = useSources();

const [editedSourceId, setEditedSourceId] = useState<string | null>(null);
const [isCreatingSource, setIsCreatingSource] = useState(false);

return (
<Box id="sources">
<Text size="md">Sources</Text>
<Divider my="md" />
<Card variant="muted">
<Stack>
{sources?.map(s => (
<Fragment key={s.id}>
<Flex justify="space-between" align="center">
<div>
<Text>{s.name}</Text>
<Text size="xxs" c="dimmed" mt="xs" component="div">
<Group gap="xs">
{capitalizeFirstLetter(s.kind)}
<Group gap={2}>
<IconServer size={14} />
{connections?.find(c => c.id === s.connection)?.name}
</Group>
<Group gap={2}>
{s.from && (
<>
<IconDatabase size={14} />
{s.from.databaseName}
{
s.kind === SourceKind.Metric
? ''
: '.' /** Metrics dont have table names */
}
{s.from.tableName}
</>
)}
</Group>
</Group>
</Text>
</div>
{editedSourceId !== s.id && (
<Button
variant="subtle"
onClick={() => setEditedSourceId(s.id)}
size="sm"
>
<IconChevronDown size={14} />
</Button>
)}
{editedSourceId === s.id && (
<Button
variant="subtle"
onClick={() => setEditedSourceId(null)}
size="sm"
>
<IconChevronUp size={14} />
</Button>
)}
</Flex>
{editedSourceId === s.id && (
<TableSourceForm
sourceId={s.id}
onSave={() => setEditedSourceId(null)}
/>
)}
<Divider />
</Fragment>
))}
{!IS_LOCAL_MODE && isCreatingSource && (
<TableSourceForm
isNew
onCreate={() => {
setIsCreatingSource(false);
}}
onCancel={() => setIsCreatingSource(false)}
/>
)}
{!IS_LOCAL_MODE && !isCreatingSource && (
<Button variant="default" onClick={() => setIsCreatingSource(true)}>
Add Source
</Button>
)}
</Stack>
</Card>
<SourcesList
withBorder={false}
variant="default"
showEmptyState={false}
/>
</Box>
);
}
Expand All @@ -242,7 +157,7 @@ function IntegrationsSection() {
<Box id="integrations">
<Text size="md">Integrations</Text>
<Divider my="md" />
<Card variant="muted">
<Card>
<Stack gap="md">
<WebhooksSection />
</Stack>
Expand Down Expand Up @@ -290,7 +205,7 @@ function TeamNameSection() {
<Box id="team_name">
<Text size="md">Team Name</Text>
<Divider my="md" />
<Card variant="muted">
<Card>
{isEditingTeamName ? (
<form onSubmit={form.handleSubmit(onSubmit)}>
<Group gap="xs">
Expand Down Expand Up @@ -552,7 +467,7 @@ function TeamQueryConfigSection() {
<Box id="team_name">
<Text size="md">ClickHouse Client Settings</Text>
<Divider my="md" />
<Card variant="muted">
<Card>
<Stack>
<ClickhouseSettingForm
settingKey="searchRowLimit"
Expand Down Expand Up @@ -670,7 +585,7 @@ function ApiKeysSection() {
<Box id="api_keys">
<Text size="md">API Keys</Text>
<Divider my="md" />
<Card variant="muted" mb="md">
<Card mb="md">
<Text mb="md">Ingestion API Key</Text>
<Group gap="xs">
{team?.apiKey && (
Expand Down Expand Up @@ -726,7 +641,7 @@ function ApiKeysSection() {
</Modal>
</Card>
{!isLoadingMe && me != null && (
<Card variant="muted">
<Card>
<Card.Section p="md">
<Text mb="md">Personal API Access Key</Text>
<APIKeyCopyButton value={me.accessKey} dataTestId="api-key" />
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/components/ConfirmDeleteMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function ConfirmDeleteMenu({
return (
<Menu withArrow>
<Menu.Target>
<Button variant="outline" size="xs">
<Button variant="danger" size="xs">
Delete
</Button>
</Menu.Target>
Expand Down
Loading
Loading