Skip to content

Commit fb542ed

Browse files
authored
feat: support tier and env deep links in rate limits table and add Talos RL Limits (#2624)
1 parent 6299cdc commit fb542ed

6 files changed

Lines changed: 186 additions & 10 deletions

File tree

docs/guides/rate-limits-project.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ You will see two rate limits for each bucket:
4242
In the **Project rate limit table** below:
4343

4444
1. Select your subscription tier from the **Tier** dropdown. Options are Developer, Production, Growth, or Enterprise.
45-
2. Select your project environment from the **Environment** dropdown. Options are Production, Staging, or Development.
45+
2. Select your project environment from the **Environment** dropdown. Options are Production, Staging, or Development. The
46+
Developer tier only supports Development environments.
4647
3. To search by API path, enter the API path into the **Search API path** box. The endpoint appears highlighted. Look to see which
4748
bucket it belongs to for its rate limit.
4849

src/components/RateLimitsTable/index.tsx

Lines changed: 101 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,40 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import useBaseUrl from "@docusaurus/useBaseUrl"
5-
import React, { useMemo, useState, useEffect } from "react"
5+
import React, { useMemo, useRef, useState, useEffect } from "react"
66
import type { Env, RateLimitsData, Tier } from "./types"
77

88
const TIERS: Tier[] = ["Developer", "Production", "Growth", "Enterprise"]
99
const ENVS: Env[] = ["Development", "Staging", "Production"]
1010
const SEARCH_DEBOUNCE_MS = 250
1111
const HIDDEN_METHODS = ["OPTIONS", "HEAD"]
1212

13+
function matchParam<T extends string>(
14+
value: string | null,
15+
allowed: readonly T[],
16+
): T | undefined {
17+
if (!value) return undefined
18+
return allowed.find((a) => a.toLowerCase() === value.toLowerCase())
19+
}
20+
21+
function allowedEnvsForTier(tier: Tier): Env[] {
22+
// The Developer tier only has Development projects.
23+
return tier === "Developer" ? ["Development"] : ENVS
24+
}
25+
26+
function coerceEnv(tier: Tier, env: Env): Env {
27+
const allowed = allowedEnvsForTier(tier)
28+
return allowed.includes(env) ? env : allowed[0]
29+
}
30+
31+
function writeUrlParams(params: { tier?: Tier; env?: Env }): void {
32+
if (typeof window === "undefined") return
33+
const url = new URL(window.location.href)
34+
if (params.tier) url.searchParams.set("tier", params.tier)
35+
if (params.env) url.searchParams.set("env", params.env)
36+
window.history.replaceState(window.history.state, "", url.toString())
37+
}
38+
1339
function useRateLimitsData(): {
1440
data: RateLimitsData | null
1541
loading: boolean
@@ -50,9 +76,15 @@ export default function RateLimitsTable({
5076
}: RateLimitsTableProps): React.ReactElement {
5177
const { data, loading, error } = useRateLimitsData()
5278
const [tier, setTier] = useState<Tier>(initialTier)
53-
const [env, setEnv] = useState<Env>(initialEnv)
79+
const [env, setEnv] = useState<Env>(() => coerceEnv(initialTier, initialEnv))
5480
const [pathSearch, setPathSearch] = useState("")
5581
const [pathSearchDebounced, setPathSearchDebounced] = useState("")
82+
// True once the tier/env selection came from the URL or the user; props
83+
// must not override it anymore.
84+
const tierPinned = useRef(false)
85+
const envPinned = useRef(false)
86+
const containerRef = useRef<HTMLDivElement>(null)
87+
const scrollPending = useRef(false)
5688

5789
useEffect(() => {
5890
const t = setTimeout(
@@ -63,10 +95,48 @@ export default function RateLimitsTable({
6395
}, [pathSearch])
6496

6597
React.useEffect(() => {
66-
setTier(initialTier)
67-
setEnv(initialEnv)
98+
if (!tierPinned.current) setTier(initialTier)
99+
if (!envPinned.current)
100+
setEnv(coerceEnv(tierPinned.current ? tier : initialTier, initialEnv))
101+
// eslint-disable-next-line react-hooks/exhaustive-deps
68102
}, [initialTier, initialEnv])
69103

104+
// Apply ?tier= and ?env= after mount; window is unavailable during SSR and
105+
// the first client render must match the pre-rendered HTML.
106+
useEffect(() => {
107+
const params = new URLSearchParams(window.location.search)
108+
const tierParam = matchParam(params.get("tier"), TIERS)
109+
const envParam = matchParam(params.get("env"), ENVS)
110+
if (tierParam) {
111+
tierPinned.current = true
112+
setTier(tierParam)
113+
}
114+
const nextEnv = coerceEnv(tierParam ?? initialTier, envParam ?? env)
115+
if (envParam || nextEnv !== env) {
116+
envPinned.current = true
117+
setEnv(nextEnv)
118+
}
119+
// Deep links should land on the table; an explicit hash wins.
120+
if ((tierParam || envParam) && !window.location.hash) {
121+
scrollPending.current = true
122+
}
123+
// eslint-disable-next-line react-hooks/exhaustive-deps
124+
}, [])
125+
126+
// Scroll once the table has rendered, so the target has its final position.
127+
useEffect(() => {
128+
if (!loading && scrollPending.current) {
129+
scrollPending.current = false
130+
const reducedMotion = window.matchMedia(
131+
"(prefers-reduced-motion: reduce)",
132+
).matches
133+
containerRef.current?.scrollIntoView({
134+
behavior: reducedMotion ? "auto" : "smooth",
135+
block: "start",
136+
})
137+
}
138+
}, [loading])
139+
70140
const filteredThresholds = useMemo(() => {
71141
if (!data) return []
72142
return data.thresholds.filter((t) => t.tier === tier && t.env === env)
@@ -134,13 +204,31 @@ export default function RateLimitsTable({
134204
}
135205

136206
return (
137-
<div className="rate-limits-table">
207+
<div
208+
ref={containerRef}
209+
className="rate-limits-table"
210+
// Keep the sticky navbar and the table heading above visible when
211+
// deep links scroll to the table.
212+
style={{ scrollMarginTop: "calc(var(--ifm-navbar-height, 60px) + 4rem)" }}
213+
>
138214
<div className="flex flex-wrap gap-4 mb-6">
139215
<label className="flex items-center gap-2">
140216
<span>Tier:</span>
141217
<select
142218
value={tier}
143-
onChange={(e) => setTier(e.target.value as Tier)}
219+
onChange={(e) => {
220+
const next = e.target.value as Tier
221+
tierPinned.current = true
222+
setTier(next)
223+
const nextEnv = coerceEnv(next, env)
224+
if (nextEnv !== env) {
225+
envPinned.current = true
226+
setEnv(nextEnv)
227+
writeUrlParams({ tier: next, env: nextEnv })
228+
} else {
229+
writeUrlParams({ tier: next })
230+
}
231+
}}
144232
aria-label="Subscription tier"
145233
className="rounded border px-2 py-1 bg-[var(--ifm-background-surface-color)] text-[var(--ifm-font-color-base)] border-[var(--ifm-color-emphasis-300)]"
146234
>
@@ -155,11 +243,16 @@ export default function RateLimitsTable({
155243
<span>Environment:</span>
156244
<select
157245
value={env}
158-
onChange={(e) => setEnv(e.target.value as Env)}
246+
onChange={(e) => {
247+
const next = e.target.value as Env
248+
envPinned.current = true
249+
setEnv(next)
250+
writeUrlParams({ env: next })
251+
}}
159252
aria-label="Project environment"
160253
className="rounded border px-2 py-1 bg-[var(--ifm-background-surface-color)] text-[var(--ifm-font-color-base)] border-[var(--ifm-color-emphasis-300)]"
161254
>
162-
{ENVS.map((e) => (
255+
{allowedEnvsForTier(tier).map((e) => (
163256
<option key={e} value={e}>
164257
{e}
165258
</option>

src/lib/rate-limits/csv-provider.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ export function createCsvProvider(
107107
}
108108
})
109109
result = dedupeThresholds(result)
110+
// The Developer tier only has Development projects; the CSV export
111+
// still contains the other combinations, so drop them here.
112+
result = result.filter(
113+
(r) => r.tier !== "Developer" || r.env === "Development",
114+
)
110115
if (options?.tier) result = result.filter((r) => r.tier === options.tier)
111116
if (options?.env) result = result.filter((r) => r.env === options.env)
112117
if (options?.bucket)

src/lib/rate-limits/data/bucket-to-endpoints.csv

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,20 @@ POST,/saml/api/oauth/authorize,polis-public-medium
216216
GET,/saml/api/oauth/oidc,polis-public-medium
217217
POST,/saml/api/oauth/saml,polis-public-medium
218218
PATCH,/api/v1/dsync/{directoryId},polis-public-medium
219+
POST,/v2alpha1/admin/issuedApiKeys,talos-admin-low
220+
PATCH,/v2alpha1/admin/issuedApiKeys/{name},talos-admin-low
221+
POST,/v2alpha1/admin/issuedApiKeys/{name}:rotate,talos-admin-low
222+
POST,/v2alpha1/admin/importedApiKeys,talos-admin-low
223+
POST,/v2alpha1/admin/importedApiKeys:batchImport,talos-admin-low
224+
PATCH,/v2alpha1/admin/importedApiKeys/{name},talos-admin-low
225+
DELETE,/v2alpha1/admin/importedApiKeys/{name},talos-admin-low
226+
POST,/v2alpha1/admin/apiKeys/{name}:revoke,talos-admin-low
227+
POST,/v2alpha1/admin/apiKeys:derive,talos-admin-medium
228+
GET,/v2alpha1/admin/issuedApiKeys,talos-admin-medium
229+
GET,/v2alpha1/admin/issuedApiKeys/{name},talos-admin-medium
230+
GET,/v2alpha1/admin/importedApiKeys,talos-admin-medium
231+
GET,/v2alpha1/admin/importedApiKeys/{name},talos-admin-medium
232+
POST,/v2alpha1/admin/apiKeys:verify,talos-admin-high
233+
POST,/v2alpha1/admin/apiKeys:batchVerify,talos-admin-high
234+
POST,/v2alpha1/apiKeys:selfRevoke,talos-public-low
235+
GET,/v2alpha1/derivedKeys/jwks.json,talos-public-high

src/lib/rate-limits/data/bucket-to-threshold.csv

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,63 @@ polis-public-medium,Growth,stage,40,5
203203
polis-public-medium,Production,dev,40,5
204204
polis-public-medium,Production,prod,60,5
205205
polis-public-medium,Production,stage,40,5
206+
talos-admin-high,Develop,dev,80,5
207+
talos-admin-high,Develop,prod,80,5
208+
talos-admin-high,Develop,stage,80,5
209+
talos-admin-high,Enterprise,dev,175,8
210+
talos-admin-high,Enterprise,prod,2500,101
211+
talos-admin-high,Enterprise,stage,175,8
212+
talos-admin-high,Growth,dev,175,8
213+
talos-admin-high,Growth,prod,1000,41
214+
talos-admin-high,Growth,stage,175,8
215+
talos-admin-high,Production,dev,175,8
216+
talos-admin-high,Production,prod,250,11
217+
talos-admin-high,Production,stage,175,8
218+
talos-admin-low,Develop,dev,15,5
219+
talos-admin-low,Develop,prod,15,5
220+
talos-admin-low,Develop,stage,15,5
221+
talos-admin-low,Enterprise,dev,30,5
222+
talos-admin-low,Enterprise,prod,200,9
223+
talos-admin-low,Enterprise,stage,30,5
224+
talos-admin-low,Growth,dev,30,5
225+
talos-admin-low,Growth,prod,100,5
226+
talos-admin-low,Growth,stage,30,5
227+
talos-admin-low,Production,dev,30,5
228+
talos-admin-low,Production,prod,35,5
229+
talos-admin-low,Production,stage,30,5
230+
talos-admin-medium,Develop,dev,30,5
231+
talos-admin-medium,Develop,prod,30,5
232+
talos-admin-medium,Develop,stage,30,5
233+
talos-admin-medium,Enterprise,dev,60,5
234+
talos-admin-medium,Enterprise,prod,500,21
235+
talos-admin-medium,Enterprise,stage,60,5
236+
talos-admin-medium,Growth,dev,60,5
237+
talos-admin-medium,Growth,prod,250,11
238+
talos-admin-medium,Growth,stage,60,5
239+
talos-admin-medium,Production,dev,60,5
240+
talos-admin-medium,Production,prod,90,5
241+
talos-admin-medium,Production,stage,60,5
242+
talos-public-high,Develop,dev,60,5
243+
talos-public-high,Develop,prod,60,5
244+
talos-public-high,Develop,stage,60,5
245+
talos-public-high,Enterprise,dev,125,6
246+
talos-public-high,Enterprise,prod,800,33
247+
talos-public-high,Enterprise,stage,125,6
248+
talos-public-high,Growth,dev,125,6
249+
talos-public-high,Growth,prod,400,17
250+
talos-public-high,Growth,stage,125,6
251+
talos-public-high,Production,dev,125,6
252+
talos-public-high,Production,prod,200,9
253+
talos-public-high,Production,stage,125,6
254+
talos-public-low,Develop,dev,15,5
255+
talos-public-low,Develop,prod,15,5
256+
talos-public-low,Develop,stage,15,5
257+
talos-public-low,Enterprise,dev,30,5
258+
talos-public-low,Enterprise,prod,200,9
259+
talos-public-low,Enterprise,stage,30,5
260+
talos-public-low,Growth,dev,30,5
261+
talos-public-low,Growth,prod,100,5
262+
talos-public-low,Growth,stage,30,5
263+
talos-public-low,Production,dev,30,5
264+
talos-public-low,Production,prod,45,5
265+
talos-public-low,Production,stage,30,5

src/static/rate-limits.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)