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

Display store config #199

Merged
merged 8 commits into from
Mar 21, 2023
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
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.
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