Skip to content
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
112 changes: 62 additions & 50 deletions app/components/CopyCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*
* Copyright Oxide Computer Company
*/
import * as m from 'motion/react-m'
import { useState, type ReactNode } from 'react'

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

type CopyCodeProps = {
type CopyCodeModalProps = {
code: string
modalButtonText: string
copyButtonText: string
modalTitle: string
footer?: ReactNode
/** rendered code */
children?: ReactNode
isOpen: boolean
onDismiss: () => void
}

export function CopyCode({
export function CopyCodeModal({
isOpen,
onDismiss,
code,
modalButtonText,
copyButtonText,
modalTitle,
children,
footer,
}: CopyCodeProps) {
const [isOpen, setIsOpen] = useState(false)
}: CopyCodeModalProps) {
const [hasCopied, setHasCopied] = useState(false)

function handleDismiss() {
setIsOpen(false)
}

useTimeout(() => setHasCopied(false), hasCopied ? 2000 : null)

const handleCopy = () => {
Expand All @@ -47,37 +44,44 @@ export function CopyCode({
}

return (
<>
<Button variant="ghost" size="sm" className="ml-2" onClick={() => setIsOpen(true)}>
{modalButtonText}
</Button>
<Modal isOpen={isOpen} onDismiss={handleDismiss} title={modalTitle} width="free">
<Modal.Section>
<pre className="w-full rounded border px-4 py-3 !normal-case !tracking-normal text-mono-md bg-default border-secondary">
{children}
</pre>
</Modal.Section>
<Modal.Footer
onDismiss={handleDismiss}
onAction={handleCopy}
actionText={
<>
<span className={hasCopied ? 'invisible' : ''}>{copyButtonText}</span>
<span
className={`absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center ${
hasCopied ? '' : 'invisible'
}`}
<Modal isOpen={isOpen} onDismiss={onDismiss} title={modalTitle} width="free">
<Modal.Section>
<pre className="w-full rounded border px-4 py-3 !normal-case !tracking-normal text-mono-md bg-default border-secondary">
{children}
</pre>
</Modal.Section>
<Modal.Footer
onDismiss={onDismiss}
onAction={handleCopy}
actionText={
<>
<m.span
className="flex items-center"
animate={{
opacity: hasCopied ? 0 : 1,
y: hasCopied ? 25 : 0,
}}
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
>
{copyButtonText}
</m.span>

{hasCopied && (
<m.span
animate={{ opacity: 1, y: '-50%', x: '-50%' }}
initial={{ opacity: 0, y: 'calc(-50% - 25px)', x: '-50%' }}
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
className="absolute left-1/2 top-1/2 flex items-center"
>
<Success12Icon className="mr-2 text-accent" />
Copied
</span>
</>
}
>
{footer}
</Modal.Footer>
</Modal>
</>
<Success12Icon className="text-accent" />
</m.span>
)}
</>
}
>
{footer}
</Modal.Footer>
</Modal>
)
}

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

const [isOpen, setIsOpen] = useState(false)

return (
<CopyCode
code={cmdParts.join(' ')}
modalButtonText="Equivalent CLI Command"
copyButtonText="Copy command"
modalTitle="CLI command"
>
<span className="mr-2 select-none text-tertiary">$</span>
{cmdParts.join(' \\\n ')}
</CopyCode>
<>
<Button variant="ghost" size="sm" className="ml-2" onClick={() => setIsOpen(true)}>
Equivalent CLI Command
</Button>
<CopyCodeModal
code={cmdParts.join(' ')}
copyButtonText="Copy command"
modalTitle="CLI command"
isOpen={isOpen}
onDismiss={() => setIsOpen(false)}
>
<span className="mr-2 select-none text-tertiary">$</span>
{cmdParts.join(' \\\n ')}
</CopyCodeModal>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
* https://github.com/oxidecomputer/omicron/tree/main/oximeter/oximeter/schema
*/

import React, { Fragment, Suspense, useEffect, useMemo } from 'react'
import React, { Fragment, Suspense, useEffect, useMemo, useState } from 'react'

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

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

const [modalOpen, setModalOpen] = useState(false)

return (
<div className="flex w-full grow flex-col rounded-lg border border-default">
<div className="flex items-center justify-between border-b px-6 py-5 border-secondary">
Expand All @@ -330,15 +333,35 @@ export function OxqlMetric({ title, description, ...queryObj }: OxqlMetricProps)
</h2>
<div className="mt-0.5 text-sans-md text-secondary">{description}</div>
</div>
<CopyCode
<MoreActionsMenu
label="Instance actions"
actions={[
{
label: 'Docs',
onActivate: () => {
// Turn into a real link when this is fixed
// https://github.com/oxidecomputer/console/issues/1855
const url = links.oxqlSchemaDocs(queryObj.metricName)
window.open(url, '_blank', 'noopener,noreferrer')
},
},
{
label: 'OxQL query',
onActivate: () => setModalOpen(true),
},
]}
isSmall
/>
<CopyCodeModal
isOpen={modalOpen}
onDismiss={() => setModalOpen(false)}
code={query}
modalButtonText="OxQL"
copyButtonText="Copy query"
modalTitle="OxQL query"
footer={<LearnMore href={links.oxqlDocs} text="OxQL" />}
>
<HighlightedOxqlQuery {...queryObj} />
</CopyCode>
</CopyCodeModal>
</div>
<div className="px-6 py-5">
<Suspense fallback={<div className="h-[300px]" />}>
Expand Down
3 changes: 3 additions & 0 deletions app/util/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const links = {
keyConceptsProjectsDocs:
'https://docs.oxide.computer/guides/key-entities-and-concepts#_projects',
oxqlDocs: 'https://docs.oxide.computer/guides/operator/system-metrics#_oxql_quickstart',
// TODO: update URL once https://github.com/oxidecomputer/docs/pull/426 is merged
oxqlSchemaDocs: (metric: string) =>
`https://docs-git-timeseries-guide-oxidecomputer.vercel.app/guides/operator/available-metric-data#_${metric.replace(':', '')}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple things about this. I like the idea of linking to a doc, though I'm not sure the schema is that helpful in understanding the graph.

I'm not that worried about us breaking these links. We can pretty easily test them directly if we want: have playwright click the link and go to the site and see whether the right thing is in the viewport. Speaking of which, it seems like the site is kind of janky around that, possibly a timing thing due to the page being so long. If I open this link in a new tab, I see it briefly at the right part of the page before jump back up to the top, presumably when something else loads or hydrates or whatever. If I hit enter in the URL bar again, it jumps to the right place.

https://docs-git-timeseries-guide-oxidecomputer.vercel.app/guides/operator/available-metric-data#_virtual_machinevcpu_usage

Related to that, it seems to cut off the heading. Other fragment links on other pages don't seem to have this problem. It could be because they render a little differently because they have the [discrete] tag on them in the asciidoc to keep them out of the sidebar ToC.

image image

Copy link
Collaborator

@david-crespo david-crespo Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, I take it back about it not being helpful — seeing that description of what this value is is pretty helpful. I do think we should consider tweaking the copy and maybe the visual hierarchy to make it easier to parse all that info. Metadata (a name I put in there) is not that great, it's actually almost the opposite! Fields are the metadata, and the table labeled metadata actually describes the data. I will work on it in the docs site PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the discrete thing and hopefully the link to the sections works better now. There was a hydration error due to asciidoc shenanigans.

projectsDocs: 'https://docs.oxide.computer/guides/onboarding-projects',
quickStart: 'https://docs.oxide.computer/guides/quickstart',
routersDocs:
Expand Down
Loading