Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions coderd/database/modelqueries.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
arg.FuzzyDisplayName,
pq.Array(arg.IDs),
arg.Deprecated,
arg.UseClassicParameterFlow,
arg.AgentsAllowed,
arg.AuthorID,
arg.AuthorUsername,
Expand Down
57 changes: 57 additions & 0 deletions coderd/database/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,63 @@ func TestGetWorkspaceAgentUsageStats(t *testing.T) {
})
}

//nolint:tparallel,paralleltest // Subtests share one database seeded by the parent test.
func TestGetTemplatesWithUseClassicParameterFlowFilter(t *testing.T) {
t.Parallel()

db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitMedium)
org := dbgen.Organization(t, db, database.Organization{})
user := dbgen.User(t, db, database.User{})
classic := dbgen.Template(t, db, database.Template{
OrganizationID: org.ID,
CreatedBy: user.ID,
UseClassicParameterFlow: true,
})
dynamic := dbgen.Template(t, db, database.Template{
OrganizationID: org.ID,
CreatedBy: user.ID,
UseClassicParameterFlow: false,
})

tests := []struct {
name string
value sql.NullBool
want []uuid.UUID
}{
{
name: "unset",
want: []uuid.UUID{classic.ID, dynamic.ID},
},
{
name: "classic",
value: sql.NullBool{Bool: true, Valid: true},
want: []uuid.UUID{classic.ID},
},
{
name: "dynamic",
value: sql.NullBool{Bool: false, Valid: true},
want: []uuid.UUID{dynamic.ID},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := db.GetTemplatesWithFilter(ctx, database.GetTemplatesWithFilterParams{
Deleted: false,
OrganizationID: org.ID,
UseClassicParameterFlow: tt.value,
})
require.NoError(t, err)
gotIDs := make([]uuid.UUID, 0, len(got))
for _, template := range got {
gotIDs = append(gotIDs, template.ID)
}
require.ElementsMatch(t, tt.want, gotIDs)
})
}
}

//nolint:tparallel,paralleltest // Subtests share one database seeded by the parent test.
func TestGetTemplatesWithAgentsAllowedFilter(t *testing.T) {
t.Parallel()
Expand Down
48 changes: 28 additions & 20 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions coderd/database/queries/templates.sql
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ WHERE
END
ELSE true
END
-- Filter by classic parameter flow
AND CASE
WHEN sqlc.narg('use_classic_parameter_flow') :: boolean IS NOT NULL THEN
t.use_classic_parameter_flow = sqlc.narg('use_classic_parameter_flow') :: boolean
ELSE true
END
-- Filter by agents_allowed
AND CASE
WHEN sqlc.narg('agents_allowed') :: boolean IS NOT NULL THEN
Expand Down
25 changes: 13 additions & 12 deletions coderd/searchquery/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,18 +366,19 @@ func Templates(ctx context.Context, db database.Store, actorID uuid.UUID, query

parser := httpapi.NewQueryParamParser()
filter := database.GetTemplatesWithFilterParams{
Deleted: parser.Boolean(values, false, "deleted"),
OrganizationID: parseOrganization(ctx, db, parser, values, "organization"),
ExactName: parser.String(values, "", "exact_name"),
ExactDisplayName: parser.String(values, "", "exact_display_name"),
FuzzyName: parser.String(values, "", "name"),
FuzzyDisplayName: parser.String(values, "", "display_name"),
IDs: parser.UUIDs(values, []uuid.UUID{}, "ids"),
Deprecated: parser.NullableBoolean(values, sql.NullBool{}, "deprecated"),
AgentsAllowed: parser.NullableBoolean(values, sql.NullBool{}, "agents-allowed"),
AuthorID: parser.UUID(values, uuid.Nil, "author_id"),
AuthorUsername: parser.String(values, "", "author"),
HasExternalAgent: parser.NullableBoolean(values, sql.NullBool{}, "has_external_agent"),
Deleted: parser.Boolean(values, false, "deleted"),
OrganizationID: parseOrganization(ctx, db, parser, values, "organization"),
ExactName: parser.String(values, "", "exact_name"),
ExactDisplayName: parser.String(values, "", "exact_display_name"),
FuzzyName: parser.String(values, "", "name"),
FuzzyDisplayName: parser.String(values, "", "display_name"),
IDs: parser.UUIDs(values, []uuid.UUID{}, "ids"),
Deprecated: parser.NullableBoolean(values, sql.NullBool{}, "deprecated"),
UseClassicParameterFlow: parser.NullableBoolean(values, sql.NullBool{}, "compatibility_mode"),
AgentsAllowed: parser.NullableBoolean(values, sql.NullBool{}, "agents-allowed"),
AuthorID: parser.UUID(values, uuid.Nil, "author_id"),
AuthorUsername: parser.String(values, "", "author"),
HasExternalAgent: parser.NullableBoolean(values, sql.NullBool{}, "has_external_agent"),
}

if filter.AuthorUsername == codersdk.Me {
Expand Down
21 changes: 21 additions & 0 deletions coderd/searchquery/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,27 @@ func TestSearchTemplates(t *testing.T) {
FuzzyDisplayName: "foobar",
},
},
{
Name: "UseClassicParameterFlowTrue",
Query: "compatibility_mode:true",
Expected: database.GetTemplatesWithFilterParams{
UseClassicParameterFlow: sql.NullBool{Bool: true, Valid: true},
},
},
{
Name: "UseClassicParameterFlowFalse",
Query: "compatibility_mode:false",
Expected: database.GetTemplatesWithFilterParams{
UseClassicParameterFlow: sql.NullBool{Bool: false, Valid: true},
},
},
{
Name: "UseClassicParameterFlowMissing",
Query: "",
Expected: database.GetTemplatesWithFilterParams{
UseClassicParameterFlow: sql.NullBool{Bool: false, Valid: false},
},
},
{
Name: "HasExternalAgent",
Query: "has_external_agent:true",
Expand Down
41 changes: 41 additions & 0 deletions coderd/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2440,6 +2440,47 @@ func TestTemplateNotifications(t *testing.T) {
})
}

func TestTemplateFilterUseClassicParameterFlow(t *testing.T) {
t.Parallel()

db, pubsub := dbtestutil.NewDB(t)
client := coderdtest.New(t, &coderdtest.Options{
Database: db,
Pubsub: pubsub,
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
classicVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
classic := coderdtest.CreateTemplate(t, client, user.OrganizationID, classicVersion.ID, func(request *codersdk.CreateTemplateRequest) {
request.Name = "classic"
request.UseClassicParameterFlow = new(true)
})
dynamicVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
dynamic := coderdtest.CreateTemplate(t, client, user.OrganizationID, dynamicVersion.ID, func(request *codersdk.CreateTemplateRequest) {
request.Name = "dynamic"
request.UseClassicParameterFlow = new(false)
})

ctx := testutil.Context(t, testutil.WaitLong)
templates, err := client.Templates(ctx, codersdk.TemplateFilter{
SearchQuery: "compatibility_mode:true",
})
require.NoError(t, err)
require.Len(t, templates, 1)
require.Equal(t, classic.ID, templates[0].ID)

templates, err = client.Templates(ctx, codersdk.TemplateFilter{
SearchQuery: "compatibility_mode:false",
})
require.NoError(t, err)
require.Len(t, templates, 1)
require.Equal(t, dynamic.ID, templates[0].ID)

templates, err = client.Templates(ctx, codersdk.TemplateFilter{})
require.NoError(t, err)
require.Len(t, templates, 2)
}

func TestTemplateFilterHasExternalAgent(t *testing.T) {
t.Parallel()

Expand Down
6 changes: 6 additions & 0 deletions site/src/pages/TemplatesPage/TemplatesFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
import { useAuthenticated } from "#/hooks/useAuthenticated";
import { useDashboard } from "#/modules/dashboard/useDashboard";

export const CLASSIC_PARAMETER_FLOW_FILTER = "compatibility_mode:true";

export type TemplateFilterState = {
filter: UseFilterResult;
menus: {
Expand Down Expand Up @@ -101,6 +103,10 @@ export const TemplatesFilter: FC<TemplatesFilterProps> = ({
{ query: "", name: "All templates" },
{ query: "author:me", name: "Templates you authored" },
{ query: "deprecated:true", name: "Deprecated templates" },
{
query: CLASSIC_PARAMETER_FLOW_FILTER,
name: "Templates using compatibility mode",
},
]}
// TODO: Add docs for this
// learnMoreLink={docs("/admin/templates#template-filtering")}
Expand Down
21 changes: 19 additions & 2 deletions site/src/pages/TemplatesPage/TemplatesPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,30 @@ import {
formatTemplateBuildTime,
} from "#/utils/templates";
import { EmptyTemplates } from "./EmptyTemplates";
import { type TemplateFilterState, TemplatesFilter } from "./TemplatesFilter";
import {
CLASSIC_PARAMETER_FLOW_FILTER,
type TemplateFilterState,
TemplatesFilter,
} from "./TemplatesFilter";

const ClassicParameterFlowAlert: FC<{ templateCount: number }> = ({
templateCount,
}) => {
return (
<Alert severity="warning" prominent className="mt-6">
<Alert
severity="warning"
prominent
className="mt-6"
actions={
<Button asChild variant="outline" size="sm">
<RouterLink
to={`/templates?filter=${encodeURIComponent(CLASSIC_PARAMETER_FLOW_FILTER)}`}
>
View templates
</RouterLink>
</Button>
}
>
<AlertTitle>
{templateCount === 1
? "1 template still uses classic parameters"
Expand Down
Loading