Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions apps/jetstream-web-extension/src/components/SfdcPageButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,15 @@ const HorizontalRule = () => {
);
};

function getActionLink(sfHost: string, pageLink: PageLink, objectName?: string) {
function getActionLink(sfHost: string, pageLink: PageLink, objectName?: Maybe<string>, recordId?: Maybe<string>) {
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()}`;
}
Expand Down Expand Up @@ -284,7 +288,7 @@ export function SfdcPageButton() {
{PAGE_LINKS.map((item) => (
<GridCol key={item.link} className="slds-m-bottom_x-small" css={ItemColStyles}>
<a
href={getActionLink(sfHost, item, objectName)}
href={getActionLink(sfHost, item, objectName, recordId)}
className="slds-button slds-button_neutral slds-button_stretch"
target="_blank"
rel="noreferrer"
Expand Down
24 changes: 24 additions & 0 deletions libs/features/load-records/src/LoadRecords.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { useResetAtom } from 'jotai/utils';
import startCase from 'lodash/startCase';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import LoadRecordsDataPreview from './components/LoadRecordsDataPreview';
import LoadRecordsProgress from './components/LoadRecordsProgress';
import LoadRecordsFieldMapping from './steps/FieldMapping';
Expand All @@ -56,7 +57,12 @@ const finalStep: Step = enabledSteps[enabledSteps.length - 1];
export const LoadRecords = () => {
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(
Expand Down Expand Up @@ -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]);
Expand Down
27 changes: 26 additions & 1 deletion libs/features/query/src/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
25 changes: 21 additions & 4 deletions libs/shared/ui-core/src/record/ViewEditCloneRecord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -134,6 +135,7 @@ export const ViewEditCloneRecord: FunctionComponent<ViewEditCloneRecordProps> =
recordId: initialRecordId,
onClose,
onChangeAction,
onSave,
onFetch,
onFetchError,
}) => {
Expand Down Expand Up @@ -343,9 +345,12 @@ export const ViewEditCloneRecord: FunctionComponent<ViewEditCloneRecordProps> =
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) {
Expand Down Expand Up @@ -646,6 +651,18 @@ export const ViewEditCloneRecord: FunctionComponent<ViewEditCloneRecordProps> =
/>
{isViewAsJson ? 'View as Record' : 'View as JSON'}
</button>
<button
className="slds-button slds-button_neutral"
onClick={() => {
setLoading(true);
fetchMetadata();
}}
disabled={loading || !initialRecord}
title="Reload the record from Salesforce"
>
<Icon type="utility" icon="refresh" className="slds-button__icon slds-button__icon_left" omitContainer />
Reload Record
</button>
Comment thread
paustint marked this conversation as resolved.
<button
className="slds-button slds-button_neutral"
onClick={() => onChangeAction('edit')}
Expand Down
21 changes: 19 additions & 2 deletions libs/shared/ui-core/src/record/ViewEditCloneRecordWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
recordActionModalClosedObservable,
useObservable,
} from '@jetstream/shared/ui-utils';
import { CloneEditView, Maybe, SalesforceOrgUi } from '@jetstream/types';
import { CloneEditView, SalesforceOrgUi } from '@jetstream/types';
import { applicationCookieState, selectedOrgState } from '@jetstream/ui/app-state';
import { useAtom, useAtomValue } from 'jotai';
import { FunctionComponent, useEffect, useState } from 'react';
Expand Down Expand Up @@ -46,6 +46,10 @@ export const ViewEditCloneRecordWrapper: FunctionComponent = () => {
const [recordId, setRecordId] = useState<string>('');
const [sobjectName, setSobjectName] = useState<string | null>(null);
const [action, setAction] = useState<CloneEditView>('view');
// Bumped after every successful save to force a fresh ViewEditCloneRecord instance,
// which clears form/breadcrumb state without manual resets.
const [instanceKey, setInstanceKey] = useState(0);
const [didSave, setDidSave] = useState(false);

const appActionEvents = useObservable(appActionObservable$.pipe(appActionRecordEventFilter));

Expand Down Expand Up @@ -120,12 +124,23 @@ export const ViewEditCloneRecordWrapper: FunctionComponent = () => {
setAction(action);
}

function onModalClose(reloadRecords?: Maybe<boolean>) {
function onSave(saved: { recordId: string; sobjectName: string }) {
setRecordId(saved.recordId);
setSobjectName(saved.sobjectName);
setAction('view');
setDidSave(true);
setInstanceKey((key) => key + 1);
}

function onModalClose() {
const objectName = sobjectName;
const reloadRecords = didSave;
setAction('view');
setModalOpen(false);
setSobjectName(null);
setRecordId('');
setDidSave(false);
setInstanceKey(0);
recordActionModalClosedObservable.next({ objectName, reloadRecords });
}

Expand All @@ -135,13 +150,15 @@ export const ViewEditCloneRecordWrapper: FunctionComponent = () => {

return (
<ViewEditCloneRecord
key={instanceKey}
apiVersion={defaultApiVersion}
selectedOrg={selectedOrg}
action={action}
sobjectName={sobjectName}
recordId={recordId}
onClose={onModalClose}
onChangeAction={onActionChange}
onSave={onSave}
/>
);
};
Loading