Skip to content

Commit 8f4aa9b

Browse files
Metrics more actions (#2700)
* OxQL metrics more actions * take CopyCodeModal refactor further, fix motion import * move oxql schema docs thing into links file --------- Co-authored-by: David Crespo <david.crespo@oxidecomputer.com>
1 parent dc8e38e commit 8f4aa9b

File tree

3 files changed

+93
-55
lines changed

3 files changed

+93
-55
lines changed

app/components/CopyCode.tsx

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import * as m from 'motion/react-m'
89
import { useState, type ReactNode } from 'react'
910

1011
import { Success12Icon } from '@oxide/design-system/icons/react'
@@ -13,31 +14,27 @@ import { Button } from '~/ui/lib/Button'
1314
import { Modal } from '~/ui/lib/Modal'
1415
import { useTimeout } from '~/ui/lib/use-timeout'
1516

16-
type CopyCodeProps = {
17+
type CopyCodeModalProps = {
1718
code: string
18-
modalButtonText: string
1919
copyButtonText: string
2020
modalTitle: string
2121
footer?: ReactNode
2222
/** rendered code */
2323
children?: ReactNode
24+
isOpen: boolean
25+
onDismiss: () => void
2426
}
2527

26-
export function CopyCode({
28+
export function CopyCodeModal({
29+
isOpen,
30+
onDismiss,
2731
code,
28-
modalButtonText,
2932
copyButtonText,
3033
modalTitle,
3134
children,
3235
footer,
33-
}: CopyCodeProps) {
34-
const [isOpen, setIsOpen] = useState(false)
36+
}: CopyCodeModalProps) {
3537
const [hasCopied, setHasCopied] = useState(false)
36-
37-
function handleDismiss() {
38-
setIsOpen(false)
39-
}
40-
4138
useTimeout(() => setHasCopied(false), hasCopied ? 2000 : null)
4239

4340
const handleCopy = () => {
@@ -47,37 +44,44 @@ export function CopyCode({
4744
}
4845

4946
return (
50-
<>
51-
<Button variant="ghost" size="sm" className="ml-2" onClick={() => setIsOpen(true)}>
52-
{modalButtonText}
53-
</Button>
54-
<Modal isOpen={isOpen} onDismiss={handleDismiss} title={modalTitle} width="free">
55-
<Modal.Section>
56-
<pre className="w-full rounded border px-4 py-3 !normal-case !tracking-normal text-mono-md bg-default border-secondary">
57-
{children}
58-
</pre>
59-
</Modal.Section>
60-
<Modal.Footer
61-
onDismiss={handleDismiss}
62-
onAction={handleCopy}
63-
actionText={
64-
<>
65-
<span className={hasCopied ? 'invisible' : ''}>{copyButtonText}</span>
66-
<span
67-
className={`absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center ${
68-
hasCopied ? '' : 'invisible'
69-
}`}
47+
<Modal isOpen={isOpen} onDismiss={onDismiss} title={modalTitle} width="free">
48+
<Modal.Section>
49+
<pre className="w-full rounded border px-4 py-3 !normal-case !tracking-normal text-mono-md bg-default border-secondary">
50+
{children}
51+
</pre>
52+
</Modal.Section>
53+
<Modal.Footer
54+
onDismiss={onDismiss}
55+
onAction={handleCopy}
56+
actionText={
57+
<>
58+
<m.span
59+
className="flex items-center"
60+
animate={{
61+
opacity: hasCopied ? 0 : 1,
62+
y: hasCopied ? 25 : 0,
63+
}}
64+
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
65+
>
66+
{copyButtonText}
67+
</m.span>
68+
69+
{hasCopied && (
70+
<m.span
71+
animate={{ opacity: 1, y: '-50%', x: '-50%' }}
72+
initial={{ opacity: 0, y: 'calc(-50% - 25px)', x: '-50%' }}
73+
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
74+
className="absolute left-1/2 top-1/2 flex items-center"
7075
>
71-
<Success12Icon className="mr-2 text-accent" />
72-
Copied
73-
</span>
74-
</>
75-
}
76-
>
77-
{footer}
78-
</Modal.Footer>
79-
</Modal>
80-
</>
76+
<Success12Icon className="text-accent" />
77+
</m.span>
78+
)}
79+
</>
80+
}
81+
>
82+
{footer}
83+
</Modal.Footer>
84+
</Modal>
8185
)
8286
}
8387

@@ -90,15 +94,23 @@ export function EquivalentCliCommand({ project, instance }: EquivProps) {
9094
`--instance ${instance}`,
9195
]
9296

97+
const [isOpen, setIsOpen] = useState(false)
98+
9399
return (
94-
<CopyCode
95-
code={cmdParts.join(' ')}
96-
modalButtonText="Equivalent CLI Command"
97-
copyButtonText="Copy command"
98-
modalTitle="CLI command"
99-
>
100-
<span className="mr-2 select-none text-tertiary">$</span>
101-
{cmdParts.join(' \\\n ')}
102-
</CopyCode>
100+
<>
101+
<Button variant="ghost" size="sm" className="ml-2" onClick={() => setIsOpen(true)}>
102+
Equivalent CLI Command
103+
</Button>
104+
<CopyCodeModal
105+
code={cmdParts.join(' ')}
106+
copyButtonText="Copy command"
107+
modalTitle="CLI command"
108+
isOpen={isOpen}
109+
onDismiss={() => setIsOpen(false)}
110+
>
111+
<span className="mr-2 select-none text-tertiary">$</span>
112+
{cmdParts.join(' \\\n ')}
113+
</CopyCodeModal>
114+
</>
103115
)
104116
}

app/pages/project/instances/instance/tabs/MetricsTab/OxqlMetric.tsx

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

14-
import React, { Fragment, Suspense, useEffect, useMemo } from 'react'
14+
import React, { Fragment, Suspense, useEffect, useMemo, useState } from 'react'
1515

1616
import { useApiQuery, type ChartDatum, type OxqlQueryResult } from '@oxide/api'
1717

18-
import { CopyCode } from '~/components/CopyCode'
18+
import { CopyCodeModal } from '~/components/CopyCode'
19+
import { MoreActionsMenu } from '~/components/MoreActionsMenu'
1920
import { LearnMore } from '~/ui/lib/SettingsGroup'
2021
import { intersperse } from '~/util/array'
2122
import { classed } from '~/util/classed'
@@ -320,6 +321,8 @@ export function OxqlMetric({ title, description, ...queryObj }: OxqlMetricProps)
320321
return getPercentChartProps(chartData, startTime, endTime)
321322
}, [unit, chartData, startTime, endTime])
322323

324+
const [modalOpen, setModalOpen] = useState(false)
325+
323326
return (
324327
<div className="flex w-full grow flex-col rounded-lg border border-default">
325328
<div className="flex items-center justify-between border-b px-6 py-5 border-secondary">
@@ -330,15 +333,35 @@ export function OxqlMetric({ title, description, ...queryObj }: OxqlMetricProps)
330333
</h2>
331334
<div className="mt-0.5 text-sans-md text-secondary">{description}</div>
332335
</div>
333-
<CopyCode
336+
<MoreActionsMenu
337+
label="Instance actions"
338+
actions={[
339+
{
340+
label: 'Docs',
341+
onActivate: () => {
342+
// Turn into a real link when this is fixed
343+
// https://github.com/oxidecomputer/console/issues/1855
344+
const url = links.oxqlSchemaDocs(queryObj.metricName)
345+
window.open(url, '_blank', 'noopener,noreferrer')
346+
},
347+
},
348+
{
349+
label: 'OxQL query',
350+
onActivate: () => setModalOpen(true),
351+
},
352+
]}
353+
isSmall
354+
/>
355+
<CopyCodeModal
356+
isOpen={modalOpen}
357+
onDismiss={() => setModalOpen(false)}
334358
code={query}
335-
modalButtonText="OxQL"
336359
copyButtonText="Copy query"
337360
modalTitle="OxQL query"
338361
footer={<LearnMore href={links.oxqlDocs} text="OxQL" />}
339362
>
340363
<HighlightedOxqlQuery {...queryObj} />
341-
</CopyCode>
364+
</CopyCodeModal>
342365
</div>
343366
<div className="px-6 py-5">
344367
<Suspense fallback={<div className="h-[300px]" />}>

app/util/links.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export const links = {
3333
keyConceptsProjectsDocs:
3434
'https://docs.oxide.computer/guides/key-entities-and-concepts#_projects',
3535
oxqlDocs: 'https://docs.oxide.computer/guides/operator/system-metrics#_oxql_quickstart',
36+
// TODO: update URL once https://github.com/oxidecomputer/docs/pull/426 is merged
37+
oxqlSchemaDocs: (metric: string) =>
38+
`https://docs-git-timeseries-guide-oxidecomputer.vercel.app/guides/operator/available-metric-data#_${metric.replace(':', '')}`,
3639
projectsDocs: 'https://docs.oxide.computer/guides/onboarding-projects',
3740
quickStart: 'https://docs.oxide.computer/guides/quickstart',
3841
routersDocs:

0 commit comments

Comments
 (0)