diff --git a/apps/jetstream-web-extension/src/components/SfdcPageButton.tsx b/apps/jetstream-web-extension/src/components/SfdcPageButton.tsx index 686a01020..dce68e3c1 100644 --- a/apps/jetstream-web-extension/src/components/SfdcPageButton.tsx +++ b/apps/jetstream-web-extension/src/components/SfdcPageButton.tsx @@ -85,11 +85,15 @@ const HorizontalRule = () => { ); }; -function getActionLink(sfHost: string, pageLink: PageLink, objectName?: string) { +function getActionLink(sfHost: string, pageLink: PageLink, objectName?: Maybe, recordId?: Maybe) { const searchParams = new URLSearchParams({ host: sfHost, url: pageLink.link }); if (pageLink.includeCurrentRecord && objectName) { - searchParams.set('url', `${pageLink.link}?objectName=${objectName}`); + const targetParams = new URLSearchParams({ objectName }); + if (recordId) { + targetParams.set('recordId', recordId); + } + searchParams.set('url', `${pageLink.link}?${targetParams.toString()}`); } return `${browser.runtime.getURL('app.html')}?${searchParams.toString()}`; } @@ -284,7 +288,7 @@ export function SfdcPageButton() { {PAGE_LINKS.map((item) => ( { useTitle(TITLES.LOAD); const isMounted = useRef(true); + const [searchParams, setSearchParams] = useSearchParams(); const { trackEvent } = useAmplitude(); + + // Capture pre-fill params (e.g. from browser extension popover) once on mount + const [initialObjectName] = useState(() => searchParams.get('objectName')); + const [hasAppliedInitialObjectName, setHasAppliedInitialObjectName] = useState(false); const { defaultApiVersion, serverUrl, google_apiKey, google_appId, google_clientId } = useAtomValue(applicationCookieState); const { hasGoogleDriveAccess, googleShowUpgradeToPro } = useAtomValue(googleDriveAccessState); const googleApiConfig = useMemo( @@ -210,6 +216,24 @@ export const LoadRecords = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedOrg]); + // Pre-select object from URL param (e.g. from browser extension popover) once sobjects are loaded + useEffect(() => { + if (!hasAppliedInitialObjectName && initialObjectName && sobjects?.length) { + const matchedSobject = sobjects.find((sobject) => sobject.name === initialObjectName); + if (matchedSobject) { + setSelectedSObject(matchedSobject); + } + setHasAppliedInitialObjectName(true); + setSearchParams( + (prev) => { + prev.delete('objectName'); + return prev; + }, + { replace: true }, + ); + } + }, [hasAppliedInitialObjectName, initialObjectName, sobjects, setSelectedSObject, setSearchParams]); + useEffect(() => { setCurrentStepIdx(enabledSteps.findIndex((step) => step.idx === currentStep.idx)); }, [currentStep]); diff --git a/libs/features/query/src/Query.tsx b/libs/features/query/src/Query.tsx index 20b18bffd..8968c771d 100644 --- a/libs/features/query/src/Query.tsx +++ b/libs/features/query/src/Query.tsx @@ -3,15 +3,23 @@ import { useTitle } from '@jetstream/shared/ui-utils'; import { Spinner } from '@jetstream/ui'; import { fromQueryState, useQueryRestore } from '@jetstream/ui-core'; import { selectedOrgState } from '@jetstream/ui/app-state'; +import { composeQuery, getField } from '@jetstreamapp/soql-parser-js'; import { useAtomValue } from 'jotai'; import { useResetAtom } from 'jotai/utils'; import { Fragment, useCallback, useEffect, useState } from 'react'; -import { Outlet, useLocation } from 'react-router-dom'; +import { Outlet, useLocation, useSearchParams } from 'react-router-dom'; export const Query = () => { useTitle(TITLES.QUERY); const location = useLocation(); + const [searchParams, setSearchParams] = useSearchParams(); const selectedOrg = useAtomValue(selectedOrgState); + + // Capture pre-fill params (e.g. from browser extension popover) once on mount + const [initialPrefillParams] = useState(() => ({ + objectName: searchParams.get('objectName'), + recordId: searchParams.get('recordId'), + })); const querySoqlState = useAtomValue(fromQueryState.querySoqlState); const isTooling = useAtomValue(fromQueryState.isTooling); const resetSobjects = useResetAtom(fromQueryState.sObjectsState); @@ -40,6 +48,23 @@ export const Query = () => { useEffect(() => { if (selectedOrg && !priorSelectedOrg) { setPriorSelectedOrg(selectedOrg.uniqueId); + const { objectName, recordId } = initialPrefillParams; + if (objectName && location.pathname === '/query') { + const soql = composeQuery({ + sObject: objectName, + fields: [getField('Id')], + where: recordId ? { left: { field: 'Id', operator: '=', value: recordId, literalType: 'STRING' } } : undefined, + }); + restore(soql, false); + setSearchParams( + (prev) => { + prev.delete('objectName'); + prev.delete('recordId'); + return prev; + }, + { replace: true }, + ); + } } else if (selectedOrg && priorSelectedOrg !== selectedOrg.uniqueId) { const soql = querySoqlState; const tooling = isTooling; diff --git a/libs/shared/ui-core/src/record/ViewEditCloneRecord.tsx b/libs/shared/ui-core/src/record/ViewEditCloneRecord.tsx index fdd7b23c4..77f3e65ce 100644 --- a/libs/shared/ui-core/src/record/ViewEditCloneRecord.tsx +++ b/libs/shared/ui-core/src/record/ViewEditCloneRecord.tsx @@ -120,8 +120,9 @@ export interface ViewEditCloneRecordProps { action: CloneEditView; sobjectName: string; recordId: string | null; - onClose: (reloadRecords?: boolean) => void; + onClose: () => void; onChangeAction: (action: CloneEditView) => void; + onSave: (saved: { recordId: string; sobjectName: string }) => void; onFetch?: (recordId: string, record: any) => void; onFetchError?: (recordId: string, sobjectName: string) => void; } @@ -134,6 +135,7 @@ export const ViewEditCloneRecord: FunctionComponent = recordId: initialRecordId, onClose, onChangeAction, + onSave, onFetch, onFetchError, }) => { @@ -343,9 +345,12 @@ export const ViewEditCloneRecord: FunctionComponent = if (isErrorResponse(recordResponse)) { setFormErrors(handleEditFormErrorResponse(recordResponse)); } else { - // record created/updated - hasEverBeenInViewMode.current ? onChangeAction('view') : onClose(true); - // onClose(true); + // record created/updated - hand off to wrapper which re-keys this component + // so all internal form state (modifiedRecord, formErrors, breadcrumbs) is reset + onSave({ + recordId: action === 'edit' ? (recordId as string) : recordResponse.id, + sobjectName, + }); } } } catch (ex) { @@ -646,6 +651,18 @@ export const ViewEditCloneRecord: FunctionComponent = /> {isViewAsJson ? 'View as Record' : 'View as JSON'} +