Skip to content

Commit b554e47

Browse files
authored
Add feature flags to OData $count endpoints (#8241)
* Add feature flags to OData $count endpoints * Rename private async methods to *Async Related to NuGet/Engineering#2902 Address NuGet/Engineering#3442
1 parent 0f304ce commit b554e47

7 files changed

Lines changed: 383 additions & 118 deletions

File tree

src/NuGetGallery.Services/Configuration/FeatureFlagService.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,20 @@ public class FeatureFlagService : IFeatureFlagService
4141
private const string EmbeddedReadmeFlightName = GalleryPrefix + "EmbeddedReadmes";
4242

4343
private const string ODataV1GetAllNonHijackedFeatureName = GalleryPrefix + "ODataV1GetAllNonHijacked";
44+
private const string ODataV1GetAllCountNonHijackedFeatureName = GalleryPrefix + "ODataV1GetAllCountNonHijacked";
4445
private const string ODataV1GetSpecificNonHijackedFeatureName = GalleryPrefix + "ODataV1GetSpecificNonHijacked";
4546
private const string ODataV1FindPackagesByIdNonHijackedFeatureName = GalleryPrefix + "ODataV1FindPackagesByIdNonHijacked";
47+
private const string ODataV1FindPackagesByIdCountNonHijackedFeatureName = GalleryPrefix + "ODataV1FindPackagesByIdCountNonHijacked";
4648
private const string ODataV1SearchNonHijackedFeatureName = GalleryPrefix + "ODataV1SearchNonHijacked";
49+
private const string ODataV1SearchCountNonHijackedFeatureName = GalleryPrefix + "ODataV1SearchCountNonHijacked";
4750

4851
private const string ODataV2GetAllNonHijackedFeatureName = GalleryPrefix + "ODataV2GetAllNonHijacked";
52+
private const string ODataV2GetAllCountNonHijackedFeatureName = GalleryPrefix + "ODataV2GetAllCountNonHijacked";
4953
private const string ODataV2GetSpecificNonHijackedFeatureName = GalleryPrefix + "ODataV2GetSpecificNonHijacked";
5054
private const string ODataV2FindPackagesByIdNonHijackedFeatureName = GalleryPrefix + "ODataV2FindPackagesByIdNonHijacked";
55+
private const string ODataV2FindPackagesByIdCountNonHijackedFeatureName = GalleryPrefix + "ODataV2FindPackagesByIdCountNonHijacked";
5156
private const string ODataV2SearchNonHijackedFeatureName = GalleryPrefix + "ODataV2SearchNonHijacked";
57+
private const string ODataV2SearchCountNonHijackedFeatureName = GalleryPrefix + "ODataV2SearchCountNonHijacked";
5258

5359
private readonly IFeatureFlagClient _client;
5460

@@ -208,6 +214,11 @@ public bool IsODataV1GetAllEnabled()
208214
return _client.IsEnabled(ODataV1GetAllNonHijackedFeatureName, defaultValue: true);
209215
}
210216

217+
public bool IsODataV1GetAllCountEnabled()
218+
{
219+
return _client.IsEnabled(ODataV1GetAllCountNonHijackedFeatureName, defaultValue: true);
220+
}
221+
211222
public bool IsODataV1GetSpecificNonHijackedEnabled()
212223
{
213224
return _client.IsEnabled(ODataV1GetSpecificNonHijackedFeatureName, defaultValue: true);
@@ -218,16 +229,31 @@ public bool IsODataV1FindPackagesByIdNonHijackedEnabled()
218229
return _client.IsEnabled(ODataV1FindPackagesByIdNonHijackedFeatureName, defaultValue: true);
219230
}
220231

232+
public bool IsODataV1FindPackagesByIdCountNonHijackedEnabled()
233+
{
234+
return _client.IsEnabled(ODataV1FindPackagesByIdCountNonHijackedFeatureName, defaultValue: true);
235+
}
236+
221237
public bool IsODataV1SearchNonHijackedEnabled()
222238
{
223239
return _client.IsEnabled(ODataV1SearchNonHijackedFeatureName, defaultValue: true);
224240
}
225241

242+
public bool IsODataV1SearchCountNonHijackedEnabled()
243+
{
244+
return _client.IsEnabled(ODataV1SearchCountNonHijackedFeatureName, defaultValue: true);
245+
}
246+
226247
public bool IsODataV2GetAllNonHijackedEnabled()
227248
{
228249
return _client.IsEnabled(ODataV2GetAllNonHijackedFeatureName, defaultValue: true);
229250
}
230251

252+
public bool IsODataV2GetAllCountNonHijackedEnabled()
253+
{
254+
return _client.IsEnabled(ODataV2GetAllCountNonHijackedFeatureName, defaultValue: true);
255+
}
256+
231257
public bool IsODataV2GetSpecificNonHijackedEnabled()
232258
{
233259
return _client.IsEnabled(ODataV2GetSpecificNonHijackedFeatureName, defaultValue: true);
@@ -238,9 +264,19 @@ public bool IsODataV2FindPackagesByIdNonHijackedEnabled()
238264
return _client.IsEnabled(ODataV2FindPackagesByIdNonHijackedFeatureName, defaultValue: true);
239265
}
240266

267+
public bool IsODataV2FindPackagesByIdCountNonHijackedEnabled()
268+
{
269+
return _client.IsEnabled(ODataV2FindPackagesByIdCountNonHijackedFeatureName, defaultValue: true);
270+
}
271+
241272
public bool IsODataV2SearchNonHijackedEnabled()
242273
{
243274
return _client.IsEnabled(ODataV2SearchNonHijackedFeatureName, defaultValue: true);
244275
}
276+
277+
public bool IsODataV2SearchCountNonHijackedEnabled()
278+
{
279+
return _client.IsEnabled(ODataV2SearchCountNonHijackedFeatureName, defaultValue: true);
280+
}
245281
}
246282
}

src/NuGetGallery.Services/Configuration/IFeatureFlagService.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ public interface IFeatureFlagService
157157
/// </summary>
158158
bool IsODataV1GetAllEnabled();
159159

160+
/// <summary>
161+
/// Whether the /Packages()/$count endpoint is enabled for the V1 OData API.
162+
/// </summary>
163+
bool IsODataV1GetAllCountEnabled();
164+
160165
/// <summary>
161166
/// Whether the /Packages(Id=,Version=) endpoint is enabled for non-hijacked queries for the V1 OData API.
162167
/// </summary>
@@ -167,16 +172,31 @@ public interface IFeatureFlagService
167172
/// </summary>
168173
bool IsODataV1FindPackagesByIdNonHijackedEnabled();
169174

175+
/// <summary>
176+
/// Whether the /FindPackagesById()/$count endpoint is enabled for non-hijacked queries for the V1 OData API.
177+
/// </summary>
178+
bool IsODataV1FindPackagesByIdCountNonHijackedEnabled();
179+
170180
/// <summary>
171181
/// Whether the /Search() endpoint is enabled for non-hijacked queries for the V1 OData API.
172182
/// </summary>
173183
bool IsODataV1SearchNonHijackedEnabled();
174184

185+
/// <summary>
186+
/// Whether the /Search()/$count endpoint is enabled for non-hijacked queries for the V1 OData API.
187+
/// </summary>
188+
bool IsODataV1SearchCountNonHijackedEnabled();
189+
175190
/// <summary>
176191
/// Whether the /Packages() endpoint is enabled for non-hijacked queries for the V2 OData API.
177192
/// </summary>
178193
bool IsODataV2GetAllNonHijackedEnabled();
179194

195+
/// <summary>
196+
/// Whether the /Packages()/$count endpoint is enabled for non-hijacked queries for the V2 OData API.
197+
/// </summary>
198+
bool IsODataV2GetAllCountNonHijackedEnabled();
199+
180200
/// <summary>
181201
/// Whether the /Packages(Id=,Version=) endpoint is enabled for non-hijacked queries for the V2 OData API.
182202
/// </summary>
@@ -187,9 +207,19 @@ public interface IFeatureFlagService
187207
/// </summary>
188208
bool IsODataV2FindPackagesByIdNonHijackedEnabled();
189209

210+
/// <summary>
211+
/// Whether the /FindPackagesById()/$count endpoint is enabled for non-hijacked queries for the V2 OData API.
212+
/// </summary>
213+
bool IsODataV2FindPackagesByIdCountNonHijackedEnabled();
214+
190215
/// <summary>
191216
/// Whether the /Search() endpoint is enabled for non-hijacked queries for the V2 OData API.
192217
/// </summary>
193218
bool IsODataV2SearchNonHijackedEnabled();
219+
220+
/// <summary>
221+
/// Whether the /Search()/$count endpoint is enabled for non-hijacked queries for the V2 OData API.
222+
/// </summary>
223+
bool IsODataV2SearchCountNonHijackedEnabled();
194224
}
195225
}

src/NuGetGallery/App_Data/Files/Content/flags.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010
"NuGetGallery.Get2FADismissFeedback": "Disabled",
1111
"NuGetGallery.UsabillaEveryPage": "Enabled",
1212
"NuGetGallery.ODataV1GetAllNonHijacked": "Enabled",
13+
"NuGetGallery.ODataV1GetAllCountNonHijacked": "Enabled",
1314
"NuGetGallery.ODataV1GetSpecificNonHijacked": "Enabled",
1415
"NuGetGallery.ODataV1FindPackagesByIdNonHijacked": "Enabled",
16+
"NuGetGallery.ODataV1FindPackagesByIdCountNonHijacked": "Enabled",
1517
"NuGetGallery.ODataV1SearchNonHijacked": "Enabled",
18+
"NuGetGallery.ODataV1SearchCountNonHijacked": "Enabled",
1619
"NuGetGallery.ODataV2GetAllNonHijacked": "Enabled",
20+
"NuGetGallery.ODataV2GetAllCountNonHijacked": "Enabled",
1721
"NuGetGallery.ODataV2GetSpecificNonHijacked": "Enabled",
1822
"NuGetGallery.ODataV2FindPackagesByIdNonHijacked": "Enabled",
19-
"NuGetGallery.ODataV2SearchNonHijacked": "Enabled"
23+
"NuGetGallery.ODataV2FindPackagesByIdCountNonHijacked": "Enabled",
24+
"NuGetGallery.ODataV2SearchNonHijacked": "Enabled",
25+
"NuGetGallery.ODataV2SearchCountNonHijacked": "Enabled"
2026
},
2127
"Flights": {
2228
"NuGetGallery.TyposquattingFlight": {

src/NuGetGallery/Controllers/ODataV1FeedController.cs

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using System.Web.Http;
1010
using System.Web.Http.OData;
1111
using System.Web.Http.OData.Query;
12-
using Microsoft.Data.OData;
1312
using NuGet.Services.Entities;
1413
using NuGetGallery.Configuration;
1514
using NuGetGallery.OData;
@@ -54,7 +53,21 @@ public ODataV1FeedController(
5453
[CacheOutput(NoCache = true)]
5554
public IHttpActionResult Get(ODataQueryOptions<V1FeedPackage> options)
5655
{
57-
if (!_featureFlagService.IsODataV1GetAllEnabled())
56+
return Get(options, _featureFlagService.IsODataV1GetAllEnabled());
57+
}
58+
59+
// /api/v1/Packages/$count
60+
[HttpGet]
61+
[CacheOutput(NoCache = true)]
62+
public IHttpActionResult GetCount(ODataQueryOptions<V1FeedPackage> options)
63+
{
64+
return Get(options, _featureFlagService.IsODataV1GetAllCountEnabled())
65+
.FormattedAsCountResult<V1FeedPackage>();
66+
}
67+
68+
private IHttpActionResult Get(ODataQueryOptions<V1FeedPackage> options, bool isNonHijackEnabled)
69+
{
70+
if (!isNonHijackEnabled)
5871
{
5972
return BadRequest(Strings.ODataDisabled);
6073
}
@@ -71,7 +84,7 @@ public IHttpActionResult Get(ODataQueryOptions<V1FeedPackage> options)
7184
{
7285
return BadRequest("Invalid OrderBy parameter");
7386
}
74-
87+
7588
var queryable = GetAll()
7689
.Where(p => !p.IsPrerelease && p.PackageStatusKey == PackageStatus.Available)
7790
.Where(SemVerLevelKey.IsUnknownPredicate())
@@ -82,14 +95,6 @@ public IHttpActionResult Get(ODataQueryOptions<V1FeedPackage> options)
8295
return TrackedQueryResult(options, queryable, MaxPageSize, customQuery: true);
8396
}
8497

85-
// /api/v1/Packages/$count
86-
[HttpGet]
87-
[CacheOutput(NoCache = true)]
88-
public IHttpActionResult GetCount(ODataQueryOptions<V1FeedPackage> options)
89-
{
90-
return Get(options).FormattedAsCountResult<V1FeedPackage>();
91-
}
92-
9398
// /api/v1/Packages(Id=,Version=)
9499
[HttpGet]
95100
[ODataCacheOutput(
@@ -99,7 +104,7 @@ public IHttpActionResult GetCount(ODataQueryOptions<V1FeedPackage> options)
99104
ClientTimeSpan = ODataCacheConfiguration.DefaultGetByIdAndVersionCacheTimeInSeconds)]
100105
public async Task<IHttpActionResult> Get(ODataQueryOptions<V1FeedPackage> options, string id, string version)
101106
{
102-
var result = await GetCore(
107+
var result = await GetCoreAsync(
103108
options,
104109
id,
105110
version,
@@ -118,12 +123,10 @@ public async Task<IHttpActionResult> Get(ODataQueryOptions<V1FeedPackage> option
118123
ClientTimeSpan = ODataCacheConfiguration.DefaultGetByIdAndVersionCacheTimeInSeconds)]
119124
public async Task<IHttpActionResult> FindPackagesById(ODataQueryOptions<V1FeedPackage> options, [FromODataUri]string id)
120125
{
121-
return await GetCore(
126+
return await FindPackagesByIdAsync(
122127
options,
123128
id,
124-
version: null,
125-
return404NotFoundWhenNoResults: false,
126-
isNonHijackEnabled: _featureFlagService.IsODataV1FindPackagesByIdNonHijackedEnabled());
129+
_featureFlagService.IsODataV1FindPackagesByIdNonHijackedEnabled());
127130
}
128131

129132
// /api/v1/FindPackagesById()/$count?id=
@@ -132,13 +135,29 @@ public async Task<IHttpActionResult> FindPackagesById(ODataQueryOptions<V1FeedPa
132135
ODataCachedEndpoint.FindPackagesByIdCount,
133136
serverTimeSpan: ODataCacheConfiguration.DefaultFindPackagesByIdCountCacheTimeInSeconds,
134137
NoCache = true)]
135-
public async Task<IHttpActionResult> FindPackagesByIdCount(ODataQueryOptions<V1FeedPackage> options, [FromODataUri]string id)
138+
public async Task<IHttpActionResult> FindPackagesByIdCount(ODataQueryOptions<V1FeedPackage> options, [FromODataUri] string id)
139+
{
140+
return (await FindPackagesByIdAsync(
141+
options,
142+
id,
143+
_featureFlagService.IsODataV1FindPackagesByIdCountNonHijackedEnabled()))
144+
.FormattedAsCountResult<V1FeedPackage>();
145+
}
146+
147+
private async Task<IHttpActionResult> FindPackagesByIdAsync(
148+
ODataQueryOptions<V1FeedPackage> options,
149+
string id,
150+
bool isNonHijackEnabled)
136151
{
137-
var result = await FindPackagesById(options, id);
138-
return result.FormattedAsCountResult<V1FeedPackage>();
152+
return await GetCoreAsync(
153+
options,
154+
id,
155+
version: null,
156+
return404NotFoundWhenNoResults: false,
157+
isNonHijackEnabled: isNonHijackEnabled);
139158
}
140159

141-
private async Task<IHttpActionResult> GetCore(
160+
private async Task<IHttpActionResult> GetCoreAsync(
142161
ODataQueryOptions<V1FeedPackage> options,
143162
string id,
144163
string version,
@@ -253,6 +272,38 @@ public async Task<IHttpActionResult> Search(
253272
ODataQueryOptions<V1FeedPackage> options,
254273
[FromODataUri]string searchTerm = "",
255274
[FromODataUri]string targetFramework = "")
275+
{
276+
return await SearchAsync(
277+
options,
278+
searchTerm,
279+
targetFramework,
280+
_featureFlagService.IsODataV1SearchNonHijackedEnabled());
281+
}
282+
283+
// /api/v1/Search()/$count?searchTerm=&targetFramework=&includePrerelease=
284+
[HttpGet]
285+
[ODataCacheOutput(
286+
ODataCachedEndpoint.Search,
287+
serverTimeSpan: ODataCacheConfiguration.DefaultSearchCacheTimeInSeconds,
288+
ClientTimeSpan = ODataCacheConfiguration.DefaultSearchCacheTimeInSeconds)]
289+
public async Task<IHttpActionResult> SearchCount(
290+
ODataQueryOptions<V1FeedPackage> options,
291+
[FromODataUri]string searchTerm = "",
292+
[FromODataUri]string targetFramework = "")
293+
{
294+
return (await SearchAsync(
295+
options,
296+
searchTerm,
297+
targetFramework,
298+
_featureFlagService.IsODataV1SearchCountNonHijackedEnabled()))
299+
.FormattedAsCountResult<V1FeedPackage>();
300+
}
301+
302+
private async Task<IHttpActionResult> SearchAsync(
303+
ODataQueryOptions<V1FeedPackage> options,
304+
string searchTerm,
305+
string targetFramework,
306+
bool isNonHijackEnabled)
256307
{
257308
// Handle OData-style |-separated list of frameworks.
258309
string[] targetFrameworkList = (targetFramework ?? "").Split(new[] { '\'', '|' }, StringSplitOptions.RemoveEmptyEntries);
@@ -288,7 +339,7 @@ public async Task<IHttpActionResult> Search(
288339
packages,
289340
searchTerm,
290341
targetFramework,
291-
includePrerelease: false,
342+
includePrerelease: false,
292343
semVerLevel: null);
293344

294345
// Packages provided by search service (even when not hijacked)
@@ -319,7 +370,7 @@ public async Task<IHttpActionResult> Search(
319370
customQuery = true;
320371
}
321372

322-
if (!_featureFlagService.IsODataV1SearchNonHijackedEnabled())
373+
if (!isNonHijackEnabled)
323374
{
324375
return BadRequest(Strings.ODataParametersDisabled);
325376
}
@@ -335,21 +386,6 @@ public async Task<IHttpActionResult> Search(
335386
return TrackedQueryResult(options, queryable, MaxPageSize, customQuery);
336387
}
337388

338-
// /api/v1/Search()/$count?searchTerm=&targetFramework=&includePrerelease=
339-
[HttpGet]
340-
[ODataCacheOutput(
341-
ODataCachedEndpoint.Search,
342-
serverTimeSpan: ODataCacheConfiguration.DefaultSearchCacheTimeInSeconds,
343-
ClientTimeSpan = ODataCacheConfiguration.DefaultSearchCacheTimeInSeconds)]
344-
public async Task<IHttpActionResult> SearchCount(
345-
ODataQueryOptions<V1FeedPackage> options,
346-
[FromODataUri]string searchTerm = "",
347-
[FromODataUri]string targetFramework = "")
348-
{
349-
var searchResults = await Search(options, searchTerm, targetFramework);
350-
return searchResults.FormattedAsCountResult<V1FeedPackage>();
351-
}
352-
353389
[HttpGet]
354390
[CacheOutput(NoCache = true)]
355391
public virtual HttpResponseMessage SimulateError([FromUri] string type = "Exception")

0 commit comments

Comments
 (0)