feat: add zendesk widget (#71)

This commit is contained in:
leangseu-edx
2022-11-30 09:33:43 -05:00
committed by GitHub
parent 552614466a
commit 2932693cda
16 changed files with 178 additions and 12 deletions

1
.env
View File

@@ -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=''

View File

@@ -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=''

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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>
);

View File

@@ -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',

View File

@@ -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>
`;

View 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,
},
}
}
/>
`;

View 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;

View 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();
});
});

View 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;

View File

@@ -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 = {};

View File

@@ -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: [

View File

@@ -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);
});
});

View File

@@ -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: () => ({