chore!: remove api.ts unnecessary calls (#22168)

> [!WARNING]  
> The change of the status code from `404` to `204` could break peoples
code downstream. Adding this as a breaking change incase.

Theres a whole ton of noise around failed requests, these are all
unrelated to the actual thing that is broken at hand (and are
confusing).

* Change `/api/v2/organizations/.../templates/.../versions/.../previous`
to return `204` instead of `404` (actually makes more sense because the
content doesn't exist, but the route is found.
* Remove unnecessary calls to `/api/v2/users/me/appearance` when the
user isn't logged in.
* Remove unnecessary calls to `/api/v2/deployment/stats` when the
deployment stats aren't allowed to be seen.
* Various changes to `workspace-sharing` so we don't make unnecessary
calls.

Whats left:

* `/api/v2/users/me` still `401`s on the login page. This persists as
when the user is logged in but tries to reach the sign-in page they
should be redirected to the app, not sign in again.
* `monaco-editor` is still upset... we theoretically could inject an
environment that can serve workers... but eh.

#### Old

```sh
% pnpm playwright:test -g "create workspace with default and required parameters"

> coder-v2@ playwright:test /home/coder/coder/site
> playwright test --config=e2e/playwright.config.ts -g 'create workspace with default and required parameters'

...

Running 2 tests using 1 worker

  ✓  1 …e/setup/addUsersAndLicense.spec.ts:7:5 › setup deployment (8.2s)
     2 ….ts:79:5 › create workspace with default and required parameters
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[response] url=http://localhost:3111/api/v2/users/me/appearance status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[response] url=http://localhost:3111/api/v2/users/me status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[console][error] Failed to load resource: the server responded with a status of 403 (Forbidden)
[response] url=http://localhost:3111/api/v2/deployment/stats status=403 body={"message":"Forbidden.","detail":"You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials."}
[console][error] Failed to load resource: the server responded with a status of 403 (Forbidden)
[response] url=http://localhost:3111/api/v2/deployment/stats status=403 body={"message":"Forbidden.","detail":"You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials."}
[console][error] Failed to load resource: the server responded with a status of 404 (Not Found)
[response] url=http://localhost:3111/api/v2/organizations//provisionerdaemons status=404 body={"message":"Resource not found or you do not have access to this resource"}
[console][error] Failed to load resource: the server responded with a status of 404 (Not Found)
[response] url=http://localhost:3111/api/v2/organizations/default/templates/a4e8096d/versions/agreeable_glenn33/previous status=404 body={"message":"No previous template version found for \"agreeable_glenn33\"."}
[console][warning] Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq
[console][warning] You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[response] url=http://localhost:3111/api/v2/users/me/appearance status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[response] url=http://localhost:3111/api/v2/users/me status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[console][error] Failed to load resource: the server responded with a status of 403 (Forbidden)
[response] url=http://localhost:3111/api/v2/deployment/stats status=403 body={"message":"Forbidden.","detail":"You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials."}
  ✓  2 …5 › create workspace with default and required parameters (7.0s)atus of 403 (Forbidden)
[response] url=http://localhost:3111/api/v2/deployment/stats status=403 body={"message":"Forbidden.","detail":"You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials."}
[console][error] Failed to load resource: the server responded with a status of 403 (Forbidden)
[response] url=http://localhost:3111/api/v2/deployment/stats status=403 body={"message":"Forbidden.","detail":"You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials."}

  2 passed (56.1s)
```

`23 LOL` (Lines of logs)

#### New

```sh
% pnpm playwright:test -g "create workspace with default and required parameters"

> coder-v2@ playwright:test /home/coder/coder/site
> playwright test --config=e2e/playwright.config.ts -g 'create workspace with default and required parameters'

...

Running 2 tests using 1 worker

  ✓  1 …e/setup/addUsersAndLicense.spec.ts:7:5 › setup deployment (8.7s)
     2 ….ts:79:5 › create workspace with default and required parameters
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[response] url=http://localhost:3111/api/v2/users/me/appearance status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[response] url=http://localhost:3111/api/v2/users/me status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[console][warning] Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq
[console][warning] You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker
  ✓  2 …5 › create workspace with default and required parameters (7.1s)atus of 401 (Unauthorized)
[console][error] Failed to load resource: the server responded with a status of 401 (Unauthorized)
[response] url=http://localhost:3111/api/v2/users/me/appearance status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}
[response] url=http://localhost:3111/api/v2/users/me status=401 body={"message":"You are signed out or your session has expired. Please sign in again to continue.","detail":"Cookie \"coder_session_token\" or query parameter must be provided."}

  2 passed (32.0s)
```

`9 LOL` (Lines of logs)
This commit is contained in:
Jake Howell
2026-04-23 06:20:35 +10:00
committed by GitHub
parent 7d044fa598
commit 4caa52844d
12 changed files with 40 additions and 33 deletions
+3
View File
@@ -5468,6 +5468,9 @@ const docTemplate = `{
"schema": { "schema": {
"$ref": "#/definitions/codersdk.TemplateVersion" "$ref": "#/definitions/codersdk.TemplateVersion"
} }
},
"204": {
"description": "No Content"
} }
}, },
"security": [ "security": [
+3
View File
@@ -4853,6 +4853,9 @@
"schema": { "schema": {
"$ref": "#/definitions/codersdk.TemplateVersion" "$ref": "#/definitions/codersdk.TemplateVersion"
} }
},
"204": {
"description": "No Content"
} }
}, },
"security": [ "security": [
+2 -3
View File
@@ -1074,6 +1074,7 @@ func (api *API) templateVersionByOrganizationTemplateAndName(rw http.ResponseWri
// @Param templatename path string true "Template name" // @Param templatename path string true "Template name"
// @Param templateversionname path string true "Template version name" // @Param templateversionname path string true "Template version name"
// @Success 200 {object} codersdk.TemplateVersion // @Success 200 {object} codersdk.TemplateVersion
// @Success 204
// @Router /organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous [get] // @Router /organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous [get]
func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.ResponseWriter, r *http.Request) { func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@@ -1126,9 +1127,7 @@ func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.Res
}) })
if err != nil { if err != nil {
if httpapi.Is404Error(err) { if httpapi.Is404Error(err) {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ rw.WriteHeader(http.StatusNoContent)
Message: fmt.Sprintf("No previous template version found for %q.", templateVersionName),
})
return return
} }
+3 -5
View File
@@ -1980,8 +1980,8 @@ func TestPreviousTemplateVersion(t *testing.T) {
templateAVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) templateAVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, templateAVersion1.ID) coderdtest.CreateTemplate(t, client, user.OrganizationID, templateAVersion1.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateAVersion1.ID) coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateAVersion1.ID)
// Create two versions for the template B to be sure if we try to get the // Create two versions for template B so we can verify that requesting
// previous version of the first version it will returns a 404 // the previous version of the first version returns nil.
templateBVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) templateBVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
templateB := coderdtest.CreateTemplate(t, client, user.OrganizationID, templateBVersion1.ID) templateB := coderdtest.CreateTemplate(t, client, user.OrganizationID, templateBVersion1.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateBVersion1.ID) coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateBVersion1.ID)
@@ -1992,9 +1992,7 @@ func TestPreviousTemplateVersion(t *testing.T) {
defer cancel() defer cancel()
_, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateB.Name, templateBVersion1.Name) _, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateB.Name, templateBVersion1.Name)
var apiErr *codersdk.Error require.ErrorIs(t, err, codersdk.ErrNoPreviousVersion)
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
}) })
t.Run("Previous version found", func(t *testing.T) { t.Run("Previous version found", func(t *testing.T) {
+8
View File
@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/xerrors"
) )
type TemplateVersionWarning string type TemplateVersionWarning string
@@ -280,12 +281,19 @@ func (c *Client) CancelTemplateVersionDryRun(ctx context.Context, version, job u
return nil return nil
} }
// ErrNoPreviousVersion is returned when no previous template version
// exists (the server responds with 204 No Content).
var ErrNoPreviousVersion = xerrors.New("no previous template version")
func (c *Client) PreviousTemplateVersion(ctx context.Context, organization uuid.UUID, templateName, versionName string) (TemplateVersion, error) { func (c *Client) PreviousTemplateVersion(ctx context.Context, organization uuid.UUID, templateName, versionName string) (TemplateVersion, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/templates/%s/versions/%s/previous", organization, templateName, versionName), nil) res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/templates/%s/versions/%s/previous", organization, templateName, versionName), nil)
if err != nil { if err != nil {
return TemplateVersion{}, err return TemplateVersion{}, err
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode == http.StatusNoContent {
return TemplateVersion{}, ErrNoPreviousVersion
}
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return TemplateVersion{}, ReadBodyAsError(res) return TemplateVersion{}, ReadBodyAsError(res)
} }
+4 -3
View File
@@ -632,9 +632,10 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
### Responses ### Responses
| Status | Meaning | Description | Schema | | Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|----------------------------------------------------------------| |--------|-----------------------------------------------------------------|-------------|----------------------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.TemplateVersion](schemas.md#codersdktemplateversion) | | 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.TemplateVersion](schemas.md#codersdktemplateversion) |
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | |
To perform this operation, you must be authenticated. [Learn more](authentication.md). To perform this operation, you must be authenticated. [Learn more](authentication.md).
+9 -17
View File
@@ -1097,25 +1097,17 @@ class ApiMethods {
templateName: string, templateName: string,
versionName: string, versionName: string,
) => { ) => {
try { const response = await this.axios.get<TypesGen.TemplateVersion>(
const response = await this.axios.get<TypesGen.TemplateVersion>( `/api/v2/organizations/${organization}/templates/${templateName}/versions/${versionName}/previous`,
`/api/v2/organizations/${organization}/templates/${templateName}/versions/${versionName}/previous`, );
);
return response.data; // The API returns 204 No Content when there is no previous version
} catch (error) { // (e.g. the first version of a template).
// When there is no previous version, like the first version of a if (response.status === 204) {
// template, the API returns 404 so in this case we can safely return return undefined;
// undefined
const is404 =
isAxiosError(error) && error.response && error.response.status === 404;
if (is404) {
return undefined;
}
throw error;
} }
return response.data;
}; };
/** /**
@@ -18,7 +18,10 @@ const HIDE_DEPLOYMENT_BANNER_PATHS = [
export const DeploymentBanner: FC = () => { export const DeploymentBanner: FC = () => {
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const deploymentStatsQuery = useQuery(deploymentStats()); const deploymentStatsQuery = useQuery({
...deploymentStats(),
enabled: permissions.viewDeploymentStats,
});
const healthQuery = useQuery({ const healthQuery = useQuery({
...health(), ...health(),
enabled: permissions.viewDeploymentConfig, enabled: permissions.viewDeploymentConfig,
@@ -259,7 +259,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
const { data: provisioners } = useQuery({ const { data: provisioners } = useQuery({
...provisionerDaemons(selectedOrg?.id ?? ""), ...provisionerDaemons(selectedOrg?.id ?? ""),
enabled: showOrganizationPicker && Boolean(selectedOrg), enabled: Boolean(showOrganizationPicker) && Boolean(selectedOrg),
}); });
// TODO: Ideally, we would have a backend endpoint that could notify the // TODO: Ideally, we would have a backend endpoint that could notify the
@@ -17,6 +17,7 @@ const OrganizationProvisionerKeysPage: FC = () => {
const { entitlements } = useDashboard(); const { entitlements } = useDashboard();
const provisionerKeyDaemonsQuery = useQuery({ const provisionerKeyDaemonsQuery = useQuery({
...provisionerDaemonGroups(organizationName), ...provisionerDaemonGroups(organizationName),
enabled: !!organization,
select: (data) => select: (data) =>
[...data].sort((a, b) => b.daemons.length - a.daemons.length), [...data].sort((a, b) => b.daemons.length - a.daemons.length),
}); });
@@ -30,6 +30,7 @@ const OrganizationProvisionersPage: FC = () => {
...queryParams, ...queryParams,
limit: 100, limit: 100,
}), }),
enabled: !!organization,
}); });
if (!organization) { if (!organization) {
+1 -3
View File
@@ -96,9 +96,7 @@ export const TasksTable: FC<TasksTableProps> = ({
<Checkbox <Checkbox
disabled={!tasks || tasks.length === 0} disabled={!tasks || tasks.length === 0}
checked={ checked={
tasks && Boolean(tasks) && checkedTaskIds.size === tasks?.length
tasks.length > 0 &&
checkedTaskIds.size === tasks.length
} }
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
if (!tasks || !onCheckChange) { if (!tasks || !onCheckChange) {