Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MPDX-7539 TntConnect Import Page #956

Merged
merged 12 commits into from
Jun 20, 2024
33 changes: 33 additions & 0 deletions pages/accountLists/[accountListId]/tools/tntConnect.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Head from 'next/head';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { loadSession } from 'pages/api/utils/pagePropsHelpers';
import Loading from 'src/components/Loading';
import TntConnect from 'src/components/Tool/TntConnect/TntConnect';
import { useAccountListId } from 'src/hooks/useAccountListId';
import useGetAppSettings from 'src/hooks/useGetAppSettings';

Check warning on line 8 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L1-L8

Added lines #L1 - L8 were not covered by tests

const TntConnectPage: React.FC = () => {
const { t } = useTranslation();
const accountListId = useAccountListId();
const { appName } = useGetAppSettings();

Check warning on line 13 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L10-L13

Added lines #L10 - L13 were not covered by tests

return (

Check warning on line 15 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L15

Added line #L15 was not covered by tests
<>
<Head>
<title>
{appName} | {t('Import Tnt')}
</title>
</Head>
{accountListId ? (
<TntConnect accountListId={accountListId} />

Check warning on line 23 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L23

Added line #L23 was not covered by tests
) : (
<Loading loading />

Check warning on line 25 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L25

Added line #L25 was not covered by tests
)}
</>
);
};

export const getServerSideProps = loadSession;

Check warning on line 31 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L31

Added line #L31 was not covered by tests

export default TntConnectPage;

Check warning on line 33 in pages/accountLists/[accountListId]/tools/tntConnect.page.tsx

View check run for this annotation

Codecov / codecov/patch

pages/accountLists/[accountListId]/tools/tntConnect.page.tsx#L33

Added line #L33 was not covered by tests
89 changes: 89 additions & 0 deletions pages/api/uploads/upload-tnt-connect-import.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { readFile } from 'fs/promises';
import { NextApiRequest, NextApiResponse } from 'next';
import formidable, { IncomingForm } from 'formidable';
import { getToken } from 'next-auth/jwt';
import fetch, { File, FormData } from 'node-fetch';

export const config = {
api: {
bodyParser: false,
},
};

const parseBody = async (
req: NextApiRequest,
): Promise<{ fields: formidable.Fields; files: formidable.Files }> => {
return new Promise((resolve, reject) => {
const form = new IncomingForm();
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
} else {
resolve({ fields, files });
}
});
});
};

const uploadTntConnect = async (
req: NextApiRequest,
res: NextApiResponse,
): Promise<void> => {
try {
if (req.method !== 'POST') {
res.status(405).send('Method Not Found');
return;
}

const jwt = await getToken({
req,
secret: process.env.JWT_SECRET,
});
const apiToken = jwt?.apiToken;
if (!apiToken) {
res.status(401).send('Unauthorized');
return;
}

const {
fields: { override, tag_list, accountListId },
files: { file },
} = await parseBody(req);
if (typeof override !== 'string') {
res.status(400).send('Missing override');
return;
}
if (!file || Array.isArray(file)) {
res.status(400).send('Missing file');
return;
}

const fileUpload = new File(
[await readFile(file.filepath)],
file.originalFilename ?? 'tntConnectUpload',
);
const form = new FormData();
form.append('data[type]', 'imports');
form.append('data[attributes][override]', override);
form.append(
'data[attributes][tag_list]',
Array.isArray(tag_list) ? tag_list.join(',') : tag_list,
);
form.append('data[attributes][file]', fileUpload);
const fetchRes = await fetch(
`${process.env.REST_API_URL}account_lists/${accountListId}/imports/tnt`,
{
method: 'POST',
headers: {
authorization: `Bearer ${apiToken}`,
},
body: form,
},
);
res.status(fetchRes.status).json({ success: fetchRes.status === 201 });
} catch (err) {
res.status(500).json({ success: false, error: err });
}
};

export default uploadTntConnect;
48 changes: 48 additions & 0 deletions pages/api/uploads/upload-tnt-connect-import.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getToken } from 'next-auth/jwt';
import { createMocks } from 'node-mocks-http';
import uploadTntConnect from './upload-tnt-connect-import.page';
import 'node-fetch';

jest.mock('node-fetch', () => jest.fn());

jest.mock('next-auth/jwt', () => ({ getToken: jest.fn() }));
jest.mock('src/lib/apollo/ssrClient', () => jest.fn());

const accountListId = 'accountListId';
const file = new File(['contents1'], 'tnt1.xml', {
type: 'text/xml',
});
const override = 'false';
const tag_list = 'tag1';

describe('upload-tnt-connect-import', () => {
it('responds with error if unauthorized', async () => {
const { req, res } = createMocks({
method: 'POST',
body: {
override: override,
file,
tag_list,
accountListId,
},
});

await uploadTntConnect(req, res);

expect(res._getStatusCode()).toBe(401);
});

it('responds with error if not sent with POST', async () => {
(getToken as jest.Mock).mockReturnValue({
apiToken: 'accessToken',
userID: 'sessionUserID',
});
const { req, res } = createMocks({
method: 'GET',
});

await uploadTntConnect(req, res);

expect(res._getStatusCode()).toBe(405);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const uploadAvatar = async ({
form.append('personId', personId);
form.append('avatar', file);

const res = await fetch(`/api/upload-person-avatar`, {
const res = await fetch(`/api/uploads/upload-person-avatar`, {
method: 'POST',
body: form,
}).catch(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,9 @@ export const ContactTags: React.FC<ContactTagsProps> = ({
<Autocomplete
multiple
freeSolo
autoSelect
autoHighlight
fullWidth
loading={loading}
popupIcon={<ContactTagIcon />}
filterSelectedOptions
value={tagList}
caleballdrin marked this conversation as resolved.
Show resolved Hide resolved
options={unusedTags || []}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Loading/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const useStyles = makeStyles()((theme: Theme) => ({
left: '50%',
marginLeft: '-28px',
marginTop: '-28px',
zIndex: 10,
zIndex: 1000,
opacity: 0,
transition: theme.transitions.create(['opacity', 'visibility'], {
duration: theme.transitions.duration.short,
Expand Down
13 changes: 1 addition & 12 deletions src/components/Tags/Tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,7 @@ export const ContactTagInput = styled(TextField)(({ theme }) => ({
borderBottom: `2px solid ${theme.palette.divider}`,
},
'&& .MuiInputBase-input': {
minWidth: '200px',
},
'& ::placeholder': {
color: theme.palette.info.main,
opacity: 1,
},
'& :hover::placeholder': {
textDecoration: 'underline',
},
'& :focus::placeholder': {
textDecoration: 'none',
color: theme.palette.cruGrayMedium.main,
minWidth: '150px',
},
margin: theme.spacing(1),
marginLeft: '0',
Expand Down
Loading
Loading