Skip to content

Commit 1f5ea2e

Browse files
authored
Merge pull request #8 from tinybirdco/multi-tenant-00
add multi-tenancy by org name
2 parents 38c93c7 + e1dbd79 commit 1f5ea2e

File tree

6 files changed

+39
-22
lines changed

6 files changed

+39
-22
lines changed

dashboard/ai-analytics/src/app/components/TabbedPane.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import { useSearchParams } from 'next/navigation';
66
import BarList from './BarList';
77
import { useState, useEffect } from 'react';
88
import { tabs } from '../constants';
9+
import { useTinybirdToken } from '@/providers/TinybirdProvider';
910

1011
interface TabbedPaneProps {
1112
filters: Record<string, string>;
1213
onFilterUpdate: (dimension: string, name: string, values: string[]) => void;
1314
}
1415

1516
export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps) {
17+
const { orgName } = useTinybirdToken();
1618
const searchParams = useSearchParams();
17-
const initialDimension = searchParams.get('dimension') || tabs[0].key;
19+
const filteredTabs = tabs.filter(tab => !orgName || tab.key !== 'organization');
20+
const initialDimension = searchParams.get('dimension') || filteredTabs[0].key;
1821
const [selectedTab, setSelectedTab] = useState<string>(initialDimension);
1922
const [barListData, setBarListData] = useState<Array<{ name: string; value: number }>>([]);
2023
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -50,7 +53,7 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
5053
window.history.replaceState({}, '', `?${params.toString()}`);
5154

5255
// Notify parent to update filters
53-
onFilterUpdate(selectedTab, tabs.find(t => t.key === selectedTab)?.name || selectedTab, newSelection);
56+
onFilterUpdate(selectedTab, filteredTabs.find(t => t.key === selectedTab)?.name || selectedTab, newSelection);
5457
};
5558

5659
// const handleRemoveFilter = (dimension: string, value: string) => {
@@ -59,7 +62,7 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
5962
// };
6063

6164
const handleTabChange = (index: number) => {
62-
const tab = tabs[index];
65+
const tab = filteredTabs[index];
6366
const dimension = tab.key;
6467

6568
// Update URL without scroll
@@ -85,11 +88,11 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
8588
return (
8689
<div className="h-full">
8790
<TabGroup
88-
defaultIndex={tabs.findIndex(t => t.key === selectedTab)}
91+
defaultIndex={filteredTabs.findIndex(t => t.key === selectedTab)}
8992
onIndexChange={handleTabChange}
9093
>
9194
<TabList className="flex space-x-2">
92-
{tabs.map((tab) => (
95+
{filteredTabs.map((tab) => (
9396
<Tab
9497
key={tab.key}
9598
// @ts-expect-error fix later

dashboard/ai-analytics/src/app/components/TimeseriesChart.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
TabPanels,
1111
} from '@tremor/react';
1212
import { useSearchParams, useRouter } from 'next/navigation';
13+
import { useTinybirdToken } from '@/providers/TinybirdProvider';
1314

1415
function classNames(...classes: (string | undefined | null | false)[]) {
1516
return classes.filter(Boolean).join(' ');
@@ -42,6 +43,7 @@ interface TimeseriesChartProps {
4243
export default function TimeseriesChart({ data, filters, onFiltersChange }: TimeseriesChartProps) {
4344
const router = useRouter();
4445
const searchParams = useSearchParams();
46+
const { orgName } = useTinybirdToken();
4547

4648
// Add null check for data
4749
if (!data?.data) {
@@ -107,8 +109,8 @@ export default function TimeseriesChart({ data, filters, onFiltersChange }: Time
107109
})),
108110
},
109111
{
110-
name: 'Organization',
111-
key: 'organization',
112+
name: 'Environment',
113+
key: 'environment',
112114
data: transformedData,
113115
categories: models,
114116
colors: defaultColors,
@@ -119,10 +121,13 @@ export default function TimeseriesChart({ data, filters, onFiltersChange }: Time
119121
.reduce((sum, item) => sum + item.total_cost, 0),
120122
color: `bg-${defaultColors[models.indexOf(model) % defaultColors.length]}-500`,
121123
})),
122-
},
123-
{
124-
name: 'Environment',
125-
key: 'environment',
124+
}
125+
];
126+
127+
if (!orgName) {
128+
tabs.push({
129+
name: 'Organization',
130+
key: 'organization',
126131
data: transformedData,
127132
categories: models,
128133
colors: defaultColors,
@@ -133,8 +138,8 @@ export default function TimeseriesChart({ data, filters, onFiltersChange }: Time
133138
.reduce((sum, item) => sum + item.total_cost, 0),
134139
color: `bg-${defaultColors[models.indexOf(model) % defaultColors.length]}-500`,
135140
})),
136-
}
137-
];
141+
})
142+
}
138143

139144
const handleTabChange = (index: number) => {
140145
const tab = tabs[index];

dashboard/ai-analytics/src/app/components/TopBar.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useSearchParams, useRouter } from 'next/navigation';
44
import { SignInButton, SignUpButton, UserButton, SignedIn, SignedOut } from "@clerk/nextjs";
55
import FilterChips from './FilterChips';
6+
import { useTinybirdToken } from '@/providers/TinybirdProvider';
67

78
interface Selection {
89
dimension: string;
@@ -18,6 +19,7 @@ interface TopBarProps {
1819
export default function TopBar({ selections, onRemoveFilter }: TopBarProps) {
1920
const router = useRouter();
2021
const searchParams = useSearchParams();
22+
const { orgName } = useTinybirdToken();
2123

2224
const handleRemoveFilter = (dimension: string, value: string) => {
2325
// Get current params
@@ -44,6 +46,9 @@ export default function TopBar({ selections, onRemoveFilter }: TopBarProps) {
4446
return (
4547
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
4648
<div className="flex items-center space-x-4">
49+
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
50+
{orgName || 'Admin User'}
51+
</span>
4752
{selections.map((selection) => (
4853
selection.values.map((value) => (
4954
<FilterChips

dashboard/ai-analytics/src/app/layout.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,21 @@ import { useTinybirdToken } from '@/providers/TinybirdProvider'
99
const inter = Inter({ subsets: ["latin"] })
1010

1111
function RootLayoutContent({ children }: { children: React.ReactNode }) {
12-
const { setToken } = useTinybirdToken()
12+
const { setToken, setOrgName } = useTinybirdToken()
1313
const [isReady, setIsReady] = useState(false)
1414

1515
useEffect(() => {
16-
console.log('Fetching token...');
1716
fetch(window.location.pathname)
1817
.then(response => {
1918
const token = response.headers.get('x-tinybird-token')
20-
console.log('Got token:', token);
19+
const orgName = response.headers.get('x-org-name')
2120
if (token) {
22-
console.log('Setting token...');
2321
setToken(token)
22+
setOrgName(orgName || '')
2423
setIsReady(true)
2524
}
2625
})
27-
}, [setToken])
26+
}, [setToken, setOrgName])
2827

2928
if (!isReady) return <div>Loading...</div>
3029

dashboard/ai-analytics/src/middleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default clerkMiddleware(async (auth) => {
2121
// const sessionToken = await authentication.getToken({
2222
// template: "tinybird-logs-explorer" // This is key for getting org data
2323
// })
24-
const orgName = orgPermissions?.[0]?.split(':').pop()
24+
const orgName = orgPermissions?.[0]?.split(':').pop() || ''
2525

2626
// Create Tinybird JWT
2727
const secret = new TextEncoder().encode(process.env.TINYBIRD_JWT_SECRET)
@@ -59,7 +59,7 @@ export default clerkMiddleware(async (auth) => {
5959
// Clone the response and add token
6060
const response = NextResponse.next()
6161
response.headers.set('x-tinybird-token', token)
62-
62+
response.headers.set('x-org-name', orgName)
6363
return response
6464
} catch (error) {
6565
console.error('Middleware error:', error)

dashboard/ai-analytics/src/providers/TinybirdProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
55

66
interface TinybirdContextType {
77
token: string | null;
8+
orgName: string | null;
89
setToken: (token: string) => void;
10+
setOrgName: (orgName: string) => void;
911
}
1012

1113
const TinybirdContext = createContext<TinybirdContextType | null>(null);
@@ -14,9 +16,10 @@ const queryClient = new QueryClient();
1416

1517
export function TinybirdProvider({ children }: { children: ReactNode }) {
1618
const [token, setToken] = useState<string | null>(null);
19+
const [orgName, setOrgName] = useState<string | null>(null);
1720

1821
return (
19-
<TinybirdContext.Provider value={{ token, setToken }}>
22+
<TinybirdContext.Provider value={{ token, orgName, setToken, setOrgName }}>
2023
<QueryClientProvider client={queryClient}>
2124
{children}
2225
</QueryClientProvider>
@@ -26,6 +29,8 @@ export function TinybirdProvider({ children }: { children: ReactNode }) {
2629

2730
export function useTinybirdToken() {
2831
const context = useContext(TinybirdContext);
29-
if (!context) throw new Error('useTinybirdToken must be used within TinybirdProvider');
32+
if (!context) {
33+
throw new Error('useTinybirdToken must be used within TinybirdProvider');
34+
}
3035
return context;
3136
}

0 commit comments

Comments
 (0)