Skip to content

Commit e140a9a

Browse files
Merge pull request #107 from solo-io/charlesthebird/rateLimitDisplayUpdates
Improves Rate Limit message accuracy, and fixes a bug with the deleted subscriptions status.
2 parents 6619837 + eafefb5 commit e140a9a

File tree

13 files changed

+245
-120
lines changed

13 files changed

+245
-120
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
changelog:
2+
- type: FIX
3+
issueLink: https://github.com/solo-io/solo-projects/issues/7043
4+
description: >-
5+
Updates the rate limit UI element to show mixed values when there is a App which
6+
has multiple subscriptions with different rate limits.
7+
- type: FIX
8+
issueLink: https://github.com/solo-io/solo-projects/issues/7066
9+
description: >-
10+
Fixes a bug where subscriptions incorrectly showed up as deleted.

projects/ui/src/Apis/api-types.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Gloo Mesh Gateway Types
33
//
44

5+
import { getEnumValues } from "../Utility/utility";
6+
57
type RateLimitPolicy = {
68
unit: "UNKNOWN" | "SECOND" | "MINUTE" | "HOUR" | "DAY";
79
requestsPerUnit: number;
@@ -171,25 +173,26 @@ export type OauthCredential = {
171173
idpClientName: string;
172174
};
173175

174-
// This list of units is used both for the type and for the dropdown in the UI.
175-
const rateLimitUnits = [
176+
export enum RateLimitUnit {
176177
"UNKNOWN",
177178
"SECOND",
178179
"MINUTE",
179180
"HOUR",
180181
"DAY",
181182
"MONTH",
182183
"YEAR",
183-
] as const; // The 'as const' tells TypeScript to treat these as literal types
184-
export const rateLimitUnitOptions = rateLimitUnits.map((unit) => ({
185-
value: unit,
186-
label: unit,
187-
}));
188-
export type RateLimitUnit = (typeof rateLimitUnits)[number];
184+
}
185+
// This list of units is used both for the type and for the dropdown in the UI.
186+
export const rateLimitUnitOptions = getEnumValues(RateLimitUnit).map(
187+
(unit) => ({
188+
value: RateLimitUnit[unit],
189+
label: RateLimitUnit[unit],
190+
})
191+
);
189192

190193
export type RateLimit = {
191194
requestsPerUnit: string;
192-
unit: RateLimitUnit;
195+
unit: string;
193196
};
194197

195198
export type SubscriptionMetadata = {

projects/ui/src/Apis/gg_hooks.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function useListFlatAppsForTeamsOmitErrors(teams: Team[]) {
5656
return { ...swrRes, data };
5757
}
5858
export function useGetAppDetails(id?: string) {
59-
return useSwrWithAuth<App>(`/apps/${id}`);
59+
return useSwrWithAuth<App>(`/apps/${id}`, id ?? null);
6060
}
6161
export function useListApiKeysForApp(appId: string) {
6262
return useSwrWithAuth<ApiKey[]>(`/apps/${appId}/api-keys`);
@@ -101,9 +101,11 @@ export function useListSubscriptionsForStatus(status: SubscriptionStatus) {
101101
}, [swrResponse]);
102102
return swrResponse;
103103
}
104-
export function useListSubscriptionsForApp(appId: string) {
104+
export function useListSubscriptionsForApp(appId: string | null) {
105+
const endpoint = `/apps/${appId}/subscriptions`;
105106
const swrResponse = useSwrWithAuth<Subscription[] | SubscriptionsListError>(
106-
`/apps/${appId}/subscriptions`
107+
endpoint,
108+
appId === null ? null : endpoint
107109
);
108110
useEffect(() => {
109111
if (isSubscriptionsListError(swrResponse.data)) {

projects/ui/src/Apis/utility.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export async function fetchJSON(...args: Parameters<typeof fetch>) {
6969
*/
7070
export const useSwrWithAuth = <T>(
7171
path: string,
72-
swrKey?: string,
72+
swrKey?: string | null,
7373
config?: Parameters<typeof useSWR<T>>[2]
7474
) => {
7575
const { latestAccessToken } = useContext(AuthContext);

projects/ui/src/Components/Apps/Details/MetadataSection/AppMetadataSection.tsx

+5-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { App } from "../../../../Apis/api-types";
33
import { DetailsPageStyles } from "../../../../Styles/shared/DetailsPageStyles";
44
import { GridCardStyles } from "../../../../Styles/shared/GridCard.style";
55
import { MetadataDisplay } from "../../../../Utility/AdminUtility/MetadataDisplay";
6-
import { EmptyData } from "../../../Common/EmptyData";
76

87
const AppMetadataSection = ({ app }: { app: App }) => {
98
//
@@ -14,17 +13,11 @@ const AppMetadataSection = ({ app }: { app: App }) => {
1413
<DetailsPageStyles.Title>Metadata</DetailsPageStyles.Title>
1514
<GridCardStyles.GridCard whiteBg wide>
1615
<Box px={"20px"} py={"25px"}>
17-
{!!app.metadata?.rateLimit ? (
18-
<MetadataDisplay
19-
item={app}
20-
customMetadata={app.metadata?.customMetadata}
21-
rateLimitInfo={app.metadata?.rateLimit}
22-
/>
23-
) : (
24-
<Box pt="10px">
25-
<EmptyData title="No App metadata was found." />
26-
</Box>
27-
)}
16+
<MetadataDisplay
17+
item={app}
18+
customMetadata={app.metadata?.customMetadata}
19+
rateLimitInfo={app.metadata?.rateLimit}
20+
/>
2821
</Box>
2922
</GridCardStyles.GridCard>
3023
</DetailsPageStyles.Section>

projects/ui/src/Components/Common/SubscriptionsList/SubscriptionInfoCard/SubscriptionInfoCardAdminFooter.tsx

-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ const SubscriptionInfoCardAdminFooter = ({
1919
const [showRejectSubModal, setShowRejectSubModal] = useState(false);
2020
const [showDeleteSubModal, setShowDeleteSubModal] = useState(false);
2121

22-
const canDeleteSubscription = subscriptionState !== SubscriptionState.DELETED;
23-
2422
//
2523
// Render
2624
//
@@ -50,7 +48,6 @@ const SubscriptionInfoCardAdminFooter = ({
5048
<Button
5149
color="danger"
5250
size="xs"
53-
disabled={!canDeleteSubscription}
5451
onClick={() => setShowDeleteSubModal(true)}
5552
>
5653
Delete

projects/ui/src/Components/Common/SubscriptionsList/SubscriptionsUtility.ts

+1-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export enum SubscriptionState {
55
PENDING,
66
APPROVED,
77
REJECTED,
8-
DELETED,
98
}
109
export const subscriptionStateMap = {
1110
[SubscriptionState.PENDING]: {
@@ -26,27 +25,15 @@ export const subscriptionStateMap = {
2625
accentColor: colors.darkRed,
2726
borderColor: colors.lightMidRed,
2827
},
29-
[SubscriptionState.DELETED]: {
30-
subscriptionState: SubscriptionState.DELETED,
31-
label: "DELETED",
32-
accentColor: colors.aprilGrey,
33-
borderColor: colors.aprilGrey,
34-
},
35-
};
36-
37-
const dateHasValue = (dateString: string | undefined) => {
38-
return !!dateString && new Date(dateString).getFullYear() !== 0;
3928
};
4029

4130
export const GetSubscriptionState = (subscription: Subscription) => {
4231
if (!!subscription.approved) {
4332
return SubscriptionState.APPROVED;
4433
}
45-
if (dateHasValue(subscription.deletedAt)) {
46-
return SubscriptionState.DELETED;
47-
}
4834
if (!!subscription.rejected) {
4935
return SubscriptionState.REJECTED;
5036
}
37+
// Deleted subscriptions aren't returned from the API.
5138
return SubscriptionState.PENDING;
5239
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { css } from "@emotion/react";
2+
import styled from "@emotion/styled";
3+
import { WarningExclamation } from "../../Assets/Icons/Icons";
4+
import { borderRadiusConstants } from "../../Styles/constants";
5+
import { Color, svgColorReplace } from "../../Styles/utils";
6+
7+
const AlertContainer = styled.div(
8+
({ theme }) => css`
9+
padding: 12px;
10+
background-color: ${theme.lightYellow};
11+
border-radius: ${borderRadiusConstants.small};
12+
13+
display: flex;
14+
align-items: center;
15+
justify-content: flex-start;
16+
17+
${svgColorReplace(theme.darkYellowDark20 as Color)};
18+
> svg {
19+
margin-right: 10px;
20+
min-width: 24px;
21+
}
22+
23+
* {
24+
line-height: 1.3rem;
25+
font-size: 0.95rem;
26+
color: ${theme.darkYellowDark20};
27+
}
28+
`
29+
);
30+
31+
export function WarningAlert(props: { children?: React.ReactNode }) {
32+
return (
33+
<AlertContainer>
34+
<WarningExclamation width={"24px"} height={"24px"} />
35+
<div>{props.children}</div>
36+
</AlertContainer>
37+
);
38+
}

projects/ui/src/Styles/colors.ts

+3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ const colorMap = {
8080
pumpkinOrangeLight10: Color(baseColors.pumpkinOrange).lighten(0.1).hex(),
8181
pumpkinOrangeLight20: Color(baseColors.pumpkinOrange).lighten(0.2).hex(),
8282

83+
lightYellowLight1: Color(baseColors.lightYellow).lighten(0.02).hex(),
8384
midYellowDark20: Color(baseColors.midYellow).darken(0.2).hex(),
85+
darkYellowDark10: Color(baseColors.darkYellow).darken(0.1).hex(),
86+
darkYellowDark20: Color(baseColors.darkYellow).darken(0.2).hex(),
8487
} as const;
8588

8689
const semanticColorMap = {

projects/ui/src/Styles/global-styles/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import "./style-reset.css";
77
// prettier-ignore
88
import "./fontFace.css";
99
// prettier-ignore
10-
import "./graphiql.min.css";
10+
// import "./graphiql.min.css";
1111
// prettier-ignore
1212
import "./highlight.js.min.css";
1313

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Box } from "@mantine/core";
22
import { useEffect, useState } from "react";
33
import { App, RateLimit, Subscription } from "../../Apis/api-types";
4+
import { useIsAdmin } from "../../Context/AuthContext";
5+
import { useInArea } from "../utility";
46
import { CustomMetadataEditor } from "./CustomMetadataEditor";
5-
import { RateLimitEditor } from "./RateLimitEditor";
7+
import { RateLimitSection } from "./RateLimitSection";
68

79
export interface SharedMetadataProps {
810
item: App | Subscription;
@@ -21,37 +23,50 @@ export const MetadataDisplay = ({
2123
}: SharedMetadataProps & {
2224
onIsWideChange?: (newIsWide: boolean) => void;
2325
}) => {
26+
const isAdmin = useIsAdmin();
2427
const [isEditingCustomMetadata, setIsEditingCustomMetadata] = useState(false);
2528
const [isEditingRateLimit, setIsEditingRateLimit] = useState(false);
2629

2730
useEffect(() => {
2831
onIsWideChange?.(isEditingCustomMetadata || isEditingCustomMetadata);
2932
}, [isEditingCustomMetadata, isEditingRateLimit]);
3033

34+
const inAppDetailsPage = useInArea([`apps/${props.item.id}`]);
35+
const isSubscription = "apiProductId" in props.item;
36+
37+
// This component is reused for apps and subscription rate limit & metadata.
38+
// Here we show the rate limit when:
39+
// - This is an admin
40+
// - We are on the app details page
41+
// - This is a subscription
42+
const showingRateLimit = !!isAdmin || inAppDetailsPage || isSubscription;
43+
3144
return (
32-
<Box sx={{ textAlign: "left" }}>
33-
<Box mb={!!customMetadata || !!isEditingCustomMetadata ? "10px" : "0px"}>
34-
<CustomMetadataEditor
35-
isEditingCustomMetadata={isEditingCustomMetadata}
36-
onIsEditingCustomMetadataChange={(value) =>
37-
setIsEditingCustomMetadata(value)
38-
}
39-
customMetadata={customMetadata}
40-
rateLimitInfo={rateLimitInfo}
41-
{...props}
42-
/>
43-
</Box>
44-
<Box mb="10px">
45-
<RateLimitEditor
46-
isEditingRateLimit={isEditingRateLimit}
47-
onIsEditingRateLimitChange={(newIsEditingRateLimit) =>
48-
setIsEditingRateLimit(newIsEditingRateLimit)
49-
}
50-
customMetadata={customMetadata}
51-
rateLimitInfo={rateLimitInfo}
52-
{...props}
53-
/>
54-
</Box>
45+
<Box sx={{ textAlign: "left", width: "100%" }}>
46+
<CustomMetadataEditor
47+
isEditingCustomMetadata={isEditingCustomMetadata}
48+
onIsEditingCustomMetadataChange={(value) =>
49+
setIsEditingCustomMetadata(value)
50+
}
51+
customMetadata={customMetadata}
52+
rateLimitInfo={rateLimitInfo}
53+
{...props}
54+
/>
55+
{showingRateLimit && (
56+
<Box mb="10px">
57+
<RateLimitSection
58+
inAppDetailsPage={inAppDetailsPage}
59+
isSubscription={isSubscription}
60+
isEditingRateLimit={isEditingRateLimit}
61+
onIsEditingRateLimitChange={(newIsEditingRateLimit) =>
62+
setIsEditingRateLimit(newIsEditingRateLimit)
63+
}
64+
customMetadata={customMetadata}
65+
rateLimitInfo={rateLimitInfo}
66+
{...props}
67+
/>
68+
</Box>
69+
)}
5570
</Box>
5671
);
5772
};

0 commit comments

Comments
 (0)