Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App Details page: API Keys Section #98

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/v0.0.36/app-details-api-keys-section.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
changelog:
- type: FIX
issueLink: https://github.com/solo-io/solo-projects/issues/6881
description: >-
Adds an API keys section to the App Details page.
10 changes: 10 additions & 0 deletions projects/ui/src/Apis/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ export type App = {
teamId: string;
};

export type ApiKey = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we already had this. Is this new or bringing it back?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's a throwback 😄 - we had this for the Gloo Mesh Gateway portal server.

With the new implementation of the Gloo Gateway portal server (the version we're moving to with these apps + teams features), it's slightly different. Luckily the code for it was mostly there, and all tested before, so I know that certain features like copyToClipboard should work across browsers.

id: string;
createdAt: string;
updatedAt: string;
deletedAt: string;
apiKey: string;
name: string;
metadata: Record<string, string>;
};

export type Team = {
createdAt: string;
description: string;
Expand Down
78 changes: 59 additions & 19 deletions projects/ui/src/Apis/gg_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import useSWRMutation from "swr/mutation";
import { AuthContext } from "../Context/AuthContext";
import { omitErrorMessageResponse } from "../Utility/utility";
import {
ApiKey,
ApiProductDetails,
ApiProductSummary,
ApiVersion,
Expand All @@ -19,15 +20,15 @@ import {
import { fetchJSON, useMultiSwrWithAuth, useSwrWithAuth } from "./utility";

//
// Queries
// region Queries
//

// User
// region User
export function useGetCurrentUser() {
return useSwrWithAuth<User>("/me");
}

// Apps
// region Apps + API Keys
export function useListAppsForTeam(team: Team) {
return useSwrWithAuth<App[]>(`/teams/${team.id}/apps`);
}
Expand Down Expand Up @@ -55,8 +56,11 @@ export function useListFlatAppsForTeamsOmitErrors(teams: Team[]) {
export function useGetAppDetails(id?: string) {
return useSwrWithAuth<App>(`/apps/${id}`);
}
export function useListApiKeysForApp(appId: string) {
return useSwrWithAuth<ApiKey[]>(`/apps/${appId}/api-keys`);
}

// Teams
// region Teams
const TEAMS_SWR_KEY = "teams";
export function useListTeams() {
return useSwrWithAuth<Team[]>(`/teams`);
Expand All @@ -68,7 +72,7 @@ export function useGetTeamDetails(id?: string) {
return useSwrWithAuth<Team>(`/teams/${id}`);
}

// Api Products
// region API Products
export function useListApiProducts() {
return useSwrWithAuth<ApiProductSummary[]>("/api-products");
}
Expand All @@ -79,7 +83,7 @@ export function useGetApiProductVersions(id?: string) {
return useSwrWithAuth<ApiVersion[]>(`/api-products/${id}/versions`);
}

// Subscriptions
// region Subscriptions
// this is an admin endpoint
export function useListSubscriptionsForStatus(status: SubscriptionStatus) {
const swrResponse = useSwrWithAuth<Subscription[] | SubscriptionsListError>(
Expand Down Expand Up @@ -115,7 +119,7 @@ export function useListSubscriptionsForApps(apps: App[]) {
}

//
// Mutations
// region Mutations
//

const getLatestAuthHeaders = (latestAccessToken: string | undefined) => {
Expand All @@ -129,7 +133,7 @@ const getLatestAuthHeaders = (latestAccessToken: string | undefined) => {
type MutationWithArgs<T> = { arg: T };

// ------------------------ //
// Create Team
// region Create Team

type CreateTeamParams = MutationWithArgs<{ name: string; description: string }>;

Expand All @@ -149,7 +153,7 @@ export function useCreateTeamMutation() {
}

// ------------------------ //
// Create Team Member
// region Create Team Member

type AddTeamMemberParams = MutationWithArgs<{ email: string; teamId: string }>;

Expand All @@ -169,7 +173,7 @@ export function useAddTeamMemberMutation() {
}

// ------------------------ //
// Remove Team Member
// region Remove Team Member

type AdminRemoveTeamMemberParams = MutationWithArgs<{
teamId: string;
Expand All @@ -194,7 +198,7 @@ export function useRemoveTeamMemberMutation() {
}

// ------------------------ //
// Create App
// region Create App

type CreateAppParams = MutationWithArgs<{ name: string; description: string }>;

Expand All @@ -220,7 +224,7 @@ export function useCreateAppMutation(teamId: string | undefined) {
}

// ------------------------ //
// Update App
// region Update App

type UpdateAppParams = MutationWithArgs<{
appId: string;
Expand All @@ -247,7 +251,7 @@ export function useUpdateAppMutation() {
}

// ------------------------ //
// Update Team
// region Update Team

type UpdateTeamParams = MutationWithArgs<{
teamId: string;
Expand All @@ -272,7 +276,7 @@ export function useUpdateTeamMutation() {
}

// ------------------------ //
// Create App and Subscription
// region Create App and Subscription

type CreateAppAndSubscriptionParams = MutationWithArgs<{
appName: string;
Expand Down Expand Up @@ -315,7 +319,7 @@ export function useCreateAppAndSubscriptionMutation() {
}

// ------------------------ //
// Create Subscription
// region Create Subscription

type CreateSubscriptionParams = MutationWithArgs<{
apiProductId: string;
Expand Down Expand Up @@ -344,7 +348,7 @@ export function useCreateSubscriptionMutation(appId: string) {
}

// -------------------------------- //
// (Admin) Approve/Reject Subscription
// region (Admin) Approve/Reject Subscription

type UpdateSubscriptionParams = MutationWithArgs<{
subscription: Subscription;
Expand Down Expand Up @@ -387,7 +391,7 @@ export function useAdminRejectSubscriptionMutation() {
}

// -------------------------------- //
// Delete Subscription
// region Delete Subscription

export function useDeleteSubscriptionMutation() {
const { latestAccessToken } = useContext(AuthContext);
Expand All @@ -406,7 +410,7 @@ export function useDeleteSubscriptionMutation() {
}

// -------------------------------- //
// Delete Team
// region Delete Team

type DeleteTeamParams = MutationWithArgs<{ teamId: string }>;

Expand All @@ -424,7 +428,7 @@ export function useDeleteTeamMutation() {
}

// -------------------------------- //
// Delete App
// region Delete App

type DeleteAppParams = MutationWithArgs<{ appId: string }>;

Expand All @@ -440,3 +444,39 @@ export function useDeleteAppMutation() {
};
return useSWRMutation(`delete-team`, deleteApp);
}

// -------------------------------- //
// region Create API Key

type CreateApiKeyParams = MutationWithArgs<{ apiKeyName: string }>;

export function useCreateApiKeyMutation(appId: string) {
const { latestAccessToken } = useContext(AuthContext);
const createApiKey = async (_: string, { arg }: CreateApiKeyParams) => {
return await fetchJSON(`/apps/${appId}/api-keys`, {
method: "POST",
headers: getLatestAuthHeaders(latestAccessToken),
body: JSON.stringify(arg),
});
};
return useSWRMutation<ApiKey, any, string, CreateApiKeyParams["arg"]>(
`/apps/${appId}/api-keys`,
createApiKey
);
}

// -------------------------------- //
// region Delete API Key

type DeleteApiKeyParams = MutationWithArgs<{ apiKeyId: string }>;

export function useDeleteApiKeyMutation(appId: string) {
const { latestAccessToken } = useContext(AuthContext);
const deleteApiKey = async (_: string, { arg }: DeleteApiKeyParams) => {
await fetchJSON(`/api-keys/${arg.apiKeyId}`, {
method: "DELETE",
headers: getLatestAuthHeaders(latestAccessToken),
});
};
return useSWRMutation(`/apps/${appId}/api-keys`, deleteApiKey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ApiVersionSchema,
} from "../../../Apis/api-types";
import { ContentWidthDiv } from "../../../Styles/ContentWidthHelpers";
import { SimpleEmptyContent } from "../../Common/EmptyData";
import { EmptyData } from "../../Common/EmptyData";
import DocsTabContent from "./DocsTab/DocsTabContent";
import SchemaTabContent from "./SchemaTab/SchemaTabContent";

Expand Down Expand Up @@ -71,7 +71,6 @@ export function ApiProductDetailsPageBody({
*/}
<Tabs.Panel value={apiProductDetailsTabValues.SPEC} pt={"xl"}>
<SchemaTabContent
apiProduct={apiProduct}
apiProductVersions={apiProductVersions}
apiVersionSpec={apiVersionSpec}
selectedApiVersion={selectedApiVersion}
Expand All @@ -81,17 +80,16 @@ export function ApiProductDetailsPageBody({
{includesDocumentation ? (
<DocsTabContent selectedApiVersion={selectedApiVersion} />
) : (
<SimpleEmptyContent title="No documentation found.">
<EmptyData title="No documentation found.">
<small>
You may add documentation for this API in the{" "}
<Code sx={{ whiteSpace: "nowrap" }}>
<Code>
spec.versions[your-version].openapiMetadata.description
</Code>{" "}
field of this{" "}
<Code sx={{ whiteSpace: "nowrap" }}>ApiProduct</Code> resource.
Markdown is supported.
field of this <Code>ApiProduct</Code> resource. Markdown is
supported.
</small>
</SimpleEmptyContent>
</EmptyData>
)}
</Tabs.Panel>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
import { Box } from "@mantine/core";
import {
ApiProductSummary,
ApiVersion,
ApiVersionSchema,
} from "../../../../Apis/api-types";
import { Box, Code } from "@mantine/core";
import { ApiVersion, ApiVersionSchema } from "../../../../Apis/api-types";
import { EmptyData } from "../../../Common/EmptyData";
import { ErrorBoundary } from "../../../Common/ErrorBoundary";
import { ApiSchemaDisplay } from "./ApiSchemaDisplay";

const SchemaTabContent = ({
apiProduct,
selectedApiVersion,
apiProductVersions,
apiVersionSpec,
}: {
apiProduct: ApiProductSummary;
selectedApiVersion: ApiVersion;
apiProductVersions: ApiVersion[];
apiVersionSpec: ApiVersionSchema | undefined;
}) => {
if (!apiProductVersions.length) {
return (
<Box m="60px">
<EmptyData
topicMessageOverride={`The API Product, "${apiProduct.name}", has no API Version data.`}
/>
<EmptyData title={`No API versions found.`}>
Add a version to the <Code>spec.versions</Code> field of this{" "}
<Code>ApiProduct</Code> for data to appear.
</EmptyData>
</Box>
);
}
Expand All @@ -36,9 +31,13 @@ const SchemaTabContent = ({
// There is a selected API version, but no schema.
return (
<Box m="60px">
<EmptyData
topicMessageOverride={`No schema was returned for the API Version: "${selectedApiVersion.name}".`}
/>
<EmptyData title={`No schema found.`}>
The schema was not returned for this <Code>ApiProduct</Code> version.
<br />
Verify that your OpenApi spec was generated correctly in the
corresponding <Code>ApiDoc</Code> resource for this{" "}
<Code>Service</Code>.
</EmptyData>
</Box>
);
}
Expand Down
12 changes: 4 additions & 8 deletions projects/ui/src/Components/Apis/EmptyApisPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AuthContext } from "../../Context/AuthContext";
import { apisImageURL } from "../../user_variables.tmplr";
import { BannerHeading } from "../Common/Banner/BannerHeading";
import { BannerHeadingTitle } from "../Common/Banner/BannerHeadingTitle";
import { SimpleEmptyContent } from "../Common/EmptyData";
import { EmptyData } from "../Common/EmptyData";
import { PageContainer } from "../Common/PageContainer";
import { StyledApisListMain } from "./gloo-mesh-gateway-components/ApisPage.style";

Expand All @@ -31,19 +31,15 @@ export const EmptyApisPageContent = () => {
const { isLoggedIn } = useContext(AuthContext);

if (!!isLoggedIn)
return (
<SimpleEmptyContent>
No API Products have been created.
</SimpleEmptyContent>
);
return <EmptyData>No API Products have been created.</EmptyData>;
return (
<SimpleEmptyContent title="No API Products were found.">
<EmptyData title="No API Products were found.">
<small>
To view API Products in private Portals, please log in.
<br />
To view API Products in public Portals, the Portal resource must have{" "}
<Code>spec.visibility.public = true</Code>.
</small>
</SimpleEmptyContent>
</EmptyData>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,13 @@ export function ApisList({
if (apiProductsList === undefined) {
return <Loading message="Getting list of apis..." />;
}
if (!apiProductsList.length) {
return <EmptyData title="No API Products were found." />;
}
if (!filteredApiProductsList.length) {
return <EmptyData topic="API" />;
return (
<EmptyData title="No API Products were found matching these filters." />
);
}
if (preferGridView) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function ApisList({
}

if (!displayedApisList.length) {
return <EmptyData topic="API" />;
return <EmptyData title="No APIs were found matching these filters." />;
}
return (
<>
Expand Down
Loading
Loading