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

DT-1144: Update Support Destination URLs #2768

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
166 changes: 166 additions & 0 deletions cypress/component/Support/SupportRequestModal.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/* eslint-disable no-undef */

import React from 'react';
import {mount} from 'cypress/react18';
import {SupportRequestModal} from '../../../src/components/modals/SupportRequestModal';
import {Storage} from '../../../src/libs/storage';

const mockUser = {
displayName: 'Display Name',
email: '[email protected]'
};

const handler = () => {
};

describe('Support Request Modal Tests', () => {

beforeEach(() => {
cy.viewport(500, 500);
cy.initApplicationConfig();
});

describe('When a user is logged in:', () => {
beforeEach(() => {
cy.stub(Storage, 'userIsLogged').returns(true);
cy.stub(Storage, 'getCurrentUser').returns(mockUser);
});

it('Renders form correctly', () => {
mount(<SupportRequestModal
onCloseRequest={handler}
onOKRequest={handler}
url={'url'}
showModal={true}
/>);
// These fields should exist
cy.get('[data-cy="closeButton"]').should('exist');
cy.get('[data-cy="supportForm"]').should('exist');
cy.get('[data-cy="supportFormEmail"]').should('not.exist');
cy.get('[data-cy="supportFormName"]').should('not.exist');
cy.get('[data-cy="supportFormType"]').should('exist');
cy.get('[data-cy="supportFormSubject"]').should('exist');
cy.get('[data-cy="supportFormDescription"]').should('exist');
cy.get('[data-cy="supportFormAttachment"]').should('exist');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormCancel"]').should('not.be.disabled');
});

it('Submits properly', () => {
mount(<SupportRequestModal
onCloseRequest={handler}
onOKRequest={handler}
url={'url'}
showModal={true}
/>);
// Ensure that all required fields are filled out before submit becomes available
cy.get('[data-cy="supportFormType"]').select('bug');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormSubject"]').type('Subject');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormDescription"]').type('Description');
// Form is complete:
cy.get('[data-cy="supportFormSubmit"]').should('not.be.disabled');
cy.get('[data-cy="supportFormCancel"]').should('not.be.disabled');
cy.intercept({method: 'POST', url: '**/support/request'}, {statusCode: 201}).as('request');
cy.intercept({method: 'POST', url: '**/support/upload'}, {statusCode: 201, body: {'token': 'token_string'}}).as('upload');
// {force: true} is necessary here due to the surrounding div that covers the input.
cy.get('[data-cy="supportFormAttachment"]').selectFile(['cypress/fixtures/example.json'], {force: true});
cy.get('[data-cy="supportFormSubmit"]').click();
cy.wait(['@request', '@upload']).then((interceptions) => {
assert(interceptions.length === 2);
});
});

});

describe('When a user is NOT logged in:', () => {
beforeEach(() => {
cy.stub(Storage, 'userIsLogged').returns(false);
cy.stub(Storage, 'getCurrentUser').returns(undefined);
});

it('Renders form correctly', () => {
mount(<SupportRequestModal
onCloseRequest={handler}
onOKRequest={handler}
url={'url'}
showModal={true}
/>);
// These fields should exist
cy.get('[data-cy="closeButton"]').should('exist');
cy.get('[data-cy="supportForm"]').should('exist');
cy.get('[data-cy="supportFormEmail"]').should('exist');
cy.get('[data-cy="supportFormName"]').should('exist');
cy.get('[data-cy="supportFormType"]').should('exist');
cy.get('[data-cy="supportFormSubject"]').should('exist');
cy.get('[data-cy="supportFormDescription"]').should('exist');
cy.get('[data-cy="supportFormAttachment"]').should('exist');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormCancel"]').should('not.be.disabled');
});

it('Submits properly', () => {
mount(<SupportRequestModal
onCloseRequest={handler}
onOKRequest={handler}
url={'url'}
showModal={true}
/>);
// Ensure that all required fields are filled out before submit becomes available
cy.get('[data-cy="supportFormName"]').type('Name');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormType"]').select('bug');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormSubject"]').type('Subject');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormDescription"]').type('Description');
cy.get('[data-cy="supportFormSubmit"]').should('be.disabled');
cy.get('[data-cy="supportFormEmail"]').type(mockUser.email);
// Form is complete:
cy.get('[data-cy="supportFormSubmit"]').should('not.be.disabled');
cy.get('[data-cy="supportFormCancel"]').should('not.be.disabled');
cy.intercept({method: 'POST', url: '**/support/request'}, {statusCode: 201}).as('request');
cy.intercept({method: 'POST', url: '**/support/upload'}, {statusCode: 201, body: {'token': 'token_string'}}).as('upload');
// {force: true} is necessary here due to the surrounding div that covers the input.
cy.get('[data-cy="supportFormAttachment"]').selectFile(['cypress/fixtures/example.json'], {force: true});
cy.get('[data-cy="supportFormSubmit"]').click();
cy.wait(['@request', '@upload']).then((interceptions) => {
assert(interceptions.length === 2);
});
});

});

describe('File Attachments', () => {
beforeEach(() => {
cy.stub(Storage, 'userIsLogged').returns(false);
cy.stub(Storage, 'getCurrentUser').returns(undefined);
});
it('Single attachment displayed', () => {
mount(<SupportRequestModal
onCloseRequest={handler}
onOKRequest={handler}
url={'url'}
showModal={true}
/>);
// {force: true} is necessary here due to the surrounding div that covers the input.
cy.get('[data-cy="supportFormAttachment"]').selectFile(['cypress/fixtures/example.json'], {force: true});
const container = cy.get('[data-cy="supportFormAttachmentContainer"]');
expect(container.contains('example.json'));
});

it('Multiple attachments displayed', () => {
mount(<SupportRequestModal
onCloseRequest={handler}
onOKRequest={handler}
url={'url'}
showModal={true}
/>);
// {force: true} is necessary here due to the surrounding div that covers the input.
cy.get('[data-cy="supportFormAttachment"]').selectFile(['cypress/fixtures/example.json', 'cypress/fixtures/dataset-registration-schema_v1.json'], {force: true});
const container = cy.get('[data-cy="supportFormAttachmentContainer"]');
expect(container.contains('2 files selected'));
});
});
});
31 changes: 21 additions & 10 deletions src/components/modals/SupportRequestModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ export const SupportRequestModal = (props) => {
const results = [];
for (let i = 0; i < modalState.attachment.length; i++) {
try {
results.push(Support.uploadAttachment(modalState.attachment[i]));
} catch (e) {
const response = await Support.uploadAttachment(modalState.attachment[i]);
results.push(response.data);
} catch (response) {
Notifications.showError({
text: 'Unable to add attachment',
text: `ERROR ${response.status}: Unable to add attachment: ${response.data?.message}`,
layout: 'topRight',
});
setModalState({
Expand Down Expand Up @@ -99,8 +100,8 @@ export const SupportRequestModal = (props) => {
if (modalState.validAttachment) {
const ticket = Support.createTicket(modalState.name, modalState.type, modalState.email,
modalState.subject, modalState.description, attachmentToken, props.url);
const response = await Support.createSupportRequest(ticket);
if (response.status === 201) {
try {
await Support.createSupportRequest(ticket);
Notifications.showSuccess({ text: 'Sent Successfully', layout: 'topRight', timeout: 1500 });
setModalState({
...modalState,
Expand All @@ -110,9 +111,9 @@ export const SupportRequestModal = (props) => {
attachment: ''
});
props.onOKRequest('support');
} else {
} catch (response) {
Notifications.showError({
text: `ERROR ${response.status} : Unable To Send`,
text: `ERROR ${response.status} : Unable To Send: ${response.data?.message}`,
layout: 'topRight',
});
}
Expand Down Expand Up @@ -245,6 +246,7 @@ export const SupportRequestModal = (props) => {
style={{ position: 'absolute', top: '20px', right: '20px' }}
className='close'
onClick={closeHandler}
data-cy={'closeButton'}
>
<span className='glyphicon glyphicon-remove default-color' />
</button>
Expand Down Expand Up @@ -277,6 +279,7 @@ export const SupportRequestModal = (props) => {
name = 'zendeskTicketForm'
noValidate = {true}
encType= 'multipart/form-data'
data-cy={'supportForm'}
>
{!modalState.isLogged && (
<div className='form-group first-form-group'>
Expand All @@ -288,6 +291,7 @@ export const SupportRequestModal = (props) => {
className='form-control col-lg-12'
onChange={nameChangeHandler}
required={true}
data-cy={'supportFormName'}
/>
</div>
)}
Expand All @@ -300,6 +304,7 @@ export const SupportRequestModal = (props) => {
value={modalState.type}
onChange={typeChangeHandler}
required={true}
data-cy={'supportFormType'}
>
<option value='question'>Question</option>
<option value='bug'>Bug</option>
Expand All @@ -312,10 +317,10 @@ export const SupportRequestModal = (props) => {
<input
id='txt_subject'
placeholder='Enter a subject'
rows='5'
className='form-control col-lg-12 vote-input'
onChange={subjectChangeHandler}
required={true}
data-cy={'supportFormSubject'}
/>
<textarea
id='txt_description'
Expand All @@ -324,6 +329,7 @@ export const SupportRequestModal = (props) => {
className='form-control col-lg-12 vote-input'
onChange={descriptionChangeHandler}
required={true}
data-cy={'supportFormDescription'}
/>
</div>

Expand All @@ -340,8 +346,10 @@ export const SupportRequestModal = (props) => {
alignItems: 'center',
border: modalState.attachment.length === 0 ? '1px dashed' : 'none',
}}>
<div {...getRootProps()}>
<input {...getInputProps()} />
<div {...getRootProps()}
data-cy={'supportFormAttachmentContainer'}>
<input {...getInputProps()}
data-cy={'supportFormAttachment'}/>
<p>
{modalState.attachment.length === 0 ? 'Drag or Click to attach a files' :
modalState.attachment.length === 1 ? modalState.attachment[0].name :
Expand Down Expand Up @@ -384,6 +392,7 @@ export const SupportRequestModal = (props) => {
value={modalState.email}
onChange={emailChangeHandler}
required={true}
data-cy={'supportFormEmail'}
/>
</div>
)}
Expand All @@ -395,13 +404,15 @@ export const SupportRequestModal = (props) => {
className='btn common-background'
onClick={OKHandler}
disabled={disableOkBtn}
data-cy={'supportFormSubmit'}
>
Submit
</button>
<button
id='btn_cancel'
className='btn dismiss-background'
onClick={closeHandler}
data-cy={'supportFormCancel'}
>
Cancel
</button>
Expand Down
5 changes: 0 additions & 5 deletions src/libs/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,6 @@ export const fetchAny = async (...args) => {
return res;
};

export const getFileNameFromHttpResponse = (response) => {
const respHeaders = response.headers;
return respHeaders.get('Content-Disposition').split(';')[1].trim().split('=')[1];
};

Comment on lines -77 to -81
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by fix: unused method.

export const reportError = async (url, status) => {
const msg = 'Error fetching response: '
.concat(JSON.stringify(url))
Expand Down
52 changes: 24 additions & 28 deletions src/libs/ajax/Support.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,36 @@
import * as fp from 'lodash/fp';
import { Config } from '../config';
import { fetchAny } from '../ajax';

import {getApiUrl} from '../ajax';
import axios from 'axios';

export const Support = {
createTicket: (name, type, email, subject, description, attachmentToken, url) => {
const ticket = {};

ticket.request = {
requester: { name: name, email: email },
createTicket: (name, type, email, subject, description, attachmentToken, url) => {
return {
name: name,
type: type.toUpperCase(),
email: email,
subject: subject,
// BEWARE changing the following ids or values! If you change them then you must thoroughly test.
custom_fields: [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what was the purpose of these fields?

{ id: 360012744452, value: type },
{ id: 360007369412, value: description },
{ id: 360012744292, value: name },
{ id: 360012782111, value: email },
{ id: 360018545031, value: email }
],
comment: {
body: description + '\n\n------------------\nSubmitted from: ' + url,
uploads: attachmentToken
},
ticket_form_id: 360000669472
description: description,
url: url,
uploads: attachmentToken
};

return ticket;

},

createSupportRequest: async (ticket) => {
const res = await fetchAny('https://broadinstitute.zendesk.com/api/v2/requests.json', fp.mergeAll([Config.jsonBody(ticket), { method: 'POST' }]));
return await res;
const url = `${await getApiUrl()}/support/request`;
return await axios.post(url, ticket, {headers: {'Content-Type': 'application/json'}}).catch(
function (error) {
return Promise.reject(error.response);
}
);
},

uploadAttachment: async (file) => {
const res = await fetchAny('https://broadinstitute.zendesk.com/api/v2/uploads?filename=Attachment', fp.mergeAll([Config.attachmentBody(file), { method: 'POST' }]));
return (await res.json()).upload;
const url = `${await getApiUrl()}/support/upload`;
return await axios.post(url, file, {headers: {'Content-Type': 'application/binary'}}).catch(
function (error) {
return Promise.reject(error.response);
}
);
},

};
7 changes: 0 additions & 7 deletions src/libs/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ export const Config = {

getNihUrl: async () => (await getConfig()).nihUrl,

getGoogleClientId: async () => (await getConfig()).clientId,

Comment on lines -20 to -21
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by fix: unused method.

getGAId: async () => (await getConfig()).gaId,

getErrorApiKey: async () => (await getConfig()).errorApiKey,
Expand Down Expand Up @@ -65,11 +63,6 @@ export const Config = {
headers: {'Content-Type': 'application/json'},
}),

attachmentBody: body => ({
body: body,
headers: {'Content-Type': 'application/binary'}
}),

};

export const Token = {
Expand Down
Loading
Loading