mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-05-01 21:24:13 +02:00
Dynamic template function args and TTL option for request chaining (#266)
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import type { FnArg, Tokens } from '@yaakapp-internal/templates';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
||||
import { useRenderTemplate } from '../hooks/useRenderTemplate';
|
||||
import { useTemplateFunctionConfig } from '../hooks/useTemplateFunctionConfig';
|
||||
import {
|
||||
templateTokensToString,
|
||||
useTemplateTokensToString,
|
||||
@@ -24,6 +32,7 @@ interface Props {
|
||||
initialTokens: Tokens;
|
||||
hide: () => void;
|
||||
onChange: (insert: string) => void;
|
||||
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
|
||||
}
|
||||
|
||||
export function TemplateFunctionDialog({ initialTokens, templateFunction, ...props }: Props) {
|
||||
@@ -84,14 +93,15 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
}
|
||||
|
||||
function InitializedTemplateFunctionDialog({
|
||||
templateFunction,
|
||||
hide,
|
||||
templateFunction: { name },
|
||||
initialArgValues,
|
||||
hide,
|
||||
onChange,
|
||||
model,
|
||||
}: Omit<Props, 'initialTokens'> & {
|
||||
initialArgValues: Record<string, string | boolean>;
|
||||
}) {
|
||||
const enablePreview = templateFunction.name !== 'secure';
|
||||
const enablePreview = name !== 'secure';
|
||||
const [showSecretsInPreview, toggleShowSecretsInPreview] = useToggle(false);
|
||||
const [argValues, setArgValues] = useState<Record<string, string | boolean>>(initialArgValues);
|
||||
|
||||
@@ -112,15 +122,16 @@ function InitializedTemplateFunctionDialog({
|
||||
type: 'tag',
|
||||
val: {
|
||||
type: 'fn',
|
||||
name: templateFunction.name,
|
||||
name,
|
||||
args: argTokens,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [argValues, templateFunction.name]);
|
||||
}, [argValues, name]);
|
||||
|
||||
const tagText = useTemplateTokensToString(tokens);
|
||||
const templateFunction = useTemplateFunctionConfig(name, argValues, model).data;
|
||||
|
||||
const handleDone = () => {
|
||||
if (tagText.data) {
|
||||
@@ -134,7 +145,7 @@ function InitializedTemplateFunctionDialog({
|
||||
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
|
||||
const dataContainsSecrets = useMemo(() => {
|
||||
for (const [name, value] of Object.entries(argValues)) {
|
||||
const arg = templateFunction.args.find((a) => 'name' in a && a.name === name);
|
||||
const arg = templateFunction?.args.find((a) => 'name' in a && a.name === name);
|
||||
const isTextPassword = arg?.type === 'text' && arg.password;
|
||||
if (isTextPassword && typeof value === 'string' && value && rendered.data?.includes(value)) {
|
||||
return true;
|
||||
@@ -145,6 +156,8 @@ function InitializedTemplateFunctionDialog({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rendered.data]);
|
||||
|
||||
if (templateFunction == null) return null;
|
||||
|
||||
return (
|
||||
<VStack
|
||||
as="form"
|
||||
@@ -155,7 +168,7 @@ function InitializedTemplateFunctionDialog({
|
||||
handleDone();
|
||||
}}
|
||||
>
|
||||
{templateFunction.name === 'secure' ? (
|
||||
{name === 'secure' ? (
|
||||
<PlainInput
|
||||
required
|
||||
label="Value"
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { activeWorkspaceAtom } from '../../../hooks/useActiveWorkspace';
|
||||
import type { WrappedEnvironmentVariable } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useEnvironmentVariables } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useRandomKey } from '../../../hooks/useRandomKey';
|
||||
@@ -36,6 +37,7 @@ import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplate
|
||||
import { showDialog } from '../../../lib/dialog';
|
||||
import { editEnvironment } from '../../../lib/editEnvironment';
|
||||
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
||||
import { jotaiStore } from '../../../lib/jotai';
|
||||
import { withEncryptionEnabled } from '../../../lib/setupOrConfigureEncryption';
|
||||
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
|
||||
import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
||||
@@ -292,18 +294,22 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
size: 'md',
|
||||
title: <InlineCode>{fn.name}(…)</InlineCode>,
|
||||
description: fn.description,
|
||||
render: ({ hide }) => (
|
||||
<TemplateFunctionDialog
|
||||
templateFunction={fn}
|
||||
hide={hide}
|
||||
initialTokens={initialTokens}
|
||||
onChange={(insert) => {
|
||||
cm.current?.view.dispatch({
|
||||
changes: [{ from: startPos, to: startPos + tagValue.length, insert }],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
render: ({ hide }) => {
|
||||
const model = jotaiStore.get(activeWorkspaceAtom)!;
|
||||
return (
|
||||
<TemplateFunctionDialog
|
||||
templateFunction={fn}
|
||||
model={model}
|
||||
hide={hide}
|
||||
initialTokens={initialTokens}
|
||||
onChange={(insert) => {
|
||||
cm.current?.view.dispatch({
|
||||
changes: [{ from: startPos, to: startPos + tagValue.length, insert }],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (fn.name === 'secure') {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
export function useHttpAuthenticationConfig(
|
||||
authName: string | null,
|
||||
values: Record<string, JsonPrimitive>,
|
||||
request: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||
) {
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||
@@ -37,7 +37,7 @@ export function useHttpAuthenticationConfig(
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
'http_authentication_config',
|
||||
request,
|
||||
model,
|
||||
authName,
|
||||
values,
|
||||
responseKey,
|
||||
@@ -53,7 +53,7 @@ export function useHttpAuthenticationConfig(
|
||||
{
|
||||
authName,
|
||||
values,
|
||||
request,
|
||||
model,
|
||||
environmentId,
|
||||
},
|
||||
);
|
||||
|
||||
60
src-web/hooks/useTemplateFunctionConfig.ts
Normal file
60
src-web/hooks/useTemplateFunctionConfig.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { httpResponsesAtom } from '@yaakapp-internal/models';
|
||||
import type { GetTemplateFunctionConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { md5 } from 'js-md5';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { activeEnvironmentIdAtom } from './useActiveEnvironment';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
|
||||
export function useTemplateFunctionConfig(
|
||||
functionName: string | null,
|
||||
values: Record<string, JsonPrimitive>,
|
||||
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||
) {
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||
const responses = useAtomValue(httpResponsesAtom);
|
||||
|
||||
// Some auth handlers like OAuth 2.0 show the current token after a successful request. To
|
||||
// handle that, we'll force the auth to re-fetch after each new response closes
|
||||
const responseKey = md5(
|
||||
responses
|
||||
.filter((r) => r.state === 'closed')
|
||||
.map((r) => r.id)
|
||||
.join(':'),
|
||||
);
|
||||
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
'template_function_config',
|
||||
model,
|
||||
functionName,
|
||||
values,
|
||||
responseKey,
|
||||
workspaceId,
|
||||
environmentId,
|
||||
],
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
if (functionName == null) return null;
|
||||
const config = await invokeCmd<GetTemplateFunctionConfigResponse>(
|
||||
'cmd_template_function_config',
|
||||
{
|
||||
functionName: functionName,
|
||||
values,
|
||||
model,
|
||||
environmentId,
|
||||
},
|
||||
);
|
||||
return config.function;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GetTemplateFunctionsResponse, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import { atom, useAtomValue , useSetAtom } from 'jotai';
|
||||
import type {
|
||||
GetTemplateFunctionSummaryResponse,
|
||||
TemplateFunction,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { TwigCompletionOption } from '../components/core/Editor/twig/completion';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
@@ -55,7 +58,9 @@ export function useSubscribeTemplateFunctions() {
|
||||
refetchInterval: numFns > 0 ? Infinity : 1000,
|
||||
refetchOnMount: true,
|
||||
queryFn: async () => {
|
||||
const result = await invokeCmd<GetTemplateFunctionsResponse[]>('cmd_template_functions');
|
||||
const result = await invokeCmd<GetTemplateFunctionSummaryResponse[]>(
|
||||
'cmd_template_function_summaries',
|
||||
);
|
||||
setNumFns(result.length);
|
||||
const functions = result.flatMap((r) => r.functions) ?? [];
|
||||
setAtom(functions);
|
||||
|
||||
@@ -40,7 +40,8 @@ type TauriCmd =
|
||||
| 'cmd_send_folder'
|
||||
| 'cmd_send_http_request'
|
||||
| 'cmd_show_workspace_key'
|
||||
| 'cmd_template_functions'
|
||||
| 'cmd_template_function_summaries'
|
||||
| 'cmd_template_function_config'
|
||||
| 'cmd_template_tokens_to_string';
|
||||
|
||||
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
|
||||
|
||||
Reference in New Issue
Block a user