Files
2024-02-15 17:01:50 +00:00

271 lines
7.4 KiB
TypeScript

import {useContext, useRef, useState} from 'react';
import styled from '@emotion/styled';
import {motion} from 'framer-motion';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {Client} from 'sentry/api';
import {Button} from 'sentry/components/button';
import Well from 'sentry/components/well';
import {IconFile, IconUpload} from 'sentry/icons';
import {t} from 'sentry/locale';
import ConfigStore from 'sentry/stores/configStore';
import {space} from 'sentry/styles/space';
import testableTransition from 'sentry/utils/testableTransition';
import useApi from 'sentry/utils/useApi';
import StepHeading from 'sentry/views/relocation/components/stepHeading';
import {RelocationOnboardingContext} from 'sentry/views/relocation/relocationOnboardingContext';
import type {StepProps} from './types';
type UploadWellProps = {
centered: boolean;
draggedOver: boolean;
onDragEnter: Function;
onDragLeave: Function;
onDragOver: Function;
onDrop: Function;
};
const DEFAULT_ERROR_MSG = t(
'An error has occurred while trying to start relocation job. Please contact support for further assistance.'
);
const IN_PROGRESS_RELOCATION_ERROR_MSG = t(
'You already have an in-progress relocation job.'
);
const THROTTLED_RELOCATION_ERROR_MSG = t(
'We have reached the daily limit of relocations - please try again tomorrow, or contact support.'
);
const SESSION_EXPIRED_ERROR_MSG = t('Your session has expired.');
export function UploadBackup({onComplete}: StepProps) {
const api = useApi({
api: new Client({headers: {Accept: 'application/json; charset=utf-8'}}),
});
const [file, setFile] = useState<File>();
const [dragCounter, setDragCounter] = useState(0);
const inputFileRef = useRef<HTMLInputElement>(null);
const relocationOnboardingContext = useContext(RelocationOnboardingContext);
const user = ConfigStore.get('user');
const handleDragEnter = event => {
event.preventDefault();
setDragCounter(dragCounter + 1);
};
const handleDragLeave = () => {
setDragCounter(dragCounter - 1);
};
const handleDrop = event => {
event.preventDefault();
setDragCounter(0);
setFile(event.dataTransfer.files[0]);
};
const handleFileChange = event => {
const newFile = event.target.files?.[0];
// No file selected (e.g. user clicked "cancel")
if (!newFile) {
return;
}
setFile(newFile);
};
const onFileUploadLinkClick = () => {
inputFileRef.current?.click();
};
const handleStartRelocation = async () => {
const {orgSlugs, regionUrl, promoCode} = relocationOnboardingContext.data;
if (!orgSlugs || !regionUrl || !file) {
addErrorMessage(DEFAULT_ERROR_MSG);
return;
}
const formData = new FormData();
formData.set('orgs', orgSlugs);
formData.set('file', file);
formData.set('owner', user.username);
if (promoCode) {
formData.set('promo_code', promoCode);
}
try {
const result = await api.requestPromise(`/relocations/`, {
method: 'POST',
host: regionUrl,
data: formData,
});
addSuccessMessage(
t(
"Your relocation has started - we'll email you with updates as soon as we have 'em!"
)
);
onComplete(result.uuid);
} catch (error) {
if (error.status === 409) {
addErrorMessage(IN_PROGRESS_RELOCATION_ERROR_MSG);
} else if (error.status === 429) {
addErrorMessage(THROTTLED_RELOCATION_ERROR_MSG);
} else if (error.status === 401) {
addErrorMessage(SESSION_EXPIRED_ERROR_MSG);
} else {
addErrorMessage(DEFAULT_ERROR_MSG);
}
}
};
return (
<Wrapper data-test-id="upload-backup">
<StepHeading step={4}>
{t('Upload Tarball to begin the relocation process')}
</StepHeading>
<motion.div
transition={testableTransition()}
variants={{
initial: {y: 30, opacity: 0},
animate: {y: 0, opacity: 1},
exit: {opacity: 0},
}}
>
<p>
{t(
'Nearly done! The file is being uploaded to sentry for the relocation process. You can close this tab if you like. We will email when complete.'
)}
</p>
{file ? (
<FinishedWell centered>
<IconFile className="file-icon" size="xl" />
<div>
<p>{file.name}</p>
<a onClick={() => setFile(undefined)}>{t('Remove file')}</a>
</div>
<StartRelocationButton
priority="primary"
onClick={handleStartRelocation}
icon={<IconUpload className="upload-icon" size="xs" />}
>
{t('Start Relocation')}
</StartRelocationButton>
</FinishedWell>
) : (
<UploadWell
onDragEnter={handleDragEnter}
onDragOver={event => event.preventDefault()}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
centered
aria-label="dropzone"
draggedOver={dragCounter > 0}
>
<StyledUploadIcon className="upload-icon" size="xl" />
<UploadWrapper>
<p>{t('Drag and Drop file here or')}</p>
<a onClick={onFileUploadLinkClick}>{t('Choose file')}</a>
<UploadInput
name="file"
type="file"
aria-label="file-upload"
accept=".tar"
ref={inputFileRef}
onChange={e => handleFileChange(e)}
hidden
title=""
/>
</UploadWrapper>
</UploadWell>
)}
</motion.div>
</Wrapper>
);
}
export default UploadBackup;
const StyledUploadIcon = styled(IconUpload)`
margin-top: ${space(2)};
margin-bottom: ${space(1)};
`;
const Wrapper = styled('div')`
max-width: 769px;
max-height: 525px;
margin-left: auto;
margin-right: auto;
padding: ${space(4)};
background-color: ${p => p.theme.surface400};
z-index: 100;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.05);
border-radius: 10px;
width: 100%;
font-size: 16px;
color: ${p => p.theme.gray300};
mark {
border-radius: 8px;
padding: ${space(0.25)} ${space(0.5)} ${space(0.25)} ${space(0.5)};
background: ${p => p.theme.gray100};
margin-right: ${space(1)};
}
h2 {
color: ${p => p.theme.gray500};
}
p {
margin-bottom: ${space(1)};
}
.encrypt-help {
color: ${p => p.theme.gray500};
}
`;
const StartRelocationButton = styled(Button)`
margin-left: auto;
`;
const FinishedWell = styled(Well)`
display: flex;
align-items: center;
text-align: left;
div {
margin-left: ${space(2)};
line-height: 1;
}
a {
color: ${p => p.theme.translucentGray200};
font-size: 14px;
}
a:hover {
color: ${p => p.theme.gray300};
}
`;
const UploadWell = styled(Well)<UploadWellProps>`
margin-top: ${space(2)};
height: 140px;
border-style: ${props => (props.draggedOver ? 'solid' : 'dashed')};
border-width: medium;
align-items: center;
.file-icon,
.upload-icon {
color: ${p => p.theme.gray500};
}
background: ${props =>
props.draggedOver ? p => p.theme.purple100 : p => p.theme.surface400};
`;
const UploadInput = styled('input')`
opacity: 0;
`;
const UploadWrapper = styled('div')`
display: flex;
justify-content: center;
a {
padding-left: ${space(0.5)};
}
input[type='file'] {
display: none;
}
`;