5.7 KB169 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {CommitInfo} from './types';
9
10import {Button} from 'isl-components/Button';
11import {Checkbox} from 'isl-components/Checkbox';
12import {Divider} from 'isl-components/Divider';
13import {TextField} from 'isl-components/TextField';
14import {Tooltip} from 'isl-components/Tooltip';
15import {useAtom, useAtomValue} from 'jotai';
16import {useState} from 'react';
17import {useAutofocusRef} from 'shared/hooks';
18import {nullthrows} from 'shared/utils';
19import {Commit} from './Commit';
20import {FlexSpacer} from './ComponentUtils';
21import {codeReviewProvider} from './codeReview/CodeReviewInfo';
22import {submitAsDraft, SubmitAsDraftCheckbox} from './codeReview/DraftCheckbox';
23import {publishWhenReady, PublishWhenReadyCheckbox} from './codeReview/PublishWhenReadyCheckbox';
24import {t, T} from './i18n';
25import {configBackedAtom, readAtom} from './jotaiUtils';
26import {CommitPreview} from './previews';
27import {useModal} from './useModal';
28
29import './ConfirmSubmitStack.css';
30
31export const confirmShouldSubmitEnabledAtom = configBackedAtom<boolean>(
32 'isl.show-stack-submit-confirmation',
33 true,
34);
35
36export type SubmitConfirmationReponse =
37 | {submitAsDraft: boolean; updateMessage?: string; publishWhenReady?: boolean}
38 | undefined;
39
40type SubmitType = 'submit' | 'submit-all' | 'resubmit';
41
42export function shouldShowSubmitStackConfirmation(): boolean {
43 const provider = readAtom(codeReviewProvider);
44 const shouldShowConfirmation = readAtom(confirmShouldSubmitEnabledAtom);
45 return (
46 shouldShowConfirmation === true &&
47 // if you can't submit as draft, no need to show the interstitial
48 provider?.supportSubmittingAsDraft != null
49 );
50}
51
52/**
53 * Show a modal to confirm if you want to bulk submit a given stack of commits.
54 * Allows you to set if you want to submit as a draft or not,
55 * and provide an update message.
56 *
57 * If your code review provider does not support submitting as draft,
58 * this function returns true immediately.
59 */
60export function useShowConfirmSubmitStack() {
61 const showModal = useModal();
62
63 return async (mode: SubmitType, stack: Array<CommitInfo>) => {
64 if (!shouldShowSubmitStackConfirmation()) {
65 const draft = readAtom(submitAsDraft);
66 return {submitAsDraft: draft ?? false};
67 }
68
69 const provider = readAtom(codeReviewProvider);
70
71 const replace = {
72 $numCommits: String(stack.length),
73 $cmd: nullthrows(provider).submitCommandName(),
74 };
75 const title =
76 mode === 'submit'
77 ? t('Submitting $numCommits commits for review with $cmd', {replace})
78 : mode === 'resubmit'
79 ? t('Submitting new versions of $numCommits commits for review with $cmd', {replace})
80 : t('Submitting all $numCommits commits in this stack for review with $cmd', {replace});
81 const response = await showModal<SubmitConfirmationReponse>({
82 type: 'custom',
83 title,
84 component: ({returnResultAndDismiss}) => (
85 <ConfirmModalContent stack={stack} returnResultAndDismiss={returnResultAndDismiss} />
86 ),
87 });
88 return response;
89 };
90}
91
92function ConfirmModalContent({
93 stack,
94 returnResultAndDismiss,
95}: {
96 stack: Array<CommitInfo>;
97 returnResultAndDismiss: (value: SubmitConfirmationReponse) => unknown;
98}) {
99 const [showSubmitConfirmation, setShowSubmitConfirmation] = useAtom(
100 confirmShouldSubmitEnabledAtom,
101 );
102 const shouldSubmitAsDraft = useAtomValue(submitAsDraft);
103 const shouldPublishWhenReady = useAtomValue(publishWhenReady);
104 const [updateMessage, setUpdateMessage] = useState('');
105 const commitsWithDiffs = stack.filter(commit => commit.diffId != null);
106
107 const submitRef = useAutofocusRef<HTMLButtonElement>();
108
109 const provider = useAtomValue(codeReviewProvider);
110 return (
111 <div className="confirm-submit-stack" data-testid="confirm-submit-stack">
112 <div className="confirm-submit-stack-content">
113 <div className="commit-list">
114 {stack.map(commit => (
115 <Commit
116 key={commit.hash}
117 commit={commit}
118 hasChildren={false}
119 previewType={CommitPreview.NON_ACTIONABLE_COMMIT}
120 />
121 ))}
122 </div>
123 {provider?.supportsUpdateMessage !== true || commitsWithDiffs.length === 0 ? null : (
124 <TextField
125 value={updateMessage}
126 data-testid="submit-update-message-input"
127 onChange={e => setUpdateMessage(e.currentTarget.value)}>
128 Update Message
129 </TextField>
130 )}
131 <SubmitAsDraftCheckbox commitsToBeSubmit={stack} />
132 <PublishWhenReadyCheckbox />
133 </div>
134 <Divider />
135 <div className="use-modal-buttons">
136 <Tooltip
137 placement="bottom"
138 title={t(
139 "Don't show this confirmation next time you submit a stack. " +
140 'Your last setting will control if it is submitted as a draft. ' +
141 'You can change this from settings.',
142 )}>
143 <Checkbox
144 checked={!showSubmitConfirmation}
145 onChange={checked => setShowSubmitConfirmation(!checked)}>
146 <T>Don't show again</T>
147 </Checkbox>
148 </Tooltip>
149 <FlexSpacer />
150 <Button onClick={() => returnResultAndDismiss(undefined)}>
151 <T>Cancel</T>
152 </Button>
153 <Button
154 ref={submitRef}
155 primary
156 onClick={() =>
157 returnResultAndDismiss({
158 submitAsDraft: shouldSubmitAsDraft,
159 updateMessage: updateMessage || undefined,
160 publishWhenReady: shouldPublishWhenReady,
161 })
162 }>
163 <T>Submit</T>
164 </Button>
165 </div>
166 </div>
167 );
168}
169