collection plugin actions

This commit is contained in:
Chris Turchin
2025-12-16 00:47:12 +01:00
parent cfbfd66eef
commit e17aae246b
11 changed files with 380 additions and 1 deletions

View File

@@ -57,6 +57,10 @@ export interface Context {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>; send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>; getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>; render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>;
list(args: { workspaceId?: string; folderId?: string }): Promise<Array<any>>;
};
folder: {
list(args: { workspaceId?: string }): Promise<Array<any>>;
}; };
httpResponse: { httpResponse: {
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>; find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
@@ -64,6 +68,10 @@ export interface Context {
templates: { templates: {
render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>; render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>;
}; };
file: {
writeText(filePath: string, content: string): Promise<void>;
readText(filePath: string): Promise<string>;
};
plugin: { plugin: {
reload(): void; reload(): void;
}; };

View File

@@ -0,0 +1,11 @@
import type { Context } from './Context';
import type { Folder, Workspace } from '../bindings/gen_models';
import type { Icon } from '../bindings/gen_events';
export type HttpCollectionAction = { label: string; icon?: Icon };
export type CallHttpCollectionActionArgs = { folder?: Folder; workspace?: Workspace };
export type HttpCollectionActionPlugin = HttpCollectionAction & {
onSelect(ctx: Context, args: CallHttpCollectionActionArgs): Promise<void> | void;
};

View File

@@ -4,6 +4,7 @@ import type { Context } from './Context';
import type { FilterPlugin } from './FilterPlugin'; import type { FilterPlugin } from './FilterPlugin';
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin'; import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin'; import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
import type { HttpCollectionActionPlugin } from './HttpCollectionActionPlugin';
import type { ImporterPlugin } from './ImporterPlugin'; import type { ImporterPlugin } from './ImporterPlugin';
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin'; import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
import type { ThemePlugin } from './ThemePlugin'; import type { ThemePlugin } from './ThemePlugin';
@@ -12,6 +13,7 @@ export type { Context };
export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin'; export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin';
export type { DynamicAuthenticationArg } from './AuthenticationPlugin'; export type { DynamicAuthenticationArg } from './AuthenticationPlugin';
export type { TemplateFunctionPlugin }; export type { TemplateFunctionPlugin };
export type { HttpCollectionActionPlugin } from './HttpCollectionActionPlugin';
/** /**
* The global structure of a Yaak plugin * The global structure of a Yaak plugin
@@ -24,6 +26,7 @@ export type PluginDefinition = {
filter?: FilterPlugin; filter?: FilterPlugin;
authentication?: AuthenticationPlugin; authentication?: AuthenticationPlugin;
httpRequestActions?: HttpRequestActionPlugin[]; httpRequestActions?: HttpRequestActionPlugin[];
httpCollectionActions?: HttpCollectionActionPlugin[];
grpcRequestActions?: GrpcRequestActionPlugin[]; grpcRequestActions?: GrpcRequestActionPlugin[];
templateFunctions?: TemplateFunctionPlugin[]; templateFunctions?: TemplateFunctionPlugin[];
}; };

View File

@@ -10,6 +10,7 @@ import {
GrpcRequestAction, GrpcRequestAction,
HttpAuthenticationAction, HttpAuthenticationAction,
HttpRequestAction, HttpRequestAction,
HttpCollectionAction,
InternalEvent, InternalEvent,
InternalEventPayload, InternalEventPayload,
ListCookieNamesResponse, ListCookieNamesResponse,
@@ -172,6 +173,24 @@ export class PluginInstance {
return; return;
} }
if (
payload.type === 'get_http_collection_actions_request' &&
Array.isArray(this.#mod?.httpCollectionActions)
) {
const reply: HttpRequestAction[] = this.#mod.httpCollectionActions.map((a) => ({
...a,
// Add everything except onSelect
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_http_collection_actions_response',
pluginRefId: this.#workerData.pluginRefId,
actions: reply,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (payload.type === 'get_themes_request' && Array.isArray(this.#mod?.themes)) { if (payload.type === 'get_themes_request' && Array.isArray(this.#mod?.themes)) {
const replyPayload: InternalEventPayload = { const replyPayload: InternalEventPayload = {
type: 'get_themes_response', type: 'get_themes_response',
@@ -302,6 +321,18 @@ export class PluginInstance {
} }
} }
if (
payload.type === 'call_http_collection_action_request' &&
Array.isArray(this.#mod.httpCollectionActions)
) {
const action = this.#mod.httpCollectionActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if ( if (
payload.type === 'call_grpc_request_action_request' && payload.type === 'call_grpc_request_action_request' &&
Array.isArray(this.#mod.grpcRequestActions) Array.isArray(this.#mod.grpcRequestActions)
@@ -611,6 +642,26 @@ export class PluginInstance {
); );
return httpRequest; return httpRequest;
}, },
list: async (args: { workspaceId?: string; folderId?: string }) => {
const payload = {
type: 'list_http_requests_request',
// plugin events use camelCase field names in Rust -> snake_case mapping
folderId: args.folderId,
workspaceId: args.workspaceId,
} as any;
const { httpRequests } = await this.#sendForReply<any>(context, payload);
return httpRequests as any[];
},
},
folder: {
list: async (args: { workspaceId?: string }) => {
const payload = {
type: 'list_folders_request',
workspaceId: args.workspaceId,
} as any;
const { folders } = await this.#sendForReply<any>(context, payload);
return folders as any[];
},
}, },
cookies: { cookies: {
getValue: async (args: GetCookieValueRequest) => { getValue: async (args: GetCookieValueRequest) => {
@@ -638,6 +689,24 @@ export class PluginInstance {
return result.data as any; return result.data as any;
}, },
}, },
file: {
writeText: async (filePath: string, content: string) => {
const payload: InternalEventPayload = {
type: 'write_text_file_request',
filePath,
content,
} as any;
await this.#sendForReply(context, payload);
},
readText: async (filePath: string) => {
const payload: InternalEventPayload = {
type: 'read_text_file_request',
filePath,
} as any;
const result = await this.#sendForReply<any>(context, payload);
return result.content;
},
},
store: { store: {
get: async <T>(key: string) => { get: async <T>(key: string) => {
const payload = { type: 'get_key_value_request', key } as const; const payload = { type: 'get_key_value_request', key } as const;

View File

@@ -43,7 +43,8 @@ use yaak_plugins::events::{
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs, CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse, CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
GetHttpRequestActionsResponse, GetTemplateFunctionConfigResponse, GetHttpRequestActionsResponse, GetHttpCollectionActionsResponse,
CallHttpCollectionActionArgs, CallHttpCollectionActionRequest, GetTemplateFunctionConfigResponse,
GetTemplateFunctionSummaryResponse, InternalEvent, InternalEventPayload, JsonPrimitive, GetTemplateFunctionSummaryResponse, InternalEvent, InternalEventPayload, JsonPrimitive,
PluginContext, RenderPurpose, ShowToastRequest, PluginContext, RenderPurpose, ShowToastRequest,
}; };
@@ -846,6 +847,40 @@ async fn cmd_http_request_actions<R: Runtime>(
Ok(plugin_manager.get_http_request_actions(&window).await?) Ok(plugin_manager.get_http_request_actions(&window).await?)
} }
#[tauri::command]
async fn cmd_http_collection_actions<R: Runtime>(
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
) -> YaakResult<Vec<GetHttpCollectionActionsResponse>> {
Ok(plugin_manager.get_http_collection_actions(&window).await?)
}
#[tauri::command]
async fn cmd_call_http_collection_action<R: Runtime>(
window: WebviewWindow<R>,
req: CallHttpCollectionActionRequest,
plugin_manager: State<'_, PluginManager>,
) -> YaakResult<()> {
let folder = match &req.args.folder {
Some(f) => Some(window.db().get_folder(&f.id)?),
None => None,
};
let workspace = match &req.args.workspace {
Some(w) => Some(window.db().get_workspace(&w.id)?),
None => None,
};
Ok(plugin_manager
.call_http_collection_action(
&window,
CallHttpCollectionActionRequest {
args: CallHttpCollectionActionArgs { folder, workspace },
..req
},
)
.await?)
}
#[tauri::command] #[tauri::command]
async fn cmd_grpc_request_actions<R: Runtime>( async fn cmd_grpc_request_actions<R: Runtime>(
window: WebviewWindow<R>, window: WebviewWindow<R>,
@@ -1448,6 +1483,7 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
cmd_call_http_authentication_action, cmd_call_http_authentication_action,
cmd_call_http_request_action, cmd_call_http_request_action,
cmd_call_http_collection_action,
cmd_call_grpc_request_action, cmd_call_grpc_request_action,
cmd_check_for_updates, cmd_check_for_updates,
cmd_create_grpc_request, cmd_create_grpc_request,
@@ -1467,6 +1503,7 @@ pub fn run() {
cmd_grpc_reflect, cmd_grpc_reflect,
cmd_grpc_request_actions, cmd_grpc_request_actions,
cmd_http_request_actions, cmd_http_request_actions,
cmd_http_collection_actions,
cmd_import_data, cmd_import_data,
cmd_install_plugin, cmd_install_plugin,
cmd_metadata, cmd_metadata,

View File

@@ -20,6 +20,7 @@ use yaak_plugins::error::Error::PluginErr;
use yaak_plugins::events::{ use yaak_plugins::events::{
Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse, Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse,
GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent,
ListHttpRequestsResponse,
InternalEventPayload, ListCookieNamesResponse, RenderGrpcRequestResponse, InternalEventPayload, ListCookieNamesResponse, RenderGrpcRequestResponse,
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
TemplateRenderResponse, WindowInfoResponse, WindowNavigateEvent, TemplateRenderResponse, WindowInfoResponse, WindowNavigateEvent,
@@ -60,6 +61,30 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
http_responses, http_responses,
}))) })))
} }
InternalEventPayload::ListHttpRequestsRequest(req) => {
let mut http_requests = Vec::new();
if let Some(folder_id) = req.folder_id {
http_requests = app_handle
.db()
.list_http_requests_for_folder_recursive(&folder_id)?;
} else if let Some(workspace_id) = req.workspace_id {
http_requests = app_handle.db().list_http_requests(&workspace_id)?;
}
Ok(Some(InternalEventPayload::ListHttpRequestsResponse(ListHttpRequestsResponse {
http_requests,
})))
}
InternalEventPayload::ListFoldersRequest(req) => {
let mut folders = Vec::new();
if let Some(workspace_id) = req.workspace_id {
folders = app_handle.db().list_folders(&workspace_id)?;
}
Ok(Some(InternalEventPayload::ListFoldersResponse(
yaak_plugins::events::ListFoldersResponse { folders },
)))
}
InternalEventPayload::GetHttpRequestByIdRequest(req) => { InternalEventPayload::GetHttpRequestByIdRequest(req) => {
let http_request = app_handle.db().get_http_request(&req.id).ok(); let http_request = app_handle.db().get_http_request(&req.id).ok();
Ok(Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse { Ok(Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
@@ -352,6 +377,26 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
environment_id, environment_id,
}))) })))
} }
InternalEventPayload::WriteTextFileRequest(req) => {
use std::fs;
use std::path::Path;
// Ensure the directory exists
if let Some(parent) = Path::new(&req.file_path).parent() {
fs::create_dir_all(parent)?;
}
fs::write(&req.file_path, &req.content)?;
Ok(Some(InternalEventPayload::WriteTextFileResponse(EmptyPayload {})))
}
InternalEventPayload::ReadTextFileRequest(req) => {
use std::fs;
let content = fs::read_to_string(&req.file_path)?;
Ok(Some(InternalEventPayload::ReadTextFileResponse(
yaak_plugins::events::ReadTextFileResponse { content },
)))
}
_ => Ok(None), _ => Ok(None),
} }
} }

View File

@@ -89,6 +89,10 @@ pub enum InternalEventPayload {
GetHttpRequestActionsRequest(EmptyPayload), GetHttpRequestActionsRequest(EmptyPayload),
GetHttpRequestActionsResponse(GetHttpRequestActionsResponse), GetHttpRequestActionsResponse(GetHttpRequestActionsResponse),
CallHttpRequestActionRequest(CallHttpRequestActionRequest), CallHttpRequestActionRequest(CallHttpRequestActionRequest),
// HTTP Collection Actions
GetHttpCollectionActionsRequest(EmptyPayload),
GetHttpCollectionActionsResponse(GetHttpCollectionActionsResponse),
CallHttpCollectionActionRequest(CallHttpCollectionActionRequest),
// Grpc Request Actions // Grpc Request Actions
GetGrpcRequestActionsRequest(EmptyPayload), GetGrpcRequestActionsRequest(EmptyPayload),
@@ -151,10 +155,19 @@ pub enum InternalEventPayload {
FindHttpResponsesRequest(FindHttpResponsesRequest), FindHttpResponsesRequest(FindHttpResponsesRequest),
FindHttpResponsesResponse(FindHttpResponsesResponse), FindHttpResponsesResponse(FindHttpResponsesResponse),
ListHttpRequestsRequest(ListHttpRequestsRequest),
ListHttpRequestsResponse(ListHttpRequestsResponse),
ListFoldersRequest(ListFoldersRequest),
ListFoldersResponse(ListFoldersResponse),
GetThemesRequest(GetThemesRequest), GetThemesRequest(GetThemesRequest),
GetThemesResponse(GetThemesResponse), GetThemesResponse(GetThemesResponse),
WriteTextFileRequest(WriteTextFileRequest),
WriteTextFileResponse(EmptyPayload),
ReadTextFileRequest(ReadTextFileRequest),
ReadTextFileResponse(ReadTextFileResponse),
/// Returned when a plugin doesn't get run, just so the server /// Returned when a plugin doesn't get run, just so the server
/// has something to listen for /// has something to listen for
EmptyResponse(EmptyPayload), EmptyResponse(EmptyPayload),
@@ -1096,6 +1109,14 @@ pub struct GetHttpRequestActionsResponse {
pub plugin_ref_id: String, pub plugin_ref_id: String,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct GetHttpCollectionActionsResponse {
pub actions: Vec<HttpCollectionAction>,
pub plugin_ref_id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
@@ -1105,6 +1126,15 @@ pub struct HttpRequestAction {
pub icon: Option<Icon>, pub icon: Option<Icon>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct HttpCollectionAction {
pub label: String,
#[ts(optional)]
pub icon: Option<Icon>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
@@ -1114,6 +1144,15 @@ pub struct CallHttpRequestActionRequest {
pub args: CallHttpRequestActionArgs, pub args: CallHttpRequestActionArgs,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct CallHttpCollectionActionRequest {
pub index: i32,
pub plugin_ref_id: String,
pub args: CallHttpCollectionActionArgs,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
@@ -1121,6 +1160,16 @@ pub struct CallHttpRequestActionArgs {
pub http_request: HttpRequest, pub http_request: HttpRequest,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct CallHttpCollectionActionArgs {
#[ts(optional)]
pub folder: Option<Folder>,
#[ts(optional)]
pub workspace: Option<Workspace>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
@@ -1185,6 +1234,38 @@ pub struct FindHttpResponsesResponse {
pub http_responses: Vec<HttpResponse>, pub http_responses: Vec<HttpResponse>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ListHttpRequestsRequest {
#[ts(optional)]
pub folder_id: Option<String>,
#[ts(optional)]
pub workspace_id: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ListHttpRequestsResponse {
pub http_requests: Vec<HttpRequest>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ListFoldersRequest {
#[ts(optional)]
pub workspace_id: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ListFoldersResponse {
pub folders: Vec<Folder>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
@@ -1238,3 +1319,25 @@ pub struct DeleteKeyValueRequest {
pub struct DeleteKeyValueResponse { pub struct DeleteKeyValueResponse {
pub deleted: bool, pub deleted: bool,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct WriteTextFileRequest {
pub file_path: String,
pub content: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ReadTextFileRequest {
pub file_path: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ReadTextFileResponse {
pub content: String,
}

View File

@@ -10,6 +10,7 @@ use crate::events::{
FilterRequest, FilterResponse, GetGrpcRequestActionsResponse, FilterRequest, FilterResponse, GetGrpcRequestActionsResponse,
GetHttpAuthenticationConfigRequest, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationConfigRequest, GetHttpAuthenticationConfigResponse,
GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse,
GetHttpCollectionActionsResponse, CallHttpCollectionActionRequest,
GetTemplateFunctionConfigRequest, GetTemplateFunctionConfigResponse, GetTemplateFunctionConfigRequest, GetTemplateFunctionConfigResponse,
GetTemplateFunctionSummaryResponse, GetThemesRequest, GetThemesResponse, ImportRequest, GetTemplateFunctionSummaryResponse, GetThemesRequest, GetThemesResponse, ImportRequest,
ImportResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginContext, ImportResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginContext,
@@ -482,6 +483,27 @@ impl PluginManager {
Ok(all_actions) Ok(all_actions)
} }
pub async fn get_http_collection_actions<R: Runtime>(
&self,
window: &WebviewWindow<R>,
) -> Result<Vec<GetHttpCollectionActionsResponse>> {
let reply_events = self
.send_and_wait(
&PluginContext::new(window),
&InternalEventPayload::GetHttpCollectionActionsRequest(EmptyPayload {}),
)
.await?;
let mut all_actions = Vec::new();
for event in reply_events {
if let InternalEventPayload::GetHttpCollectionActionsResponse(resp) = event.payload {
all_actions.push(resp.clone());
}
}
Ok(all_actions)
}
pub async fn get_template_function_config<R: Runtime>( pub async fn get_template_function_config<R: Runtime>(
&self, &self,
window: &WebviewWindow<R>, window: &WebviewWindow<R>,
@@ -564,6 +586,23 @@ impl PluginManager {
Ok(()) Ok(())
} }
pub async fn call_http_collection_action<R: Runtime>(
&self,
window: &WebviewWindow<R>,
req: CallHttpCollectionActionRequest,
) -> Result<()> {
let ref_id = req.plugin_ref_id.clone();
let plugin =
self.get_plugin_by_ref_id(ref_id.as_str()).await.ok_or(PluginNotFoundErr(ref_id))?;
let event = plugin.build_event_to_send(
&PluginContext::new(window),
&InternalEventPayload::CallHttpCollectionActionRequest(req),
None,
);
plugin.send(&event).await?;
Ok(())
}
pub async fn call_grpc_request_action<R: Runtime>( pub async fn call_grpc_request_action<R: Runtime>(
&self, &self,
window: &WebviewWindow<R>, window: &WebviewWindow<R>,

View File

@@ -37,6 +37,7 @@ import { getCreateDropdownItems } from '../hooks/useCreateDropdownItems';
import { getGrpcRequestActions } from '../hooks/useGrpcRequestActions'; import { getGrpcRequestActions } from '../hooks/useGrpcRequestActions';
import { useHotKey } from '../hooks/useHotKey'; import { useHotKey } from '../hooks/useHotKey';
import { getHttpRequestActions } from '../hooks/useHttpRequestActions'; import { getHttpRequestActions } from '../hooks/useHttpRequestActions';
import { getHttpCollectionActions } from '../hooks/useHttpCollectionActions';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { getModelAncestors } from '../hooks/useModelAncestors'; import { getModelAncestors } from '../hooks/useModelAncestors';
import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
@@ -374,6 +375,17 @@ function Sidebar({ className }: { className?: string }) {
if (request != null) await a.call(request); if (request != null) await a.call(request);
}, },
})), })),
...(items.length === 1 && (child.model === 'folder' || child.model === 'workspace')
? await getHttpCollectionActions()
: []
).map((a) => ({
label: a.label,
leftSlot: <Icon icon={a.icon ?? 'empty'} />,
onSelect: async () => {
const model = getModel(child.model, child.id);
if (model != null) await a.call(model as any);
},
})),
]; ];
const modelCreationItems: DropdownItem[] = const modelCreationItems: DropdownItem[] =
items.length === 1 && child.model === 'folder' items.length === 1 && child.model === 'folder'

View File

@@ -0,0 +1,50 @@
import { useQuery } from '@tanstack/react-query';
import type { Folder, Workspace } from '@yaakapp-internal/models';
import type {
CallHttpCollectionActionRequest,
GetHttpCollectionActionsResponse,
HttpCollectionAction,
} from '@yaakapp-internal/plugins';
import { useMemo } from 'react';
import { invokeCmd } from '../lib/tauri';
import { usePluginsKey } from './usePlugins';
export type CallableHttpCollectionAction = Pick<HttpCollectionAction, 'label' | 'icon'> & {
call: (model: Folder | Workspace) => Promise<void>;
};
export function useHttpCollectionActions() {
const pluginsKey = usePluginsKey();
const actionsResult = useQuery<CallableHttpCollectionAction[]>({
queryKey: ['http_collection_actions', pluginsKey],
queryFn: () => getHttpCollectionActions(),
});
// biome-ignore lint/correctness/useExhaustiveDependencies: none
const actions = useMemo(() => {
return actionsResult.data ?? [];
}, [JSON.stringify(actionsResult.data)]);
return actions;
}
export async function getHttpCollectionActions() {
const responses = await invokeCmd<GetHttpCollectionActionsResponse[]>('cmd_http_collection_actions');
const actions = responses.flatMap((r) =>
r.actions.map((a, i) => ({
label: a.label,
icon: a.icon,
call: async (model: Folder | Workspace) => {
const payload: CallHttpCollectionActionRequest = {
index: i,
pluginRefId: r.pluginRefId,
args: (model as any).model === 'folder' ? { folder: model as Folder } : { workspace: model as Workspace },
} as any;
await invokeCmd('cmd_call_http_collection_action', { req: payload });
},
})),
);
return actions;
}

View File

@@ -5,6 +5,7 @@ type TauriCmd =
| 'cmd_call_grpc_request_action' | 'cmd_call_grpc_request_action'
| 'cmd_call_http_authentication_action' | 'cmd_call_http_authentication_action'
| 'cmd_call_http_request_action' | 'cmd_call_http_request_action'
| 'cmd_call_http_collection_action'
| 'cmd_check_for_updates' | 'cmd_check_for_updates'
| 'cmd_create_grpc_request' | 'cmd_create_grpc_request'
| 'cmd_curl_to_request' | 'cmd_curl_to_request'
@@ -24,6 +25,7 @@ type TauriCmd =
| 'cmd_grpc_reflect' | 'cmd_grpc_reflect'
| 'cmd_grpc_request_actions' | 'cmd_grpc_request_actions'
| 'cmd_http_request_actions' | 'cmd_http_request_actions'
| 'cmd_http_collection_actions'
| 'cmd_http_response_body' | 'cmd_http_response_body'
| 'cmd_import_data' | 'cmd_import_data'
| 'cmd_install_plugin' | 'cmd_install_plugin'