fix(site): display tasks link when no templates contain an AI task (#19184)

Fixes https://github.com/coder/coder/issues/19059

This change ensures we always show the "Tasks" link, even when a deployment doesn't yet have a tasks template set up.
This commit is contained in:
Danielle Maywood
2025-08-05 19:30:17 +01:00
committed by GitHub
parent 760dc8b467
commit cc609cbd53
9 changed files with 7 additions and 65 deletions
-5
View File
@@ -3617,11 +3617,6 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti
return q.db.GetWorkspacesEligibleForTransition(ctx, now)
}
func (q *querier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) {
// Anyone can call HasTemplateVersionsWithAITask.
return q.db.HasTemplateVersionsWithAITask(ctx)
}
func (q *querier) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) {
return insert(q.log, q.auth,
rbac.ResourceApiKey.WithOwner(arg.UserID.String()),
-3
View File
@@ -4826,9 +4826,6 @@ func (s *MethodTestSuite) TestSystemFunctions() {
})
check.Args(j.ID).Asserts(v.RBACObject(tpl), policy.ActionRead).Returns(j)
}))
s.Run("HasTemplateVersionsWithAITask", s.Subtest(func(db database.Store, check *expects) {
check.Args().Asserts()
}))
}
func (s *MethodTestSuite) TestNotifications() {
@@ -2098,13 +2098,6 @@ func (m queryMetricsStore) GetWorkspacesEligibleForTransition(ctx context.Contex
return workspaces, err
}
func (m queryMetricsStore) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) {
start := time.Now()
r0, r1 := m.s.HasTemplateVersionsWithAITask(ctx)
m.queryLatencies.WithLabelValues("HasTemplateVersionsWithAITask").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) {
start := time.Now()
key, err := m.s.InsertAPIKey(ctx, arg)
-15
View File
@@ -4472,21 +4472,6 @@ func (mr *MockStoreMockRecorder) GetWorkspacesEligibleForTransition(ctx, now any
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspacesEligibleForTransition", reflect.TypeOf((*MockStore)(nil).GetWorkspacesEligibleForTransition), ctx, now)
}
// HasTemplateVersionsWithAITask mocks base method.
func (m *MockStore) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HasTemplateVersionsWithAITask", ctx)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HasTemplateVersionsWithAITask indicates an expected call of HasTemplateVersionsWithAITask.
func (mr *MockStoreMockRecorder) HasTemplateVersionsWithAITask(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasTemplateVersionsWithAITask", reflect.TypeOf((*MockStore)(nil).HasTemplateVersionsWithAITask), ctx)
}
// InTx mocks base method.
func (m *MockStore) InTx(arg0 func(database.Store) error, arg1 *database.TxOptions) error {
m.ctrl.T.Helper()
-2
View File
@@ -473,8 +473,6 @@ type sqlcQuerier interface {
GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]GetWorkspacesAndAgentsByOwnerIDRow, error)
GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error)
GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error)
// Determines if the template versions table has any rows with has_ai_task = TRUE.
HasTemplateVersionsWithAITask(ctx context.Context) (bool, error)
InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error)
// We use the organization_id as the id
// for simplicity since all users is
-12
View File
@@ -13014,18 +13014,6 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create
return items, nil
}
const hasTemplateVersionsWithAITask = `-- name: HasTemplateVersionsWithAITask :one
SELECT EXISTS (SELECT 1 FROM template_versions WHERE has_ai_task = TRUE)
`
// Determines if the template versions table has any rows with has_ai_task = TRUE.
func (q *sqlQuerier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) {
row := q.db.QueryRowContext(ctx, hasTemplateVersionsWithAITask)
var exists bool
err := row.Scan(&exists)
return exists, err
}
const insertTemplateVersion = `-- name: InsertTemplateVersion :exec
INSERT INTO
template_versions (
@@ -234,7 +234,3 @@ FROM
WHERE
template_versions.id IN (archived_versions.id)
RETURNING template_versions.id;
-- name: HasTemplateVersionsWithAITask :one
-- Determines if the template versions table has any rows with has_ai_task = TRUE.
SELECT EXISTS (SELECT 1 FROM template_versions WHERE has_ai_task = TRUE);
+1 -16
View File
@@ -448,7 +448,6 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
var user database.User
var themePreference string
var terminalFont string
var tasksTabVisible bool
orgIDs := []uuid.UUID{}
eg.Go(func() error {
var err error
@@ -484,20 +483,6 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
orgIDs = memberIDs[0].OrganizationIDs
return err
})
eg.Go(func() error {
// If HideAITasks is true, force hide the tasks tab
if h.opts.HideAITasks {
tasksTabVisible = false
return nil
}
hasAITask, err := h.opts.Database.HasTemplateVersionsWithAITask(ctx)
if err != nil {
return err
}
tasksTabVisible = hasAITask
return nil
})
err := eg.Wait()
if err == nil {
var wg sync.WaitGroup
@@ -571,7 +556,7 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
wg.Add(1)
go func() {
defer wg.Done()
tasksTabVisible, err := json.Marshal(tasksTabVisible)
tasksTabVisible, err := json.Marshal(!h.opts.HideAITasks)
if err == nil {
state.TasksTabVisible = html.EscapeString(string(tasksTabVisible))
}
+6 -1
View File
@@ -13,6 +13,7 @@ import { AvatarData } from "components/Avatar/AvatarData";
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
import { Button } from "components/Button/Button";
import { displayError } from "components/GlobalSnackbar/utils";
import { Link } from "components/Link/Link";
import { Margins } from "components/Margins/Margins";
import {
PageHeader,
@@ -60,6 +61,7 @@ import { Helmet } from "react-helmet-async";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import TextareaAutosize from "react-textarea-autosize";
import { docs } from "utils/docs";
import { pageTitle } from "utils/page";
import { relativeTime } from "utils/time";
import { type UserOption, UsersCombobox } from "./UsersCombobox";
@@ -139,7 +141,10 @@ const NoTemplatesPlaceholder: FC = () => {
No Task templates found
</h3>
<span className="text-content-secondary text-sm">
Create a Task template to get started
<Link href={docs("/ai-coder/tasks")} target="_blank" rel="noreferrer">
Learn about Tasks
</Link>{" "}
to get started.
</span>
</div>
</div>