mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat(site): move template's readme to its own tab (#6863)
* feat(site): display template's readme first on template page * chore: prettier * move readme to a new docs tab * test * prettier * fix tests * prettier
This commit is contained in:
@ -132,9 +132,15 @@ const WorkspaceSettingsPage = lazy(
|
|||||||
const CreateTokenPage = lazy(
|
const CreateTokenPage = lazy(
|
||||||
() => import("./pages/CreateTokenPage/CreateTokenPage"),
|
() => import("./pages/CreateTokenPage/CreateTokenPage"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const TemplateDocsPage = lazy(
|
||||||
|
() => import("./pages/TemplatePage/TemplateDocsPage/TemplateDocsPage"),
|
||||||
|
)
|
||||||
|
|
||||||
const TemplateFilesPage = lazy(
|
const TemplateFilesPage = lazy(
|
||||||
() => import("./pages/TemplatePage/TemplateFilesPage/TemplateFilesPage"),
|
() => import("./pages/TemplatePage/TemplateFilesPage/TemplateFilesPage"),
|
||||||
)
|
)
|
||||||
|
|
||||||
const TemplateVersionsPage = lazy(
|
const TemplateVersionsPage = lazy(
|
||||||
() =>
|
() =>
|
||||||
import("./pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage"),
|
import("./pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage"),
|
||||||
@ -174,6 +180,7 @@ export const AppRouter: FC = () => {
|
|||||||
<Route path=":template">
|
<Route path=":template">
|
||||||
<Route element={<TemplateLayout />}>
|
<Route element={<TemplateLayout />}>
|
||||||
<Route index element={<TemplateSummaryPage />} />
|
<Route index element={<TemplateSummaryPage />} />
|
||||||
|
<Route path="docs" element={<TemplateDocsPage />} />
|
||||||
<Route path="files" element={<TemplateFilesPage />} />
|
<Route path="files" element={<TemplateFilesPage />} />
|
||||||
<Route path="versions" element={<TemplateVersionsPage />} />
|
<Route path="versions" element={<TemplateVersionsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
@ -110,6 +110,18 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
|||||||
>
|
>
|
||||||
Summary
|
Summary
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
end
|
||||||
|
to={`/templates/${templateName}/docs`}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
combineClasses([
|
||||||
|
styles.tabItem,
|
||||||
|
isActive ? styles.tabItemActive : undefined,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Docs
|
||||||
|
</NavLink>
|
||||||
{data.permissions.canUpdateTemplate && (
|
{data.permissions.canUpdateTemplate && (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/templates/${templateName}/files`}
|
to={`/templates/${templateName}/files`}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { screen } from "@testing-library/react"
|
||||||
|
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout"
|
||||||
|
import { ResizeObserver } from "resize-observer"
|
||||||
|
import { renderWithAuth } from "testHelpers/renderHelpers"
|
||||||
|
import TemplateDocsPage from "./TemplateDocsPage"
|
||||||
|
|
||||||
|
jest.mock("remark-gfm", () => jest.fn())
|
||||||
|
|
||||||
|
const TEMPLATE_NAME = "coder-ts"
|
||||||
|
|
||||||
|
Object.defineProperty(window, "ResizeObserver", {
|
||||||
|
value: ResizeObserver,
|
||||||
|
})
|
||||||
|
|
||||||
|
const renderPage = () =>
|
||||||
|
renderWithAuth(
|
||||||
|
<TemplateLayout>
|
||||||
|
<TemplateDocsPage />
|
||||||
|
</TemplateLayout>,
|
||||||
|
{
|
||||||
|
route: `/templates/${TEMPLATE_NAME}/docs`,
|
||||||
|
path: "/templates/:template/docs",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
describe("TemplateSummaryPage", () => {
|
||||||
|
it("shows the template readme", async () => {
|
||||||
|
renderPage()
|
||||||
|
await screen.findByTestId("markdown")
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,51 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import { MemoizedMarkdown } from "components/Markdown/Markdown"
|
||||||
|
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout"
|
||||||
|
import frontMatter from "front-matter"
|
||||||
|
import { Helmet } from "react-helmet-async"
|
||||||
|
import { pageTitle } from "util/page"
|
||||||
|
|
||||||
|
export default function TemplateDocsPage() {
|
||||||
|
const { template, activeVersion } = useTemplateLayoutContext()
|
||||||
|
const styles = useStyles()
|
||||||
|
|
||||||
|
const readme = frontMatter(activeVersion.readme)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{pageTitle(`${template.name} · Documentation`)}</title>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
|
<div className={styles.markdownSection} id="readme">
|
||||||
|
<div className={styles.readmeLabel}>README.md</div>
|
||||||
|
<div className={styles.markdownWrapper}>
|
||||||
|
<MemoizedMarkdown>{readme.body}</MemoizedMarkdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStyles = makeStyles((theme) => {
|
||||||
|
return {
|
||||||
|
markdownSection: {
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
},
|
||||||
|
|
||||||
|
readmeLabel: {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontWeight: 600,
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
markdownWrapper: {
|
||||||
|
padding: theme.spacing(0, 3, 5),
|
||||||
|
maxWidth: 800,
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
@ -4,7 +4,6 @@ import { rest } from "msw"
|
|||||||
import { ResizeObserver } from "resize-observer"
|
import { ResizeObserver } from "resize-observer"
|
||||||
import {
|
import {
|
||||||
MockTemplate,
|
MockTemplate,
|
||||||
MockWorkspaceResource,
|
|
||||||
MockTemplateVersion,
|
MockTemplateVersion,
|
||||||
MockMemberPermissions,
|
MockMemberPermissions,
|
||||||
} from "testHelpers/entities"
|
} from "testHelpers/entities"
|
||||||
@ -31,15 +30,13 @@ const renderPage = () =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
describe("TemplateSummaryPage", () => {
|
describe("TemplateSummaryPage", () => {
|
||||||
it("shows the template name, readme and resources", async () => {
|
it("shows the template name and resources", async () => {
|
||||||
// Mocking the dayjs module within the createDayString file
|
// Mocking the dayjs module within the createDayString file
|
||||||
const mock = jest.spyOn(CreateDayString, "createDayString")
|
const mock = jest.spyOn(CreateDayString, "createDayString")
|
||||||
mock.mockImplementation(() => "a minute ago")
|
mock.mockImplementation(() => "a minute ago")
|
||||||
|
|
||||||
renderPage()
|
renderPage()
|
||||||
await screen.findByText(MockTemplate.display_name)
|
await screen.findByText(MockTemplate.display_name)
|
||||||
await screen.findByTestId("markdown")
|
|
||||||
screen.getByText(MockWorkspaceResource.name)
|
|
||||||
screen.queryAllByText(`${MockTemplateVersion.name}`).length
|
screen.queryAllByText(`${MockTemplateVersion.name}`).length
|
||||||
})
|
})
|
||||||
it("does not allow a member to delete a template", () => {
|
it("does not allow a member to delete a template", () => {
|
||||||
|
@ -5,14 +5,13 @@ import {
|
|||||||
WorkspaceResource,
|
WorkspaceResource,
|
||||||
} from "api/typesGenerated"
|
} from "api/typesGenerated"
|
||||||
import { Loader } from "components/Loader/Loader"
|
import { Loader } from "components/Loader/Loader"
|
||||||
import { MemoizedMarkdown } from "components/Markdown/Markdown"
|
|
||||||
import { Stack } from "components/Stack/Stack"
|
import { Stack } from "components/Stack/Stack"
|
||||||
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable"
|
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable"
|
||||||
import { TemplateStats } from "components/TemplateStats/TemplateStats"
|
import { TemplateStats } from "components/TemplateStats/TemplateStats"
|
||||||
import frontMatter from "front-matter"
|
import { FC, useEffect } from "react"
|
||||||
import { FC } from "react"
|
|
||||||
import { DAUChart } from "../../../components/DAUChart/DAUChart"
|
import { DAUChart } from "../../../components/DAUChart/DAUChart"
|
||||||
import { TemplateSummaryData } from "./data"
|
import { TemplateSummaryData } from "./data"
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
export interface TemplateSummaryPageViewProps {
|
export interface TemplateSummaryPageViewProps {
|
||||||
data?: TemplateSummaryData
|
data?: TemplateSummaryData
|
||||||
@ -25,14 +24,22 @@ export const TemplateSummaryPageView: FC<TemplateSummaryPageViewProps> = ({
|
|||||||
template,
|
template,
|
||||||
activeVersion,
|
activeVersion,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles()
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.hash === "#readme") {
|
||||||
|
// We moved the readme to the docs page, but we known that some users
|
||||||
|
// have bookmarked the readme or linked it elsewhere. Redirect them to the docs page.
|
||||||
|
navigate(`/templates/${template.name}/docs`, { replace: true })
|
||||||
|
}
|
||||||
|
}, [template, navigate, location])
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <Loader />
|
return <Loader />
|
||||||
}
|
}
|
||||||
|
|
||||||
const { daus, resources } = data
|
const { daus, resources } = data
|
||||||
const readme = frontMatter(activeVersion.readme)
|
|
||||||
|
|
||||||
const getStartedResources = (resources: WorkspaceResource[]) => {
|
const getStartedResources = (resources: WorkspaceResource[]) => {
|
||||||
return resources.filter(
|
return resources.filter(
|
||||||
@ -45,13 +52,6 @@ export const TemplateSummaryPageView: FC<TemplateSummaryPageViewProps> = ({
|
|||||||
<TemplateStats template={template} activeVersion={activeVersion} />
|
<TemplateStats template={template} activeVersion={activeVersion} />
|
||||||
{daus && <DAUChart daus={daus} />}
|
{daus && <DAUChart daus={daus} />}
|
||||||
<TemplateResourcesTable resources={getStartedResources(resources)} />
|
<TemplateResourcesTable resources={getStartedResources(resources)} />
|
||||||
|
|
||||||
<div className={styles.markdownSection} id="readme">
|
|
||||||
<div className={styles.readmeLabel}>README.md</div>
|
|
||||||
<div className={styles.markdownWrapper}>
|
|
||||||
<MemoizedMarkdown>{readme.body}</MemoizedMarkdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user