addons/isl/src/ConfirmSubmitStack.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {CommitInfo} from './types';
b69ab319
b69ab3110import {Button} from 'isl-components/Button';
b69ab3111import {Checkbox} from 'isl-components/Checkbox';
b69ab3112import {Divider} from 'isl-components/Divider';
b69ab3113import {TextField} from 'isl-components/TextField';
b69ab3114import {Tooltip} from 'isl-components/Tooltip';
b69ab3115import {useAtom, useAtomValue} from 'jotai';
b69ab3116import {useState} from 'react';
b69ab3117import {useAutofocusRef} from 'shared/hooks';
b69ab3118import {nullthrows} from 'shared/utils';
b69ab3119import {Commit} from './Commit';
b69ab3120import {FlexSpacer} from './ComponentUtils';
b69ab3121import {codeReviewProvider} from './codeReview/CodeReviewInfo';
b69ab3122import {submitAsDraft, SubmitAsDraftCheckbox} from './codeReview/DraftCheckbox';
b69ab3123import {publishWhenReady, PublishWhenReadyCheckbox} from './codeReview/PublishWhenReadyCheckbox';
b69ab3124import {t, T} from './i18n';
b69ab3125import {configBackedAtom, readAtom} from './jotaiUtils';
b69ab3126import {CommitPreview} from './previews';
b69ab3127import {useModal} from './useModal';
b69ab3128
b69ab3129import './ConfirmSubmitStack.css';
b69ab3130
b69ab3131export const confirmShouldSubmitEnabledAtom = configBackedAtom<boolean>(
b69ab3132 'isl.show-stack-submit-confirmation',
b69ab3133 true,
b69ab3134);
b69ab3135
b69ab3136export type SubmitConfirmationReponse =
b69ab3137 | {submitAsDraft: boolean; updateMessage?: string; publishWhenReady?: boolean}
b69ab3138 | undefined;
b69ab3139
b69ab3140type SubmitType = 'submit' | 'submit-all' | 'resubmit';
b69ab3141
b69ab3142export function shouldShowSubmitStackConfirmation(): boolean {
b69ab3143 const provider = readAtom(codeReviewProvider);
b69ab3144 const shouldShowConfirmation = readAtom(confirmShouldSubmitEnabledAtom);
b69ab3145 return (
b69ab3146 shouldShowConfirmation === true &&
b69ab3147 // if you can't submit as draft, no need to show the interstitial
b69ab3148 provider?.supportSubmittingAsDraft != null
b69ab3149 );
b69ab3150}
b69ab3151
b69ab3152/**
b69ab3153 * Show a modal to confirm if you want to bulk submit a given stack of commits.
b69ab3154 * Allows you to set if you want to submit as a draft or not,
b69ab3155 * and provide an update message.
b69ab3156 *
b69ab3157 * If your code review provider does not support submitting as draft,
b69ab3158 * this function returns true immediately.
b69ab3159 */
b69ab3160export function useShowConfirmSubmitStack() {
b69ab3161 const showModal = useModal();
b69ab3162
b69ab3163 return async (mode: SubmitType, stack: Array<CommitInfo>) => {
b69ab3164 if (!shouldShowSubmitStackConfirmation()) {
b69ab3165 const draft = readAtom(submitAsDraft);
b69ab3166 return {submitAsDraft: draft ?? false};
b69ab3167 }
b69ab3168
b69ab3169 const provider = readAtom(codeReviewProvider);
b69ab3170
b69ab3171 const replace = {
b69ab3172 $numCommits: String(stack.length),
b69ab3173 $cmd: nullthrows(provider).submitCommandName(),
b69ab3174 };
b69ab3175 const title =
b69ab3176 mode === 'submit'
b69ab3177 ? t('Submitting $numCommits commits for review with $cmd', {replace})
b69ab3178 : mode === 'resubmit'
b69ab3179 ? t('Submitting new versions of $numCommits commits for review with $cmd', {replace})
b69ab3180 : t('Submitting all $numCommits commits in this stack for review with $cmd', {replace});
b69ab3181 const response = await showModal<SubmitConfirmationReponse>({
b69ab3182 type: 'custom',
b69ab3183 title,
b69ab3184 component: ({returnResultAndDismiss}) => (
b69ab3185 <ConfirmModalContent stack={stack} returnResultAndDismiss={returnResultAndDismiss} />
b69ab3186 ),
b69ab3187 });
b69ab3188 return response;
b69ab3189 };
b69ab3190}
b69ab3191
b69ab3192function ConfirmModalContent({
b69ab3193 stack,
b69ab3194 returnResultAndDismiss,
b69ab3195}: {
b69ab3196 stack: Array<CommitInfo>;
b69ab3197 returnResultAndDismiss: (value: SubmitConfirmationReponse) => unknown;
b69ab3198}) {
b69ab3199 const [showSubmitConfirmation, setShowSubmitConfirmation] = useAtom(
b69ab31100 confirmShouldSubmitEnabledAtom,
b69ab31101 );
b69ab31102 const shouldSubmitAsDraft = useAtomValue(submitAsDraft);
b69ab31103 const shouldPublishWhenReady = useAtomValue(publishWhenReady);
b69ab31104 const [updateMessage, setUpdateMessage] = useState('');
b69ab31105 const commitsWithDiffs = stack.filter(commit => commit.diffId != null);
b69ab31106
b69ab31107 const submitRef = useAutofocusRef<HTMLButtonElement>();
b69ab31108
b69ab31109 const provider = useAtomValue(codeReviewProvider);
b69ab31110 return (
b69ab31111 <div className="confirm-submit-stack" data-testid="confirm-submit-stack">
b69ab31112 <div className="confirm-submit-stack-content">
b69ab31113 <div className="commit-list">
b69ab31114 {stack.map(commit => (
b69ab31115 <Commit
b69ab31116 key={commit.hash}
b69ab31117 commit={commit}
b69ab31118 hasChildren={false}
b69ab31119 previewType={CommitPreview.NON_ACTIONABLE_COMMIT}
b69ab31120 />
b69ab31121 ))}
b69ab31122 </div>
b69ab31123 {provider?.supportsUpdateMessage !== true || commitsWithDiffs.length === 0 ? null : (
b69ab31124 <TextField
b69ab31125 value={updateMessage}
b69ab31126 data-testid="submit-update-message-input"
b69ab31127 onChange={e => setUpdateMessage(e.currentTarget.value)}>
b69ab31128 Update Message
b69ab31129 </TextField>
b69ab31130 )}
b69ab31131 <SubmitAsDraftCheckbox commitsToBeSubmit={stack} />
b69ab31132 <PublishWhenReadyCheckbox />
b69ab31133 </div>
b69ab31134 <Divider />
b69ab31135 <div className="use-modal-buttons">
b69ab31136 <Tooltip
b69ab31137 placement="bottom"
b69ab31138 title={t(
b69ab31139 "Don't show this confirmation next time you submit a stack. " +
b69ab31140 'Your last setting will control if it is submitted as a draft. ' +
b69ab31141 'You can change this from settings.',
b69ab31142 )}>
b69ab31143 <Checkbox
b69ab31144 checked={!showSubmitConfirmation}
b69ab31145 onChange={checked => setShowSubmitConfirmation(!checked)}>
b69ab31146 <T>Don't show again</T>
b69ab31147 </Checkbox>
b69ab31148 </Tooltip>
b69ab31149 <FlexSpacer />
b69ab31150 <Button onClick={() => returnResultAndDismiss(undefined)}>
b69ab31151 <T>Cancel</T>
b69ab31152 </Button>
b69ab31153 <Button
b69ab31154 ref={submitRef}
b69ab31155 primary
b69ab31156 onClick={() =>
b69ab31157 returnResultAndDismiss({
b69ab31158 submitAsDraft: shouldSubmitAsDraft,
b69ab31159 updateMessage: updateMessage || undefined,
b69ab31160 publishWhenReady: shouldPublishWhenReady,
b69ab31161 })
b69ab31162 }>
b69ab31163 <T>Submit</T>
b69ab31164 </Button>
b69ab31165 </div>
b69ab31166 </div>
b69ab31167 );
b69ab31168}