mirror of
https://github.com/Kong/insomnia.git
synced 2026-08-04 11:52:33 -04:00
feat: fix UI and remove debug settings in design spec and test (#9823)
* fix: fix UI style * feat: remove debug settings in spec * fix: remove debug settings in document test * feat: add segment events [INS-2356]
This commit is contained in:
@@ -40,6 +40,8 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
|
||||
|
||||
export interface SendActionParams {
|
||||
requestId: string;
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
shouldPromptForPathAfterResponse?: boolean;
|
||||
ignoreUndefinedEnvVariable?: boolean;
|
||||
}
|
||||
@@ -342,7 +344,8 @@ export const sendActionImplementation = async (options: {
|
||||
|
||||
export async function clientAction({ request, params }: Route.ClientActionArgs) {
|
||||
const { requestId } = params;
|
||||
const { shouldPromptForPathAfterResponse, ignoreUndefinedEnvVariable } = (await request.json()) as SendActionParams;
|
||||
const { shouldPromptForPathAfterResponse, ignoreUndefinedEnvVariable, workspaceId, projectId } =
|
||||
(await request.json()) as SendActionParams;
|
||||
|
||||
try {
|
||||
await sendActionImplementation({
|
||||
@@ -363,6 +366,9 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
window.main.trackSegmentEvent({
|
||||
event: SegmentEvent.requestExecuted,
|
||||
properties: {
|
||||
project_id: projectId,
|
||||
collection_id: workspaceId,
|
||||
request_key_id: requestId,
|
||||
preferredHttpVersion: settings.preferredHttpVersion,
|
||||
// @ts-expect-error -- who cares
|
||||
authenticationType: activeRequest.authentication?.type,
|
||||
|
||||
@@ -131,6 +131,9 @@ export async function clientAction({ params, request }: Route.ClientActionArgs)
|
||||
properties: {
|
||||
requestType,
|
||||
protocol: requestType,
|
||||
project_id: projectId,
|
||||
collection_id: workspaceId,
|
||||
request_key_id: activeRequestId,
|
||||
has_prescript: !!req?.preRequestScript,
|
||||
has_postscript: !!req?.afterResponseScript,
|
||||
request_header_names: req?.headers?.map(h => h.name) || [],
|
||||
|
||||
@@ -507,38 +507,6 @@ const Component = ({ params }: Route.ComponentProps) => {
|
||||
workspaceId={workspaceId}
|
||||
className="border-b border-solid border-(--hl-sm)"
|
||||
/>
|
||||
<div className="flex w-full flex-col items-start gap-2 p-(--padding-sm)">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<EnvironmentPicker
|
||||
isOpen={isEnvironmentPickerOpen}
|
||||
onOpenChange={setIsEnvironmentPickerOpen}
|
||||
onOpenEnvironmentSettingsModal={() => setEnvironmentModalOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onPress={() => setIsCookieModalOpen(true)}
|
||||
className="flex max-w-full flex-1 items-center justify-center gap-2 truncate rounded-xs px-4 py-1 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
|
||||
>
|
||||
<Icon icon="cookie-bite" className="w-5 shrink-0" />
|
||||
<span className="truncate">
|
||||
{activeCookieJar.cookies.length === 0 ? 'Add' : 'Manage'} Cookies{' '}
|
||||
{activeCookieJar.cookies.length > 0 ? `(${activeCookieJar.cookies.length})` : ''}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => setCertificatesModalOpen(true)}
|
||||
className="flex max-w-full flex-1 items-center justify-center gap-2 truncate rounded-xs px-4 py-1 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
|
||||
>
|
||||
<Icon icon="file-contract" className="w-5 shrink-0" />
|
||||
<span className="truncate">
|
||||
{clientCertificates.length === 0 || caCertificate ? 'Add' : 'Manage'} Certificates{' '}
|
||||
{[...clientCertificates, caCertificate].filter(cert => !cert?.disabled).filter(isNotNullOrUndefined)
|
||||
.length > 0
|
||||
? `(${[...clientCertificates, caCertificate].filter(cert => !cert?.disabled).filter(isNotNullOrUndefined).length})`
|
||||
: ''}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2 p-(--padding-sm)">
|
||||
<Heading className="text-(--hl) uppercase">Spec</Heading>
|
||||
<span className="flex-1" />
|
||||
|
||||
@@ -319,38 +319,6 @@ const Component = () => {
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
<DocumentTab organizationId={organizationId} projectId={projectId} workspaceId={workspaceId} />
|
||||
<div className="flex w-full flex-col items-start gap-2 p-(--padding-sm)">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<EnvironmentPicker
|
||||
isOpen={isEnvironmentPickerOpen}
|
||||
onOpenChange={setIsEnvironmentPickerOpen}
|
||||
onOpenEnvironmentSettingsModal={() => setEnvironmentModalOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onPress={() => setIsCookieModalOpen(true)}
|
||||
className="flex max-w-full flex-1 items-center justify-center gap-2 truncate rounded-xs px-4 py-1 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
|
||||
>
|
||||
<Icon icon="cookie-bite" className="w-5 shrink-0" />
|
||||
<span className="truncate">
|
||||
{activeCookieJar.cookies.length === 0 ? 'Add' : 'Manage'} Cookies{' '}
|
||||
{activeCookieJar.cookies.length > 0 ? `(${activeCookieJar.cookies.length})` : ''}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => setCertificatesModalOpen(true)}
|
||||
className="flex max-w-full flex-1 items-center justify-center gap-2 truncate rounded-xs px-4 py-1 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
|
||||
>
|
||||
<Icon icon="file-contract" className="w-5 shrink-0" />
|
||||
<span className="truncate">
|
||||
{clientCertificates.length === 0 || caCertificate ? 'Add' : 'Manage'} Certificates{' '}
|
||||
{[...clientCertificates, caCertificate].filter(cert => !cert?.disabled).filter(isNotNullOrUndefined)
|
||||
.length > 0
|
||||
? `(${[...clientCertificates, caCertificate].filter(cert => !cert?.disabled).filter(isNotNullOrUndefined).length})`
|
||||
: ''}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-(--padding-sm)">
|
||||
<Button
|
||||
|
||||
@@ -220,7 +220,15 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
})
|
||||
)._id;
|
||||
|
||||
window.main.trackSegmentEvent({ event: SegmentEvent.requestCreated, properties: { requestType: 'HTTP' } });
|
||||
window.main.trackSegmentEvent({
|
||||
event: SegmentEvent.requestCreated,
|
||||
properties: {
|
||||
requestType: 'HTTP',
|
||||
project_id: projectId,
|
||||
collection_id: workspace._id,
|
||||
request_key_id: activeRequestId,
|
||||
},
|
||||
});
|
||||
|
||||
return redirect(
|
||||
href(`/organization/:organizationId/project/:projectId/workspace/:workspaceId/debug/request/:requestId`, {
|
||||
|
||||
@@ -231,7 +231,10 @@ const Component = ({ loaderData }: Route.ComponentProps) => {
|
||||
<OrganizationSelect
|
||||
organizationId={organizationId}
|
||||
organizations={organizations || []}
|
||||
onSelect={id => navigate(`/organization/${id}`)}
|
||||
onSelect={id => {
|
||||
window.main.trackSegmentEvent({ event: SegmentEvent.organizationSwitched });
|
||||
navigate(`/organization/${id}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -117,6 +117,9 @@ export enum SegmentEvent {
|
||||
mcpResponseHeadersCopyAllClicked = 'mcp-response-headers-copy-all-clicked',
|
||||
kongKonnectPatValidated = 'kong-konnect-pat-validated',
|
||||
kongKonnectSyncCompleted = 'kong-konnect-sync-completed',
|
||||
projectListFiltered = 'project-list-filtered',
|
||||
projectSwitched = 'project-switched',
|
||||
organizationSwitched = 'organization-switched',
|
||||
}
|
||||
|
||||
type PushPull = 'push' | 'pull';
|
||||
|
||||
@@ -53,7 +53,9 @@ export const EnvironmentPicker = ({
|
||||
};
|
||||
|
||||
const { features } = useOrganizationPermissions();
|
||||
const isUsingInsomniaCloudSync = Boolean(models.project.isRemoteProject(activeProject) && !activeWorkspaceMeta?.gitRepositoryId);
|
||||
const isUsingInsomniaCloudSync = Boolean(
|
||||
models.project.isRemoteProject(activeProject) && !activeWorkspaceMeta?.gitRepositoryId,
|
||||
);
|
||||
const isUsingGitSync = Boolean(features.gitSync.enabled && activeWorkspaceMeta?.gitRepositoryId);
|
||||
|
||||
const setActiveEnvironmentFetcher = useSetActiveEnvironmentFetcher();
|
||||
@@ -91,10 +93,10 @@ export const EnvironmentPicker = ({
|
||||
<DialogTrigger isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<Button
|
||||
aria-label="Manage Environments"
|
||||
className="flex max-w-full flex-col items-start gap-2 truncate rounded-xs px-4 py-1 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
|
||||
className="flex max-w-full items-start gap-2 truncate rounded-xs px-4 py-1 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
|
||||
>
|
||||
{activeGlobalEnvironment && activeGlobalBaseEnvironment && (
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="flex w-full">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Icon
|
||||
icon={
|
||||
@@ -110,10 +112,7 @@ export const EnvironmentPicker = ({
|
||||
className="w-5 shrink-0"
|
||||
/>
|
||||
<span className="truncate">{activeGlobalEnvironment.name}</span>
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Icon icon="0" className="invisible w-5 shrink-0" />
|
||||
<span className="shrink truncate text-xs text-(--hl)">{activeGlobalBaseEnvironment.workspaceName}</span>
|
||||
<Icon icon="plus" className="w-3 shrink-0 text-(--hl)" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -224,7 +224,13 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
|
||||
}
|
||||
|
||||
try {
|
||||
send({ requestId, shouldPromptForPathAfterResponse, ignoreUndefinedEnvVariable });
|
||||
send({
|
||||
requestId,
|
||||
workspaceId: activeWorkspace._id,
|
||||
projectId,
|
||||
shouldPromptForPathAfterResponse,
|
||||
ignoreUndefinedEnvVariable,
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
showModal(AlertModal, {
|
||||
@@ -240,7 +246,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
|
||||
});
|
||||
}
|
||||
},
|
||||
[activeEnvironment._id, activeRequest, activeWorkspace._id, connect, requestId, send, updateTabById],
|
||||
[activeEnvironment._id, activeRequest, activeWorkspace._id, connect, requestId, send, updateTabById, projectId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { models, services } from '~/insomnia-data';
|
||||
import type { SyncResult } from '~/konnect/sync';
|
||||
import { useRootLoaderData } from '~/root';
|
||||
import { useProjectLoaderData } from '~/routes/organization.$organizationId.project.$projectId';
|
||||
import { SegmentEvent } from '~/ui/analytics';
|
||||
import { showModal } from '~/ui/components/modals';
|
||||
import { AlertModal } from '~/ui/components/modals/alert-modal';
|
||||
import { AskModal } from '~/ui/components/modals/ask-modal';
|
||||
@@ -105,6 +106,15 @@ export const ProjectNavigationSidebar = ({ storageRules, konnectSyncEnabled }: P
|
||||
`${organizationId}:project-navigation-sidebar-filter`,
|
||||
'',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (projectNavigationSidebarFilter) {
|
||||
window.main.trackSegmentEvent({
|
||||
event: SegmentEvent.projectListFiltered,
|
||||
});
|
||||
}
|
||||
}, [projectNavigationSidebarFilter]);
|
||||
|
||||
const [konnectFilter, setKonnectFilter] = reactUse.useLocalStorage(
|
||||
`${organizationId}:project-navigation-konnect-filter`,
|
||||
'',
|
||||
@@ -692,6 +702,7 @@ export const ProjectNavigationSidebar = ({ storageRules, konnectSyncEnabled }: P
|
||||
if (routeInfo?.resourceId === docId) {
|
||||
toggleProjectOrWorkspace(docId);
|
||||
} else {
|
||||
!isScratchPad && window.main.trackSegmentEvent({ event: SegmentEvent.projectSwitched });
|
||||
!isScratchPad && navigate(`/organization/${organizationId}/project/${docId}`);
|
||||
}
|
||||
} else if (item.kind === 'workspace') {
|
||||
|
||||
@@ -25,6 +25,11 @@ export function ResourceIcon({ resource }: { resource: any }) {
|
||||
'mcp': ['fac', 'mcp'],
|
||||
}[resource.scope] as IconProp) || null;
|
||||
}
|
||||
|
||||
if (models.requestGroup.isRequestGroup(resource)) {
|
||||
icon = 'folder';
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
return <Icon icon={icon} className="w-3 shrink-0" />;
|
||||
}
|
||||
|
||||
@@ -26,61 +26,71 @@ export function useWorkspaceBreadcrumbs({ isMcp }: { isMcp: boolean }) {
|
||||
const reqOrGrp = collection.find(col => col.doc._id === reqOrGrpId);
|
||||
|
||||
// workspace is not in collection, so we need to filter it out.
|
||||
const ancestors = reqOrGrp?.ancestors
|
||||
? reqOrGrp.ancestors.map((id: string) => resourcesById.get(id)).filter(Boolean)
|
||||
: [];
|
||||
const breadcrumbs: PaneBreadcrumb[] = [
|
||||
{
|
||||
id: 'project',
|
||||
label: activeProject.name,
|
||||
to: `/organization/${organizationId}/project/${activeProject._id}`,
|
||||
icon: <ResourceIcon resource={activeProject} />,
|
||||
},
|
||||
];
|
||||
const ancestors = useMemo(() => {
|
||||
if (!reqOrGrp?.ancestors) {
|
||||
return [];
|
||||
}
|
||||
return reqOrGrp.ancestors
|
||||
.map((id: string) => resourcesById.get(id))
|
||||
.filter(Boolean) as (typeof collection)[number]['doc'][];
|
||||
}, [reqOrGrp, resourcesById]);
|
||||
|
||||
if (activeWorkspace) {
|
||||
breadcrumbs.push({
|
||||
id: activeWorkspace._id,
|
||||
label: activeWorkspace.name,
|
||||
to: buildResourceUrl({
|
||||
organizationId: organizationId!,
|
||||
projectId: activeProject._id,
|
||||
workspaceId: activeWorkspace._id,
|
||||
resource: activeWorkspace,
|
||||
}),
|
||||
icon: <ResourceIcon resource={activeWorkspace} />,
|
||||
});
|
||||
}
|
||||
const breadcrumbs: PaneBreadcrumb[] = useMemo(() => {
|
||||
const crumbs: PaneBreadcrumb[] = [
|
||||
{
|
||||
id: 'project',
|
||||
label: activeProject.name,
|
||||
to: `/organization/${organizationId}/project/${activeProject._id}`,
|
||||
icon: <ResourceIcon resource={activeProject} />,
|
||||
},
|
||||
];
|
||||
|
||||
if (ancestors.length) {
|
||||
breadcrumbs.push(
|
||||
...ancestors.map(doc => ({
|
||||
id: doc!._id,
|
||||
label: doc!.name,
|
||||
if (activeWorkspace) {
|
||||
crumbs.push({
|
||||
id: activeWorkspace._id,
|
||||
label: activeWorkspace.name,
|
||||
to: buildResourceUrl({
|
||||
organizationId: organizationId!,
|
||||
projectId: activeProject._id,
|
||||
workspaceId: activeWorkspace!._id,
|
||||
resource: doc!,
|
||||
workspaceId: activeWorkspace._id,
|
||||
resource: activeWorkspace,
|
||||
}),
|
||||
icon: <ResourceIcon resource={doc!} />,
|
||||
})),
|
||||
);
|
||||
}
|
||||
icon: <ResourceIcon resource={activeWorkspace} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (activeRequest && !isMcp) {
|
||||
breadcrumbs.push({
|
||||
id: activeRequest._id,
|
||||
label: activeRequest.name || 'Untitled request',
|
||||
icon: <ResourceIcon resource={activeRequest} />,
|
||||
});
|
||||
} else if (activeRequestGroup) {
|
||||
breadcrumbs.push({
|
||||
id: activeRequestGroup._id,
|
||||
label: activeRequestGroup.name || 'Untitled request group',
|
||||
icon: <ResourceIcon resource={activeRequestGroup} />,
|
||||
});
|
||||
}
|
||||
if (ancestors.length) {
|
||||
crumbs.push(
|
||||
...ancestors.map(doc => ({
|
||||
id: doc!._id,
|
||||
label: doc!.name,
|
||||
to: buildResourceUrl({
|
||||
organizationId: organizationId!,
|
||||
projectId: activeProject._id,
|
||||
workspaceId: activeWorkspace!._id,
|
||||
resource: doc!,
|
||||
}),
|
||||
icon: <ResourceIcon resource={doc!} />,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (activeRequest && !isMcp) {
|
||||
crumbs.push({
|
||||
id: activeRequest._id,
|
||||
label: activeRequest.name || 'Untitled request',
|
||||
icon: <ResourceIcon resource={activeRequest} />,
|
||||
});
|
||||
} else if (activeRequestGroup) {
|
||||
crumbs.push({
|
||||
id: activeRequestGroup._id,
|
||||
label: activeRequestGroup.name || 'Untitled group',
|
||||
icon: <ResourceIcon resource={activeRequestGroup} />,
|
||||
});
|
||||
}
|
||||
|
||||
return crumbs;
|
||||
}, [activeProject, activeWorkspace, ancestors, activeRequest, activeRequestGroup, organizationId]);
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Button } from 'react-aria-components';
|
||||
|
||||
import { isNotNullOrUndefined } from '~/common/misc';
|
||||
@@ -20,6 +20,22 @@ export default function WorkspacePaneHeader({ hasSettings }: { hasSettings: bool
|
||||
const { activeCookieJar, caCertificate, clientCertificates, activeWorkspace } = useWorkspaceLoaderData()!;
|
||||
const { activeRequest } = useRequestLoaderData() || {};
|
||||
|
||||
const breadcrumbs = useWorkspaceBreadcrumbs();
|
||||
const realBreadcrumbs = useMemo(() => {
|
||||
if (breadcrumbs.length > 4) {
|
||||
return [
|
||||
breadcrumbs[0],
|
||||
breadcrumbs[1],
|
||||
{
|
||||
id: '_ellipsis',
|
||||
label: '...', // not interactive currently
|
||||
},
|
||||
breadcrumbs[breadcrumbs.length - 1],
|
||||
];
|
||||
}
|
||||
return breadcrumbs;
|
||||
}, [breadcrumbs]);
|
||||
|
||||
const [isEnvironmentPickerOpen, setIsEnvironmentPickerOpen] = useState(false);
|
||||
const [isEnvironmentModalOpen, setEnvironmentModalOpen] = useState(false);
|
||||
const [isCookieModalOpen, setIsCookieModalOpen] = useState(false);
|
||||
@@ -44,7 +60,7 @@ export default function WorkspacePaneHeader({ hasSettings }: { hasSettings: bool
|
||||
|
||||
return (
|
||||
<PaneHeader
|
||||
breadcrumbs={breadcrumbs}
|
||||
breadcrumbs={realBreadcrumbs}
|
||||
rightSlot={
|
||||
hasSettings ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user