Skip to content

Commit

Permalink
feat: allow to copy evaluate command in a CLI format (#3154)
Browse files Browse the repository at this point in the history
* feat: allow to copy evaluate command in a CLI format

Signed-off-by: kyryl.perepelytsia <[email protected]>

* fix: pass each key/value as a seprate context option

Signed-off-by: kyryl.perepelytsia <[email protected]>

---------

Signed-off-by: kyryl.perepelytsia <[email protected]>
  • Loading branch information
kyryl-perepelytsia authored Jun 12, 2024
1 parent 47752dd commit a143262
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 21 deletions.
86 changes: 66 additions & 20 deletions ui/src/app/console/Console.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { json } from '@codemirror/lang-json';
import { ArrowPathIcon } from '@heroicons/react/20/solid';
import { DocumentDuplicateIcon } from '@heroicons/react/24/outline';
import { tokyoNight } from '@uiw/codemirror-theme-tokyo-night';
import CodeMirror from '@uiw/react-codemirror';
import { Form, Formik, useFormikContext } from 'formik';
Expand All @@ -11,10 +12,12 @@ import * as Yup from 'yup';
import { useListAuthProvidersQuery } from '~/app/auth/authApi';
import { useListFlagsQuery } from '~/app/flags/flagsApi';
import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import { selectCurrentRef } from '~/app/refs/refsSlice';
import { ContextEditor } from '~/components/console/ContextEditor';
import EmptyState from '~/components/EmptyState';
import Button from '~/components/forms/buttons/Button';
import Combobox from '~/components/forms/Combobox';
import Dropdown from '~/components/forms/Dropdown';
import Input from '~/components/forms/Input';
import { evaluateURL, evaluateV2 } from '~/data/api';
import { useError } from '~/data/hooks/error';
Expand All @@ -25,14 +28,15 @@ import {
requiredValidation
} from '~/data/validations';
import { IAuthMethod } from '~/types/Auth';
import { Command } from '~/types/Cli';
import { FilterableFlag, FlagType, flagTypeToLabel, IFlag } from '~/types/Flag';
import { INamespace } from '~/types/Namespace';
import {
copyTextToClipboard,
generateCliCommand,
generateCurlCommand,
getErrorMessage
} from '~/utils/helpers';
import { selectCurrentRef } from '~/app/refs/refsSlice';

function ResetOnNamespaceChange({ namespace }: { namespace: INamespace }) {
const { resetForm } = useFormikContext();
Expand Down Expand Up @@ -133,6 +137,35 @@ export default function Console() {
});
};

const handleCopyAsCli = (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 contextOptions = Object.entries(parsed).map(([key, value]) => ({
key: '--context',
value: `${key}=${value}`
}));

const command = generateCliCommand({
commandName: Command.Evaluate,
arguments: [values.flagKey],
options: [
{ key: '--entity-id', value: values.entityId },
{ key: '--namespace', value: namespace.key },
...contextOptions
]
});
copyTextToClipboard(command);
setSuccess('Command copied to clipboard');
};

const handleCopyAsCurl = (values: ConsoleFormValues) => {
let parsed = null;
try {
Expand Down Expand Up @@ -180,7 +213,7 @@ export default function Console() {

return (
<>
<div className="flex flex-col">
<div className="relative flex flex-col">
<h1 className="text-gray-900 text-2xl font-bold leading-7 sm:truncate sm:text-3xl">
Console
</h1>
Expand Down Expand Up @@ -265,24 +298,37 @@ export default function Console() {
</div>
</div>
<div className="flex justify-end">
<Button
className="ml-3"
type="button"
disabled={!(formik.dirty && formik.isValid)}
onClick={() => {
handleCopyAsCurl(formik.values);
}}
>
Copy as curl
</Button>
<Button
variant="primary"
className="ml-3"
type="submit"
disabled={!(formik.dirty && formik.isValid)}
>
Evaluate
</Button>
<div className="absolute left-[45%] mt-0.5">
<Dropdown
label="Copy"
actions={[
{
id: 'curl',
disabled: !(formik.dirty && formik.isValid),
label: 'Curl Request',
onClick: () => handleCopyAsCurl(formik.values),
icon: DocumentDuplicateIcon
},
{
id: 'cli',
disabled: !(formik.dirty && formik.isValid),
label: 'Flipt CLI',
onClick: () => handleCopyAsCli(formik.values),
icon: DocumentDuplicateIcon
}
]}
/>
</div>
<div>
<Button
variant="primary"
className="ml-3"
type="submit"
disabled={!(formik.dirty && formik.isValid)}
>
Evaluate
</Button>
</div>
</div>
</div>
<ResetOnNamespaceChange namespace={namespace} />
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/forms/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function Dropdown(props: DropdownProps) {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="bg-white absolute right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<Menu.Items className="bg-white absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{actions.map((action) => (
<div className="py-1" key={action.id}>
{!action.disabled && (
Expand Down
14 changes: 14 additions & 0 deletions ui/src/types/Cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export enum Command {
Evaluate = 'evaluate'
}

export interface IOption {
key: string;
value: string;
}

export interface ICommand {
commandName: Command;
arguments?: string[];
options?: IOption[];
}
5 changes: 5 additions & 0 deletions ui/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { defaultHeaders } from '~/data/api';
import { ICommand } from '~/types/Cli';
import { ICurlOptions } from '~/types/Curl';

export function cls(...args: ClassValue[]) {
Expand Down Expand Up @@ -116,3 +117,7 @@ export function generateCurlCommand(curlOptions: ICurlOptions) {
curlOptions.uri
].join(' ');
}

export function generateCliCommand(command: ICommand): string {
return `flipt ${command.commandName} ${command.arguments?.join(' ')} ${command.options?.map(({ key, value }) => `${key} ${value}`).join(' ')}`;
}

0 comments on commit a143262

Please sign in to comment.