Force refresh cleanup (#5184)

* remove forceRefreshCounter from app.tsx

* simplify refresh counter for ws request pane

* remove forceRefreshKey from remaining panes

* remove unused nunjucks key

* cleanup forceUpdate drills

* undrill handleSetActiveResponse

* put restore back in

* more uniqueness

Co-authored-by: jackkav <jackkav@gmail.com>
This commit is contained in:
James Gatz
2022-09-16 14:56:41 +02:00
committed by GitHub
parent fbf2fde422
commit 9857ee8712
14 changed files with 161 additions and 203 deletions

View File

@@ -1,6 +1,6 @@
import fs from 'fs';
import { database as db } from '../common/database';
import { database as db, Query } from '../common/database';
import * as requestOperations from './helpers/request-operations';
import type { BaseModel } from './index';
import * as models from './index';
@@ -144,6 +144,32 @@ export async function create(patch: Partial<WebSocketResponse> = {}, maxResponse
return db.docCreate(type, patch);
}
async function _findRecentForRequest(
requestId: string,
environmentId: string | null,
limit: number,
) {
const query: Query = {
parentId: requestId,
};
// Filter responses by environment if setting is enabled
if ((await models.settings.getOrCreate()).filterResponsesByEnv) {
query.environmentId = environmentId;
}
return db.findMostRecentlyModified<WebSocketResponse>(type, query, limit);
}
export async function getLatestForRequest(
requestId: string,
environmentId: string | null,
) {
const responses = await _findRecentForRequest(requestId, environmentId, 1);
const response = responses[0] as WebSocketResponse | null | undefined;
return response || null;
}
export function getLatestByParentId(parentId: string) {
return db.getMostRecentlyModified<WebSocketResponse>(type, {
parentId,

View File

@@ -8,6 +8,7 @@ import { decompressObject } from '../../../common/misc';
import * as models from '../../../models/index';
import { Response } from '../../../models/response';
import { isWebSocketResponse, WebSocketResponse } from '../../../models/websocket-response';
import { updateRequestMetaByParentId } from '../../hooks/create-request';
import { selectActiveEnvironment, selectActiveRequest, selectActiveRequestResponses, selectRequestVersions } from '../../redux/selectors';
import { type DropdownHandle, Dropdown } from '../base/dropdown/dropdown';
import { DropdownButton } from '../base/dropdown/dropdown-button';
@@ -23,14 +24,12 @@ import { TimeFromNow } from '../time-from-now';
interface Props<GenericResponse extends Response | WebSocketResponse> {
activeResponse: GenericResponse;
handleSetActiveResponse: (requestId: string, activeResponse: GenericResponse | null) => void;
className?: string;
requestId: string;
}
export const ResponseHistoryDropdown = <GenericResponse extends Response | WebSocketResponse>({
activeResponse,
handleSetActiveResponse,
className,
requestId,
}: Props<GenericResponse>) => {
@@ -48,29 +47,53 @@ export const ResponseHistoryDropdown = <GenericResponse extends Response | WebSo
other: [],
};
const handleSetActiveResponse = useCallback(async (requestId: string, activeResponse: Response | WebSocketResponse) => {
if (isWebSocketResponse(activeResponse)) {
window.main.webSocket.close({ requestId });
}
if (activeResponse.requestVersionId) {
await models.requestVersion.restore(activeResponse.requestVersionId);
}
await updateRequestMetaByParentId(requestId, { activeResponseId: activeResponse._id });
}, []);
const handleDeleteResponses = useCallback(async () => {
const environmentId = activeEnvironment ? activeEnvironment._id : null;
if (isWebSocketResponse(activeResponse)) {
window.main.webSocket.closeAll();
await models.webSocketResponse.removeForRequest(requestId, environmentId);
} else {
await models.response.removeForRequest(requestId, environmentId);
}
if (activeRequest && activeRequest._id === requestId) {
handleSetActiveResponse(requestId, null);
await updateRequestMetaByParentId(requestId, { activeResponseId: null });
}
}, [activeEnvironment, activeRequest, activeResponse, handleSetActiveResponse, requestId]);
}, [activeEnvironment, activeRequest, activeResponse, requestId]);
const handleDeleteResponse = useCallback(async () => {
let response: Response | WebSocketResponse | null = null;
if (activeResponse) {
if (isWebSocketResponse(activeResponse)) {
window.main.webSocket.close({ requestId });
await models.webSocketResponse.remove(activeResponse);
const environmentId = activeEnvironment?._id || null;
response = await models.webSocketResponse.getLatestForRequest(requestId, environmentId);
} else {
await models.response.remove(activeResponse);
const environmentId = activeEnvironment?._id || null;
response = await models.response.getLatestForRequest(requestId, environmentId);
}
if (response?.requestVersionId) {
// Deleting a response restores latest request body
await models.requestVersion.restore(response.requestVersionId);
}
await updateRequestMetaByParentId(requestId, { activeResponseId: response?._id || null });
}
handleSetActiveResponse(requestId, null);
}, [activeResponse, handleSetActiveResponse, requestId]);
}, [activeEnvironment?._id, activeResponse, requestId]);
responses.forEach(response => {
const responseTime = new Date(response.created);
@@ -103,6 +126,7 @@ export const ResponseHistoryDropdown = <GenericResponse extends Response | WebSo
const active = response._id === activeResponseId;
const requestVersion = requestVersions.find(({ _id }) => _id === response.requestVersionId);
const request = requestVersion ? decompressObject(requestVersion.compressedRequest) : null;
return (
<DropdownItem
key={response._id}

View File

@@ -12,11 +12,10 @@ import {
} from '../../../../common/constants';
import { documentationLinks } from '../../../../common/documentation';
import { getContentTypeHeader } from '../../../../common/misc';
import { update } from '../../../../models/helpers/request-operations';
import * as models from '../../../../models';
import type {
Request,
RequestBodyParameter,
RequestHeader,
} from '../../../../models/request';
import {
newBodyFile,
@@ -37,7 +36,6 @@ import { RawEditor } from './raw-editor';
import { UrlEncodedEditor } from './url-encoded-editor';
interface Props {
onChangeHeaders: (r: Request, headers: RequestHeader[]) => Promise<Request>;
request: Request;
workspace: Workspace;
settings: Settings;
@@ -45,7 +43,6 @@ interface Props {
}
export const BodyEditor: FC<Props> = ({
onChangeHeaders,
request,
workspace,
settings,
@@ -54,28 +51,28 @@ export const BodyEditor: FC<Props> = ({
const handleRawChange = useCallback((rawValue: string) => {
const oldContentType = request.body.mimeType || '';
const body = newBodyRaw(rawValue, oldContentType);
update(request, { body });
models.request.update(request, { body });
}, [request]);
const handleGraphQLChange = useCallback((content: string) => {
const body = newBodyRaw(content, CONTENT_TYPE_GRAPHQL);
update(request, { body });
models.request.update(request, { body });
}, [request]);
const handleFormUrlEncodedChange = useCallback((parameters: RequestBodyParameter[]) => {
const body = newBodyFormUrlEncoded(parameters);
update(request, { body });
models.request.update(request, { body });
}, [request]);
const handleFormChange = useCallback((parameters: RequestBodyParameter[]) => {
const body = newBodyForm(parameters);
update(request, { body });
models.request.update(request, { body });
}, [request]);
const handleFileChange = async (path: string) => {
const headers = clone(request.headers);
const body = newBodyFile(path);
const newRequest = await update(request, { body });
const newRequest = await models.request.update(request, { body });
let contentTypeHeader = getContentTypeHeader(headers);
if (!contentTypeHeader) {
@@ -100,7 +97,7 @@ export const BodyEditor: FC<Props> = ({
</p>,
onDone: (saidYes: boolean) => {
if (saidYes) {
onChangeHeaders(newRequest, headers);
models.request.update(newRequest, { headers });
}
},
});

View File

@@ -11,7 +11,6 @@ import { TagEditor } from '../templating/tag-editor';
import { VariableEditor } from '../templating/variable-editor';
interface Props {
uniqueKey: string;
workspace: Workspace;
}
@@ -66,7 +65,7 @@ export class NunjucksModal extends PureComponent<Props, State> {
}
render() {
const { uniqueKey, workspace } = this.props;
const { workspace } = this.props;
const { defaultTemplate } = this.state;
let editor: JSX.Element | null = null;
let title = '';
@@ -80,7 +79,7 @@ export class NunjucksModal extends PureComponent<Props, State> {
}
return (
<Modal ref={this._setModalRef} onHide={this._handleModalHide} key={uniqueKey}>
<Modal ref={this._setModalRef} onHide={this._handleModalHide}>
<ModalHeader>Edit {title}</ModalHeader>
<ModalBody className="pad" key={defaultTemplate}>
<form onSubmit={this._handleSubmit}>{editor}</form>

View File

@@ -1,5 +1,6 @@
import { SvgIcon } from 'insomnia-components';
import React, { FunctionComponent, useCallback } from 'react';
import { useSelector } from 'react-redux';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import styled from 'styled-components';
@@ -10,6 +11,8 @@ import { executeHotKey } from '../../../../common/hotkeys-listener';
import type { GrpcRequest } from '../../../../models/grpc-request';
import type { Settings } from '../../../../models/settings';
import { useGrpc } from '../../../context/grpc';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../../hooks/use-vcs-version';
import { selectActiveEnvironment } from '../../../redux/selectors';
import { GrpcSendButton } from '../../buttons/grpc-send-button';
import { OneLineEditor } from '../../codemirror/one-line-editor';
import { GrpcMethodDropdown } from '../../dropdowns/grpc-method-dropdown/grpc-method-dropdown';
@@ -26,7 +29,6 @@ import useProtoFileReload from './use-proto-file-reload';
import useSelectedMethod from './use-selected-method';
interface Props {
forceRefreshKey: number;
activeRequest: GrpcRequest;
environmentId: string;
workspaceId: string;
@@ -55,7 +57,6 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
activeRequest,
environmentId,
workspaceId,
forceRefreshKey,
}) => {
const [state, dispatch] = useGrpc(activeRequest._id);
const { requestMessages, running, methods } = state;
@@ -66,9 +67,11 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
// @ts-expect-error -- TSCONVERSION methodType can be undefined
const handleAction = useActionHandlers(activeRequest._id, environmentId, methodType, dispatch);
const getExistingGrpcUrls = useExistingGrpcUrls(workspaceId, activeRequest._id);
// Used to refresh input fields to their default value when switching between requests.
// This is a common pattern in this codebase.
const uniquenessKey = `${forceRefreshKey}::${activeRequest._id}`;
const gitVersion = useGitVCSVersion();
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
const activeEnvironment = useSelector(selectActiveEnvironment);
// Reset the response pane state when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
const uniquenessKey = `${activeEnvironment?.modified}::${activeRequest?._id}::${gitVersion}::${activeRequestSyncVersion}`;
const { start } = handleAction;
const _handleKeyDown = useCallback((event: KeyboardEvent) => {

View File

@@ -1,21 +1,26 @@
import React, { FunctionComponent } from 'react';
import { useSelector } from 'react-redux';
import type { GrpcRequest } from '../../../models/grpc-request';
import { useGrpcRequestState } from '../../context/grpc';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import { selectActiveEnvironment } from '../../redux/selectors';
import { GrpcSpinner } from '../grpc-spinner';
import { GrpcStatusTag } from '../tags/grpc-status-tag';
import { GrpcTabbedMessages } from '../viewers/grpc-tabbed-messages';
import { Pane, PaneBody, PaneHeader } from './pane';
interface Props {
forceRefreshKey: number;
activeRequest: GrpcRequest;
}
export const GrpcResponsePane: FunctionComponent<Props> = ({ activeRequest, forceRefreshKey }) => {
// Used to refresh input fields to their default value when switching between requests.
// This is a common pattern in this codebase.
const uniquenessKey = `${forceRefreshKey}::${activeRequest._id}`;
export const GrpcResponsePane: FunctionComponent<Props> = ({ activeRequest }) => {
const gitVersion = useGitVCSVersion();
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
const activeEnvironment = useSelector(selectActiveEnvironment);
// Force re-render when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
const uniquenessKey = `${activeEnvironment?.modified}::${activeRequest?._id}::${gitVersion}::${activeRequestSyncVersion}`;
const { responseMessages, status, error } = useGrpcRequestState(activeRequest._id);
return (
<Pane type="response">

View File

@@ -1,6 +1,7 @@
import classnames from 'classnames';
import { deconstructQueryStringToParams, extractQueryStringFromUrl } from 'insomnia-url';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { useMount } from 'react-use';
@@ -8,12 +9,11 @@ import { getContentTypeFromHeaders } from '../../../common/constants';
import * as models from '../../../models';
import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls';
import { update } from '../../../models/helpers/request-operations';
import type {
Request,
RequestHeader,
} from '../../../models/request';
import type { Request } from '../../../models/request';
import type { Settings } from '../../../models/settings';
import type { Workspace } from '../../../models/workspace';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import { selectActiveEnvironment, selectActiveRequestMeta } from '../../redux/selectors';
import { AuthDropdown } from '../dropdowns/auth-dropdown';
import { ContentTypeDropdown } from '../dropdowns/content-type-dropdown';
import { AuthWrapper } from '../editors/auth/auth-wrapper';
@@ -31,9 +31,6 @@ import { PlaceholderRequestPane } from './placeholder-request-pane';
interface Props {
environmentId: string;
forceRefreshCounter: number;
forceUpdateRequest: (r: Request, patch: Partial<Request>) => Promise<Request>;
forceUpdateRequestHeaders: (r: Request, headers: RequestHeader[]) => Promise<Request>;
handleImport: Function;
request?: Request | null;
settings: Settings;
@@ -42,9 +39,6 @@ interface Props {
export const RequestPane: FC<Props> = ({
environmentId,
forceRefreshCounter,
forceUpdateRequest,
forceUpdateRequestHeaders,
handleImport,
request,
settings,
@@ -99,12 +93,12 @@ export const RequestPane: FC<Props> = ({
// Only update if url changed
if (url !== request.url) {
forceUpdateRequest(request, {
models.request.update(request, {
url,
parameters,
});
}
}, [request, forceUpdateRequest]);
}, [request]);
const requestUrlBarRef = useRef<RequestUrlBarHandle>(null);
useMount(() => {
@@ -116,6 +110,13 @@ export const RequestPane: FC<Props> = ({
request?._id, // happens when the user switches requests
settings.hasPromptedAnalytics, // happens when the user dismisses the analytics modal
]);
const gitVersion = useGitVCSVersion();
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
const activeEnvironment = useSelector(selectActiveEnvironment);
// const activeResponse = useSelector(selectActiveResponse);
const activeRequestMeta = useSelector(selectActiveRequestMeta);
// Force re-render when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
const uniqueKey = `${activeEnvironment?.modified}::${request?._id}::${gitVersion}::${activeRequestSyncVersion}::${activeRequestMeta?.activeResponseId}`;
if (!request) {
return (
@@ -139,7 +140,6 @@ export const RequestPane: FC<Props> = ({
const numParameters = request.parameters.filter(p => !p.disabled).length;
const numHeaders = request.headers.filter(h => !h.disabled).length;
const urlHasQueryParameters = request.url.indexOf('?') >= 0;
const uniqueKey = `${forceRefreshCounter}::${request._id}`;
const contentType = getContentTypeFromHeaders(request.headers) || request.body.mimeType;
return (
<Pane type="request">
@@ -195,7 +195,6 @@ export const RequestPane: FC<Props> = ({
workspace={workspace}
environmentId={environmentId}
settings={settings}
onChangeHeaders={forceUpdateRequestHeaders}
/>
</TabPanel>
<TabPanel className="react-tabs__tab-panel scrollable-container">

View File

@@ -33,12 +33,10 @@ import { PlaceholderResponsePane } from './placeholder-response-pane';
interface Props {
handleSetFilter: (filter: string) => void;
handleSetActiveResponse: (requestId: string, activeResponse: Response | null) => void;
request?: Request | null;
}
export const ResponsePane: FC<Props> = ({
handleSetFilter,
handleSetActiveResponse,
request,
}) => {
const response = useSelector(selectActiveResponse) as Response | null;
@@ -135,7 +133,6 @@ export const ResponsePane: FC<Props> = ({
</div>
<ResponseHistoryDropdown
activeResponse={response}
handleSetActiveResponse={handleSetActiveResponse}
requestId={request._id}
className="tall pane__header__right"
/>

View File

@@ -6,9 +6,11 @@ import styled from 'styled-components';
import { AuthType, CONTENT_TYPE_JSON } from '../../../common/constants';
import { getRenderContext, render, RENDER_PURPOSE_SEND } from '../../../common/render';
import * as models from '../../../models';
import { Environment } from '../../../models/environment';
import { WebSocketRequest } from '../../../models/websocket-request';
import { ReadyState, useWSReadyState } from '../../context/websocket-client/use-ws-ready-state';
import { selectSettings } from '../../redux/selectors';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import { selectActiveRequestMeta, selectSettings } from '../../redux/selectors';
import { CodeEditor, UnconnectedCodeEditor } from '../codemirror/code-editor';
import { AuthDropdown } from '../dropdowns/auth-dropdown';
import { WebSocketPreviewModeDropdown } from '../dropdowns/websocket-preview-mode';
@@ -152,15 +154,14 @@ const WebSocketRequestForm: FC<FormProps> = ({
interface Props {
request: WebSocketRequest;
workspaceId: string;
environmentId: string;
forceRefreshKey: number;
environment: Environment | null;
}
// requestId is something we can read from the router params in the future.
// essentially we can lift up the states and merge request pane and response pane into a single page and divide the UI there.
// currently this is blocked by the way page layout divide the panes with dragging functionality
// TODO: @gatzjames discuss above assertion in light of request and settings drills
export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environmentId, forceRefreshKey }) => {
export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environment }) => {
const readyState = useWSReadyState(request._id);
const { useBulkParametersEditor } = useSelector(selectSettings);
@@ -205,7 +206,12 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
}
};
const uniqueKey = `${forceRefreshKey}::${request._id}`;
const gitVersion = useGitVCSVersion();
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
const activeRequestMeta = useSelector(selectActiveRequestMeta);
// Reset the response pane state when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
const uniqueKey = `${environment?.modified}::${request?._id}::${gitVersion}::${activeRequestSyncVersion}::${activeRequestMeta?.activeResponseId}`;
return (
<Pane type="request">
@@ -214,7 +220,7 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
key={uniqueKey}
request={request}
workspaceId={workspaceId}
environmentId={environmentId}
environmentId={environment?._id || ''}
defaultValue={request.url}
readyState={readyState}
onChange={handleOnChange}
@@ -249,12 +255,12 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
key={uniqueKey}
request={request}
previewMode={previewMode}
environmentId={environmentId}
environmentId={environment?._id || ''}
/>
</PayloadTabPanel>
<TabPanel className="react-tabs__tab-panel">
<AuthWrapper
key={`${uniqueKey}-${request.authentication.type}-auth-header`}
key={uniqueKey}
disabled={disabled}
/>
</TabPanel>
@@ -285,7 +291,7 @@ export const WebSocketRequestPane: FC<Props> = ({ request, workspaceId, environm
</TabPanel>
<TabPanel className="react-tabs__tab-panel header-editor">
<RequestHeadersEditor
key={`${uniqueKey}-${readyState}-header-editor`}
key={uniqueKey}
request={request}
bulk={false}
isDisabled={readyState === ReadyState.OPEN}

View File

@@ -48,10 +48,9 @@ const PaneBodyContent = styled.div({
gridTemplateRows: 'repeat(auto-fit, minmax(0, 1fr))',
});
export const WebSocketResponsePane: FC<{ requestId: string; handleSetActiveResponse: (requestId: string, activeResponse: WebSocketResponse | null) => void }> =
export const WebSocketResponsePane: FC<{ requestId: string }> =
({
requestId,
handleSetActiveResponse,
}) => {
const response = useSelector(selectActiveResponse) as WebSocketResponse | null;
if (!response) {
@@ -72,13 +71,12 @@ export const WebSocketResponsePane: FC<{ requestId: string; handleSetActiveRespo
</Pane>
);
}
return <WebSocketActiveResponsePane requestId={requestId} response={response} handleSetActiveResponse={handleSetActiveResponse} />;
return <WebSocketActiveResponsePane requestId={requestId} response={response} />;
};
const WebSocketActiveResponsePane: FC<{ requestId: string; response: WebSocketResponse; handleSetActiveResponse: (requestId: string, activeResponse: WebSocketResponse | null) => void }> = ({
const WebSocketActiveResponsePane: FC<{ requestId: string; response: WebSocketResponse }> = ({
requestId,
response,
handleSetActiveResponse,
}) => {
const [selectedEvent, setSelectedEvent] = useState<WebSocketEvent | null>(null);
const [timeline, setTimeline] = useState<ResponseTimelineEntry[]>([]);
@@ -87,11 +85,6 @@ const WebSocketActiveResponsePane: FC<{ requestId: string; response: WebSocketRe
setSelectedEvent((selected: WebSocketEvent | null) => selected?._id === event._id ? null : event);
};
const setActiveResponseAndDisconnect = (requestId: string, response: WebSocketResponse | null) => {
handleSetActiveResponse(requestId, response);
window.main.webSocket.close({ requestId });
};
useEffect(() => {
setSelectedEvent(null);
}, [response._id]);
@@ -123,7 +116,6 @@ const WebSocketActiveResponsePane: FC<{ requestId: string; response: WebSocketRe
</div>
<ResponseHistoryDropdown
activeResponse={response}
handleSetActiveResponse={setActiveResponseAndDisconnect}
requestId={requestId}
className="tall pane__header__right"
/>

View File

@@ -3,10 +3,7 @@ import { useSelector } from 'react-redux';
import { isGrpcRequest } from '../../models/grpc-request';
import { isRemoteProject } from '../../models/project';
import { Request, RequestHeader } from '../../models/request';
import type { Response } from '../../models/response';
import { isWebSocketRequest } from '../../models/websocket-request';
import { WebSocketResponse } from '../../models/websocket-response';
import { isCollection, isDesign } from '../../models/workspace';
import { VCS } from '../../sync/vcs/vcs';
import {
@@ -35,25 +32,17 @@ import { WorkspacePageHeader } from './workspace-page-header';
import type { HandleActivityChange } from './wrapper';
interface Props {
forceRefreshKey: number;
gitSyncDropdown: ReactNode;
handleActivityChange: HandleActivityChange;
handleSetActiveEnvironment: (id: string | null) => void;
handleSetActiveResponse: (requestId: string, activeResponse: Response | WebSocketResponse | null) => void;
handleForceUpdateRequest: (r: Request, patch: Partial<Request>) => Promise<Request>;
handleForceUpdateRequestHeaders: (r: Request, headers: RequestHeader[]) => Promise<Request>;
handleImport: Function;
handleSetResponseFilter: (filter: string) => void;
vcs: VCS | null;
}
export const WrapperDebug: FC<Props> = ({
forceRefreshKey,
gitSyncDropdown,
handleActivityChange,
handleSetActiveEnvironment,
handleSetActiveResponse,
handleForceUpdateRequest,
handleForceUpdateRequestHeaders,
handleImport,
handleSetResponseFilter,
vcs,
@@ -121,24 +110,18 @@ export const WrapperDebug: FC<Props> = ({
activeRequest={activeRequest}
environmentId={activeEnvironment ? activeEnvironment._id : ''}
workspaceId={activeWorkspace._id}
forceRefreshKey={forceRefreshKey}
settings={settings}
/>
) : (
isWebSocketRequest(activeRequest) ? (
<WebSocketRequestPane
key={activeRequest._id}
request={activeRequest}
workspaceId={activeWorkspace._id}
environmentId={activeEnvironment ? activeEnvironment._id : ''}
forceRefreshKey={forceRefreshKey}
environment={activeEnvironment}
/>
) : (
<RequestPane
environmentId={activeEnvironment ? activeEnvironment._id : ''}
forceRefreshCounter={forceRefreshKey}
forceUpdateRequest={handleForceUpdateRequest}
forceUpdateRequestHeaders={handleForceUpdateRequestHeaders}
handleImport={handleImport}
request={activeRequest}
settings={settings}
@@ -155,19 +138,16 @@ export const WrapperDebug: FC<Props> = ({
isGrpcRequest(activeRequest) ? (
<GrpcResponsePane
activeRequest={activeRequest}
forceRefreshKey={forceRefreshKey}
/>
) : (
isWebSocketRequest(activeRequest) ? (
<WebSocketResponsePane
requestId={activeRequest._id}
handleSetActiveResponse={handleSetActiveResponse}
/>
) : (
<ResponsePane
handleSetFilter={handleSetResponseFilter}
request={activeRequest}
handleSetActiveResponse={handleSetActiveResponse}
/>
)
)

View File

@@ -15,20 +15,15 @@ import {
} from '../../common/constants';
import { importRaw } from '../../common/import';
import { initializeSpectral, isLintError } from '../../common/spectral';
import { update } from '../../models/helpers/request-operations';
import * as requestOperations from '../../models/helpers/request-operations';
import * as models from '../../models/index';
import {
isRequest,
Request,
RequestHeader,
} from '../../models/request';
import { Response } from '../../models/response';
import { WebSocketResponse } from '../../models/websocket-response';
import { GitVCS } from '../../sync/git/git-vcs';
import { VCS } from '../../sync/vcs/vcs';
import { CookieModifyModal } from '../components/modals/cookie-modify-modal';
import { GrpcDispatchModalWrapper } from '../context/grpc';
import { updateRequestMetaByParentId } from '../hooks/create-request';
import { RootState } from '../redux/modules';
import { setActiveActivity } from '../redux/modules/global';
import { selectActiveActivity, selectActiveApiSpec, selectActiveCookieJar, selectActiveEnvironment, selectActiveGitRepository, selectActiveRequest, selectActiveResponse, selectActiveWorkspace, selectActiveWorkspaceMeta, selectSettings } from '../redux/selectors';
@@ -131,29 +126,15 @@ export type HandleActivityChange = (options: {
}) => Promise<void>;
interface State {
forceRefreshKey: number;
activeGitBranch: string;
}
@autoBindMethodsForReact(AUTOBIND_CFG)
export class WrapperClass extends PureComponent<Props, State> {
state: State = {
forceRefreshKey: Date.now(),
activeGitBranch: 'no-vcs',
};
// Request updaters
async _handleForceUpdateRequest(r: Request, patch: Partial<Request>) {
const newRequest = await update(r, patch);
this._forceRequestPaneRefreshAfterDelay();
return newRequest;
}
_handleForceUpdateRequestHeaders(r: Request, headers: RequestHeader[]) {
return this._handleForceUpdateRequest(r, { headers });
}
async _handleImport(text: string) {
const { activeRequest } = this.props;
@@ -166,7 +147,7 @@ export class WrapperClass extends PureComponent<Props, State> {
if (r && r._type === 'request' && activeRequest && isRequest(activeRequest)) {
// Only pull fields that we want to update
return this._handleForceUpdateRequest(activeRequest, {
return requestOperations.update(activeRequest, {
url: r.url,
method: r.method,
headers: r.headers,
@@ -184,31 +165,6 @@ export class WrapperClass extends PureComponent<Props, State> {
return null;
}
async handleSetActiveResponse(requestId: string, activeResponse: Response | WebSocketResponse | null = null) {
const { activeEnvironment } = this.props;
const activeResponseId = activeResponse ? activeResponse._id : null;
await updateRequestMetaByParentId(requestId, {
activeResponseId,
});
let response: Response | null;
if (activeResponseId) {
response = await models.response.getById(activeResponseId);
} else {
const environmentId = activeEnvironment ? activeEnvironment._id : null;
response = await models.response.getLatestForRequest(requestId, environmentId);
}
if (!response || !response.requestVersionId) {
return;
}
const request = await models.requestVersion.restore(response.requestVersionId);
if (!request) {
return;
}
// Refresh app to reflect changes. Using timeout because we need to
// wait for the request update to propagate.
setTimeout(() => this._forceRequestPaneRefresh(), 500);
}
async _handleWorkspaceActivityChange({ workspaceId, nextActivity }: Parameters<HandleActivityChange>[0]): ReturnType<HandleActivityChange> {
const { activeActivity, activeApiSpec, handleSetActiveActivity } = this.props;
@@ -275,19 +231,6 @@ export class WrapperClass extends PureComponent<Props, State> {
this.props.handleSetResponseFilter(activeRequestId, filter);
}
_forceRequestPaneRefreshAfterDelay(): void {
// Give it a second for the app to render first. If we don't wait, it will refresh
// on the old request and won't catch the newest one.
// TODO: Move this refresh key into redux store so we don't need timeout
window.setTimeout(this._forceRequestPaneRefresh, 100);
}
_forceRequestPaneRefresh() {
this.setState({
forceRefreshKey: Date.now(),
});
}
_handleGitBranchChanged(branch: string) {
this.setState({
activeGitBranch: branch || 'no-vcs',
@@ -298,10 +241,6 @@ export class WrapperClass extends PureComponent<Props, State> {
if (this.props.activeWorkspaceMeta) {
await models.workspaceMeta.update(this.props.activeWorkspaceMeta, { activeEnvironmentId });
}
// Give it time to update and re-render
setTimeout(() => {
this._forceRequestPaneRefresh();
}, 300);
}
render() {
@@ -366,7 +305,6 @@ export class WrapperClass extends PureComponent<Props, State> {
</> : null}
<NunjucksModal
uniqueKey={`key::${this.state.forceRefreshKey}`}
ref={registerModal}
workspace={activeWorkspace}
/>
@@ -480,15 +418,11 @@ export class WrapperClass extends PureComponent<Props, State> {
element={
<Suspense fallback={<div />}>
<WrapperDebug
forceRefreshKey={this.state.forceRefreshKey}
gitSyncDropdown={gitSyncDropdown}
handleActivityChange={this._handleWorkspaceActivityChange}
handleSetActiveEnvironment={this._handleSetActiveEnvironment}
handleForceUpdateRequest={this._handleForceUpdateRequest}
handleForceUpdateRequestHeaders={this._handleForceUpdateRequestHeaders}
handleImport={this._handleImport}
handleSetResponseFilter={this._handleSetResponseFilter}
handleSetActiveResponse={this.handleSetActiveResponse}
vcs={vcs}
/>
</Suspense>

View File

@@ -21,7 +21,6 @@ import {
generateId,
} from '../../common/misc';
import * as models from '../../models';
import { isEnvironment } from '../../models/environment';
import { GrpcRequest, isGrpcRequest } from '../../models/grpc-request';
import { getByParentId as getGrpcRequestMetaByParentId } from '../../models/grpc-request-meta';
import * as requestOperations from '../../models/helpers/request-operations';
@@ -55,7 +54,7 @@ import { SyncMergeModal } from '../components/modals/sync-merge-modal';
import { WorkspaceEnvironmentsEditModal } from '../components/modals/workspace-environments-edit-modal';
import { WorkspaceSettingsModal } from '../components/modals/workspace-settings-modal';
import { Toast } from '../components/toast';
import { type WrapperClass, Wrapper } from '../components/wrapper';
import { Wrapper } from '../components/wrapper';
import withDragDropContext from '../context/app/drag-drop-context';
import { GrpcProvider } from '../context/grpc';
import { NunjucksEnabledProvider } from '../context/nunjucks/nunjucks-enabled-context';
@@ -89,7 +88,6 @@ export type AppProps = ReturnType<typeof mapStateToProps> & ReturnType<typeof ma
interface State {
vcs: VCS | null;
gitVCS: GitVCS | null;
forceRefreshCounter: number;
isMigratingChildren: boolean;
}
@@ -97,7 +95,6 @@ interface State {
class App extends PureComponent<AppProps, State> {
private _globalKeyMap: any;
private _updateVCSLock: any;
private _wrapper: WrapperClass | null = null;
private _responseFilterHistorySaveTimeout: NodeJS.Timeout | null = null;
constructor(props: AppProps) {
@@ -106,7 +103,6 @@ class App extends PureComponent<AppProps, State> {
this.state = {
vcs: null,
gitVCS: null,
forceRefreshCounter: 0,
isMigratingChildren: false,
};
@@ -356,10 +352,6 @@ class App extends PureComponent<AppProps, State> {
showModal(SettingsModal, tabIndex);
}
_setWrapperRef(wrapper: WrapperClass) {
this._wrapper = wrapper;
}
async _handleReloadPlugins() {
const { settings } = this.props;
await plugins.reloadPlugins();
@@ -406,13 +398,6 @@ class App extends PureComponent<AppProps, State> {
this._ensureWorkspaceChildren();
// Force app refresh if login state changes
if (prevProps.isLoggedIn !== this.props.isLoggedIn) {
this.setState(state => ({
forceRefreshCounter: state.forceRefreshCounter + 1,
}));
}
// Check on VCS things
const { activeWorkspace, activeProject, activeGitRepository } = this.props;
const changingWorkspace = prevProps.activeWorkspace?._id !== activeWorkspace?._id;
@@ -540,38 +525,16 @@ class App extends PureComponent<AppProps, State> {
}
}
async _handleDbChange(changes: ChangeBufferEvent[]) {
let needsRefresh = false;
async listenforWorkspaceDelete(changes: ChangeBufferEvent[]) {
for (const change of changes) {
const [type, doc, fromSync] = change;
const [type, doc] = change;
const { vcs } = this.state;
const { activeRequest } = this.props;
// Force refresh if environment changes
// TODO: Only do this for environments in this workspace (not easy because they're nested)
if (isEnvironment(doc)) {
console.log('[App] Forcing update from environment change', change);
needsRefresh = true;
}
// Force refresh if sync changes the active request
if (fromSync && activeRequest && doc._id === activeRequest._id) {
needsRefresh = true;
console.log('[App] Forcing update from request change', change);
}
// Delete VCS project if workspace deleted
if (vcs && isWorkspace(doc) && type === db.CHANGE_REMOVE) {
await vcs.removeBackendProjectsForRoot(doc._id);
}
}
if (needsRefresh) {
setTimeout(() => {
this._wrapper?._forceRequestPaneRefresh();
}, 300);
}
}
async componentDidMount() {
@@ -584,7 +547,7 @@ class App extends PureComponent<AppProps, State> {
// Update VCS
await this._updateVCS();
await this._updateGitVCS();
db.onChange(this._handleDbChange);
db.onChange(this.listenforWorkspaceDelete);
ipcRenderer.on('toggle-preferences', () => {
App._handleShowSettingsModal();
});
@@ -703,7 +666,7 @@ class App extends PureComponent<AppProps, State> {
}
componentWillUnmount() {
db.offChange(this._handleDbChange);
db.offChange(this.listenforWorkspaceDelete);
}
async _ensureWorkspaceChildren() {
@@ -768,13 +731,12 @@ class App extends PureComponent<AppProps, State> {
return null;
}
const { activeWorkspace } = this.props;
const { activeWorkspace, isLoggedIn } = this.props;
const {
gitVCS,
vcs,
forceRefreshCounter,
} = this.state;
const uniquenessKey = `${forceRefreshCounter}::${activeWorkspace?._id || 'n/a'}`;
const uniquenessKey = `${isLoggedIn}::${activeWorkspace?._id || 'n/a'}`;
return (
<KeydownBinder onKeydown={this._handleKeyDown}>
<GrpcProvider>
@@ -784,7 +746,6 @@ class App extends PureComponent<AppProps, State> {
<div className="app" key={uniquenessKey}>
<ErrorBoundary showAlert>
<Wrapper
ref={this._setWrapperRef}
handleSetResponseFilter={this._handleSetResponseFilter}
vcs={vcs}
gitVCS={gitVCS}

View File

@@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { database } from '../../common/database';
import { selectActiveRequest, selectActiveWorkspaceMeta } from '../redux/selectors';
// We use this hook to determine if the active request has been updated from the VCS
// For example, by pulling a new version from the remote, switching branches, etc.
export function useActiveRequestSyncVCSVersion() {
const [version, setVersion] = useState(0);
const activeRequest = useSelector(selectActiveRequest);
useEffect(() => {
database.onChange(changes => {
for (const change of changes) {
const [, doc, fromSync] = change;
// Force refresh if sync changes the active request
if (activeRequest?._id === doc._id && fromSync) {
setVersion(v => v + 1);
}
}
});
}, [activeRequest?._id]);
return version;
}
// We use this hook to determine if the active workspace has been updated from the Git VCS
// For example, by pulling a new version from the remote, switching branches, etc.
export function useGitVCSVersion() {
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
return ((activeWorkspaceMeta?.cachedGitLastCommitTime + '') + activeWorkspaceMeta?.cachedGitRepositoryBranch) + '';
}