Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit64ea2bf

Browse files
committed
Add experiments detail API & tests
Signed-off-by: Danny Kopping <danny@coder.com>
1 parentd3790bb commit64ea2bf

File tree

4 files changed

+208
-4
lines changed

4 files changed

+208
-4
lines changed

‎coderd/coderd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ func New(options *Options) *API {
754754
r.Route("/experiments",func(r chi.Router) {
755755
r.Use(apiKeyMiddleware)
756756
r.Get("/available",handleExperimentsSafe)
757+
r.Get("/detail",api.handleExperimentsDetail)
757758
r.Get("/",api.handleExperimentsGet)
758759
})
759760
r.Get("/updatecheck",api.updateCheck)
@@ -1455,7 +1456,7 @@ func ReadExperiments(log slog.Logger, raw []string) codersdk.Experiments {
14551456
exps:=make([]codersdk.Experiment,0,len(raw))
14561457
for_,v:=rangeraw {
14571458
switchv {
1458-
case"*":
1459+
casecodersdk.ExperimentsAllWildcard:
14591460
exps=append(exps,codersdk.ExperimentsAll...)
14601461
default:
14611462
ex:=codersdk.Experiment(strings.ToLower(v))

‎coderd/experiments.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,15 @@ func handleExperimentsSafe(rw http.ResponseWriter, r *http.Request) {
3232
Safe:codersdk.ExperimentsAll,
3333
})
3434
}
35+
36+
// @Summary Get experiments' details
37+
// @ID get-experiments-details
38+
// @Security CoderSessionToken
39+
// @Produce json
40+
// @Tags General
41+
// @Success 200 {array} codersdk.ExperimentDetail
42+
// @Router /experiments/detail [get]
43+
func (api*API)handleExperimentsDetail(rw http.ResponseWriter,r*http.Request) {
44+
ctx:=r.Context()
45+
httpapi.Write(ctx,rw,http.StatusOK,codersdk.ExperimentDetails(api.Experiments))
46+
}

‎coderd/experiments_test.go

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func Test_Experiments(t *testing.T) {
5757
t.Run("wildcard",func(t*testing.T) {
5858
t.Parallel()
5959
cfg:=coderdtest.DeploymentValues(t)
60-
cfg.Experiments= []string{"*"}
60+
cfg.Experiments= []string{codersdk.ExperimentsAllWildcard}
6161
client:=coderdtest.New(t,&coderdtest.Options{
6262
DeploymentValues:cfg,
6363
})
@@ -79,7 +79,7 @@ func Test_Experiments(t *testing.T) {
7979
t.Run("alternate wildcard with manual opt-in",func(t*testing.T) {
8080
t.Parallel()
8181
cfg:=coderdtest.DeploymentValues(t)
82-
cfg.Experiments= []string{"*","dAnGeR"}
82+
cfg.Experiments= []string{codersdk.ExperimentsAllWildcard,"dAnGeR"}
8383
client:=coderdtest.New(t,&coderdtest.Options{
8484
DeploymentValues:cfg,
8585
})
@@ -102,7 +102,7 @@ func Test_Experiments(t *testing.T) {
102102
t.Run("Unauthorized",func(t*testing.T) {
103103
t.Parallel()
104104
cfg:=coderdtest.DeploymentValues(t)
105-
cfg.Experiments= []string{"*"}
105+
cfg.Experiments= []string{codersdk.ExperimentsAllWildcard}
106106
client:=coderdtest.New(t,&coderdtest.Options{
107107
DeploymentValues:cfg,
108108
})
@@ -133,4 +133,140 @@ func Test_Experiments(t *testing.T) {
133133
require.NotNil(t,experiments)
134134
require.ElementsMatch(t,codersdk.ExperimentsAll,experiments.Safe)
135135
})
136+
137+
t.Run("experiments detail",func(t*testing.T) {
138+
t.Parallel()
139+
140+
const (
141+
invalidExp="bob"
142+
expiredExp="auto-fill-parameters"// using a string here not a constant since this experiment has expired & will be deleted eventually
143+
)
144+
145+
tests:= []struct {
146+
namestring
147+
enabledValid []codersdk.Experiment
148+
enabledInvalid []codersdk.Experiment
149+
expectedExtraCountint
150+
}{
151+
{
152+
name:"using defaults",
153+
},
154+
{
155+
name:"use all (*)",
156+
enabledValid: []codersdk.Experiment{codersdk.Experiment(codersdk.ExperimentsAllWildcard)},
157+
},
158+
{
159+
name:"only valid experiments",
160+
enabledValid:codersdk.ExperimentsAll,
161+
},
162+
{
163+
name:"use all (*) + invalid",
164+
enabledValid: []codersdk.Experiment{codersdk.Experiment(codersdk.ExperimentsAllWildcard),codersdk.Experiment(expiredExp)},
165+
expectedExtraCount:1,
166+
},
167+
{
168+
name:"valid + expired experiments",
169+
enabledValid:codersdk.ExperimentsAll,
170+
enabledInvalid: []codersdk.Experiment{codersdk.Experiment(expiredExp)},
171+
expectedExtraCount:1,
172+
},
173+
{
174+
name:"valid + expired + invalid experiments",
175+
enabledValid:codersdk.ExperimentsAll,
176+
enabledInvalid: []codersdk.Experiment{codersdk.Experiment(invalidExp),codersdk.Experiment(expiredExp)},
177+
expectedExtraCount:2,
178+
},
179+
{
180+
name:"only expired",
181+
enabledInvalid: []codersdk.Experiment{codersdk.Experiment(expiredExp)},
182+
expectedExtraCount:1,
183+
},
184+
{
185+
name:"only invalid",
186+
enabledInvalid: []codersdk.Experiment{codersdk.Experiment(invalidExp)},
187+
expectedExtraCount:1,
188+
},
189+
{
190+
name:"expired + invalid experiments",
191+
enabledInvalid: []codersdk.Experiment{codersdk.Experiment(invalidExp),codersdk.Experiment(expiredExp)},
192+
expectedExtraCount:2,
193+
},
194+
}
195+
196+
for_,tc:=rangetests {
197+
tc:=tc
198+
199+
t.Run(tc.name,func(t*testing.T) {
200+
t.Parallel()
201+
202+
varexps []string
203+
204+
// given
205+
for_,e:=rangetc.enabledValid {
206+
exps=append(exps,string(e))
207+
}
208+
for_,e:=rangetc.enabledInvalid {
209+
exps=append(exps,string(e))
210+
}
211+
212+
cfg:=coderdtest.DeploymentValues(t)
213+
cfg.Experiments=exps
214+
client:=coderdtest.New(t,&coderdtest.Options{
215+
DeploymentValues:cfg,
216+
})
217+
_=coderdtest.CreateFirstUser(t,client)
218+
219+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
220+
defercancel()
221+
222+
// when
223+
experiments,err:=client.ExperimentDetails(ctx)
224+
225+
// then
226+
require.NoError(t,err)
227+
require.Len(t,experiments,len(codersdk.ExperimentsAll)+tc.expectedExtraCount)
228+
require.Conditionf(t,func() (successbool) {
229+
varenabled []bool
230+
231+
varvalidCountint
232+
for_,exp:=rangetc.enabledValid {
233+
// don't count wildcard experiment itself as a single experiment
234+
ifexp==codersdk.ExperimentsAllWildcard {
235+
validCount+=len(codersdk.ExperimentsAll)
236+
}else {
237+
validCount++
238+
}
239+
}
240+
241+
for_,exp:=rangeappend(tc.enabledValid,tc.enabledInvalid...) {
242+
for_,e:=rangeexperiments {
243+
// * is special-cased to mean all experiments
244+
if (exp==codersdk.ExperimentsAllWildcard||e.Name==exp)&&e.Enabled {
245+
// codersdk.ExperimentsAllWildcard cannot include invalid experiments
246+
ifexp==codersdk.ExperimentsAllWildcard&&e.Invalid {
247+
continue
248+
}
249+
250+
enabled=append(enabled,true)
251+
}
252+
}
253+
}
254+
255+
returnlen(enabled)==validCount+len(tc.enabledInvalid)
256+
},"enabled experiment(s) were either not found or not marked as enabled")
257+
require.Conditionf(t,func() (successbool) {
258+
varinvalid []bool
259+
for_,exp:=rangetc.enabledInvalid {
260+
for_,e:=rangeexperiments {
261+
ife.Name==exp&&e.Invalid {
262+
invalid=append(invalid,true)
263+
}
264+
}
265+
}
266+
267+
returnlen(invalid)==len(tc.enabledInvalid)
268+
},"invalid experiment(s) were either not found or not marked as invalid")
269+
})
270+
}
271+
})
136272
}

‎codersdk/deployment.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,6 +2178,12 @@ func (c *Client) BuildInfo(ctx context.Context) (BuildInfoResponse, error) {
21782178

21792179
typeExperimentstring
21802180

2181+
typeExperimentDetailstruct {
2182+
NameExperiment`json:"name"`
2183+
Enabledbool`json:"enabled"`
2184+
Invalidbool`json:"invalid"`
2185+
}
2186+
21812187
const (
21822188
// Add new experiments here!
21832189
ExperimentExampleExperiment="example"// This isn't used for anything.
@@ -2193,6 +2199,8 @@ var ExperimentsAll = Experiments{
21932199
ExperimentSharedPorts,
21942200
}
21952201

2202+
constExperimentsAllWildcard="*"
2203+
21962204
// Experiments is a list of experiments.
21972205
// Multiple experiments may be enabled at the same time.
21982206
// Experiments are not safe for production use, and are not guaranteed to
@@ -2209,6 +2217,39 @@ func (e Experiments) Enabled(ex Experiment) bool {
22092217
returnfalse
22102218
}
22112219

2220+
// ExperimentDetails returns a list of all experiments, including those enabled via configuration options, and returns
2221+
// details for each experiment about whether they are active or invalid.
2222+
// Unknown experiments are indistinguishable from removed experiments, so we treat them the same way (as "invalid").
2223+
funcExperimentDetails(expsExperiments) []ExperimentDetail {
2224+
invalid:=make(map[Experiment]bool,len(exps))
2225+
enabled:=make(map[Experiment]struct{},len(exps))
2226+
2227+
// ExperimentsAll gives us all safe experiments, which we mark as not invalid
2228+
for_,e:=rangeExperimentsAll {
2229+
invalid[e]=false
2230+
}
2231+
2232+
// given experiments might not be included in the list of safe experiments, and are therefore marked invalid
2233+
for_,e:=rangeexps {
2234+
enabled[e]=struct{}{}
2235+
2236+
if_,found:=invalid[e];found {
2237+
// already known to be safe, can be skipped
2238+
continue
2239+
}
2240+
2241+
invalid[e]=true
2242+
}
2243+
2244+
out:=make([]ExperimentDetail,0,len(invalid))
2245+
fore,expired:=rangeinvalid {
2246+
_,found:=enabled[e]
2247+
out=append(out,ExperimentDetail{Name:e,Invalid:expired,Enabled:found})
2248+
}
2249+
2250+
returnout
2251+
}
2252+
22122253
func (c*Client)Experiments(ctx context.Context) (Experiments,error) {
22132254
res,err:=c.Request(ctx,http.MethodGet,"/api/v2/experiments",nil)
22142255
iferr!=nil {
@@ -2241,6 +2282,20 @@ func (c *Client) SafeExperiments(ctx context.Context) (AvailableExperiments, err
22412282
returnexp,json.NewDecoder(res.Body).Decode(&exp)
22422283
}
22432284

2285+
func (c*Client)ExperimentDetails(ctx context.Context) ([]ExperimentDetail,error) {
2286+
varexp []ExperimentDetail
2287+
2288+
res,err:=c.Request(ctx,http.MethodGet,"/api/v2/experiments/detail",nil)
2289+
iferr!=nil {
2290+
returnexp,err
2291+
}
2292+
deferres.Body.Close()
2293+
ifres.StatusCode!=http.StatusOK {
2294+
returnexp,ReadBodyAsError(res)
2295+
}
2296+
returnexp,json.NewDecoder(res.Body).Decode(&exp)
2297+
}
2298+
22442299
typeDAUsResponsestruct {
22452300
Entries []DAUEntry`json:"entries"`
22462301
TZHourOffsetint`json:"tz_hour_offset"`

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp