✨ [FFL-2858] Feature Flags tab — catalog browsing (stacked PR 2 of 3) - #4916
✨ [FFL-2858] Feature Flags tab — catalog browsing (stacked PR 2 of 3)#4916kellyw1806 wants to merge 1 commit into
Conversation
Bundles Sizes Evolution
|
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 4dcadc8 | Docs | Datadog PR Page | Give us feedback! |
84c903a to
ba2f761
Compare
4d4f152 to
415b54b
Compare
ba2f761 to
5683248
Compare
415b54b to
4a0a996
Compare
5683248 to
4af8227
Compare
4a0a996 to
de6b4ea
Compare
4af8227 to
7376fbf
Compare
de6b4ea to
9b5a9f3
Compare
7376fbf to
fb4faa6
Compare
|
@kellyw1806 In my opinion I would keep the stacked PR 2 and 3 in draft until the first one is approved. That way after changes are requested in the first one, this one remains clean for the reviewer. |
9b5a9f3 to
6d5a341
Compare
fb4faa6 to
230ae07
Compare
6d5a341 to
4596d8b
Compare
230ae07 to
673a2b1
Compare
4596d8b to
a2d74a1
Compare
673a2b1 to
2f033a5
Compare
a2d74a1 to
59a5aa6
Compare
2f033a5 to
98058ac
Compare
59a5aa6 to
dbf0fe9
Compare
e55866f to
322814a
Compare
dbf0fe9 to
cf550d4
Compare
322814a to
87a1c1a
Compare
cf550d4 to
3414896
Compare
87a1c1a to
875de0b
Compare
BeltranBulbarellaDD
left a comment
There was a problem hiding this comment.
Let me know what you think! I think these comments will make the flags load almost instantly.
| let offset = 0 | ||
|
|
||
| for (let page = 0; page < MAX_PAGES; page++) { | ||
| const url = new URL(`https://${host}/api/ui/ffe/feature-flags`) | ||
| url.searchParams.set('limit', String(PAGE_LIMIT)) | ||
| url.searchParams.set('offset', String(offset)) | ||
|
|
||
| const response = await fetch(url.toString(), { | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
| let offset = 0 | |
| for (let page = 0; page < MAX_PAGES; page++) { | |
| const url = new URL(`https://${host}/api/ui/ffe/feature-flags`) | |
| url.searchParams.set('limit', String(PAGE_LIMIT)) | |
| url.searchParams.set('offset', String(offset)) | |
| const response = await fetch(url.toString(), { | |
| headers: { | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| }) | |
| const url = new URL(`https://${host}/api/ui/ffe/feature-flags`) | |
| url.searchParams.set('page[limit]', String(request.pageSize)) | |
| url.searchParams.set('page[offset]', String((request.page - 1) * request.pageSize)) | |
| if (request.search) { | |
| url.searchParams.set('search', request.search) | |
| } | |
| for (const type of request.typeFilter) { | |
| url.searchParams.append('value_type', type) | |
| } | |
| for (const tag of request.tagFilter) { | |
| url.searchParams.append('tags', tag) | |
| } | |
| const response = await fetch(url.toString(), { | |
| headers: { | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| }) |
Instead of a for loop what do you think of this.
Because reading the API docs: "Pagination is always applied: the response includes meta.page (with total) and pagination links." Link.
So no need to do the loop to get the total we can request the 20 we will show and offset on page change. I think this way it loads faster and does not get unnecessary info.
There was a problem hiding this comment.
And then we can really simplify the useFlagCatalogView
There was a problem hiding this comment.
I think this might not work because it clashes with a couple of features the later PRs in this stack add, which both need the whole flag list loaded at once. For example, for overrides, you can override a flag's value locally, and we bump the ones you've overridden to the top so they're easy to find and the server can't do that (because it doesnt know which flags you overrode since they are stored locally in browser) A couple of the filters also break like the Tag and My Teams filter. So thinking maybe we keep the list loading client-side for now? Might also discuss this more with my team to see if they want to analyze this tradeoff
There was a problem hiding this comment.
Good point about locally overridden flags, but I don’t think caching every fetched flag in local storage solves it. Also I believe the endpoint supports search, type, regular-tag, and team-tag filtering server-side, so those filters shouldn’t require the complete catalog.
As for the following PRs in my opinion is better to tackle it when it comes. Else it's hard to keep track of both at the same time.
I'm thinking, what about having 2 parts?. The locally overriden flags in a set() above, when we open the extension we do a search for each to see if they match the server. And then below we have the rest. Where each time we do a pagination/search/filter we first check the results agains the set() so we don't show them twice.
This is because I am not confortable with fetching 2k feature flags on load each time.
| placeholder="Filter your feature flags" | ||
| leftSection={<IconSearch size={14} />} | ||
| value={view.search} | ||
| onChange={(event) => view.setSearch(event.currentTarget.value)} |
There was a problem hiding this comment.
I think we could use a debounce here, like 3 secs so we don't run a search as soon as we write a character.
3414896 to
34fc366
Compare
875de0b to
2c6bb67
Compare
Builds on FFL-2597: fetches the flag catalog via the OAuth-capable FFE UI endpoint (transparently refreshing the token), and renders it with search + type/tag filters and pagination. Read-only; overriding flags comes in FFL-2596.
2c6bb67 to
4dcadc8
Compare
Motivation
Stacked on #4913 (OAuth). With the sign in functionality, this PR helps include the fetch for the total feature-flag catalog. Users are now able to browse their feature-flag catalog. It does not involve overriding flags as that is the next PR (#4912).
Stack (review bottom-up)
mainffl-2597ffl-2858Changes
GET /api/ui/ffe/feature-flagsendpoint (paginated), transparently refreshing the token (getValidAccessToken, added here).Demo
This is what the three PRs look like working together:
Screen.Recording.2026-07-24.at.5.14.31.PM.mov
Checklist
oauth.spec.ts—getValidAccessToken;flagCatalog.spec.ts).