Skip to content

Commit

Permalink
feat(ui/console): handle auth case in generating curl command
Browse files Browse the repository at this point in the history
  • Loading branch information
AmineRhazzar committed Dec 1, 2023
1 parent 944bd6e commit 457feff
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 30 deletions.
90 changes: 69 additions & 21 deletions ui/src/app/console/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Form, Formik, useFormikContext } from 'formik';
import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/json';
import 'highlight.js/styles/tomorrow-night-bright.css';
import React, { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
Expand All @@ -14,14 +14,20 @@ import EmptyState from '~/components/EmptyState';
import Button from '~/components/forms/buttons/Button';
import Combobox from '~/components/forms/Combobox';
import Input from '~/components/forms/Input';
import { evaluateURL, evaluateV2, listFlags } from '~/data/api';
import {
evaluateURL,
evaluateV2,
listAuthMethods,
listFlags
} from '~/data/api';
import { useError } from '~/data/hooks/error';
import { useSuccess } from '~/data/hooks/success';
import {
jsonValidation,
keyValidation,
requiredValidation
} from '~/data/validations';
import { IAuthMethod, IAuthMethodList } from '~/types/Auth';
import {
FilterableFlag,
FlagType,
Expand Down Expand Up @@ -60,13 +66,16 @@ export default function Console() {
const [selectedFlag, setSelectedFlag] = useState<FilterableFlag | null>(null);
const [response, setResponse] = useState<string | null>(null);
const [hasEvaluationError, setHasEvaluationError] = useState<boolean>(false);
const [isAuthRequired, setIsAuthRequired] = useState<boolean>(false);

const { setError, clearError } = useError();
const navigate = useNavigate();
const {setSuccess} = useSuccess();
const { setSuccess } = useSuccess();

const namespace = useSelector(selectCurrentNamespace);

const codeRef = useRef<HTMLElement>(null);

const loadData = useCallback(async () => {
const initialFlagList = (await listFlags(namespace.key)) as IFlagList;
const { flags } = initialFlagList;
Expand All @@ -85,6 +94,20 @@ export default function Console() {
);
}, [namespace.key]);

const checkIsAuthRequired = useCallback(() => {
listAuthMethods()
.then((resp: IAuthMethodList) => {
const enabledAuthMethods = resp.methods.filter(
(m: IAuthMethod) => m.enabled
);
setIsAuthRequired(enabledAuthMethods.length != 0);
clearError();
})
.catch((err) => {
setError(err);
});
}, [setError, clearError]);

const handleSubmit = (flag: IFlag | null, values: ConsoleFormValues) => {
const { entityId, context } = values;

Expand Down Expand Up @@ -125,28 +148,47 @@ export default function Console() {
parsed = JSON.parse(values.context);
} catch (err) {
setHasEvaluationError(true);
setResponse('error: ' + getErrorMessage(err));
setError('Context provided is invalid.');
return;
}
const uri =
window.location.origin +
evaluateURL +
(selectedFlag?.type === FlagType.BOOLEAN ? '/boolean' : '/variant');

let headers: Record<string, string> = {};

let route =
selectedFlag?.type === FlagType.BOOLEAN ? '/boolean' : '/variant';
const uri = window.location.origin + evaluateURL + route;
if (isAuthRequired) {
// user can generate an auth token and use it
headers['Authorization'] = 'Bearer <api-token>';

Check failure on line 163 in ui/src/app/console/Console.tsx

View workflow job for this annotation

GitHub Actions / Lint UI

["Authorization"] is better written in dot notation
}

const command = generateCurlCommand('POST', uri, {
...values,
context: parsed,
namespaceKey: namespace.key
const command = generateCurlCommand({
method: 'POST',
body: {
...values,
context: parsed,
namespaceKey: namespace.key
},
headers,
uri
});

copyTextToClipboard(command);

setSuccess("Command copied to clipboard.")
setSuccess(
'Command copied to clipboard. Generate an API token if necessary.'
);
};

useEffect(() => {
if (codeRef.current) {
// must unset property 'highlighted' so that it can be highlighted again
// otherwise it gets highlighted the first time only
delete codeRef.current.dataset.highlighted;
}
hljs.highlightAll();
}, [response]);
}, [response, codeRef]);

useEffect(() => {
loadData()
Expand All @@ -156,6 +198,10 @@ export default function Console() {
});
}, [clearError, loadData, setError]);

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

const initialvalues: ConsoleFormValues = {
flagKey: selectedFlag?.key || '',
entityId: uuidv4(),
Expand Down Expand Up @@ -286,14 +332,16 @@ export default function Console() {
<div className="mt-8 w-full overflow-hidden md:w-1/2 md:pl-4">
{response && (
<pre className="p-2 text-sm md:h-full">
<code
className={classNames(
hasEvaluationError ? 'border-red-400 border-4' : '',
'json rounded-sm md:h-full'
)}
>
{response as React.ReactNode}
</code>
{hasEvaluationError ? (
<p className="border-red-400 border-4">{response}</p>
) : (
<code
className="hljs json rounded-sm md:h-full"
ref={codeRef}
>
{response as React.ReactNode}
</code>
)}
</pre>
)}
{!response && (
Expand Down
5 changes: 3 additions & 2 deletions ui/src/components/console/ContextEditor.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.ContextEditor {
width: 100vw;
height: 50vh;
width: 100%;
min-height: 50vh;
height: 100%
}
2 changes: 1 addition & 1 deletion ui/src/types/Auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface IAuthMethod {
method: 'METHOD_TOKEN' | 'METHOD_OIDC' | 'METHOD_GITHUB';
method: 'METHOD_TOKEN' | 'METHOD_OIDC' | 'METHOD_GITHUB' | 'METHOD_KUBERNETES';

Check warning on line 2 in ui/src/types/Auth.ts

View workflow job for this annotation

GitHub Actions / Lint UI

Replace `·'METHOD_TOKEN'·|·'METHOD_OIDC'·|·'METHOD_GITHUB'` with `⏎····|·'METHOD_TOKEN'⏎····|·'METHOD_OIDC'⏎····|·'METHOD_GITHUB'⏎···`
enabled: boolean;
sessionCompatible: boolean;
metadata: { [key: string]: any };
Expand Down
6 changes: 6 additions & 0 deletions ui/src/types/Curl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ICurlOptions {
method: 'GET' | 'POST'; // maybe we'll need to extend this in the future
headers?: Record<string, string>;
body?: any;
uri: string;
}
18 changes: 12 additions & 6 deletions ui/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defaultHeaders } from "~/data/api";
import { defaultHeaders } from '~/data/api';
import { ICurlOptions } from '~/types/Curl';

export function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
Expand Down Expand Up @@ -90,13 +91,18 @@ export function getErrorMessage(error: unknown) {
return toErrorWithMessage(error).message;
}

export function generateCurlCommand(method: string, uri: string, body?: any) {
const headers = defaultHeaders();
export function generateCurlCommand(curlOptions: ICurlOptions) {
const headers = { ...defaultHeaders(), ...curlOptions.headers };
const curlHeaders = Object.keys(headers)
.map((key) => `-H "${key}: ${headers[key]}"`)
.join(' ');

const curlData = `-d '${JSON.stringify(body)}'`;

return ['curl', `-X ${method}`, curlHeaders, curlData, uri].join(' ');
const curlData = `-d '${JSON.stringify(curlOptions.body)}'`;
return [
'curl',
`-X ${curlOptions.method}`,
curlHeaders,
curlData,
curlOptions.uri
].join(' ');
}

0 comments on commit 457feff

Please sign in to comment.