Skip to content

Commit d099966

Browse files
authored
Make OxQL instance metrics work for non-fleet viewer users (#2773)
* make instance metrics work for non fleet viewer * use apiq and useQuery
1 parent e7bfb23 commit d099966

File tree

4 files changed

+70
-15
lines changed

4 files changed

+70
-15
lines changed

app/components/oxql-metrics/OxqlMetric.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
* https://github.com/oxidecomputer/omicron/tree/main/oximeter/oximeter/schema
1212
*/
1313

14+
import { useQuery } from '@tanstack/react-query'
1415
import { Children, useEffect, useMemo, useState, type ReactNode } from 'react'
1516
import type { LoaderFunctionArgs } from 'react-router'
1617

17-
import { apiQueryClient, useApiQuery } from '@oxide/api'
18+
import { apiq, queryClient } from '@oxide/api'
1819

1920
import { CopyCodeModal } from '~/components/CopyCode'
2021
import { MoreActionsMenu } from '~/components/MoreActionsMenu'
21-
import { getInstanceSelector } from '~/hooks/use-params'
22+
import { getInstanceSelector, useProjectSelector } from '~/hooks/use-params'
2223
import { useMetricsContext } from '~/pages/project/instances/common'
2324
import { LearnMore } from '~/ui/lib/CardBlock'
2425
import * as Dropdown from '~/ui/lib/DropdownMenu'
@@ -37,10 +38,9 @@ import {
3738

3839
export async function loader({ params }: LoaderFunctionArgs) {
3940
const { project, instance } = getInstanceSelector(params)
40-
await apiQueryClient.prefetchQuery('instanceView', {
41-
path: { instance },
42-
query: { project },
43-
})
41+
await queryClient.prefetchQuery(
42+
apiq('instanceView', { path: { instance }, query: { project } })
43+
)
4444
return null
4545
}
4646

@@ -53,10 +53,10 @@ export type OxqlMetricProps = OxqlQuery & {
5353
export function OxqlMetric({ title, description, unit, ...queryObj }: OxqlMetricProps) {
5454
// only start reloading data once an intial dataset has been loaded
5555
const { setIsIntervalPickerEnabled } = useMetricsContext()
56+
const { project } = useProjectSelector()
5657
const query = toOxqlStr(queryObj)
57-
const { data: metrics, error } = useApiQuery(
58-
'systemTimeseriesQuery',
59-
{ body: { query } }
58+
const { data: metrics, error } = useQuery(
59+
apiq('timeseriesQuery', { body: { query }, query: { project } })
6060
// avoid graphs flashing blank while loading when you change the time
6161
// { placeholderData: (x) => x }
6262
)

app/pages/project/instances/MetricsTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default function MetricsTab() {
3838

3939
const { intervalPicker } = useIntervalPicker({
4040
enabled: isIntervalPickerEnabled && preset !== 'custom',
41-
isLoading: useIsFetching({ queryKey: ['systemTimeseriesQuery'] }) > 0,
41+
isLoading: useIsFetching({ queryKey: ['timeseriesQuery'] }) > 0,
4242
// sliding the range forward is sufficient to trigger a refetch
4343
fn: () => onRangeChange(preset),
4444
})

mock-api/msw/handlers.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,12 +1602,25 @@ export const handlers = makeHandlers({
16021602
requireFleetViewer(params.cookies)
16031603
return handleMetrics(params)
16041604
},
1605-
async systemTimeseriesQuery(params) {
1606-
if (Math.random() > 0.95) throw 500 // random failure
1607-
requireFleetViewer(params.cookies)
1605+
async timeseriesQuery({ body, query }) {
1606+
lookup.project(query) // 404 if project doesn't exist
1607+
1608+
// We could try to do something analogous to what the API does, namely
1609+
// adding on silo and project to the oxql query to make sure only allowed
1610+
// data turns up, but since this endpoint is always called from within a
1611+
// project and constructed with project IDs, this would be unlikely to catch
1612+
// console bugs.
1613+
// https://github.com/oxidecomputer/omicron/blob/cf38148d/nexus/src/app/metrics.rs#L154-L179
1614+
1615+
// timeseries queries are slower than most other queries
1616+
await delay(1000)
1617+
return handleOxqlMetrics(body)
1618+
},
1619+
async systemTimeseriesQuery({ cookies, body }) {
1620+
requireFleetViewer(cookies)
16081621
// timeseries queries are slower than most other queries
16091622
await delay(1000)
1610-
return handleOxqlMetrics(params.body)
1623+
return handleOxqlMetrics(body)
16111624
},
16121625
siloMetric: handleMetrics,
16131626
affinityGroupList: ({ query }) => {
@@ -1787,7 +1800,6 @@ export const handlers = makeHandlers({
17871800
systemTimeseriesSchemaList: NotImplemented,
17881801
targetReleaseView: NotImplemented,
17891802
targetReleaseUpdate: NotImplemented,
1790-
timeseriesQuery: NotImplemented,
17911803
userBuiltinList: NotImplemented,
17921804
userBuiltinView: NotImplemented,
17931805
})

test/e2e/instance-metrics.e2e.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright Oxide Computer Company
7+
*/
8+
9+
import { expect, test } from '@playwright/test'
10+
11+
import { getPageAsUser } from './utils'
12+
13+
test('Click through instance metrics', async ({ page }) => {
14+
await page.goto('/projects/mock-project/instances/db1/metrics/cpu')
15+
16+
await expect(
17+
page.getByRole('heading', { name: 'CPU Utilization: Running' })
18+
).toBeVisible()
19+
await expect(page.getByText('Something went wrong')).toBeHidden()
20+
21+
await page.getByRole('tab', { name: 'Disk' }).click()
22+
await expect(page).toHaveURL('/projects/mock-project/instances/db1/metrics/disk')
23+
await expect(page.getByRole('heading', { name: 'Disk Reads' })).toBeVisible()
24+
await expect(page.getByText('Something went wrong')).toBeHidden()
25+
26+
// exact distinguishes from top level "networking" tab
27+
await page.getByRole('tab', { name: 'Network', exact: true }).click()
28+
await expect(page).toHaveURL('/projects/mock-project/instances/db1/metrics/network')
29+
await expect(page.getByRole('heading', { name: 'Bytes Sent' })).toBeVisible()
30+
await expect(page.getByText('Something went wrong')).toBeHidden()
31+
})
32+
33+
// TODO: more detailed tests using the dropdowns to change CPU state and disk
34+
35+
test('Instance metrics work for non-fleet viewer', async ({ browser }) => {
36+
const page = await getPageAsUser(browser, 'Hans Jonas')
37+
await page.goto('/projects/mock-project/instances/db1/metrics/cpu')
38+
await expect(
39+
page.getByRole('heading', { name: 'CPU Utilization: Running' })
40+
).toBeVisible()
41+
// we don't want an error, we want the data!
42+
await expect(page.getByText('Something went wrong')).toBeHidden()
43+
})

0 commit comments

Comments
 (0)