chore: improve the setup experience with various fixes (#8130)

* Move updatecheck logs to debug mode

This was causing logs to be emitted immediately after
initial setup, which seemed odd for the user.

* Fix setup page text to say "Create" instead of "Setup" account

* Improve copy on the empty workspaces page

- This view can be seen after the user creates their first workspace, so saying first is bad.
- It should describe what a workspace is, so I modified the description.
- The create from template button wasn't helpful!

* Improve the copy for the empty templates view

- This didn't describe what a template actually is.
- The title had the same problem as workspaces, where first makes no sense.

* Improve text consistency on the Create Template page

* Fix "View activity" displaying for non-Enterprise users

This was causing an exception to be thrown.

* Improve messaging of empty groups view

* Appropriately capitalize Workspace and Template

* Improve Docker template taglines

* Fix types
This commit is contained in:
Kyle Carberry
2023-06-21 18:23:59 -05:00
committed by GitHub
parent a28d422c35
commit 2a492b7008
31 changed files with 84 additions and 79 deletions

View File

@ -145,10 +145,10 @@ func (c *Checker) start() {
diff := time.Until(r.Checked.Add(c.opts.Interval))
if diff > 0 {
c.log.Info(c.ctx, "time until next update check", slog.F("duration", diff))
c.log.Debug(c.ctx, "time until next update check", slog.F("duration", diff))
t.Reset(diff)
} else {
c.log.Info(c.ctx, "time until next update check", slog.F("duration", c.opts.Interval))
c.log.Debug(c.ctx, "time until next update check", slog.F("duration", c.opts.Interval))
}
for {
@ -164,7 +164,7 @@ func (c *Checker) start() {
c.notifyIfNewer(r, rr)
r = rr
}
c.log.Info(c.ctx, "time until next update check", slog.F("duration", c.opts.Interval))
c.log.Debug(c.ctx, "time until next update check", slog.F("duration", c.opts.Interval))
t.Reset(c.opts.Interval)
case <-c.ctx.Done():
return
@ -176,7 +176,7 @@ func (c *Checker) update() (r Result, err error) {
ctx, cancel := context.WithTimeout(c.ctx, c.opts.UpdateTimeout)
defer cancel()
c.log.Info(c.ctx, "checking for update")
c.log.Debug(c.ctx, "checking for update")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.opts.URL, nil)
if err != nil {
return r, xerrors.Errorf("new request: %w", err)
@ -203,7 +203,7 @@ func (c *Checker) update() (r Result, err error) {
Version: rr.GetTagName(),
URL: rr.GetHTMLURL(),
}
c.log.Info(ctx, "update check result", slog.F("latest_version", r.Version))
c.log.Debug(ctx, "update check result", slog.F("latest_version", r.Version))
b, err := json.Marshal(r)
if err != nil {

View File

@ -54,7 +54,7 @@ Coder with Docker has the following advantages:
<img src="../images/platforms/docker/login.png">
Then navigate to `Templates > docker > Create workspace`
Then navigate to `Templates > docker > Create Workspace`
<img src="../images/platforms/docker/create-workspace.png">

View File

@ -2,7 +2,7 @@
With Coder, you can deploy workspaces in additional Kubernetes clusters using different [authentication methods](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication) in the Terraform provider.
![Region picker in "Create workspace" screen](../../images/platforms/kubernetes/region-picker.png)
![Region picker in "Create Workspace" screen](../../images/platforms/kubernetes/region-picker.png)
## Option 1) Kubernetes contexts and kubeconfig

View File

@ -88,7 +88,7 @@ To support any infrastructure and software stack, Coder provides a generic appro
> Be sure to replace `YOUR_ACCESS_URL` with your Coder access url (e.g. https://coder.example.com) and `YOUR_TEMPLATE` with the name of your template.
1. Optional: pre-fill parameter values in the "Create workspace" page
1. Optional: pre-fill parameter values in the "Create Workspace" page
This can be used to pre-fill the git repo URL, disk size, image, etc.

View File

@ -1,6 +1,6 @@
---
name: Develop in Docker with a dotfiles URL
description: Run workspaces on a Docker host using registry images
description: Develop inside Docker containers using your local daemon
tags: [local, docker]
icon: /icon/docker.png
---

View File

@ -1,6 +1,6 @@
---
name: Develop in Docker
description: Run workspaces on a Docker host using registry images
description: Develop inside Docker containers using your local daemon
tags: [local, docker]
icon: /icon/docker.png
---

View File

@ -2,12 +2,12 @@ import { Page } from "@playwright/test"
import path from "path"
export const buttons = {
starterTemplates: "Starter templates",
starterTemplates: "Starter Templates",
dockerTemplate: "Develop in Docker",
useTemplate: "Use template",
createTemplate: "Create template",
createWorkspace: "Create workspace",
submitCreateWorkspace: "Create workspace",
useTemplate: "Create Workspace",
createTemplate: "Create Template",
createWorkspace: "Create Workspace",
submitCreateWorkspace: "Create Workspace",
stopWorkspace: "Stop",
startWorkspace: "Start",
}

View File

@ -7,7 +7,7 @@ import { MouseEvent, useState } from "react"
export interface TableRowMenuProps<TData> {
data: TData
menuItems: Array<{
label: string
label: React.ReactNode
disabled: boolean
onClick: (data: TData) => void
}>
@ -45,9 +45,9 @@ export const TableRowMenu = <T,>({
open={Boolean(anchorEl)}
onClose={handleClose}
>
{menuItems.map((item) => (
{menuItems.map((item, index) => (
<MenuItem
key={item.label}
key={index}
disabled={item.disabled}
onClick={() => {
handleClose()

View File

@ -106,7 +106,7 @@ const CreateWorkspaceButton: FC<{
component={RouterLink}
to={`/templates/${templateName}/workspace`}
>
Create workspace
Create Workspace
</Button>
)

View File

@ -11,7 +11,7 @@ const Language = {
workspaceTooltipTitle: "What is a workspace?",
workspaceTooltipText:
"A workspace is your development environment in the cloud. It includes the infrastructure and tools you need to work on your project.",
workspaceTooltipLink1: "Create workspaces",
workspaceTooltipLink1: "Create Workspaces",
workspaceTooltipLink2: "Connect with SSH",
workspaceTooltipLink3: "Editors and IDEs",
}

View File

@ -28,6 +28,7 @@ Editable.args = {
users: [MockUser, MockUser2],
roles: MockAssignableSiteRoles,
canEditUsers: true,
canViewActivity: true,
}
export const Empty = Template.bind({})

View File

@ -22,6 +22,7 @@ export interface UsersTableProps {
roles?: TypesGen.AssignableRoles[]
isUpdatingUserRoles?: boolean
canEditUsers?: boolean
canViewActivity?: boolean
isLoading?: boolean
onSuspendUser: (user: TypesGen.User) => void
onActivateUser: (user: TypesGen.User) => void
@ -49,6 +50,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
onUpdateUserRoles,
isUpdatingUserRoles,
canEditUsers,
canViewActivity,
isLoading,
isNonInitialPage,
actorID,
@ -78,6 +80,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
roles={roles}
isLoading={isLoading}
canEditUsers={canEditUsers}
canViewActivity={canViewActivity}
isUpdatingUserRoles={isUpdatingUserRoles}
onActivateUser={onActivateUser}
onDeleteUser={onDeleteUser}

View File

@ -15,6 +15,7 @@ import { TableLoaderSkeleton } from "../TableLoader/TableLoader"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
import { EditRolesButton } from "components/EditRolesButton/EditRolesButton"
import { Stack } from "components/Stack/Stack"
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges"
const isOwnerRole = (role: TypesGen.Role): boolean => {
return role.name === "owner"
@ -34,6 +35,7 @@ interface UsersTableBodyProps {
isUpdatingUserRoles?: boolean
canEditUsers?: boolean
isLoading?: boolean
canViewActivity?: boolean
onSuspendUser: (user: TypesGen.User) => void
onDeleteUser: (user: TypesGen.User) => void
onListWorkspaces: (user: TypesGen.User) => void
@ -62,6 +64,7 @@ export const UsersTableBody: FC<
onUpdateUserRoles,
isUpdatingUserRoles,
canEditUsers,
canViewActivity,
isLoading,
isNonInitialPage,
actorID,
@ -167,14 +170,18 @@ export const UsersTableBody: FC<
(user.status === "active"
? [
{
label: t("suspendMenuItem"),
label: t(
"suspendMenuItem",
) as React.ReactNode,
onClick: onSuspendUser,
disabled: false,
},
]
: [
{
label: t("activateMenuItem"),
label: t(
"activateMenuItem",
) as React.ReactNode,
onClick: onActivateUser,
disabled: false,
},
@ -196,9 +203,14 @@ export const UsersTableBody: FC<
disabled: false,
},
{
label: "View activity",
label: (
<>
View activity
{!canViewActivity && <EnterpriseBadge />}
</>
),
onClick: onViewActivity,
disabled: false,
disabled: !canViewActivity,
},
)
}

View File

@ -2,12 +2,12 @@
"title": "Create Template",
"form": {
"generalInfo": {
"title": "General info",
"description": "The name is used to identify the template in URLs and the API. It must be unique within your organization."
"title": "General",
"description": "The name is used to identify the template in URLs and the API."
},
"displayInfo": {
"title": "Display info",
"description": "Give your template a friendly name, description, and icon."
"title": "Display",
"description": "A friendly name, description, and icon to help developers identify your template."
},
"schedule": {
"title": "Schedule",

View File

@ -2,7 +2,7 @@
"templateLabel": "Template",
"nameLabel": "Workspace Name",
"ownerLabel": "Owner",
"createWorkspace": "Create workspace",
"createWorkspace": "Create Workspace",
"validationNumberLesserThan": "Value must be greater than {{min}}.",
"validationNumberGreaterThan": "Value must be lesser than {{max}}.",
"validationNumberNotInRange": "Value must be between {{min}} and {{max}}.",

View File

@ -1,6 +1,6 @@
{
"actions": {
"viewSourceCode": "View source code",
"useTemplate": "Use template"
"useTemplate": "Use Template"
}
}

View File

@ -1,6 +1,6 @@
{
"title": "Starter Templates",
"subtitle": "Pick one of the built-in templates to start using Coder",
"subtitle": "Import a built-in template to start developing in the cloud",
"filterCaption": "Filter",
"tags": {
"all": "All templates",

View File

@ -1,11 +1,11 @@
{
"generalInfo": {
"title": "General info",
"title": "General",
"description": "The name is used to identify the template in URLs and the API. It must be unique."
},
"displayInfo": {
"title": "Display info",
"description": "Give your template a friendly name, description, and icon."
"title": "Display",
"description": "A friendly name, description, and icon to help developers identify your template."
},
"schedule": {
"title": "Schedule",

View File

@ -33,11 +33,11 @@
"allowUsersCancelHelperText": "If checked, users may be able to corrupt their workspace.",
"generalInfo": {
"title": "General info",
"description": "The name is used to identify the template in URLs and the API. It must be unique within your organization."
"description": "The name is used to identify the template in URLs and the API."
},
"displayInfo": {
"title": "Display info",
"description": "Give your template a friendly name, description, and icon."
"description": "A friendly name, description, and icon to help developers identify your template."
},
"schedule": {
"title": "Schedule",

View File

@ -4,7 +4,7 @@
"getTemplatesError": "Something went wrong fetching templates."
},
"empty": {
"message": "Create your first template",
"message": "Create a Template",
"descriptionWithoutPermissions": "Contact your Coder administrator to create a template. You can share the code below."
}
}

View File

@ -1,7 +1,7 @@
{
"emptyCreateWorkspaceMessage": "Create your first workspace",
"emptyCreateWorkspaceDescription": "Start editing your source code and building your software",
"createFromTemplateButton": "Create from template",
"emptyCreateWorkspaceMessage": "Create a Workspace",
"emptyCreateWorkspaceDescription": "A workspace is your personal, customizable development environment in the cloud",
"createFromTemplateButton": "Select a Template",
"emptyResultsMessage": "No results matched your search",
"emptyPageMessage": "No results on this page",
"updateVersionError": "Error on update workspace version"

View File

@ -223,8 +223,8 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
<HorizontalForm onSubmit={form.handleSubmit}>
{/* General info */}
<FormSection
title="General info"
description="The name is used to identify the template in URLs and the API. It must be unique within your organization."
title="General"
description="The name is used to identify the template in URLs and the API."
>
<FormFields>
{starterTemplate ? (
@ -255,8 +255,8 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
{/* Display info */}
<FormSection
title="Display info"
description="Give your template a friendly name, description, and icon."
title="Display"
description="A friendly name, description, and icon to help developers identify your template."
>
<FormFields>
<TextField
@ -349,7 +349,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
/>
<Stack spacing={0.5}>
<strong>
Allow users to autostart workspaces on a schedule.
Allow users to automatically start workspaces on a schedule.
</strong>
</Stack>
</Stack>

View File

@ -141,7 +141,7 @@ describe("CreateWorkspacePage", () => {
renderCreateWorkspacePage()
await waitForLoaderToBeRemoved()
const element = await screen.findByText("Create workspace")
const element = await screen.findByText("Create Workspace")
expect(element).toBeDefined()
const secondParameter = await screen.findByText(
MockTemplateVersionParameter2.description,

View File

@ -184,7 +184,7 @@ export const CreateWorkspacePageView: FC<
{/* General info */}
<FormSection
title="General info"
title="General"
description="The template and name of your new workspace."
>
<FormFields>
@ -206,8 +206,8 @@ export const CreateWorkspacePageView: FC<
{/* Workspace owner */}
{props.canCreateForUser && (
<FormSection
title="Workspace owner"
description="The user that is going to own this workspace. If you are admin, you can create workspace for others."
title="Workspace Owner"
description="Only admins can create workspace for other users."
>
<FormFields>
<UserAutocomplete

View File

@ -44,8 +44,8 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
<ChooseOne>
<Cond condition={!isTemplateRBACEnabled}>
<Paywall
message="User groups"
description="Organize the users into groups and manage their permissions. To use this feature, you have to upgrade your account."
message="Groups"
description="Organize users into groups with restricted access to templates. You need an Enterprise license to use this feature."
cta={
<Stack direction="row" alignItems="center">
<Button
@ -55,7 +55,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
startIcon={<ArrowRightAltOutlined />}
variant="contained"
>
See how to upgrade
Learn about Enterprise
</Button>
<Link

View File

@ -19,7 +19,7 @@ export const Language = {
emailInvalid: "Please enter a valid email address.",
emailRequired: "Please enter an email address.",
passwordRequired: "Please enter a password.",
create: "Setup account",
create: "Create account",
welcomeMessage: <>Welcome to Coder</>,
}

View File

@ -259,7 +259,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
/>
<Stack spacing={0.5}>
<strong>
Allow users to autostart workspaces on a schedule.
Allow users to automatically start workspaces on a schedule.
</strong>
</Stack>
</Stack>

View File

@ -50,17 +50,15 @@ export const EmptyTemplates: FC<{
message={t("empty.message")}
description={
<>
You can create a template using our starter templates or{" "}
<Link component={RouterLink} to="/new">
uploading a template
</Link>
. You can also{" "}
Templates are written in Terraform and describe the infrastructure
for workspaces (e.g., docker_container, aws_instance,
kubernetes_pod). Select a starter template below or
<Link
href="https://coder.com/docs/coder-oss/latest/templates#add-a-template"
target="_blank"
rel="noreferrer"
>
use the CLI
create your own
</Link>
.
</>

View File

@ -1,5 +1,4 @@
import Button from "@mui/material/Button"
import Link from "@mui/material/Link"
import { makeStyles } from "@mui/styles"
import Table from "@mui/material/Table"
import TableBody from "@mui/material/TableBody"
@ -127,7 +126,7 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => {
navigate(`/templates/${template.name}/workspace`)
}}
>
Use template
Create Workspace
</Button>
</TableCell>
</TableRow>
@ -151,7 +150,7 @@ export const TemplatesPageView: FC<
actions={
<Maybe condition={permissions.createTemplates}>
<Button component={RouterLink} to="/starter-templates">
Starter templates
Starter Templates
</Button>
<Button
startIcon={<AddIcon />}
@ -159,7 +158,7 @@ export const TemplatesPageView: FC<
to="new"
variant="contained"
>
Add template
Create Template
</Button>
</Maybe>
}
@ -172,21 +171,7 @@ export const TemplatesPageView: FC<
</PageHeaderTitle>
<Maybe condition={Boolean(templates && templates.length > 0)}>
<PageHeaderSubtitle>
Choose a template to create a new workspace
{permissions.createTemplates ? (
<>
, or{" "}
<Link
href="https://coder.com/docs/coder-oss/latest/templates#add-a-template"
target="_blank"
>
manage templates
</Link>{" "}
from the CLI.
</>
) : (
"."
)}
Select a template to create a workspace.
</PageHeaderSubtitle>
</Maybe>
</PageHeader>

View File

@ -19,6 +19,7 @@ import { pageTitle } from "../../utils/page"
import { UsersPageView } from "./UsersPageView"
import { useStatusFilterMenu } from "./UsersFilter"
import { useFilter } from "components/Filter/filter"
import { useDashboard } from "components/Dashboard/DashboardProvider"
export const Language = {
suspendDialogTitle: "Suspend user",
@ -35,6 +36,7 @@ const getSelectedUser = (id: string, users?: User[]) =>
export const UsersPage: FC<{ children?: ReactNode }> = () => {
const navigate = useNavigate()
const searchParamsResult = useSearchParams()
const { entitlements } = useDashboard()
const [searchParams, setSearchParams] = searchParamsResult
const filter = searchParams.get("filter") ?? ""
const [usersState, usersSend] = useMachine(usersMachine, {
@ -148,6 +150,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
isUpdatingUserRoles={usersState.matches("updatingUserRoles")}
isLoading={isLoading}
canEditUsers={canEditUsers}
canViewActivity={entitlements.features.audit_log.enabled}
paginationRef={paginationRef}
isNonInitialPage={nonInitialPage(searchParams)}
actorID={me.id}

View File

@ -16,6 +16,7 @@ export interface UsersPageViewProps {
roles?: TypesGen.AssignableRoles[]
isUpdatingUserRoles?: boolean
canEditUsers?: boolean
canViewActivity?: boolean
isLoading?: boolean
onSuspendUser: (user: TypesGen.User) => void
onDeleteUser: (user: TypesGen.User) => void
@ -46,6 +47,7 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
onUpdateUserRoles,
isUpdatingUserRoles,
canEditUsers,
canViewActivity,
isLoading,
filterProps,
paginationRef,
@ -75,6 +77,7 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
onUpdateUserRoles={onUpdateUserRoles}
isUpdatingUserRoles={isUpdatingUserRoles}
canEditUsers={canEditUsers}
canViewActivity={canViewActivity}
isLoading={isLoading}
isNonInitialPage={isNonInitialPage}
actorID={actorID}