Skip to content

Commit ca9986a

Browse files
committed
feat: serverless database view
1 parent de83881 commit ca9986a

File tree

11 files changed

+306
-93
lines changed

11 files changed

+306
-93
lines changed

src/containers/Tenant/Diagnostics/Diagnostics.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function Diagnostics(props: DiagnosticsProps) {
6060

6161
const tenantName = isDatabaseEntityType(type) ? path : database;
6262

63-
const {controlPlane} = useTenantBaseInfo(isDatabaseEntityType(type) ? path : '');
63+
const {controlPlane, databaseType} = useTenantBaseInfo(isDatabaseEntityType(type) ? path : '');
6464

6565
const hasFeatureFlags = useFeatureFlagsAvailable();
6666
const hasTopicData = useTopicDataAvailable();
@@ -72,6 +72,7 @@ function Diagnostics(props: DiagnosticsProps) {
7272
hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane),
7373
hasConfigs: isViewerUser,
7474
hasAccess: uiFactory.hasAccess,
75+
isServerless: databaseType === 'Serverless',
7576
});
7677
let activeTab = pages.find((el) => el.id === diagnosticsTab);
7778
if (!activeTab) {
@@ -187,10 +188,10 @@ function Diagnostics(props: DiagnosticsProps) {
187188
<TabProvider value={activeTab?.id}>
188189
<TabList size="l">
189190
{pages.map(({id, title}) => {
190-
const path = getDiagnosticsPageLink(id);
191+
const linkPath = getDiagnosticsPageLink(id);
191192
return (
192193
<Tab key={id} value={id}>
193-
<InternalLink to={path} as="tab">
194+
<InternalLink to={linkPath} as="tab">
194195
{title}
195196
</InternalLink>
196197
</Tab>

src/containers/Tenant/Diagnostics/DiagnosticsPages.ts

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ type Page = {
1414
title: string;
1515
};
1616

17+
interface GetPagesOptions {
18+
hasFeatureFlags?: boolean;
19+
hasTopicData?: boolean;
20+
isTopLevel?: boolean;
21+
hasBackups?: boolean;
22+
hasConfigs?: boolean;
23+
hasAccess?: boolean;
24+
isServerless?: boolean;
25+
}
26+
1727
const overview = {
1828
id: TENANT_DIAGNOSTICS_TABS_IDS.overview,
1929
title: 'Info',
@@ -118,6 +128,16 @@ const DATABASE_PAGES = [
118128
backups,
119129
];
120130

131+
const SERVERLESS_DATABASE_PAGES = [
132+
overview,
133+
topQueries,
134+
topShards,
135+
tablets,
136+
describe,
137+
configs,
138+
operations,
139+
];
140+
121141
const TABLE_PAGES = [overview, schema, topShards, nodes, graph, tablets, hotKeys, describe, access];
122142
const COLUMN_TABLE_PAGES = [overview, schema, topShards, nodes, tablets, describe, access];
123143

@@ -168,41 +188,52 @@ const pathSubTypeToPages: Record<EPathSubType, Page[] | undefined> = {
168188
[EPathSubType.EPathSubTypeEmpty]: undefined,
169189
};
170190

171-
export const getPagesByType = (
172-
type?: EPathType,
173-
subType?: EPathSubType,
174-
options?: {
175-
hasFeatureFlags?: boolean;
176-
hasTopicData?: boolean;
177-
isTopLevel?: boolean;
178-
hasBackups?: boolean;
179-
hasConfigs?: boolean;
180-
hasAccess?: boolean;
181-
},
182-
) => {
191+
function computeInitialPages(type?: EPathType, subType?: EPathSubType) {
183192
const subTypePages = subType ? pathSubTypeToPages[subType] : undefined;
184193
const typePages = type ? pathTypeToPages[type] : undefined;
185-
let pages = subTypePages || typePages || DIR_PAGES;
194+
return subTypePages || typePages || DIR_PAGES;
195+
}
196+
197+
function getDatabasePages(isServerless?: boolean) {
198+
return isServerless ? SERVERLESS_DATABASE_PAGES : DATABASE_PAGES;
199+
}
200+
201+
function applyFilters(pages: Page[], type?: EPathType, options: GetPagesOptions = {}) {
202+
let result = pages;
186203

187-
if (isTopicEntityType(type) && !options?.hasTopicData) {
188-
pages = pages?.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.topicData);
204+
if (isTopicEntityType(type) && !options.hasTopicData) {
205+
result = result.filter((p) => p.id !== TENANT_DIAGNOSTICS_TABS_IDS.topicData);
189206
}
190-
if (isDatabaseEntityType(type) || options?.isTopLevel) {
191-
pages = DATABASE_PAGES;
192-
if (!options?.hasFeatureFlags) {
193-
pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs);
194-
}
207+
208+
const removals: TenantDiagnosticsTab[] = [];
209+
if (!options.hasBackups) {
210+
removals.push(TENANT_DIAGNOSTICS_TABS_IDS.backups);
195211
}
196-
if (!options?.hasBackups) {
197-
pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.backups);
212+
if (!options.hasConfigs) {
213+
removals.push(TENANT_DIAGNOSTICS_TABS_IDS.configs);
198214
}
199-
if (!options?.hasConfigs) {
200-
pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs);
215+
if (!options.hasAccess) {
216+
removals.push(TENANT_DIAGNOSTICS_TABS_IDS.access);
201217
}
202-
if (!options?.hasAccess) {
203-
pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.access);
218+
219+
return result.filter((p) => !removals.includes(p.id));
220+
}
221+
222+
export const getPagesByType = (
223+
type?: EPathType,
224+
subType?: EPathSubType,
225+
options?: GetPagesOptions,
226+
) => {
227+
const base = computeInitialPages(type, subType);
228+
const dbContext = isDatabaseEntityType(type) || options?.isTopLevel;
229+
const seeded = dbContext ? getDatabasePages(options?.isServerless) : base;
230+
231+
let withFlags = seeded;
232+
if (!options?.hasFeatureFlags) {
233+
withFlags = seeded.filter((p) => p.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs);
204234
}
205-
return pages;
235+
236+
return applyFilters(withFlags, type, options);
206237
};
207238

208239
export const useDiagnosticsPageLinkGetter = () => {

src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,15 @@
224224
padding-right: 0;
225225
}
226226
}
227+
228+
// Serverless variant: exactly two tabs (CPU, Storage) at 50% width each
229+
230+
.tenant-metrics-tabs_serverless {
231+
.tenant-metrics-tabs__link-container_placeholder {
232+
pointer-events: none;
233+
234+
.tenant-tab-card__card-container {
235+
opacity: 0;
236+
}
237+
}
238+
}

src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx

Lines changed: 94 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ interface MetricsTabsProps {
3333
tabletStorageStats?: TenantStorageStats[];
3434
networkStats?: TenantMetricStats[];
3535
storageGroupsCount?: number;
36+
isServerless?: boolean;
37+
activeTab?: TenantMetricsTab;
3638
}
3739

3840
export function MetricsTabs({
@@ -42,6 +44,8 @@ export function MetricsTabs({
4244
tabletStorageStats,
4345
networkStats,
4446
storageGroupsCount,
47+
isServerless,
48+
activeTab,
4549
}: MetricsTabsProps) {
4650
const location = useLocation();
4751
const {metricsTab} = useTypedSelector((state) => state.tenant);
@@ -83,11 +87,13 @@ export function MetricsTabs({
8387
const [showNetworkUtilization] = useSetting<boolean>(SHOW_NETWORK_UTILIZATION);
8488
const networkMetrics = networkStats ? calculateMetricAggregates(networkStats) : null;
8589

90+
const active = activeTab ?? metricsTab;
91+
8692
return (
87-
<Flex className={b()} alignItems="center">
93+
<Flex className={b({serverless: Boolean(isServerless)})} alignItems="center">
8894
<div
8995
className={b('link-container', {
90-
active: metricsTab === TENANT_METRICS_TABS_IDS.cpu,
96+
active: active === TENANT_METRICS_TABS_IDS.cpu,
9197
})}
9298
>
9399
<Link to={tabLinks.cpu} className={b('link')}>
@@ -96,64 +102,109 @@ export function MetricsTabs({
96102
value={cpuMetrics.totalUsed}
97103
limit={cpuMetrics.totalLimit}
98104
legendFormatter={formatCoresLegend}
99-
active={metricsTab === TENANT_METRICS_TABS_IDS.cpu}
105+
active={active === TENANT_METRICS_TABS_IDS.cpu}
100106
helpText={i18n('context_cpu-description')}
107+
variant={isServerless ? 'serverless' : 'default'}
108+
subtitle={isServerless ? i18n('serverless.autoscaled') : undefined}
101109
/>
102110
</Link>
103111
</div>
104112
<div
105113
className={b('link-container', {
106-
active: metricsTab === TENANT_METRICS_TABS_IDS.storage,
114+
active: active === TENANT_METRICS_TABS_IDS.storage,
107115
})}
108116
>
109117
<Link to={tabLinks.storage} className={b('link')}>
110118
<TabCard
111-
text={
112-
storageGroupsCount === undefined
113-
? i18n('cards.storage-label')
114-
: i18n('context_storage-groups', {count: storageGroupsCount})
115-
}
119+
text={i18n('cards.storage-label')}
116120
value={storageMetrics.totalUsed}
117121
limit={storageMetrics.totalLimit}
118122
legendFormatter={formatStorageLegend}
119-
active={metricsTab === TENANT_METRICS_TABS_IDS.storage}
123+
active={active === TENANT_METRICS_TABS_IDS.storage}
120124
helpText={i18n('context_storage-description')}
125+
variant={isServerless ? 'serverless' : 'default'}
126+
subtitle={
127+
isServerless && storageMetrics.totalLimit
128+
? i18n('serverless.storage-subtitle', {
129+
groups: String(storageGroupsCount ?? 0),
130+
legend: formatStorageLegend({
131+
value: storageMetrics.totalUsed,
132+
capacity: storageMetrics.totalLimit,
133+
}),
134+
})
135+
: undefined
136+
}
121137
/>
122138
</Link>
123139
</div>
124-
<div
125-
className={b('link-container', {
126-
active: metricsTab === TENANT_METRICS_TABS_IDS.memory,
127-
})}
128-
>
129-
<Link to={tabLinks.memory} className={b('link')}>
130-
<TabCard
131-
text={i18n('context_memory-used')}
132-
value={memoryMetrics.totalUsed}
133-
limit={memoryMetrics.totalLimit}
134-
legendFormatter={formatStorageLegend}
135-
active={metricsTab === TENANT_METRICS_TABS_IDS.memory}
136-
helpText={i18n('context_memory-description')}
137-
/>
138-
</Link>
139-
</div>
140-
{showNetworkUtilization && networkStats && networkMetrics && (
141-
<div
142-
className={b('link-container', {
143-
active: metricsTab === TENANT_METRICS_TABS_IDS.network,
144-
})}
145-
>
146-
<Link to={tabLinks.network} className={b('link')}>
147-
<TabCard
148-
text={i18n('context_network-usage')}
149-
value={networkMetrics.totalUsed}
150-
limit={networkMetrics.totalLimit}
151-
legendFormatter={formatSpeedLegend}
152-
active={metricsTab === TENANT_METRICS_TABS_IDS.network}
153-
helpText={i18n('context_network-description')}
154-
/>
155-
</Link>
156-
</div>
140+
{!isServerless && (
141+
<>
142+
<div
143+
className={b('link-container', {
144+
active: active === TENANT_METRICS_TABS_IDS.memory,
145+
})}
146+
>
147+
<Link to={tabLinks.memory} className={b('link')}>
148+
<TabCard
149+
text={i18n('context_memory-used')}
150+
value={memoryMetrics.totalUsed}
151+
limit={memoryMetrics.totalLimit}
152+
legendFormatter={formatStorageLegend}
153+
active={active === TENANT_METRICS_TABS_IDS.memory}
154+
helpText={i18n('context_memory-description')}
155+
/>
156+
</Link>
157+
</div>
158+
{showNetworkUtilization && networkStats && networkMetrics && (
159+
<div
160+
className={b('link-container', {
161+
active: active === TENANT_METRICS_TABS_IDS.network,
162+
})}
163+
>
164+
<Link to={tabLinks.network} className={b('link')}>
165+
<TabCard
166+
text={i18n('context_network-usage')}
167+
value={networkMetrics.totalUsed}
168+
limit={networkMetrics.totalLimit}
169+
legendFormatter={formatSpeedLegend}
170+
active={active === TENANT_METRICS_TABS_IDS.network}
171+
helpText={i18n('context_network-description')}
172+
/>
173+
</Link>
174+
</div>
175+
)}
176+
</>
177+
)}
178+
179+
{isServerless && (
180+
<>
181+
<div className={b('link-container', {placeholder: true})}>
182+
<div className={b('link')}>
183+
<TabCard
184+
text={'\u00A0'}
185+
value={0}
186+
limit={1}
187+
legendFormatter={formatCoresLegend}
188+
active={false}
189+
variant={isServerless ? 'serverless' : 'default'}
190+
subtitle={'\u00A0'}
191+
/>
192+
</div>
193+
</div>
194+
<div className={b('link-container', {placeholder: true})}>
195+
<div className={b('link')}>
196+
<TabCard
197+
text={'\u00A0'}
198+
value={0}
199+
limit={1}
200+
legendFormatter={formatCoresLegend}
201+
active={false}
202+
variant={isServerless ? 'serverless' : 'default'}
203+
subtitle={'\u00A0'}
204+
/>
205+
</div>
206+
</div>
207+
</>
157208
)}
158209
</Flex>
159210
);

src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,53 @@ interface TabCardProps {
1515
active?: boolean;
1616
helpText?: string;
1717
legendFormatter: (params: {value: number; capacity: number}) => string;
18+
variant?: 'default' | 'serverless';
19+
subtitle?: string;
1820
}
1921

20-
export function TabCard({text, value, limit, active, helpText, legendFormatter}: TabCardProps) {
22+
export function TabCard({
23+
text,
24+
value,
25+
limit,
26+
active,
27+
helpText,
28+
legendFormatter,
29+
variant = 'default',
30+
subtitle,
31+
}: TabCardProps) {
2132
const {status, percents, legend, fill} = getDiagramValues({
2233
value,
2334
capacity: limit,
2435
legendFormatter,
2536
});
2637

38+
if (variant === 'serverless') {
39+
return (
40+
<div className={b({active})}>
41+
<Card
42+
className={b('card-container', {active})}
43+
type="container"
44+
view={active ? 'outlined' : 'raised'}
45+
>
46+
<div className={b('legend-wrapper')}>
47+
<DoughnutMetrics.Legend
48+
variant="subheader-2"
49+
note={helpText}
50+
noteIconSize="s"
51+
>
52+
{text}
53+
</DoughnutMetrics.Legend>
54+
{subtitle ? (
55+
<DoughnutMetrics.Legend variant="body-1" color="secondary">
56+
{subtitle}
57+
</DoughnutMetrics.Legend>
58+
) : null}
59+
</div>
60+
</Card>
61+
</div>
62+
);
63+
}
64+
2765
return (
2866
<div className={b({active})}>
2967
<Card

0 commit comments

Comments
 (0)