chore: don't require an organization to read starter templates (#14190)

This commit is contained in:
Kayla Washburn-Love
2024-08-06 11:26:26 -06:00
committed by GitHub
parent fab196043e
commit ff785588fe
14 changed files with 165 additions and 28 deletions
+29
View File
@@ -3000,6 +3000,7 @@ const docTemplate = `{
],
"summary": "Get template examples by organization",
"operationId": "get-template-examples-by-organization",
"deprecated": true,
"parameters": [
{
"type": "string",
@@ -3421,6 +3422,34 @@ const docTemplate = `{
}
}
},
"/templates/examples": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Templates"
],
"summary": "Get template examples",
"operationId": "get-template-examples",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.TemplateExample"
}
}
}
}
}
},
"/templates/{template}": {
"get": {
"security": [
+25
View File
@@ -2630,6 +2630,7 @@
"tags": ["Templates"],
"summary": "Get template examples by organization",
"operationId": "get-template-examples-by-organization",
"deprecated": true,
"parameters": [
{
"type": "string",
@@ -3005,6 +3006,30 @@
}
}
},
"/templates/examples": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Templates"],
"summary": "Get template examples",
"operationId": "get-template-examples",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.TemplateExample"
}
}
}
}
}
},
"/templates/{template}": {
"get": {
"security": [
+2 -1
View File
@@ -871,7 +871,7 @@ func New(options *Options) *API {
r.Route("/templates", func(r chi.Router) {
r.Post("/", api.postTemplateByOrganization)
r.Get("/", api.templatesByOrganization())
r.Get("/examples", api.templateExamples)
r.Get("/examples", api.templateExamplesByOrganization)
r.Route("/{templatename}", func(r chi.Router) {
r.Get("/", api.templateByOrganizationAndName)
r.Route("/versions/{templateversionname}", func(r chi.Router) {
@@ -915,6 +915,7 @@ func New(options *Options) *API {
apiKeyMiddleware,
)
r.Get("/", api.fetchTemplates(nil))
r.Get("/examples", api.templateExamples)
r.Route("/{template}", func(r chi.Router) {
r.Use(
httpmw.ExtractTemplateParam(options.Database),
+29 -1
View File
@@ -821,7 +821,8 @@ func (api *API) templateDAUs(rw http.ResponseWriter, r *http.Request) {
// @Param organization path string true "Organization ID" format(uuid)
// @Success 200 {array} codersdk.TemplateExample
// @Router /organizations/{organization}/templates/examples [get]
func (api *API) templateExamples(rw http.ResponseWriter, r *http.Request) {
// @Deprecated Use /templates/examples instead
func (api *API) templateExamplesByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
organization = httpmw.OrganizationParam(r)
@@ -844,6 +845,33 @@ func (api *API) templateExamples(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, ex)
}
// @Summary Get template examples
// @ID get-template-examples
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Success 200 {array} codersdk.TemplateExample
// @Router /templates/examples [get]
func (api *API) templateExamples(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if !api.Authorize(r, policy.ActionRead, rbac.ResourceTemplate.AnyOrganization()) {
httpapi.ResourceNotFound(rw)
return
}
ex, err := examples.List()
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching examples.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, ex)
}
func (api *API) convertTemplates(templates []database.Template) []codersdk.Template {
apiTemplates := make([]codersdk.Template, 0, len(templates))
+3 -3
View File
@@ -1097,17 +1097,17 @@ func TestPreviousTemplateVersion(t *testing.T) {
})
}
func TestTemplateExamples(t *testing.T) {
func TestStarterTemplates(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
ex, err := client.TemplateExamples(ctx, user.OrganizationID)
ex, err := client.StarterTemplates(ctx)
require.NoError(t, err)
ls, err := examples.List()
require.NoError(t, err)
+10 -3
View File
@@ -472,9 +472,16 @@ type AgentStatsReportResponse struct {
TxBytes int64 `json:"tx_bytes"`
}
// TemplateExamples lists example templates embedded in coder.
func (c *Client) TemplateExamples(ctx context.Context, organizationID uuid.UUID) ([]TemplateExample, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/templates/examples", organizationID), nil)
// TemplateExamples lists example templates available in Coder.
//
// Deprecated: Use StarterTemplates instead.
func (c *Client) TemplateExamples(ctx context.Context, _ uuid.UUID) ([]TemplateExample, error) {
return c.StarterTemplates(ctx)
}
// StarterTemplates lists example templates available in Coder.
func (c *Client) StarterTemplates(ctx context.Context) ([]TemplateExample, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/templates/examples", nil)
if err != nil {
return nil, err
}
+54
View File
@@ -761,6 +761,60 @@ Status Code **200**
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get template examples
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templates/examples \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templates/examples`
### Example responses
> 200 Response
```json
[
{
"description": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"markdown": "string",
"name": "string",
"tags": ["string"],
"url": "string"
}
]
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.TemplateExample](schemas.md#codersdktemplateexample) |
<h3 id="get-template-examples-responseschema">Response Schema</h3>
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| --------------- | ------------ | -------- | ------------ | ----------- |
| `[array item]` | array | false | | |
| `» description` | string | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» markdown` | string | false | | |
| `» name` | string | false | | |
| `» tags` | array | false | | |
| `» url` | string | false | | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get template metadata by ID
### Code samples
+3 -7
View File
@@ -483,7 +483,7 @@ class ApiMethods {
};
deleteToken = async (keyId: string): Promise<void> => {
await this.axios.delete("/api/v2/users/me/keys/" + keyId);
await this.axios.delete(`/api/v2/users/me/keys/${keyId}`);
};
createToken = async (
@@ -1754,12 +1754,8 @@ class ApiMethods {
/**
* @param organization Can be the organization's ID or name
*/
getTemplateExamples = async (
organization: string,
): Promise<TypesGen.TemplateExample[]> => {
const response = await this.axios.get(
`/api/v2/organizations/${organization}/templates/examples`,
);
getTemplateExamples = async (): Promise<TypesGen.TemplateExample[]> => {
const response = await this.axios.get(`/api/v2/templates/examples`);
return response.data;
};
+3 -6
View File
@@ -112,13 +112,10 @@ export const setGroupRole = (
};
};
export const templateExamples = (organizationId: string) => {
export const templateExamples = () => {
return {
queryKey: [
...getTemplatesByOrganizationQueryKey(organizationId),
"examples",
],
queryFn: () => API.getTemplateExamples(organizationId),
queryKey: ["templates", "examples"],
queryFn: () => API.getTemplateExamples(),
};
};
@@ -31,7 +31,7 @@ export const ImportStarterTemplateView: FC<CreateTemplatePageViewProps> = ({
const { multiple_organizations: organizationsEnabled } =
useFeatureVisibility();
const [searchParams] = useSearchParams();
const templateExamplesQuery = useQuery(templateExamples("default"));
const templateExamplesQuery = useQuery(templateExamples());
const templateExample = templateExamplesQuery.data?.find(
(e) => e.id === searchParams.get("exampleId")!,
);
@@ -11,7 +11,7 @@ import { StarterTemplatesPageView } from "./StarterTemplatesPageView";
const CreateTemplatesGalleryPage: FC = () => {
const { experiments } = useDashboard();
const templateExamplesQuery = useQuery(templateExamples("default"));
const templateExamplesQuery = useQuery(templateExamples());
const starterTemplatesByTag = templateExamplesQuery.data
? // Currently, the scratch template should not be displayed on the starter templates page.
getTemplatesByTag(removeScratchExample(templateExamplesQuery.data))
@@ -8,7 +8,7 @@ import { StarterTemplatePageView } from "./StarterTemplatePageView";
const StarterTemplatePage: FC = () => {
const { exampleId } = useParams() as { exampleId: string };
const templateExamplesQuery = useQuery(templateExamples("default"));
const templateExamplesQuery = useQuery(templateExamples());
const starterTemplate = templateExamplesQuery.data?.find(
(example) => example.id === exampleId,
);
@@ -11,7 +11,7 @@ export const TemplatesPage: FC = () => {
const templatesQuery = useQuery(templates());
const examplesQuery = useQuery({
...templateExamples("default"),
...templateExamples(),
enabled: permissions.createTemplates,
});
const error = templatesQuery.error || examplesQuery.error;
+3 -3
View File
@@ -47,9 +47,6 @@ export const handlers = [
http.get("/api/v2/organizations/:organizationId", () => {
return HttpResponse.json(M.MockOrganization);
}),
http.get("/api/v2/organizations/:organizationId/templates/examples", () => {
return HttpResponse.json([M.MockTemplateExample, M.MockTemplateExample2]);
}),
http.get(
"/api/v2/organizations/:organizationId/templates/:templateId",
() => {
@@ -81,6 +78,9 @@ export const handlers = [
),
// templates
http.get("/api/v2/templates/examples", () => {
return HttpResponse.json([M.MockTemplateExample, M.MockTemplateExample2]);
}),
http.get("/api/v2/templates/:templateId", () => {
return HttpResponse.json(M.MockTemplate);
}),