Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Add create template from the UI #5427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 61 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
300a763
feat: add examples to api
f0ssel Dec 6, 2022
da9fc00
add support for example id in route
f0ssel Dec 7, 2022
a9f04f3
move files
f0ssel Dec 7, 2022
90e6a9d
fix existing tests
f0ssel Dec 7, 2022
7084ceb
add tests
f0ssel Dec 7, 2022
50fb405
more tests
f0ssel Dec 7, 2022
b00a3b9
more tests
f0ssel Dec 7, 2022
86350be
Display starter templates
BrunoQuaresma Dec 8, 2022
c3b2e67
Add styles to the template card
BrunoQuaresma Dec 8, 2022
c144866
Mock entity and handler
BrunoQuaresma Dec 8, 2022
c133cc3
Add storybook
BrunoQuaresma Dec 8, 2022
e7aec29
Add loader
BrunoQuaresma Dec 8, 2022
3062814
Add tests
BrunoQuaresma Dec 8, 2022
d7455c1
Add basic page to starter template
BrunoQuaresma Dec 8, 2022
193c930
Add buttons
BrunoQuaresma Dec 8, 2022
9a5b1de
Fix title
BrunoQuaresma Dec 8, 2022
c820f4b
Addd storybook
BrunoQuaresma Dec 8, 2022
9829ad0
Add test
BrunoQuaresma Dec 8, 2022
c9d1c7a
Use translation
BrunoQuaresma Dec 8, 2022
85266d0
add icon and tag parsing
f0ssel Dec 9, 2022
9f3108f
remove extra test work
f0ssel Dec 9, 2022
0dce958
Merge branch 'f0ssel/examples' of github.com:coder/coder into bq/fe-e…
BrunoQuaresma Dec 9, 2022
3091bea
Merge branch 'main' of github.com:coder/coder into bq/fe-examples
BrunoQuaresma Dec 9, 2022
79442ef
Add icons to page header
BrunoQuaresma Dec 9, 2022
3529c74
Add filters
BrunoQuaresma Dec 9, 2022
efa624c
Improve markdown code
BrunoQuaresma Dec 9, 2022
e05a8dc
Merge branch 'main' of github.com:coder/coder into bq/fe-examples
BrunoQuaresma Dec 12, 2022
e05d148
Add filter
BrunoQuaresma Dec 12, 2022
4fc75b7
Add basic create template form structure
BrunoQuaresma Dec 12, 2022
9805c2f
Add translation
BrunoQuaresma Dec 12, 2022
439ec15
Add services and actions into machine
BrunoQuaresma Dec 12, 2022
ce82a85
Pre-fill info from example data
BrunoQuaresma Dec 12, 2022
36282fc
Create Icon fiels and remove extra console.log
BrunoQuaresma Dec 12, 2022
5b01321
Add basic API for template creation
BrunoQuaresma Dec 13, 2022
ca606a4
Merge branch 'main' of github.com:coder/coder into bq/fe-examples
BrunoQuaresma Dec 13, 2022
521803f
Fix create template from example id
BrunoQuaresma Dec 13, 2022
d39cabe
Show parameters
BrunoQuaresma Dec 14, 2022
fe71988
Add upload
BrunoQuaresma Dec 14, 2022
a9f3e18
Fix steps
BrunoQuaresma Dec 14, 2022
9ae5ed1
Update layout
BrunoQuaresma Dec 14, 2022
e19bd13
Merge branch 'main' of github.com:coder/coder into bq/fe-examples
BrunoQuaresma Dec 14, 2022
cc3a2cb
Update verbiage
BrunoQuaresma Dec 14, 2022
273a711
Merge branch 'main' of github.com:coder/coder into bq/fe-examples
BrunoQuaresma Dec 16, 2022
2d8430b
Use data
BrunoQuaresma Dec 16, 2022
a2e07fb
Show logs on error
BrunoQuaresma Dec 16, 2022
80a2989
Merge branch 'main' of github.com:coder/coder into bq/fe-examples
BrunoQuaresma Dec 19, 2022
43c8faa
Add templates link
BrunoQuaresma Dec 20, 2022
e64db78
Fix upload
BrunoQuaresma Dec 20, 2022
cec4b00
Add link to starter templates
BrunoQuaresma Dec 20, 2022
8b0f95b
Add empty state
BrunoQuaresma Dec 20, 2022
6de5c52
Create empty state and experimental tags
BrunoQuaresma Dec 20, 2022
228bc58
Add help tooltip
BrunoQuaresma Dec 20, 2022
155e901
Fix tests
BrunoQuaresma Dec 20, 2022
4edfed8
Lazy load starter template page
BrunoQuaresma Dec 20, 2022
30e017e
Apply suggestions from code review
BrunoQuaresma Dec 21, 2022
98a5f84
No need to trim display name and description
BrunoQuaresma Dec 21, 2022
6aa0bfa
Return undefined it is not n api error
BrunoQuaresma Dec 21, 2022
f8e8d33
Display error
BrunoQuaresma Dec 21, 2022
4b47137
Merge branch 'bq/fe-examples' of github.com:coder/coder into bq/fe-ex…
BrunoQuaresma Dec 21, 2022
be4de6a
Return error
BrunoQuaresma Dec 21, 2022
e75714f
Fix test
BrunoQuaresma Dec 21, 2022
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
38 changes: 38 additions & 0 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ const GitAuthPage = lazy(() => import("./pages/GitAuthPage/GitAuthPage"))
const TemplateVersionPage = lazy(
() => import("./pages/TemplateVersionPage/TemplateVersionPage"),
)
const StarterTemplatesPage = lazy(
() => import("./pages/StarterTemplatesPage/StarterTemplatesPage"),
)
const StarterTemplatePage = lazy(
() => import("pages/StarterTemplatePage/StarterTemplatePage"),
)
const CreateTemplatePage = lazy(
() => import("./pages/CreateTemplatePage/CreateTemplatePage"),
)

export const AppRouter: FC = () => {
const xServices = useContext(XServiceContext)
Expand Down Expand Up @@ -141,6 +150,26 @@ export const AppRouter: FC = () => {
}
/>

<Route path="starter-templates">
<Route
index
element={
<AuthAndFrame>
<StarterTemplatesPage />
</AuthAndFrame>
}
/>

<Route
path=":exampleId"
element={
<AuthAndFrame>
<StarterTemplatePage />
</AuthAndFrame>
}
></Route>
</Route>

<Route path="templates">
<Route
index
Expand All @@ -151,6 +180,15 @@ export const AppRouter: FC = () => {
}
/>

<Route
path="new"
element={
<RequireAuth>
<CreateTemplatePage />
</RequireAuth>
}
/>

<Route path=":template">
<Route
index
Expand Down
60 changes: 60 additions & 0 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,37 @@ export const getPreviousTemplateVersionByName = async (
}
}

export const createTemplateVersion = async (
organizationId: string,
data: TypesGen.CreateTemplateVersionRequest,
): Promise<TypesGen.TemplateVersion> => {
const response = await axios.post<TypesGen.TemplateVersion>(
`/api/v2/organizations/${organizationId}/templateversions`,
data,
)
return response.data
}

export const getTemplateVersionParameters = async (
versionId: string,
): Promise<TypesGen.Parameter[]> => {
const response = await axios.get(
`/api/v2/templateversions/${versionId}/parameters`,
)
return response.data
}

export const createTemplate = async (
organizationId: string,
data: TypesGen.CreateTemplateRequest,
): Promise<TypesGen.Template> => {
const response = await axios.post(
`/api/v2/organizations/${organizationId}/templates`,
data,
)
return response.data
}

export const updateTemplateMeta = async (
templateId: string,
data: TypesGen.UpdateTemplateMeta,
Expand Down Expand Up @@ -703,3 +734,32 @@ export const setServiceBanner = async (
const response = await axios.put(`/api/v2/service-banner`, b)
return response.data
}

export const getTemplateExamples = async (
organizationId: string,
): Promise<TypesGen.TemplateExample[]> => {
const response = await axios.get(
`/api/v2/organizations/${organizationId}/templates/examples`,
)
return response.data
}

export const uploadTemplateFile = async (
file: File,
): Promise<TypesGen.UploadResponse> => {
const response = await axios.post("/api/v2/files", file, {
headers: {
"Content-Type": "application/x-tar",
},
})
return response.data
}

export const getTemplateVersionLogs = async (
versionId: string,
): Promise<TypesGen.ProvisionerJobLog[]> => {
const response = await axios.get<TypesGen.ProvisionerJobLog[]>(
`/api/v2/templateversions/${versionId}/logs`,
)
return response.data
}
4 changes: 4 additions & 0 deletions site/src/api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export const mapApiErrorToFieldErrors = (
return result
}

export const isApiValidationError = (error: unknown): error is ApiError => {
return isApiError(error) && hasApiFieldErrors(error)
}

/**
*
* @param error
Expand Down
113 changes: 113 additions & 0 deletions site/src/components/IconField/IconField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Button from "@material-ui/core/Button"
import InputAdornment from "@material-ui/core/InputAdornment"
import Popover from "@material-ui/core/Popover"
import TextField, { TextFieldProps } from "@material-ui/core/TextField"
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows"
import { useRef, FC, useState } from "react"
import Picker from "@emoji-mart/react"
import { makeStyles } from "@material-ui/core/styles"
import { colors } from "theme/colors"
import { useTranslation } from "react-i18next"
import data from "@emoji-mart/data/sets/14/twitter.json"

export const IconField: FC<
TextFieldProps & { onPickEmoji: (value: string) => void }
> = ({ onPickEmoji, ...textFieldProps }) => {
if (
typeof textFieldProps.value !== "string" &&
typeof textFieldProps.value !== "undefined"
) {
throw new Error(`Invalid icon value "${typeof textFieldProps.value}"`)
}

const styles = useStyles()
const emojiButtonRef = useRef<HTMLButtonElement>(null)
const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false)
const { t } = useTranslation("templateSettingsPage")
const hasIcon = textFieldProps.value && textFieldProps.value !== ""

return (
<div className={styles.iconField}>
<TextField
{...textFieldProps}
fullWidth
label={t("iconLabel")}
variant="outlined"
InputProps={{
endAdornment: hasIcon ? (
<InputAdornment position="end" className={styles.adornment}>
<img
alt=""
src={textFieldProps.value}
// This prevent browser to display the ugly error icon if the
// image path is wrong or user didn't finish typing the url
onError={(e) => (e.currentTarget.style.display = "none")}
onLoad={(e) => (e.currentTarget.style.display = "inline")}
/>
</InputAdornment>
) : undefined,
}}
/>

<Button
fullWidth
ref={emojiButtonRef}
variant="outlined"
size="small"
endIcon={<OpenDropdown />}
onClick={() => {
setIsEmojiPickerOpen((v) => !v)
}}
>
{t("selectEmoji")}
</Button>

<Popover
id="emoji"
open={isEmojiPickerOpen}
anchorEl={emojiButtonRef.current}
onClose={() => {
setIsEmojiPickerOpen(false)
}}
>
<Picker
theme="dark"
data={data}
onEmojiSelect={(emojiData) => {
// See: https://github.com/missive/emoji-mart/issues/51#issuecomment-287353222
const value = `/emojis/${emojiData.unified.replace(
/-fe0f$/,
"",
)}.png`
onPickEmoji(value)
setIsEmojiPickerOpen(false)
}}
/>
</Popover>
</div>
)
}

const useStyles = makeStyles((theme) => ({
"@global": {
"em-emoji-picker": {
"--rgb-background": theme.palette.background.paper,
"--rgb-input": colors.gray[17],
"--rgb-color": colors.gray[4],
},
},
adornment: {
width: theme.spacing(3),
height: theme.spacing(3),
display: "flex",
alignItems: "center",
justifyContent: "center",

"& img": {
maxWidth: "100%",
},
},
iconField: {
paddingBottom: theme.spacing(0.5),
},
}))
36 changes: 26 additions & 10 deletions site/src/components/Logs/Logs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeStyles } from "@material-ui/core/styles"
import { LogLevel } from "api/typesGenerated"
import dayjs from "dayjs"
import { FC } from "react"
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
Expand All @@ -7,6 +8,7 @@ import { combineClasses } from "../../util/combineClasses"
interface Line {
time: string
output: string
level: LogLevel
}

export interface LogsProps {
Expand All @@ -22,15 +24,17 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({

return (
<div className={combineClasses([className, styles.root])}>
{lines.map((line, idx) => (
<div className={styles.line} key={idx}>
<span className={styles.time}>
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
</span>
<span className={styles.space}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span>{line.output}</span>
</div>
))}
<div className={styles.scrollWrapper}>
{lines.map((line, idx) => (
<div className={combineClasses([styles.line, line.level])} key={idx}>
<span className={styles.time}>
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
</span>
<span className={styles.space}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span>{line.output}</span>
</div>
))}
</div>
</div>
)
}
Expand All @@ -43,13 +47,25 @@ const useStyles = makeStyles((theme) => ({
fontFamily: MONOSPACE_FONT_FAMILY,
fontSize: 13,
wordBreak: "break-all",
padding: theme.spacing(2),
padding: theme.spacing(2, 0),
borderRadius: theme.shape.borderRadius,
overflowX: "auto",
},
scrollWrapper: {
width: "fit-content",
},
line: {
// Whitespace is significant in terminal output for alignment
whiteSpace: "pre",
padding: theme.spacing(0, 3),

"&.error": {
backgroundColor: theme.palette.error.dark,
},

"&.warning": {
backgroundColor: theme.palette.warning.dark,
},
},
space: {
userSelect: "none",
Expand Down
13 changes: 9 additions & 4 deletions site/src/components/Markdown/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ export const Markdown: FC<{ children: string }> = ({ children }) => {
<SyntaxHighlighter
style={darcula}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined
language={match[1] ?? "language-shell"}
language={match[1].toLowerCase() ?? "language-shell"}
useInlineStyles={false}
// Use inline styles does not work correctly
// https://github.com/react-syntax-highlighter/react-syntax-highlighter/issues/329
codeTagProps={{ style: {} }}
{...props}
>
{String(children).replace(/\n$/, "")}
{String(children)}
</SyntaxHighlighter>
) : (
<code className={styles.codeWithoutLanguage} {...props}>
Expand Down Expand Up @@ -135,19 +135,24 @@ const useStyles = makeStyles((theme) => ({
background: theme.palette.background.paperLight,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2, 3),
overflowX: "auto",

"& code": {
color: theme.palette.text.secondary,
},

"& .key, & .property": {
"& .key, & .property, & .inserted, .keyword": {
color: colors.turquoise[7],
},

"& .deleted": {
color: theme.palette.error.light,
},
},
},

codeWithoutLanguage: {
padding: theme.spacing(0.5, 1),
padding: theme.spacing(0.125, 0.5),
background: theme.palette.divider,
borderRadius: 4,
color: theme.palette.text.primary,
Expand Down
Loading