feat: add zendesk widget (#71)
This commit is contained in:
1
.env
1
.env
@@ -32,3 +32,4 @@ ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
|
||||
LEARNING_BASE_URL=''
|
||||
PERSONALIZED_RECOMMENDATION_COOKIE_NAME = 'edx-user-personalized-recommendation'
|
||||
ZENDESK_KEY=''
|
||||
|
||||
@@ -39,3 +39,4 @@ ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||
LEARNING_BASE_URL='http://localhost:2000'
|
||||
SESSION_COOKIE_DOMAIN='localhost'
|
||||
PERSONALIZED_RECOMMENDATION_COOKIE_NAME = 'edx-user-personalized-recommendation'
|
||||
ZENDESK_KEY=''
|
||||
|
||||
@@ -37,3 +37,4 @@ ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||
LEARNING_BASE_URL='http://localhost:2000'
|
||||
ZENDESK_KEY='test-zendesk-key'
|
||||
|
||||
17
package-lock.json
generated
17
package-lock.json
generated
@@ -48,6 +48,7 @@
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"react-share": "^4.4.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.1.1",
|
||||
"redux-beacon": "^2.1.0",
|
||||
"redux-devtools-extension": "2.13.9",
|
||||
@@ -25002,6 +25003,14 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-zendesk": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/react-zendesk/-/react-zendesk-0.1.13.tgz",
|
||||
"integrity": "sha512-9UNzzgdgC8nr2nZ13PNudspUClZZgsnS3FofnuGK1I7+yDPNAP8iDFD2WSQRJmYDAzH+mTlVB4K+G8lY1/0B+w==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/reactifex": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reactifex/-/reactifex-1.1.1.tgz",
|
||||
@@ -49049,6 +49058,14 @@
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-zendesk": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/react-zendesk/-/react-zendesk-0.1.13.tgz",
|
||||
"integrity": "sha512-9UNzzgdgC8nr2nZ13PNudspUClZZgsnS3FofnuGK1I7+yDPNAP8iDFD2WSQRJmYDAzH+mTlVB4K+G8lY1/0B+w==",
|
||||
"requires": {
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"reactifex": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reactifex/-/reactifex-1.1.1.tgz",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"react-share": "^4.4.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.1.1",
|
||||
"redux-beacon": "^2.1.0",
|
||||
"redux-devtools-extension": "2.13.9",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from 'data/redux';
|
||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
import ZendeskFab from 'components/ZendeskFab';
|
||||
|
||||
import fakeData from 'data/services/lms/fakeData/courses';
|
||||
|
||||
@@ -72,6 +73,7 @@ export const App = () => {
|
||||
) : (<Dashboard />)}
|
||||
</main>
|
||||
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ jest.mock('@edx/frontend-component-footer', () => 'Footer');
|
||||
|
||||
jest.mock('containers/Dashboard', () => 'Dashboard');
|
||||
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
|
||||
jest.mock('components/ZendeskFab', () => 'ZendeskFab');
|
||||
jest.mock('data/redux', () => ({
|
||||
selectors: 'redux.selectors',
|
||||
actions: 'redux.actions',
|
||||
|
||||
@@ -24,6 +24,7 @@ exports[`App router component component initialize failure snapshot 1`] = `
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
`;
|
||||
@@ -46,6 +47,7 @@ exports[`App router component component no network failure snapshot 1`] = `
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
`;
|
||||
@@ -74,6 +76,7 @@ exports[`App router component component refresh failure snapshot 1`] = `
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
`;
|
||||
|
||||
54
src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
Normal file
54
src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ZendeskFab snapshot 1`] = `
|
||||
<Zendesk
|
||||
cookies={true}
|
||||
defer={true}
|
||||
webWidget={
|
||||
Object {
|
||||
"answerBot": Object {
|
||||
"avatar": Object {
|
||||
"name": Object {
|
||||
"*": "edX Support",
|
||||
},
|
||||
"url": "https://edx-cdn.org/v3/prod/favicon.ico",
|
||||
},
|
||||
"contactOnlyAfterQuery": true,
|
||||
"suppress": false,
|
||||
"title": Object {
|
||||
"*": "edX Support",
|
||||
},
|
||||
},
|
||||
"chat": Object {
|
||||
"suppress": false,
|
||||
},
|
||||
"contactForm": Object {
|
||||
"attachments": true,
|
||||
"selectTicketForm": Object {
|
||||
"*": "Please choose your request type:",
|
||||
},
|
||||
"ticketForms": Array [
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"id": "description",
|
||||
"prefill": Object {
|
||||
"*": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
"id": 360003368814,
|
||||
"subject": false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"contactOptions": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
"helpCenter": Object {
|
||||
"originalArticleButton": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
53
src/components/ZendeskFab/index.jsx
Normal file
53
src/components/ZendeskFab/index.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import Zendesk from 'react-zendesk';
|
||||
import messages from './messages';
|
||||
|
||||
const ZendeskFab = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const setting = {
|
||||
cookies: true,
|
||||
webWidget: {
|
||||
contactOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
chat: {
|
||||
suppress: false,
|
||||
},
|
||||
contactForm: {
|
||||
ticketForms: [
|
||||
{
|
||||
id: 360003368814,
|
||||
subject: false,
|
||||
fields: [{ id: 'description', prefill: { '*': '' } }],
|
||||
},
|
||||
],
|
||||
selectTicketForm: {
|
||||
'*': formatMessage(messages.selectTicketForm),
|
||||
},
|
||||
attachments: true,
|
||||
},
|
||||
helpCenter: {
|
||||
originalArticleButton: true,
|
||||
},
|
||||
answerBot: {
|
||||
suppress: false,
|
||||
contactOnlyAfterQuery: true,
|
||||
title: { '*': formatMessage(messages.supportTitle) },
|
||||
avatar: {
|
||||
url: 'https://edx-cdn.org/v3/prod/favicon.ico',
|
||||
name: { '*': formatMessage(messages.supportTitle) },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ZendeskFab;
|
||||
12
src/components/ZendeskFab/index.test.jsx
Normal file
12
src/components/ZendeskFab/index.test.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import ZendeskFab from '.';
|
||||
|
||||
jest.mock('react-zendesk', () => 'Zendesk');
|
||||
|
||||
describe('ZendeskFab', () => {
|
||||
test('snapshot', () => {
|
||||
const wrapper = shallow(<ZendeskFab />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
16
src/components/ZendeskFab/messages.js
Normal file
16
src/components/ZendeskFab/messages.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
export const messages = StrictDict({
|
||||
supportTitle: {
|
||||
id: 'zendesk.supportTitle',
|
||||
description: 'Title for the support button',
|
||||
defaultMessage: 'edX Support',
|
||||
},
|
||||
selectTicketForm: {
|
||||
id: 'zendesk.selectTicketForm',
|
||||
description: 'Select ticket form',
|
||||
defaultMessage: 'Please choose your request type:',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -12,6 +12,8 @@ const configuration = {
|
||||
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
|
||||
PERSONALIZED_RECOMMENDATION_COOKIE_NAME: process.env.PERSONALIZED_RECOMMENDATION_COOKIE_NAME || '',
|
||||
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
|
||||
ZENDESK_KEY: process.env.ZENDESK_KEY,
|
||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||
};
|
||||
|
||||
const features = {};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { configuration } from './config';
|
||||
|
||||
import messages from './i18n';
|
||||
|
||||
@@ -49,9 +50,7 @@ console.log({ SEGEMENT_KEY: process.env.SEGMENT_KEY });
|
||||
initialize({
|
||||
handlers: {
|
||||
config: () => {
|
||||
mergeConfig({
|
||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||
}, appName);
|
||||
mergeConfig(configuration, appName);
|
||||
},
|
||||
},
|
||||
messages: [
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
|
||||
import appMessages from './i18n';
|
||||
import { configuration } from './config';
|
||||
import * as app from '.';
|
||||
|
||||
jest.mock('react-dom', () => ({
|
||||
@@ -34,7 +35,6 @@ jest.mock('@edx/frontend-component-footer', () => ({
|
||||
jest.mock('data/store', () => ({ redux: 'store' }));
|
||||
jest.mock('./App', () => 'App');
|
||||
|
||||
const testValue = 'my-test-value';
|
||||
describe('app registry', () => {
|
||||
let getElement;
|
||||
|
||||
@@ -70,15 +70,9 @@ describe('app registry', () => {
|
||||
expect(initializeArg.messages).toEqual([appMessages, footerMessages]);
|
||||
expect(initializeArg.requireAuthenticatedUser).toEqual(true);
|
||||
});
|
||||
test('initialize config loads support url if available', () => {
|
||||
const oldEnv = process.env;
|
||||
test('initialize config', () => {
|
||||
const initializeArg = initialize.mock.calls[0][0];
|
||||
delete process.env.SUPPORT_URL;
|
||||
initializeArg.handlers.config();
|
||||
expect(mergeConfig).toHaveBeenCalledWith({ SUPPORT_URL: null }, app.appName);
|
||||
process.env.SUPPORT_URL = testValue;
|
||||
initializeArg.handlers.config();
|
||||
expect(mergeConfig).toHaveBeenCalledWith({ SUPPORT_URL: testValue }, app.appName);
|
||||
process.env = oldEnv;
|
||||
expect(mergeConfig).toHaveBeenCalledWith(configuration, app.appName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
prettyDOM,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
initialize,
|
||||
mergeConfig,
|
||||
} from '@edx/frontend-platform';
|
||||
|
||||
import thunk from 'redux-thunk';
|
||||
import { useIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
@@ -41,6 +45,11 @@ jest.unmock('hooks');
|
||||
jest.mock('containers/WidgetContainers/LoadedSidebar', () => 'loaded-widget-sidebar');
|
||||
jest.mock('containers/WidgetContainers/NoCoursesSidebar', () => 'no-courses-widget-sidebar');
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform'),
|
||||
getConfig: () => jest.requireActual('../config').configuration,
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: () => ({
|
||||
|
||||
Reference in New Issue
Block a user