Request Inheritance (#209)

This commit is contained in:
Gregory Schier
2025-05-23 08:13:25 -07:00
committed by GitHub
parent 13d959799a
commit 4cd2e9cd31
41 changed files with 1031 additions and 403 deletions

View File

@@ -0,0 +1,64 @@
import type { Folder } from '@yaakapp-internal/models';
import { patchModel } from '@yaakapp-internal/models';
import { useMemo } from 'react';
import { IconTooltip } from '../components/core/IconTooltip';
import { HStack } from '../components/core/Stacks';
import type { TabItem } from '../components/core/Tabs/Tabs';
import { useHttpAuthenticationSummaries } from './useHttpAuthentication';
import type { AuthenticatedModel} from './useInheritedAuthentication';
import { useInheritedAuthentication } from './useInheritedAuthentication';
export function useAuthTab<T extends string>(tabValue: T, model: AuthenticatedModel | null) {
const authentication = useHttpAuthenticationSummaries();
const inheritedAuth = useInheritedAuthentication(model);
return useMemo<TabItem[]>(() => {
if (model == null) return [];
const tab: TabItem = {
value: tabValue,
label: 'Auth',
options: {
value: model.authenticationType,
items: [
...authentication.map((a) => ({
label: a.label || 'UNKNOWN',
shortLabel: a.shortLabel,
value: a.name,
})),
{ type: 'separator' },
{
label: 'Inherit from Parent',
shortLabel:
inheritedAuth != null && inheritedAuth.authenticationType != 'none' ? (
<HStack space={1.5}>
{authentication.find((a) => a.name === inheritedAuth.authenticationType)
?.shortLabel ?? 'UNKNOWN'}
<IconTooltip
icon="magic_wand"
iconSize="xs"
content="Authenticatin was inherited from an ancestor"
/>
</HStack>
) : (
'Auth'
),
value: null,
},
{ label: 'No Auth', shortLabel: 'No Auth', value: 'none' },
],
onChange: async (authenticationType) => {
let authentication: Folder['authentication'] = model.authentication;
if (model.authenticationType !== authenticationType) {
authentication = {
// Reset auth if changing types
};
}
await patchModel(model, { authentication, authenticationType });
},
},
};
return [tab];
}, [authentication, inheritedAuth, model, tabValue]);
}

View File

@@ -0,0 +1,31 @@
import React, { useMemo } from 'react';
import { CountBadge } from '../components/core/CountBadge';
import type { TabItem } from '../components/core/Tabs/Tabs';
import type { HeaderModel } from './useInheritedHeaders';
import { useInheritedHeaders } from './useInheritedHeaders';
export function useHeadersTab<T extends string>(
tabValue: T,
model: HeaderModel | null,
label?: string,
) {
const inheritedHeaders = useInheritedHeaders(model);
return useMemo<TabItem[]>(() => {
if (model == null) return [];
const allHeaders = [
...inheritedHeaders,
...(model.model === 'grpc_request' ? model.metadata : model.headers),
];
const numHeaders = allHeaders.filter((h) => h.name).length;
const tab: TabItem = {
value: tabValue,
label: label ?? 'Headers',
rightSlot: <CountBadge count={numHeaders} />,
};
return [tab];
}, [inheritedHeaders, label, model, tabValue]);
}

View File

@@ -1,5 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
import type {
Folder,
GrpcRequest,
HttpRequest,
WebsocketRequest,
Workspace,
} from '@yaakapp-internal/models';
import { httpResponsesAtom } from '@yaakapp-internal/models';
import type { GetHttpAuthenticationConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
import { useAtomValue } from 'jotai';
@@ -49,13 +55,15 @@ export function useHttpAuthenticationConfig(
...config,
actions: config.actions?.map((a, i) => ({
...a,
call: async ({ id: requestId }: HttpRequest | GrpcRequest | WebsocketRequest) => {
call: async ({
id: modelId,
}: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace) => {
await invokeCmd('cmd_call_http_authentication_action', {
pluginRefId: config.pluginRefId,
actionIndex: i,
authName,
values,
requestId,
modelId,
});
// Ensure the config is refreshed after the action is done

View File

@@ -0,0 +1,50 @@
import type {
Folder,
GrpcRequest,
HttpRequest,
WebsocketRequest,
Workspace,
} from '@yaakapp-internal/models';
import { foldersAtom, workspacesAtom } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai';
const ancestorsAtom = atom(function (get) {
return [...get(foldersAtom), ...get(workspacesAtom)];
});
export type AuthenticatedModel = HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
export function useInheritedAuthentication(
baseModel: AuthenticatedModel | null,
) {
const parents = useAtomValue(ancestorsAtom);
if (baseModel == null) return null;
const next = (child: AuthenticatedModel) => {
// We hit the top
if (child.model === 'workspace') {
return child.authenticationType == null ? null : child;
}
// Has valid auth
if (child.authenticationType !== null) {
return child;
}
// Recurse up the tree
const parent = parents.find((p) => {
if (child.folderId) return p.id === child.folderId;
else return p.id === child.workspaceId;
});
// Failed to find parent (should never happen)
if (parent == null) {
return null;
}
return next(parent);
};
return next(baseModel);
}

View File

@@ -0,0 +1,46 @@
import type {
Folder,
GrpcRequest,
HttpRequest,
HttpRequestHeader,
WebsocketRequest,
Workspace,
} from '@yaakapp-internal/models';
import { foldersAtom, workspacesAtom } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai';
const ancestorsAtom = atom(function (get) {
return [...get(foldersAtom), ...get(workspacesAtom)];
});
export type HeaderModel = HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
export function useInheritedHeaders(baseModel: HeaderModel | null) {
const parents = useAtomValue(ancestorsAtom);
if (baseModel == null) return [];
if (baseModel.model === 'workspace') return [];
const next = (child: HeaderModel): HttpRequestHeader[] => {
// Short-circuit
if (child.model === 'workspace') {
return [];
}
// Recurse up the tree
const parent = parents.find((p) => {
if (child.folderId) return p.id === child.folderId;
else return p.id === child.workspaceId;
});
// Failed to find parent (should never happen)
if (parent == null) {
return [];
}
const headers = next(parent);
return [...headers, ...parent.headers];
};
return next(baseModel);
}