Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.
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
12 changes: 12 additions & 0 deletions app/settings/data/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client';

import DataSettings from '@/components/settings/data/DataSettings';
import { DATA_SECTION_ROLES, RoleGuard } from '@/lib/rbac';

export default function SettingsDataPage() {
return (
<RoleGuard roles={DATA_SECTION_ROLES}>
<DataSettings />
</RoleGuard>
);
}
21 changes: 0 additions & 21 deletions app/settings/warehouse/page.tsx

This file was deleted.

24 changes: 10 additions & 14 deletions components/__tests__/main-layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,30 +79,26 @@ describe('getNavItems', () => {
expect(access).toBeDefined();
});

it('shows the Settings → Warehouse item for data roles but hides it for member', () => {
it('shows the Settings → Data item for data roles but hides it for member', () => {
for (const role of [ROLES.ANALYST, ROLES.ADMIN, ROLES.SUPER_ADMIN]) {
const settings = getNavItems('/', false, () => false, undefined, role).find(
(i) => i.title === 'Settings'
);
const warehouse = settings?.children?.find((c) => c.title === 'Warehouse');
expect(warehouse?.href).toBe('/settings/warehouse');
expect(warehouse?.hide).toBeFalsy();
const data = settings?.children?.find((c) => c.title === 'Data');
expect(data?.href).toBe('/settings/data');
expect(data?.hide).toBeFalsy();
}
const memberSettings = getNavItems('/', false, () => false, undefined, ROLES.MEMBER).find(
(i) => i.title === 'Settings'
);
expect(memberSettings?.children?.find((c) => c.title === 'Warehouse')?.hide).toBe(true);
expect(memberSettings?.children?.find((c) => c.title === 'Data')?.hide).toBe(true);
});

it('marks the Warehouse item active on /settings/warehouse', () => {
const settings = getNavItems(
'/settings/warehouse',
false,
() => false,
undefined,
ROLES.ADMIN
).find((i) => i.title === 'Settings');
expect(settings?.children?.find((c) => c.title === 'Warehouse')?.isActive).toBe(true);
it('marks the Data item active on /settings/data', () => {
const settings = getNavItems('/settings/data', false, () => false, undefined, ROLES.ADMIN).find(
(i) => i.title === 'Settings'
);
expect(settings?.children?.find((c) => c.title === 'Data')?.isActive).toBe(true);
});

it('shows all items for the admin role', () => {
Expand Down
2 changes: 1 addition & 1 deletion components/ingest/redesign/__tests__/ingest-view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('IngestView progressive reveal', () => {
const chip = screen.getByTestId('warehouse-chip');
// It navigates (anchor), not a dialog trigger.
expect(chip.tagName).toBe('A');
expect(chip).toHaveAttribute('href', '/settings/warehouse');
expect(chip).toHaveAttribute('href', '/settings/data');
expect(chip).toHaveTextContent('Warehouse');
expect(screen.getByText('hobbit_pantry_1')).toBeInTheDocument();
expect(screen.getByText('postgres')).toBeInTheDocument(); // uppercased via CSS
Expand Down
6 changes: 3 additions & 3 deletions components/ingest/redesign/ingest-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import type { Warehouse } from '@/types/warehouse';

/**
* Compact top-right chip showing the org's single warehouse. It links out to the
* warehouse's home in Settings (Settings → Warehouse), where it can be viewed,
* edited, or deleted — the warehouse is org infrastructure, not an ingest concern.
* warehouse's home in Settings (Settings → Data → Warehouse), where it can be
* viewed, edited, or deleted — the warehouse is org infrastructure, not an ingest concern.
*/
function WarehouseChip({ warehouse }: { warehouse: Warehouse }) {
return (
<Link
href="/settings/warehouse"
href="/settings/data"
className="group inline-flex items-center gap-1.5 text-sm leading-none cursor-pointer"
data-testid="warehouse-chip"
>
Expand Down
6 changes: 3 additions & 3 deletions components/main-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,10 @@ export const getNavItems = (
visibleToRoles: ACCESS_PAGE_ROLES,
},
{
title: 'Warehouse',
href: '/settings/warehouse',
title: 'Data',
href: '/settings/data',
icon: Database,
isActive: currentPath.startsWith('/settings/warehouse'),
isActive: currentPath.startsWith('/settings/data'),
visibleToRoles: DATA_SECTION_ROLES,
},
...(isFeatureFlagEnabled(FeatureFlagKeys.USAGE_DASHBOARD) && hasSupersetSetup
Expand Down
67 changes: 67 additions & 0 deletions components/settings/data/ConnectionsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

import { useState } from 'react';
import useSWR from 'swr';
import { Switch } from '@/components/ui/switch';
import { apiGet, apiPut } from '@/lib/api';
import { toastError, toastSuccess } from '@/lib/toast';
import { PERMISSIONS, useRbac } from '@/lib/rbac';

interface OrgPreferencesResponse {
success: boolean;
res: {
auto_accept_non_breaking_schema_changes: boolean;
};
}

const PREFS_KEY = '/api/orgpreferences/';

export default function ConnectionsTab() {
const { hasPermission } = useRbac();
const canEdit = hasPermission(PERMISSIONS.CAN_EDIT_SCHEMA_CHANGE_SETTINGS);

const { data, mutate, isLoading } = useSWR<OrgPreferencesResponse>(PREFS_KEY, apiGet, {
revalidateOnFocus: false,
});
const [saving, setSaving] = useState(false);

const checked = data?.res?.auto_accept_non_breaking_schema_changes ?? false;

const onToggle = async (next: boolean) => {
setSaving(true);
try {
await apiPut('/api/orgpreferences/auto-accept-schema-changes', {
auto_accept_non_breaking_schema_changes: next,
});
await mutate();
toastSuccess.saved('auto-accept setting');
} catch (err: any) {
toastError.save(err, 'auto-accept setting');
} finally {
setSaving(false);
}
};

return (
<div className="max-w-3xl bg-white border rounded-lg p-6">
<div className="flex items-start justify-between gap-6">
<div className="flex-1">
<h2 className="text-lg font-semibold">Auto-accept non-breaking schema changes</h2>
<p className="text-xs text-muted-foreground mt-0.5">
Org-wide — applies to every connection.
</p>
<p className="text-sm text-muted-foreground mt-2">
When a source adds a column or a new stream, Dalgo will apply the change automatically.
Breaking changes still require manual review.
</p>
</div>
<Switch
checked={checked}
disabled={!canEdit || saving || isLoading}
onCheckedChange={onToggle}
data-testid="auto-accept-schema-changes-toggle"
/>
</div>
</div>
);
}
67 changes: 67 additions & 0 deletions components/settings/data/DataSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

import { useState } from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { WarehouseDisplay } from '@/components/ingest/warehouse/warehouse-display';
import { DocsLink } from '@/components/ui/docs-link';
import ConnectionsTab from './ConnectionsTab';
import { trackFeatureView } from '@/lib/analytics';
import { FEATURES } from '@/constants/analytics';

const TAB_TRIGGER_CLASS =
'relative bg-transparent border-0 shadow-none rounded-none px-1 py-2.5 text-sm font-medium uppercase tracking-wide text-gray-500 cursor-pointer data-[state=active]:text-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-transparent data-[state=active]:after:bg-primary';

export default function DataSettings() {
const [tab, setTab] = useState('warehouse');

return (
<div className="h-full flex flex-col min-h-0">
<Tabs
value={tab}
onValueChange={(value) => {
setTab(value);
trackFeatureView(FEATURES.SETTINGS_DATA, { tab: value });
}}
className="w-full flex-1 flex flex-col min-h-0"
>
<div className="flex-shrink-0 border-b bg-background">
<div className="p-6 pb-0">
<DocsLink path="/settings/data">
<h1 className="text-3xl font-bold">Data</h1>
</DocsLink>
<p className="text-muted-foreground mt-1">
Warehouse configuration and connection behavior
</p>
</div>
<div className="flex items-center gap-2 px-6 pt-4 pb-0">
<TabsList className="bg-transparent p-0 h-auto gap-4">
<TabsTrigger
value="warehouse"
className={TAB_TRIGGER_CLASS}
data-testid="tab-warehouse"
>
Warehouse
</TabsTrigger>
<TabsTrigger
value="connections"
className={TAB_TRIGGER_CLASS}
data-testid="tab-connections"
>
Connections
</TabsTrigger>
</TabsList>
</div>
</div>

<div className="flex-1 overflow-auto p-6">
<TabsContent value="warehouse" className="mt-0">
<WarehouseDisplay />
</TabsContent>
<TabsContent value="connections" className="mt-0">
<ConnectionsTab />
</TabsContent>
</div>
</Tabs>
</div>
);
}
8 changes: 4 additions & 4 deletions constants/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,9 @@ export const FEATURES = {
SETTINGS_SUPERSET_USAGE: 'settings_superset_usage',
SETTINGS_BRANDING: 'settings_branding',
SETTINGS_COPILOT: 'settings_copilot',
// The warehouse moved out of the ingest page onto its own Settings route; without an
// entry here (and in PATHNAME_TO_FEATURE) that page fired no feature:viewed at all.
SETTINGS_WAREHOUSE: 'settings_warehouse',
// Data settings consolidates warehouse config + connection-level org preferences
// (e.g. auto-accept non-breaking schema changes) behind a tabbed page.
SETTINGS_DATA: 'settings_data',
// Pre-auth free-trial screens. Three separate features (not one `free_trial`)
// because useFeatureTracking dedupes on the FEATURE, not the pathname — a single
// id would make the three screens indistinguishable and destroy the funnel.
Expand Down Expand Up @@ -762,7 +762,7 @@ export const PATHNAME_TO_FEATURE: ReadonlyArray<{ prefix: string; feature: Featu
{ prefix: '/settings/about', feature: FEATURES.SETTINGS_ABOUT },
{ prefix: '/settings/branding', feature: FEATURES.SETTINGS_BRANDING },
{ prefix: '/settings/copilot', feature: FEATURES.SETTINGS_COPILOT },
{ prefix: '/settings/warehouse', feature: FEATURES.SETTINGS_WAREHOUSE },
{ prefix: '/settings/data', feature: FEATURES.SETTINGS_DATA },
{ prefix: '/free-trial/activate', feature: FEATURES.FREE_TRIAL_ACTIVATE },
{ prefix: '/free-trial/consent', feature: FEATURES.FREE_TRIAL_CONSENT },
{ prefix: '/free-trial/progress', feature: FEATURES.FREE_TRIAL_PROGRESS },
Expand Down
4 changes: 2 additions & 2 deletions lib/__tests__/feature-route-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ describe('feature:viewed route coverage', () => {
expect(unmapped).toEqual([]);
});

it('maps the warehouse settings route specifically', () => {
expect(featureForPathname('/settings/warehouse')).toBe('settings_warehouse');
it('maps the data settings route specifically', () => {
expect(featureForPathname('/settings/data')).toBe('settings_data');
});

// Guards the matcher itself: a prefix must not swallow a sibling route.
Expand Down
1 change: 1 addition & 0 deletions lib/rbac.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const PERMISSIONS = {
CAN_VIEW_FLAGS: 'can_view_flags',
CAN_EDIT_LLM_SETTINGS: 'can_edit_llm_settings',
CAN_EDIT_ORG_NOTIFICATION_SETTINGS: 'can_edit_org_notification_settings',
CAN_EDIT_SCHEMA_CHANGE_SETTINGS: 'can_edit_schema_change_settings',
CAN_INITIATE_ORG_PLAN_UPGRADE: 'can_initiate_org_plan_upgrade',
CAN_REQUEST_LLM_ANALYSIS_FEATURE: 'can_request_llm_analysis_feature',
CAN_USE_CHAT_WITH_DATA: 'can_use_chat_with_data',
Expand Down
1 change: 0 additions & 1 deletion types/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ export interface SchemaChange {
change_type: string; // e.g. 'breaking' | 'non_breaking'
created_at?: string;
updated_at?: string;
schedule_job?: number | null;
}

export interface CatalogDiff {
Expand Down
Loading