feat(site): display version message (#8435)

This commit is contained in:
Bruno Quaresma
2023-07-13 10:36:10 -03:00
committed by GitHub
parent 44e25185ff
commit b833861960
23 changed files with 291 additions and 89 deletions
+1
View File
@@ -644,6 +644,7 @@ export interface PatchGroupRequest {
// From codersdk/templateversions.go
export interface PatchTemplateVersionRequest {
readonly name: string
readonly message?: string
}
// From codersdk/workspaceproxy.go
@@ -32,10 +32,12 @@ export const PublishTemplateVersionDialog: FC<
const form = useFormik({
initialValues: {
name: defaultName,
message: "",
isActiveVersion: false,
},
validationSchema: Yup.object({
name: Yup.string().required(),
message: Yup.string(),
isActiveVersion: Yup.boolean(),
}),
onSubmit: onConfirm,
@@ -70,6 +72,16 @@ export const PublishTemplateVersionDialog: FC<
disabled={isPublishing}
/>
<TextField
{...getFieldHelpers("message")}
label="Message"
placeholder="Write a short message about the changes you made..."
autoFocus
disabled={isPublishing}
multiline
rows={5}
/>
<FormControlLabel
label="Promote to default version"
control={
@@ -1,66 +0,0 @@
import RefreshIcon from "@mui/icons-material/Refresh"
import { FC } from "react"
import {
HelpTooltip,
HelpTooltipAction,
HelpTooltipLinksGroup,
HelpTooltipText,
HelpTooltipTitle,
} from "./HelpTooltip"
import InfoIcon from "@mui/icons-material/InfoOutlined"
import { makeStyles } from "@mui/styles"
import { colors } from "theme/colors"
export const Language = {
outdatedLabel: "Outdated",
versionTooltipText:
"This workspace version is outdated and a newer version is available.",
updateVersionLabel: "Update version",
}
interface TooltipProps {
onUpdateVersion: () => void
ariaLabel?: string
}
export const OutdatedHelpTooltip: FC<React.PropsWithChildren<TooltipProps>> = ({
onUpdateVersion,
ariaLabel,
}) => {
const styles = useStyles()
return (
<HelpTooltip
size="small"
icon={InfoIcon}
iconClassName={styles.icon}
buttonClassName={styles.button}
>
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>
<HelpTooltipText>{Language.versionTooltipText}</HelpTooltipText>
<HelpTooltipLinksGroup>
<HelpTooltipAction
icon={RefreshIcon}
onClick={onUpdateVersion}
ariaLabel={ariaLabel}
>
{Language.updateVersionLabel}
</HelpTooltipAction>
</HelpTooltipLinksGroup>
</HelpTooltip>
)
}
const useStyles = makeStyles(() => ({
icon: {
color: colors.yellow[5],
},
button: {
opacity: 1,
"&:hover": {
opacity: 1,
},
},
}))
@@ -0,0 +1,140 @@
import RefreshIcon from "@mui/icons-material/Refresh"
import { FC } from "react"
import {
HelpTooltip,
HelpTooltipAction,
HelpTooltipLinksGroup,
HelpTooltipText,
HelpTooltipTitle,
} from "./HelpTooltip"
import InfoIcon from "@mui/icons-material/InfoOutlined"
import { makeStyles } from "@mui/styles"
import { colors } from "theme/colors"
import { useQuery } from "@tanstack/react-query"
import { getTemplate, getTemplateVersion } from "api/api"
import Box from "@mui/material/Box"
import Skeleton from "@mui/material/Skeleton"
import Link from "@mui/material/Link"
export const Language = {
outdatedLabel: "Outdated",
versionTooltipText:
"This workspace version is outdated and a newer version is available.",
updateVersionLabel: "Update version",
}
interface TooltipProps {
onUpdateVersion: () => void
templateId: string
templateName: string
ariaLabel?: string
}
export const WorkspaceOutdatedTooltip: FC<TooltipProps> = ({
onUpdateVersion,
ariaLabel,
templateId,
templateName,
}) => {
const styles = useStyles()
const { data: activeVersion } = useQuery({
queryFn: async () => {
const template = await getTemplate(templateId)
const activeVersion = await getTemplateVersion(template.active_version_id)
return activeVersion
},
queryKey: ["templates", templateId, "activeVersion"],
})
return (
<HelpTooltip
size="small"
icon={InfoIcon}
iconClassName={styles.icon}
buttonClassName={styles.button}
>
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>
<HelpTooltipText>{Language.versionTooltipText}</HelpTooltipText>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 1,
py: 1,
fontSize: 13,
}}
>
<Box>
<Box
sx={{
color: (theme) => theme.palette.text.primary,
fontWeight: 600,
}}
>
New version
</Box>
<Box>
{activeVersion ? (
<Link
href={`/templates/${templateName}/versions/${activeVersion.name}`}
target="_blank"
sx={{ color: (theme) => theme.palette.primary.light }}
>
{activeVersion.name}
</Link>
) : (
<Skeleton variant="text" height={20} width={100} />
)}
</Box>
</Box>
<Box>
<Box
sx={{
color: (theme) => theme.palette.text.primary,
fontWeight: 600,
}}
>
Message
</Box>
<Box>
{activeVersion ? (
activeVersion.message === "" ? (
"No message"
) : (
activeVersion.message
)
) : (
<Skeleton variant="text" height={20} width={150} />
)}
</Box>
</Box>
</Box>
<HelpTooltipLinksGroup>
<HelpTooltipAction
icon={RefreshIcon}
onClick={onUpdateVersion}
ariaLabel={ariaLabel}
>
{Language.updateVersionLabel}
</HelpTooltipAction>
</HelpTooltipLinksGroup>
</HelpTooltip>
)
}
const useStyles = makeStyles(() => ({
icon: {
color: colors.yellow[5],
},
button: {
opacity: 1,
"&:hover": {
opacity: 1,
},
},
}))
+1 -1
View File
@@ -1,4 +1,4 @@
export { AuditHelpTooltip } from "./AuditHelpTooltip"
export { OutdatedHelpTooltip } from "./OutdatedHelpTooltip"
export { WorkspaceOutdatedTooltip } from "./WorkspaceOutdatedTooltip"
export { UserRoleHelpTooltip } from "./UserRoleHelpTooltip"
export { WorkspaceHelpTooltip } from "./WorkspaceHelpTooltip"
@@ -1,5 +1,5 @@
import Link from "@mui/material/Link"
import { OutdatedHelpTooltip } from "components/Tooltips"
import { WorkspaceOutdatedTooltip } from "components/Tooltips"
import { FC, useRef, useState } from "react"
import { Link as RouterLink } from "react-router-dom"
import { createDayString } from "utils/createDayString"
@@ -101,7 +101,9 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
</Link>
{workspace.outdated && (
<OutdatedHelpTooltip
<WorkspaceOutdatedTooltip
templateName={workspace.template_name}
templateId={workspace.template_id}
onUpdateVersion={handleUpdate}
ariaLabel="update version"
/>
@@ -9,7 +9,7 @@ import { useNavigate } from "react-router-dom"
import { getDisplayWorkspaceTemplateName } from "utils/workspace"
import { LastUsed } from "../LastUsed/LastUsed"
import { Workspace } from "api/typesGenerated"
import { OutdatedHelpTooltip } from "components/Tooltips/OutdatedHelpTooltip"
import { WorkspaceOutdatedTooltip } from "components/Tooltips/WorkspaceOutdatedTooltip"
import { Avatar } from "components/Avatar/Avatar"
import { Stack } from "components/Stack/Stack"
import { useClickableTableRow } from "hooks/useClickableTableRow"
@@ -42,7 +42,9 @@ export const WorkspacesRow: FC<{
<Stack direction="row" spacing={0} alignItems="center">
{workspace.name}
{workspace.outdated && (
<OutdatedHelpTooltip
<WorkspaceOutdatedTooltip
templateName={workspace.template_name}
templateId={workspace.template_id}
onUpdateVersion={() => {
onUpdateWorkspace(workspace)
}}
@@ -16,7 +16,7 @@ jest.mock("components/TemplateResourcesTable/TemplateResourcesTable", () => {
}
})
test("Use custom name and set it as active when publishing", async () => {
test("Use custom name, message and set it as active when publishing", async () => {
const user = userEvent.setup()
renderWithAuth(<TemplateVersionEditorPage />, {
extraRoutes: [
@@ -64,6 +64,9 @@ test("Use custom name and set it as active when publishing", async () => {
const nameField = within(publishDialog).getByLabelText("Version name")
await user.clear(nameField)
await user.type(nameField, "v1.0")
const messageField = within(publishDialog).getByLabelText("Message")
await user.clear(messageField)
await user.type(messageField, "Informative message")
await user.click(
within(publishDialog).getByLabelText("Promote to default version"),
)
@@ -73,6 +76,7 @@ test("Use custom name and set it as active when publishing", async () => {
await waitFor(() => {
expect(patchTemplateVersion).toBeCalledWith("new-version-id", {
name: "v1.0",
message: "Informative message",
})
})
expect(updateActiveTemplateVersion).toBeCalledWith("test-template", {
@@ -134,12 +138,17 @@ test("Do not mark as active if promote is not checked", async () => {
await waitFor(() => {
expect(patchTemplateVersion).toBeCalledWith("new-version-id", {
name: "v1.0",
message: "",
})
})
expect(updateActiveTemplateVersion).toBeCalledTimes(0)
})
test("Patch request is not send when the name is not updated", async () => {
test("Patch request is not send when there are no changes", async () => {
const MockTemplateVersionWithEmptyMessage = {
...MockTemplateVersion,
message: "",
}
const user = userEvent.setup()
renderWithAuth(<TemplateVersionEditorPage />, {
extraRoutes: [
@@ -155,10 +164,11 @@ test("Patch request is not send when the name is not updated", async () => {
jest.spyOn(api, "uploadTemplateFile").mockResolvedValueOnce({ hash: "hash" })
jest
.spyOn(api, "createTemplateVersion")
.mockResolvedValueOnce(MockTemplateVersion)
jest
.spyOn(api, "getTemplateVersion")
.mockResolvedValue({ ...MockTemplateVersion, id: "new-version-id" })
.mockResolvedValueOnce(MockTemplateVersionWithEmptyMessage)
jest.spyOn(api, "getTemplateVersion").mockResolvedValue({
...MockTemplateVersionWithEmptyMessage,
id: "new-version-id",
})
jest
.spyOn(api, "watchBuildLogsByTemplateVersionId")
.mockImplementation((_, options) => {
@@ -174,7 +184,7 @@ test("Patch request is not send when the name is not updated", async () => {
// Publish
const patchTemplateVersion = jest
.spyOn(api, "patchTemplateVersion")
.mockResolvedValue(MockTemplateVersion)
.mockResolvedValue(MockTemplateVersionWithEmptyMessage)
await within(topbar).findByText("Success")
const publishButton = within(topbar).getByRole("button", {
name: "Publish version",
@@ -183,7 +193,7 @@ test("Patch request is not send when the name is not updated", async () => {
const publishDialog = await screen.findByTestId("dialog")
// It is using the name from the template version
const nameField = within(publishDialog).getByLabelText("Version name")
expect(nameField).toHaveValue(MockTemplateVersion.name)
expect(nameField).toHaveValue(MockTemplateVersionWithEmptyMessage.name)
// Publish
await user.click(
within(publishDialog).getByRole("button", { name: "Publish" }),
@@ -1,4 +1,5 @@
export type PublishVersionData = {
name: string
message: string
isActiveVersion: boolean
}
@@ -6,6 +6,7 @@ import { Margins } from "components/Margins/Margins"
import {
PageHeader,
PageHeaderCaption,
PageHeaderSubtitle,
PageHeaderTitle,
} from "components/PageHeader/PageHeader"
import { Stack } from "components/Stack/Stack"
@@ -52,6 +53,11 @@ export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
>
<PageHeaderCaption>{t("header.caption")}</PageHeaderCaption>
<PageHeaderTitle>{versionName}</PageHeaderTitle>
{currentVersion &&
currentVersion.message &&
currentVersion.message !== "" && (
<PageHeaderSubtitle>{currentVersion.message}</PageHeaderSubtitle>
)}
</PageHeader>
{!currentFiles && !error && <Loader />}
@@ -387,7 +387,7 @@ export const templateVersionEditorMachine = createMachine(
},
publishingVersion: async (
{ version, templateId },
{ name, isActiveVersion },
{ name, message, isActiveVersion },
) => {
if (!version) {
throw new Error("Version is not set")
@@ -395,10 +395,10 @@ export const templateVersionEditorMachine = createMachine(
if (!templateId) {
throw new Error("Template is not set")
}
const haveChanges = name !== version.name || message !== version.message
await Promise.all([
// Only do a patch if the name is different
name !== version.name
? API.patchTemplateVersion(version.id, { name })
haveChanges
? API.patchTemplateVersion(version.id, { name, message })
: Promise.resolve(),
isActiveVersion
? API.updateActiveTemplateVersion(templateId, {