diff --git a/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx b/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx index 09a72b27b9..5fa9be05b3 100644 --- a/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx +++ b/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx @@ -333,7 +333,6 @@ export const GraphQLEditor: FC = ({ }; const handleQueryUserActivity = () => { const newOperationName = getCurrentOperation(); - console.log('newOperationName', newOperationName); const { query, variables, operationName } = state.body; if (newOperationName !== operationName) { diff --git a/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx b/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx index d659b12d3b..e9205a916d 100644 --- a/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx +++ b/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx @@ -1,5 +1,6 @@ import fs from 'fs'; -import React, { FC, useEffect, useState } from 'react'; +import { SvgIcon } from 'insomnia-components'; +import React, { FC, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { Tab, TabList, TabPanel, Tabs } from 'react-tabs'; import styled from 'styled-components'; @@ -10,6 +11,7 @@ import { WebSocketEvent } from '../../../main/network/websocket'; import { WebSocketResponse } from '../../../models/websocket-response'; import { useWebSocketConnectionEvents } from '../../context/websocket-client/use-ws-connection-events'; import { selectActiveResponse } from '../../redux/selectors'; +import { Button } from '../base/button'; import { ResponseHistoryDropdown } from '../dropdowns/response-history-dropdown'; import { ErrorBoundary } from '../error-boundary'; import { EmptyStatePane } from '../panes/empty-state-pane'; @@ -48,6 +50,39 @@ const PaneBodyContent = styled.div({ gridTemplateRows: 'repeat(auto-fit, minmax(0, 1fr))', }); +const EventSearchFormControl = styled.div({ + outline: 'none', + width: '100%', + boxSizing: 'border-box', + position: 'relative', + display: 'flex', + border: '1px solid var(--hl-md)', + borderRadius: 'var(--radius-md)', +}); + +const EventSearchInput = styled.input({ + paddingRight: '2em', + padding: 'var(--padding-sm)', + backgroundColor: 'var(--hl-xxs)', + width: '100%', + display: 'block', + boxSizing: 'border-box', + + // Remove the default search input cancel button + '::-webkit-search-cancel-button': { + display: 'none', + }, + + ':focus': { + backgroundColor: 'transparent', + borderColor: 'var(--hl-lg)', + }, +}); + +const PaddedButton = styled(Button)({ + padding: 'var(--padding-sm)', +}); + export const WebSocketResponsePane: FC<{ requestId: string }> = ({ requestId, @@ -80,13 +115,50 @@ const WebSocketActiveResponsePane: FC<{ requestId: string; response: WebSocketRe }) => { const [selectedEvent, setSelectedEvent] = useState(null); const [timeline, setTimeline] = useState([]); - const events = useWebSocketConnectionEvents({ responseId: response._id }); + + const searchInputRef = useRef(null); + const [clearEventsBefore, setClearEventsBefore] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [eventType, setEventType] = useState(); + const allEvents = useWebSocketConnectionEvents({ responseId: response._id }); const handleSelection = (event: WebSocketEvent) => { setSelectedEvent((selected: WebSocketEvent | null) => selected?._id === event._id ? null : event); }; + const events = allEvents.filter(event => { + // Filter out events that are earlier than the clearEventsBefore timestamp + if (clearEventsBefore && event.timestamp <= clearEventsBefore) { + return false; + } + + // Filter out events that don't match the selected event type + if (eventType && event.type !== eventType) { + return false; + } + + // Filter out events that don't match the search query + if (searchQuery) { + if (event.type === 'message') { + return event.data.toString().toLowerCase().includes(searchQuery.toLowerCase()); + } + if (event.type === 'error') { + return event.message.toLowerCase().includes(searchQuery.toLowerCase()); + } + if (event.type === 'close') { + return event.reason.toLowerCase().includes(searchQuery.toLowerCase()); + } + + // Filter out open events + return false; + } + + return true; + }); + useEffect(() => { setSelectedEvent(null); + setSearchQuery(''); + setClearEventsBefore(null); }, [response._id]); useEffect(() => { @@ -149,15 +221,58 @@ const WebSocketActiveResponsePane: FC<{ requestId: string; response: WebSocketRe {response.error ? : <> - {Boolean(events?.length) && ( - + +
+ + + + setSearchQuery(e.currentTarget.value)} + /> + {searchQuery && ( + { + setSearchQuery(''); + searchInputRef.current?.focus(); + }} + > + + + )} + + { + const lastEvent = events[0]; + setClearEventsBefore(lastEvent.timestamp); + }} + > +
+ {Boolean(events?.length) && ( -
- )} + )} +
{selectedEvent && ( ([]); + + useEffect(() => { + setEvents([]); + }, [responseId]); + useInterval( () => { let isMounted = true; @@ -21,5 +26,6 @@ export function useWebSocketConnectionEvents({ responseId }: { responseId: strin }, 500 ); + return events; }