feat: add audit page title, subtitle, and CLI snippet (#3419)

* resolves #3356

* scaffolded out new audit page header

resolves #3357

* added tests and stories

* run prettier
This commit is contained in:
Kira Pilot
2022-08-08 21:08:36 -04:00
committed by GitHub
parent 049e7cb5df
commit e62677efab
10 changed files with 163 additions and 16 deletions

View File

@ -8,18 +8,28 @@ export interface CodeExampleProps {
code: string
className?: string
buttonClassName?: string
tooltipTitle?: string
}
/**
* Component to show single-line code examples, with a copy button
*/
export const CodeExample: FC<CodeExampleProps> = ({ code, className, buttonClassName }) => {
export const CodeExample: FC<CodeExampleProps> = ({
code,
className,
buttonClassName,
tooltipTitle,
}) => {
const styles = useStyles()
return (
<div className={combineClasses([styles.root, className])}>
<code className={styles.code}>{code}</code>
<CopyButton text={code} buttonClassName={combineClasses([styles.button, buttonClassName])} />
<CopyButton
text={code}
tooltipTitle={tooltipTitle}
buttonClassName={combineClasses([styles.button, buttonClassName])}
/>
</div>
)
}

View File

@ -11,6 +11,12 @@ interface CopyButtonProps {
ctaCopy?: string
wrapperClassName?: string
buttonClassName?: string
tooltipTitle?: string
}
export const Language = {
tooltipTitle: "Copy to clipboard",
ariaLabel: "Copy to clipboard",
}
/**
@ -21,6 +27,7 @@ export const CopyButton: React.FC<CopyButtonProps> = ({
ctaCopy,
wrapperClassName = "",
buttonClassName = "",
tooltipTitle = Language.tooltipTitle,
}) => {
const styles = useStyles()
const [isCopied, setIsCopied] = useState<boolean>(false)
@ -56,12 +63,13 @@ export const CopyButton: React.FC<CopyButtonProps> = ({
}
return (
<Tooltip title="Copy to Clipboard" placement="top">
<Tooltip title={tooltipTitle} placement="top">
<div className={combineClasses([styles.copyButtonWrapper, wrapperClassName])}>
<IconButton
className={combineClasses([styles.copyButton, buttonClassName])}
onClick={copyToClipboard}
size="small"
aria-label={Language.ariaLabel}
>
{isCopied ? (
<Check className={styles.fileCopyIcon} />

View File

@ -5,18 +5,23 @@ import { combineClasses } from "../../util/combineClasses"
type Direction = "column" | "row"
interface StyleProps {
direction: Direction
spacing: number
export interface StackProps {
className?: string
direction?: Direction
spacing?: number
alignItems?: CSSProperties["alignItems"]
justifyContent?: CSSProperties["justifyContent"]
}
type StyleProps = Omit<StackProps, "className">
const useStyles = makeStyles((theme) => ({
stack: {
display: "flex",
flexDirection: ({ direction }: StyleProps) => direction,
gap: ({ spacing }: StyleProps) => theme.spacing(spacing),
gap: ({ spacing }: StyleProps) => spacing && theme.spacing(spacing),
alignItems: ({ alignItems }: StyleProps) => alignItems,
justifyContent: ({ justifyContent }: StyleProps) => justifyContent,
[theme.breakpoints.down("sm")]: {
width: "100%",
@ -24,21 +29,15 @@ const useStyles = makeStyles((theme) => ({
},
}))
export interface StackProps {
className?: string
direction?: Direction
spacing?: number
alignItems?: CSSProperties["alignItems"]
}
export const Stack: FC<StackProps> = ({
children,
className,
direction = "column",
spacing = 2,
alignItems,
justifyContent,
}) => {
const styles = useStyles({ spacing, direction, alignItems })
const styles = useStyles({ spacing, direction, alignItems, justifyContent })
return <div className={combineClasses([styles.stack, className])}>{children}</div>
}

View File

@ -0,0 +1,16 @@
import { FC } from "react"
import { HelpTooltip, HelpTooltipText, HelpTooltipTitle } from "./HelpTooltip"
export const Language = {
title: "What is an audit log?",
body: "An audit log is a record of events and changes made throughout a system.",
}
export const AuditHelpTooltip: FC = () => {
return (
<HelpTooltip>
<HelpTooltipTitle>{Language.title}</HelpTooltipTitle>
<HelpTooltipText>{Language.body}</HelpTooltipText>
</HelpTooltip>
)
}

View File

@ -15,6 +15,10 @@ export interface HelpTooltipProps {
size?: Size
}
export const Language = {
ariaLabel: "tooltip",
}
const HelpTooltipContext = createContext<{ open: boolean; onClose: () => void } | undefined>(
undefined,
)
@ -52,6 +56,7 @@ export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size =
onMouseEnter={() => {
setIsOpen(true)
}}
aria-label={Language.ariaLabel}
>
<HelpIcon className={styles.icon} />
</button>

View File

@ -1,4 +1,5 @@
export { AgentHelpTooltip } from "./AgentHelpTooltip"
export { AuditHelpTooltip } from "./AuditHelpTooltip"
export { OutdatedHelpTooltip } from "./OutdatedHelpTooltip"
export { ResourcesHelpTooltip } from "./ResourcesHelpTooltip"
export { WorkspaceHelpTooltip } from "./WorkspaceHelpTooltip"

View File

@ -0,0 +1,32 @@
import { fireEvent, screen } from "@testing-library/react"
import { Language as CopyButtonLanguage } from "components/CopyButton/CopyButton"
import { Language as AuditTooltipLanguage } from "components/Tooltips/AuditHelpTooltip"
import { Language as TooltipLanguage } from "components/Tooltips/HelpTooltip/HelpTooltip"
import { render } from "testHelpers/renderHelpers"
import AuditPage from "./AuditPage"
import { Language as AuditViewLanguage } from "./AuditPageView"
describe("AuditPage", () => {
it("renders a page with a title and subtitle", async () => {
// When
render(<AuditPage />)
// Then
await screen.findByText(AuditViewLanguage.title)
await screen.findByText(AuditViewLanguage.subtitle)
const tooltipIcon = await screen.findByRole("button", { name: TooltipLanguage.ariaLabel })
fireEvent.mouseOver(tooltipIcon)
expect(await screen.findByText(AuditTooltipLanguage.title)).toBeInTheDocument()
})
it("describes the CLI command", async () => {
// When
render(<AuditPage />)
// Then
await screen.findByText("coder audit [organization_ID]") // CLI command; untranslated
const copyIcon = await screen.findByRole("button", { name: CopyButtonLanguage.ariaLabel })
fireEvent.mouseOver(copyIcon)
expect(await screen.findByText(AuditViewLanguage.tooltipTitle)).toBeInTheDocument()
})
})

View File

@ -1,8 +1,9 @@
import { FC } from "react"
import { AuditPageView } from "./AuditPageView"
// REMARK: This page is in-progress and hidden from users
const AuditPage: FC = () => {
return <div>Audit</div>
return <AuditPageView />
}
export default AuditPage

View File

@ -0,0 +1,16 @@
import { ComponentMeta, Story } from "@storybook/react"
import { AuditPageView } from "./AuditPageView"
export default {
title: "pages/AuditPageView",
component: AuditPageView,
} as ComponentMeta<typeof AuditPageView>
const Template: Story = (args) => <AuditPageView {...args} />
export const AuditPage = Template.bind({})
export const AuditPageSmallViewport = Template.bind({})
AuditPageSmallViewport.parameters = {
chromatic: { viewports: [600] },
}

View File

@ -0,0 +1,59 @@
import { makeStyles } from "@material-ui/core/styles"
import { CodeExample } from "components/CodeExample/CodeExample"
import { Margins } from "components/Margins/Margins"
import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "components/PageHeader/PageHeader"
import { Stack } from "components/Stack/Stack"
import { AuditHelpTooltip } from "components/Tooltips"
import { FC } from "react"
export const Language = {
title: "Audit",
subtitle: "View events in your audit log.",
tooltipTitle: "Copy to clipboard and try the Coder CLI",
}
export const AuditPageView: FC = () => {
const styles = useStyles()
return (
<Margins>
<Stack justifyContent="space-between" className={styles.headingContainer}>
<PageHeader className={styles.headingStyles}>
<PageHeaderTitle>
<Stack direction="row" spacing={1} alignItems="center">
<span>{Language.title}</span>
<AuditHelpTooltip />
</Stack>
</PageHeaderTitle>
<PageHeaderSubtitle>{Language.subtitle}</PageHeaderSubtitle>
</PageHeader>
<CodeExample
className={styles.codeExampleStyles}
tooltipTitle={Language.tooltipTitle}
code="coder audit [organization_ID]"
/>
</Stack>
</Margins>
)
}
const useStyles = makeStyles((theme) => ({
headingContainer: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(5),
flexDirection: "row",
alignItems: "center",
[theme.breakpoints.down("sm")]: {
flexDirection: "column",
alignItems: "start",
},
},
headingStyles: {
paddingTop: "0px",
paddingBottom: "0px",
},
codeExampleStyles: {
height: "fit-content",
},
}))