diff --git a/ui/jest.config.ts b/ui/jest.config.ts index aca3d7d661..2d7304e055 100644 --- a/ui/jest.config.ts +++ b/ui/jest.config.ts @@ -88,7 +88,9 @@ export default { // ], // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: {}, + moduleNameMapper: { + "^~/(.*)$": "/src/$1" + }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // modulePathIgnorePatterns: [], diff --git a/ui/src/app/console/Console.tsx b/ui/src/app/console/Console.tsx index 380b4666bb..20cb013d64 100644 --- a/ui/src/app/console/Console.tsx +++ b/ui/src/app/console/Console.tsx @@ -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'; @@ -14,21 +14,34 @@ 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 { 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, flagTypeToLabel, IFlag, IFlagList } from '~/types/Flag'; import { INamespace } from '~/types/Namespace'; -import { classNames, getErrorMessage } from '~/utils/helpers'; +import { + classNames, + copyTextToClipboard, + generateCurlCommand, + getErrorMessage +} from '~/utils/helpers'; hljs.registerLanguage('json', javascript); @@ -53,12 +66,16 @@ export default function Console() { const [selectedFlag, setSelectedFlag] = useState(null); const [response, setResponse] = useState(null); const [hasEvaluationError, setHasEvaluationError] = useState(false); + const [isAuthRequired, setIsAuthRequired] = useState(false); const { setError, clearError } = useError(); const navigate = useNavigate(); + const { setSuccess } = useSuccess(); const namespace = useSelector(selectCurrentNamespace); + const codeRef = useRef(null); + const loadData = useCallback(async () => { const initialFlagList = (await listFlags(namespace.key)) as IFlagList; const { flags } = initialFlagList; @@ -77,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; @@ -110,9 +141,54 @@ export default function Console() { }); }; + const handleCopyAsCurl = (values: ConsoleFormValues) => { + let parsed = null; + try { + // need to unescape the context string + parsed = JSON.parse(values.context); + } catch (err) { + setHasEvaluationError(true); + setError('Context provided is invalid.'); + return; + } + const uri = + window.location.origin + + evaluateURL + + (selectedFlag?.type === FlagType.BOOLEAN ? '/boolean' : '/variant'); + + let headers: Record = {}; + + if (isAuthRequired) { + // user can generate an auth token and use it + headers.Authorization = 'Bearer '; + } + + const command = generateCurlCommand({ + method: 'POST', + body: { + ...values, + context: parsed, + namespaceKey: namespace.key + }, + headers, + uri + }); + + copyTextToClipboard(command); + + setSuccess( + 'Command copied to clipboard' + ); + }; + 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() @@ -122,6 +198,10 @@ export default function Console() { }); }, [clearError, loadData, setError]); + useEffect(() => { + checkIsAuthRequired(); + }, [checkIsAuthRequired]); + const initialvalues: ConsoleFormValues = { flagKey: selectedFlag?.key || '', entityId: uuidv4(), @@ -223,6 +303,16 @@ export default function Console() {
+