mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: support order
property of coder_app
resource (#12077)
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -414,6 +415,16 @@ func AppSubdomain(dbApp database.WorkspaceApp, agentName, workspaceName, ownerNa
|
||||
}
|
||||
|
||||
func Apps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) []codersdk.WorkspaceApp {
|
||||
sort.Slice(dbApps, func(i, j int) bool {
|
||||
if dbApps[i].DisplayOrder != dbApps[j].DisplayOrder {
|
||||
return dbApps[i].DisplayOrder < dbApps[j].DisplayOrder
|
||||
}
|
||||
if dbApps[i].DisplayName != dbApps[j].DisplayName {
|
||||
return dbApps[i].DisplayName < dbApps[j].DisplayName
|
||||
}
|
||||
return dbApps[i].Slug < dbApps[j].Slug
|
||||
})
|
||||
|
||||
apps := make([]codersdk.WorkspaceApp, 0)
|
||||
for _, dbApp := range dbApps {
|
||||
apps = append(apps, codersdk.WorkspaceApp{
|
||||
|
@ -465,6 +465,7 @@ func WorkspaceApp(t testing.TB, db database.Store, orig database.WorkspaceApp) d
|
||||
HealthcheckInterval: takeFirst(orig.HealthcheckInterval, 60),
|
||||
HealthcheckThreshold: takeFirst(orig.HealthcheckThreshold, 60),
|
||||
Health: takeFirst(orig.Health, database.WorkspaceAppHealthHealthy),
|
||||
DisplayOrder: takeFirst(orig.DisplayOrder, 1),
|
||||
})
|
||||
require.NoError(t, err, "insert app")
|
||||
return resource
|
||||
|
@ -5826,6 +5826,7 @@ func (q *FakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW
|
||||
HealthcheckInterval: arg.HealthcheckInterval,
|
||||
HealthcheckThreshold: arg.HealthcheckThreshold,
|
||||
Health: arg.Health,
|
||||
DisplayOrder: arg.DisplayOrder,
|
||||
}
|
||||
q.workspaceApps = append(q.workspaceApps, workspaceApp)
|
||||
return workspaceApp, nil
|
||||
|
5
coderd/database/dump.sql
generated
5
coderd/database/dump.sql
generated
@ -1112,9 +1112,12 @@ CREATE TABLE workspace_apps (
|
||||
subdomain boolean DEFAULT false NOT NULL,
|
||||
sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL,
|
||||
slug text NOT NULL,
|
||||
external boolean DEFAULT false NOT NULL
|
||||
external boolean DEFAULT false NOT NULL,
|
||||
display_order integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN workspace_apps.display_order IS 'Specifies the order in which to display agent app in user interfaces.';
|
||||
|
||||
CREATE TABLE workspace_build_parameters (
|
||||
workspace_build_id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
|
@ -0,0 +1 @@
|
||||
ALTER TABLE workspace_apps DROP COLUMN display_order;
|
@ -0,0 +1,4 @@
|
||||
ALTER TABLE workspace_apps ADD COLUMN display_order integer NOT NULL DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN workspace_apps.display_order
|
||||
IS 'Specifies the order in which to display agent app in user interfaces.';
|
@ -2321,6 +2321,8 @@ type WorkspaceApp struct {
|
||||
SharingLevel AppSharingLevel `db:"sharing_level" json:"sharing_level"`
|
||||
Slug string `db:"slug" json:"slug"`
|
||||
External bool `db:"external" json:"external"`
|
||||
// Specifies the order in which to display agent app in user interfaces.
|
||||
DisplayOrder int32 `db:"display_order" json:"display_order"`
|
||||
}
|
||||
|
||||
// A record of workspace app usage statistics
|
||||
|
@ -9618,7 +9618,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentStats(ctx context.Context, arg InsertWo
|
||||
}
|
||||
|
||||
const getWorkspaceAppByAgentIDAndSlug = `-- name: GetWorkspaceAppByAgentIDAndSlug :one
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external FROM workspace_apps WHERE agent_id = $1 AND slug = $2
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order FROM workspace_apps WHERE agent_id = $1 AND slug = $2
|
||||
`
|
||||
|
||||
type GetWorkspaceAppByAgentIDAndSlugParams struct {
|
||||
@ -9645,12 +9645,13 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg Ge
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
&i.External,
|
||||
&i.DisplayOrder,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) {
|
||||
@ -9678,6 +9679,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
&i.External,
|
||||
&i.DisplayOrder,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -9693,7 +9695,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
||||
}
|
||||
|
||||
const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) {
|
||||
@ -9721,6 +9723,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
&i.External,
|
||||
&i.DisplayOrder,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -9736,7 +9739,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
||||
}
|
||||
|
||||
const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) {
|
||||
@ -9764,6 +9767,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
&i.External,
|
||||
&i.DisplayOrder,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -9795,10 +9799,11 @@ INSERT INTO
|
||||
healthcheck_url,
|
||||
healthcheck_interval,
|
||||
healthcheck_threshold,
|
||||
health
|
||||
health,
|
||||
display_order
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order
|
||||
`
|
||||
|
||||
type InsertWorkspaceAppParams struct {
|
||||
@ -9817,6 +9822,7 @@ type InsertWorkspaceAppParams struct {
|
||||
HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"`
|
||||
HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"`
|
||||
Health WorkspaceAppHealth `db:"health" json:"health"`
|
||||
DisplayOrder int32 `db:"display_order" json:"display_order"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) {
|
||||
@ -9836,6 +9842,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
||||
arg.HealthcheckInterval,
|
||||
arg.HealthcheckThreshold,
|
||||
arg.Health,
|
||||
arg.DisplayOrder,
|
||||
)
|
||||
var i WorkspaceApp
|
||||
err := row.Scan(
|
||||
@ -9854,6 +9861,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
&i.External,
|
||||
&i.DisplayOrder,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -27,10 +27,11 @@ INSERT INTO
|
||||
healthcheck_url,
|
||||
healthcheck_interval,
|
||||
healthcheck_threshold,
|
||||
health
|
||||
health,
|
||||
display_order
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING *;
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING *;
|
||||
|
||||
-- name: UpdateWorkspaceAppHealthByID :exec
|
||||
UPDATE
|
||||
|
@ -1648,6 +1648,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
HealthcheckInterval: app.Healthcheck.Interval,
|
||||
HealthcheckThreshold: app.Healthcheck.Threshold,
|
||||
Health: health,
|
||||
DisplayOrder: int32(app.Order),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert app: %w", err)
|
||||
|
@ -2543,6 +2543,66 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
require.EqualValues(t, app.Healthcheck.Threshold, got.Healthcheck.Threshold)
|
||||
})
|
||||
|
||||
t.Run("Apps_DisplayOrder", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
apps := []*proto.App{
|
||||
{
|
||||
Slug: "aaa",
|
||||
DisplayName: "aaa",
|
||||
},
|
||||
{
|
||||
Slug: "aaa-code-server",
|
||||
Order: 4,
|
||||
},
|
||||
{
|
||||
Slug: "bbb-code-server",
|
||||
Order: 3,
|
||||
},
|
||||
{
|
||||
Slug: "bbb",
|
||||
},
|
||||
}
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
Agents: []*proto.Agent{{
|
||||
Id: "something",
|
||||
Auth: &proto.Agent_Token{},
|
||||
Apps: apps,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
workspace, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspace.LatestBuild.Resources[0].Agents, 1)
|
||||
agent := workspace.LatestBuild.Resources[0].Agents[0]
|
||||
require.Len(t, agent.Apps, 4)
|
||||
require.Equal(t, "bbb", agent.Apps[0].Slug) // empty-display-name < "aaa"
|
||||
require.Equal(t, "aaa", agent.Apps[1].Slug) // no order < any order
|
||||
require.Equal(t, "bbb-code-server", agent.Apps[2].Slug) // order = 3 < order = 4
|
||||
require.Equal(t, "aaa-code-server", agent.Apps[3].Slug)
|
||||
})
|
||||
|
||||
t.Run("Metadata", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
|
Reference in New Issue
Block a user