Skip to content

Commit

Permalink
Merge pull request #199 from OpenWebhook/display-store-config
Browse files Browse the repository at this point in the history
Display store config
  • Loading branch information
Samox authored Mar 21, 2023
2 parents 9e0d243 + 0f51bac commit 98f87cf
Show file tree
Hide file tree
Showing 16 changed files with 305 additions and 11 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"@pluralsight/ps-design-system-button": "^24.1.1",
"@pluralsight/ps-design-system-carousel": "^14.1.1",
"@pluralsight/ps-design-system-core": "^10.0.4",
"@pluralsight/ps-design-system-datawell": "^7.0.11",
"@pluralsight/ps-design-system-dialog": "^15.0.12",
"@pluralsight/ps-design-system-dropdown": "^13.1.1",
"@pluralsight/ps-design-system-emptystate": "^14.1.1",
"@pluralsight/ps-design-system-icon": "^25.4.0",
Expand All @@ -32,6 +34,7 @@
"@pluralsight/ps-design-system-navitem": "^6.1.1",
"@pluralsight/ps-design-system-navuser": "^5.0.16",
"@pluralsight/ps-design-system-normalize": "^7.0.4",
"@pluralsight/ps-design-system-position": "^9.1.2",
"@pluralsight/ps-design-system-table": "^17.1.1",
"@pluralsight/ps-design-system-text": "^20.1.27",
"@pluralsight/ps-design-system-textinput": "^12.1.1",
Expand All @@ -48,7 +51,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-github-btn": "^1.3.0",
"react-table": "^7.7.0"
"react-table": "^7.7.0",
"use-http": "^1.0.27"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
Expand Down
10 changes: 10 additions & 0 deletions patches/use-http+1.0.27.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
diff --git a/node_modules/use-http/dist/cjs/types.d.ts b/node_modules/use-http/dist/cjs/types.d.ts
index e33cb72..37a643e 100644
--- a/node_modules/use-http/dist/cjs/types.d.ts
+++ b/node_modules/use-http/dist/cjs/types.d.ts
@@ -215,4 +215,5 @@ export declare type NonObjectKeysOf<T> = {
}[keyof T];
export declare type ObjectValuesOf<T extends Record<string, any>> = Exclude<Exclude<Extract<ValueOf<T>, object>, never>, Array<any>>;
export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
+//@ts-ignore
export declare type Flatten<T> = Pick<T, NonObjectKeysOf<T>> & UnionToIntersection<ObjectValuesOf<T>>;
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const WebhookList = React.lazy(
() => import("./WebhookDisplay/WebhookList.component")
);
import { ApolloProvider } from "@apollo/client";
import { WebhookStoreUrlContext } from "./WebhookStoreUrl/WebhookStoreUrl.context";
import { WebhookStoreUrlContext } from "./NavBar/WebhookStoreUrl/WebhookStoreUrl.context";
import { createApolloClient } from "./apollo.client";
import { useStateInLocalStorage } from "./use-state-with-local-storage.hook";
import { isValidHttpUrl } from "./utils/is-valid-url";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useRef } from "react";
import helpIcon from "../animations/help.json";
import helpIcon from "../../animations/help.json";
import lottie from "lottie-web";
import { Label } from "@pluralsight/ps-design-system-text";
import "../animations/animation-container.css";
import "../../animations/animation-container.css";

const animationName = "help";

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
79 changes: 79 additions & 0 deletions src/NavBar/StoreConfig/StoreConfigDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import DataWell from "@pluralsight/ps-design-system-datawell";
import Link from "@pluralsight/ps-design-system-link/dist/esm/react";
import { Heading, List, P } from "@pluralsight/ps-design-system-text";
import React from "react";

export const StoreConfigInnerDialog = ({
availableStores,
storageLimit,
defaultTargets,
accessConfig,
userHasAccessToStore,
}: {
availableStores: { url: string; display: string }[];
accessConfig: { type: "public" | "private"; sublabel: string };
storageLimit?: number;
defaultTargets?: string[];
userHasAccessToStore: boolean;
}) => {
return (
<>
<Heading>
<h1>Store Config</h1>
</Heading>
<div style={{ display: "flex" }}>
<DataWell label="Access" subLabel={accessConfig.sublabel}>
{accessConfig.type} {accessConfig.type === "public" ? "⚠️" : null}
</DataWell>
{storageLimit && (
<DataWell label="Storage Limit">{storageLimit}</DataWell>
)}
{defaultTargets && (
<DataWell label="Default Target">
<List>
{defaultTargets.map((target) => (
<List.Item>{target}</List.Item>
))}
</List>
</DataWell>
)}
</div>

{userHasAccessToStore
? "You have access to this store"
: "You don't have access to this store"}

<Heading>
<h1>Your private webhooks stores</h1>
</Heading>

<List>
{availableStores.length > 0
? availableStores.map((store) => (
<List.Item>
<Link>
<a href={store.url}>{store.display}</a>
</Link>
</List.Item>
))
: null}
</List>

<P>
<Link>
<a href="https://www.openwebhook.io/docs/intro/#authentication">
WebhookStore access documentation
</a>
</Link>
</P>

<P>
<Link>
<a href="https://www.openwebhook.io/docs/intro/#%EF%B8%8F-public-organisation-membership">
You don't see your organisation here?
</a>
</Link>
</P>
</>
);
};
140 changes: 140 additions & 0 deletions src/NavBar/StoreConfig/StoreConfigNavItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Label } from "@pluralsight/ps-design-system-text";
import { Below } from "@pluralsight/ps-design-system-position";

import React, { useContext, useEffect, useState } from "react";
import Button from "@pluralsight/ps-design-system-button";
import { StoreConfigInnerDialog } from "./StoreConfigDialog";
import Dialog from "@pluralsight/ps-design-system-dialog";
import useFetch from "use-http";
import { WebhookStoreUrlContext } from "../WebhookStoreUrl/WebhookStoreUrl.context";
import { ACCESS_TOKEN_KEY, IDENTITY_TOKEN_KEY } from "../../local-storage";
import { decodeJWT } from "../../utils/decode-jwt";

export type AuthMetadata =
| { protected: true; protectionRule: "hostname webhook.store" }
| { protected: true; protectionRule: "github-org"; ghOrg: string }
| { protected: false };

export const StoreConfigNavItem = () => {
const [isClicked, setClicked] = useState<boolean>(false);

const [authConfig, setAuthConfig] = useState<AuthMetadata>({
protected: false,
});
const [storeConfig, setStoreConfig] = useState<{
maxNumberOfWebhookPerHost?: number;
defaultTarget?: string[];
userHasAccessToStore: boolean;
}>({ userHasAccessToStore: false });

const { value: webhookStoreUrl } = useContext(WebhookStoreUrlContext);
const accessToken = localStorage.getItem(ACCESS_TOKEN_KEY);
const { get, response } = useFetch(webhookStoreUrl, {
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
});
const idToken = localStorage.getItem(IDENTITY_TOKEN_KEY);
const identityToken =
idToken &&
decodeJWT<{ name: string; ghOrganisations: string[] }, any>(idToken);

useEffect(() => {
getConfigs();
}, []);

async function getConfigs() {
const initialiseAuthConfig = await get("auth-metadata");
if (response.ok) setAuthConfig(initialiseAuthConfig);
const initialiseStoreConfig = await get("store-metadata");
if (response.ok)
setStoreConfig({ ...initialiseStoreConfig, userHasAccessToStore: true });
}

const accessConfig = describeAccessFromAuthConfig(
authConfig,
webhookStoreUrl
);
const availableStores = identityToken
? [
{
url: `https://${identityToken.payload.name}.github-org.webhook.store/?access_token=${idToken}`,
display: `${identityToken.payload.name}.github-org.webhook.store`,
},
...identityToken.payload.ghOrganisations.map((orgName) => ({
url: `https://${orgName}.github.webhook.store/?access_token=${idToken}`,
display: `${orgName}.github-org.webhook.store`,
})),
]
: [
{
url: "https://github.webhook.store",
display: "github.webhook.store",
},
];
const defaultTargets = storeConfig.defaultTarget;
const storageLimit = storeConfig.maxNumberOfWebhookPerHost;

return (
<Below
show={
<Dialog>
<StoreConfigInnerDialog
accessConfig={accessConfig}
availableStores={availableStores}
defaultTargets={defaultTargets}
storageLimit={storageLimit}
userHasAccessToStore={storeConfig.userHasAccessToStore}
/>
</Dialog>
}
when={isClicked}
>
<Button
appearance={Button.appearances.flat}
onClick={(_) => setClicked(!isClicked)}
>
<Label size={Label.sizes.xSmall}>
Store Config {authConfig.protected ? null : "⚠️"}
</Label>
</Button>
</Below>
);
};

const describeAccessFromAuthConfig = (
authConfig: AuthMetadata,
webhookStoreUrl: string
): { type: "public" | "private"; sublabel: string } => {
if (!authConfig.protected) {
return { type: "public", sublabel: "Anyone with the link" };
}

if (authConfig.protectionRule === "github-org") {
return {
type: "private",
sublabel: `Only members of ${authConfig.ghOrg} on GitHub`,
};
}

if (authConfig.protectionRule === "hostname webhook.store") {
const webhookStoreDomain = new URL(webhookStoreUrl).hostname;
if (webhookStoreDomain.endsWith(".github.webhook.store")) {
const githubUserName =
webhookStoreDomain.split(".")[webhookStoreDomain.split(".").length - 4];
return {
type: "private",
sublabel: `Only Github user ${githubUserName}`,
};
}
if (webhookStoreDomain.endsWith(".github-org.webhook.store")) {
const githubOrgaName =
webhookStoreDomain.split(".")[webhookStoreDomain.split(".").length - 4];
return {
type: "private",
sublabel: `Only members of ${githubOrgaName} on GitHub`,
};
}
return { type: "public", sublabel: "Anyone with the link" };
}

return { type: "public", sublabel: "Anyone with the link" };
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback } from "react";
import NavUser from "@pluralsight/ps-design-system-navuser";
import { decodeJWT } from "../utils/decode-jwt";
import { ACCESS_TOKEN_KEY, IDENTITY_TOKEN_KEY } from "../local-storage";
import { decodeJWT } from "../../utils/decode-jwt";
import { ACCESS_TOKEN_KEY, IDENTITY_TOKEN_KEY } from "../../local-storage";

const getIdentityTokenFromStorageAndCleanUrl = (): string | null => {
const storedIdentityToken = localStorage.getItem(IDENTITY_TOKEN_KEY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function WebhookStoreUrlInput() {
size={TextInput.sizes.small}
defaultValue={value}
onBlur={(event) => {
setValue(event.target.value);
setValue(new URL(event.target.value).origin);
}}
></TextInput>
</>
Expand Down
8 changes: 5 additions & 3 deletions src/TopNav.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import NavBar from "@pluralsight/ps-design-system-navbar";
import NavBrand from "@pluralsight/ps-design-system-navbrand";
import React from "react";
import { WebhookStoreUrlInput } from "./WebhookStoreUrl/WebhookStoreUrl.component";
import { WebhookStoreUrlInput } from "./NavBar/WebhookStoreUrl/WebhookStoreUrl.component";
import GitHubButton from "react-github-btn";
import NavItem from "@pluralsight/ps-design-system-navitem";
import { ProxyStatus } from "./ProxyStatus/ProxyStatus.component";
import { LoginOrDisplayUser } from "./User/LoginOrDisplayUser";
import { ProxyStatus } from "./NavBar/ProxyStatus/ProxyStatus.component";
import { LoginOrDisplayUser } from "./NavBar/User/LoginOrDisplayUser";
import { StoreConfigNavItem } from "./NavBar/StoreConfig/StoreConfigNavItem";

function SkillsLogo() {
return (
Expand Down Expand Up @@ -73,6 +74,7 @@ export default function TopNav() {
<WebhookStoreUrlInput />
</NavItem>,
<ProxyStatus key={"ProxyStatus"} />,
<StoreConfigNavItem key={"StoreConfig"} />,
]}
utility={
<NavItem key={"GitHubButton"}>
Expand Down
2 changes: 1 addition & 1 deletion src/WebhookDisplay/WebhookList.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "react-table";
import { forwardWebhookToLocalhost } from "../forward-to-localhost";
import posthog from "posthog-js";
import { WebhookStoreUrlContext } from "../WebhookStoreUrl/WebhookStoreUrl.context";
import { WebhookStoreUrlContext } from "../NavBar/WebhookStoreUrl/WebhookStoreUrl.context";
import { UpdateQueryFn } from "@apollo/client/core/watchQueryOptions";

const largePayloadCellStyle: React.CSSProperties = {
Expand Down
4 changes: 4 additions & 0 deletions src/apollo.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ const getAccessToken = async (
}
);
const json = await accessTokenRequest.json();
if (json.statusCode > 300) {
console.error("Cannot refresh token", json);
throw new Error(json.message);
}
const accessToken = json.accessToken;
localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);

Expand Down
Loading

0 comments on commit 98f87cf

Please sign in to comment.