Skip to content

Commit 33f61f9

Browse files
babyleafymeta-codesync[bot]
authored andcommitted
Refactor copyFromParent
Summary: Refactors the "copy from parent" logic. Reduces prop-drilling and keeps the field/textarea components simpler. Reviewed By: SajidBashar Differential Revision: D93452995 fbshipit-source-id: 9bcf908117e428ccdaf88163f5aaeb1d8fb00a7b
1 parent ff4e486 commit 33f61f9

4 files changed

Lines changed: 79 additions & 39 deletions

File tree

addons/isl/src/CommitInfoView/CommitInfoField.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import type {FieldConfig} from './types';
1111
import {Icon} from 'isl-components/Icon';
1212
import {extractTokens, TokensList} from 'isl-components/Tokens';
1313
import {DOCUMENTATION_DELAY, Tooltip} from 'isl-components/Tooltip';
14+
import {useAtomValue} from 'jotai';
1415
import {Fragment} from 'react';
1516
import {tracker} from '../analytics';
1617
import {Copyable} from '../Copyable';
1718
import {T} from '../i18n';
19+
import {copyFromParentCommit, parentCommitContextAtom} from './CommitInfoState';
20+
import {isFieldNonEmpty} from './CommitMessageFields';
1821
import {RenderMarkup} from './RenderMarkup';
1922
import {SeeMoreContainer} from './SeeMoreContainer';
2023
import {CommitInfoTextArea} from './TextArea';
@@ -29,7 +32,6 @@ export function CommitInfoField({
2932
editedField,
3033
startEditingField,
3134
setEditedField,
32-
copyFromParent,
3335
extra,
3436
autofocus,
3537
}: {
@@ -40,10 +42,12 @@ export function CommitInfoField({
4042
content?: string | Array<string>;
4143
editedField: string | Array<string> | undefined;
4244
setEditedField: (value: string) => unknown;
43-
copyFromParent?: () => void;
4445
extra?: JSX.Element;
4546
autofocus?: boolean;
4647
}): JSX.Element | null {
48+
const parentFields = useAtomValue(parentCommitContextAtom)?.parentFields;
49+
const showCopyFromParent =
50+
!readonly && parentFields != null && isFieldNonEmpty(parentFields[field.key]);
4751
const editedFieldContent =
4852
editedField == null ? '' : Array.isArray(editedField) ? editedField.join(', ') : editedField;
4953
if (field.type === 'title') {
@@ -73,9 +77,9 @@ export function CommitInfoField({
7377
</ClickToEditField>
7478
<div className="commit-info-field-buttons">
7579
{readonly ? null : <EditFieldButton onClick={startEditingField} />}
76-
{readonly || copyFromParent == null ? null : (
77-
<CopyFromParentButton onClick={copyFromParent} />
78-
)}
80+
{showCopyFromParent ? (
81+
<CopyFromParentButton onClick={() => copyFromParentCommit(field.key)} />
82+
) : null}
7983
</div>
8084
</div>
8185
)}
@@ -129,7 +133,6 @@ export function CommitInfoField({
129133
autoFocus={autofocus ?? false}
130134
editedMessage={editedFieldContent}
131135
setEditedField={setEditedField}
132-
copyFromParent={copyFromParent}
133136
/>
134137
)}
135138
{extra}
@@ -187,9 +190,9 @@ export function CommitInfoField({
187190
<T>{field.key}</T>
188191
<div className="commit-info-field-buttons">
189192
{readonly ? null : <EditFieldButton onClick={startEditingField} />}
190-
{readonly || copyFromParent == null ? null : (
191-
<CopyFromParentButton onClick={copyFromParent} />
192-
)}
193+
{showCopyFromParent ? (
194+
<CopyFromParentButton onClick={() => copyFromParentCommit(field.key)} />
195+
) : null}
193196
</div>
194197
</SmallCapsTitle>
195198
<ClickToEditField

addons/isl/src/CommitInfoView/CommitInfoState.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,54 @@ export const commitInfoViewCurrentCommits = atom(get => {
293293
return selected.length > 1 ? selected : [commit];
294294
}
295295
});
296+
297+
/**
298+
* Derived atom that reactively computes the parent commit context and parsed fields.
299+
* Returns undefined if there is no eligible parent commit.
300+
*/
301+
export const parentCommitContextAtom = atom(get => {
302+
const currentCommits = get(commitInfoViewCurrentCommits);
303+
const mode = get(commitMode);
304+
const commit = currentCommits?.length === 1 ? currentCommits[0] : undefined;
305+
if (!commit) {
306+
return undefined;
307+
}
308+
const isCommitMode = mode === 'commit';
309+
const parentCommit = get(dagWithPreviews).get(isCommitMode ? commit.hash : commit.parents[0]);
310+
if (!parentCommit || parentCommit.phase === 'public') {
311+
return undefined;
312+
}
313+
const schema = get(commitMessageFieldsSchema);
314+
const parentFields = parseCommitMessageFields(
315+
schema,
316+
parentCommit.title,
317+
parentCommit.description,
318+
);
319+
return {commit, isCommitMode, parentFields};
320+
});
321+
322+
/**
323+
* Copy a field's value from the parent commit into the current commit's edited message.
324+
*/
325+
export function copyFromParentCommit(fieldKey: string): void {
326+
const ctx = readAtom(parentCommitContextAtom);
327+
if (!ctx) {
328+
return;
329+
}
330+
const {commit, isCommitMode, parentFields} = ctx;
331+
const parentVal = parentFields[fieldKey];
332+
if (!parentVal) {
333+
return;
334+
}
335+
336+
tracker.track('CopyCommitFieldsFromParent');
337+
const schema = readAtom(commitMessageFieldsSchema);
338+
const field = schema.find(f => f.key === fieldKey);
339+
const hashOrHead = isCommitMode ? 'head' : commit.hash;
340+
const val = Array.isArray(parentVal) ? parentVal.join(',') : parentVal;
341+
const newVal = field?.type === 'field' ? val + ',' : val;
342+
writeAtom(editedCommitMessages(hashOrHead), prev => ({
343+
...prev,
344+
[fieldKey]: field?.type === 'field' ? newVal.split(',') : newVal,
345+
}));
346+
}

addons/isl/src/CommitInfoView/CommitInfoView.tsx

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import {getCachedGeneratedFileStatuses, useGeneratedFileStatuses} from '../Gener
4949
import {t, T} from '../i18n';
5050
import {IrrelevantCwdIcon} from '../icons/IrrelevantCwdIcon';
5151
import {numPendingImageUploads} from '../ImageUpload';
52-
import {readAtom, useAtomGet, writeAtom} from '../jotaiUtils';
52+
import {readAtom, writeAtom} from '../jotaiUtils';
5353
import {Link} from '../Link';
5454
import {
5555
messageSyncingEnabledState,
@@ -244,12 +244,6 @@ export function CommitInfoDetails({commit}: {commit: CommitInfo}) {
244244

245245
const parsedFields = useAtomValue(latestCommitMessageFields(hashOrHead));
246246

247-
const parentCommit = useAtomGet(dagWithPreviews, isCommitMode ? commit.hash : commit.parents[0]);
248-
const parentFields =
249-
parentCommit && parentCommit.phase !== 'public'
250-
? parseCommitMessageFields(schema, parentCommit.title, parentCommit.description)
251-
: undefined;
252-
253247
const provider = useAtomValue(codeReviewProvider);
254248
const startEditingField = (field: string) => {
255249
const original = parsedFields[field];
@@ -312,20 +306,18 @@ export function CommitInfoDetails({commit}: {commit: CommitInfo}) {
312306
return;
313307
}
314308

315-
const setField = (newVal: string) =>
316-
setEditedCommitMessage(val => ({
317-
...val,
318-
[field.key]: field.type === 'field' ? newVal.split(',') : newVal,
319-
}));
320-
321309
let editedFieldValue = editedMessage?.[field.key];
322310
if (editedFieldValue == null && isCommitMode) {
323311
// If the field is supposed to edited but not in the editedMessage,
324312
// it means we're loading from a blank slate. This is when we can load from the commit template.
325313
editedFieldValue = parsedFields[field.key];
326314
}
327315

328-
const parentVal = parentFields?.[field.key];
316+
const setField = (newVal: string) =>
317+
setEditedCommitMessage(val => ({
318+
...val,
319+
[field.key]: field.type === 'field' ? newVal.split(',') : newVal,
320+
}));
329321

330322
return (
331323
<CommitInfoField
@@ -338,15 +330,6 @@ export function CommitInfoDetails({commit}: {commit: CommitInfo}) {
338330
startEditingField={() => startEditingField(field.key)}
339331
editedField={editedFieldValue}
340332
setEditedField={setField}
341-
copyFromParent={
342-
parentVal != null
343-
? () => {
344-
tracker.track('CopyCommitFieldsFromParent');
345-
const val = Array.isArray(parentVal) ? parentVal.join(',') : parentVal;
346-
setField(field.type === 'field' ? val + ',' : val);
347-
}
348-
: undefined
349-
}
350333
extra={
351334
field.key === 'Title' ? (
352335
<>

addons/isl/src/CommitInfoView/TextArea.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {Button} from 'isl-components/Button';
1111
import {Icon} from 'isl-components/Icon';
1212
import {TextArea} from 'isl-components/TextArea';
1313
import {Tooltip} from 'isl-components/Tooltip';
14+
import {useAtomValue} from 'jotai';
1415
import {useEffect, useRef} from 'react';
1516
import {InternalFieldName} from 'shared/constants';
1617
import {
@@ -20,6 +21,8 @@ import {
2021
useUploadFilesCallback,
2122
} from '../ImageUpload';
2223
import {Internal} from '../Internal';
24+
import {copyFromParentCommit, parentCommitContextAtom} from './CommitInfoState';
25+
import {isFieldNonEmpty} from './CommitMessageFields';
2326
import {MinHeightTextField} from './MinHeightTextField';
2427
import {convertFieldNameToKey} from './utils';
2528

@@ -33,14 +36,12 @@ export function CommitInfoTextArea({
3336
autoFocus,
3437
editedMessage,
3538
setEditedField,
36-
copyFromParent,
3739
}: {
3840
kind: 'title' | 'textarea' | 'field';
3941
name: string;
4042
autoFocus: boolean;
4143
editedMessage: string;
4244
setEditedField: (fieldValue: string) => unknown;
43-
copyFromParent?: () => void;
4445
}) {
4546
const ref = useRef<HTMLTextAreaElement>(null);
4647
useEffect(() => {
@@ -98,7 +99,6 @@ export function CommitInfoTextArea({
9899
fieldName={name}
99100
uploadFiles={supportsImageUpload ? uploadFiles : undefined}
100101
textAreaRef={ref}
101-
copyFromParent={copyFromParent}
102102
/>
103103
</div>
104104
);
@@ -116,12 +116,10 @@ export function EditorToolbar({
116116
fieldName,
117117
textAreaRef,
118118
uploadFiles,
119-
copyFromParent,
120119
}: {
121120
fieldName: string;
122121
uploadFiles?: (files: Array<File>) => unknown;
123122
textAreaRef: RefObject<HTMLTextAreaElement>;
124-
copyFromParent?: () => void;
125123
}) {
126124
const parts: Array<ReactNode> = [];
127125
if (uploadFiles != null) {
@@ -135,10 +133,15 @@ export function EditorToolbar({
135133
if (fieldName === InternalFieldName.Summary && Internal.GenerateSummaryButton) {
136134
parts.push(<Internal.GenerateSummaryButton key="generate-summary" />);
137135
}
138-
if (copyFromParent != null) {
136+
const parentFields = useAtomValue(parentCommitContextAtom)?.parentFields;
137+
if (
138+
parentFields &&
139+
isFieldNonEmpty(parentFields[fieldName]) &&
140+
(fieldName === InternalFieldName.Summary || fieldName === InternalFieldName.TestPlan)
141+
) {
139142
parts.push(
140143
<Tooltip title="Copy from previous commit" key="copy-parent">
141-
<Button icon onClick={copyFromParent}>
144+
<Button icon onClick={() => copyFromParentCommit(fieldName)}>
142145
<Icon icon="clippy" />
143146
</Button>
144147
</Tooltip>,

0 commit comments

Comments
 (0)