From 2932693cda013ae583adc0d0a50b3414bd6cc163 Mon Sep 17 00:00:00 2001
From: leangseu-edx <83240113+leangseu-edx@users.noreply.github.com>
Date: Wed, 30 Nov 2022 09:33:43 -0500
Subject: [PATCH] feat: add zendesk widget (#71)
---
.env | 1 +
.env.development | 1 +
.env.test | 1 +
package-lock.json | 17 ++++++
package.json | 1 +
src/App.jsx | 2 +
src/App.test.jsx | 1 +
src/__snapshots__/App.test.jsx.snap | 3 ++
.../__snapshots__/index.test.jsx.snap | 54 +++++++++++++++++++
src/components/ZendeskFab/index.jsx | 53 ++++++++++++++++++
src/components/ZendeskFab/index.test.jsx | 12 +++++
src/components/ZendeskFab/messages.js | 16 ++++++
src/config/index.js | 2 +
src/index.jsx | 5 +-
src/index.test.jsx | 12 ++---
src/test/app.test.jsx | 9 ++++
16 files changed, 178 insertions(+), 12 deletions(-)
create mode 100644 src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
create mode 100644 src/components/ZendeskFab/index.jsx
create mode 100644 src/components/ZendeskFab/index.test.jsx
create mode 100644 src/components/ZendeskFab/messages.js
diff --git a/.env b/.env
index 3839137..33109c3 100644
--- a/.env
+++ b/.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=''
diff --git a/.env.development b/.env.development
index ce711ef..e40eb03 100644
--- a/.env.development
+++ b/.env.development
@@ -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=''
diff --git a/.env.test b/.env.test
index 1da6de5..acf05c3 100644
--- a/.env.test
+++ b/.env.test
@@ -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'
diff --git a/package-lock.json b/package-lock.json
index 789e37d..962373c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 24406d9..fc5584a 100755
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/App.jsx b/src/App.jsx
index 4876318..7f23515 100755
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -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 = () => {
) : ()}
+
);
diff --git a/src/App.test.jsx b/src/App.test.jsx
index b9873ae..e18a207 100644
--- a/src/App.test.jsx
+++ b/src/App.test.jsx
@@ -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',
diff --git a/src/__snapshots__/App.test.jsx.snap b/src/__snapshots__/App.test.jsx.snap
index 74e9246..d819557 100644
--- a/src/__snapshots__/App.test.jsx.snap
+++ b/src/__snapshots__/App.test.jsx.snap
@@ -24,6 +24,7 @@ exports[`App router component component initialize failure snapshot 1`] = `
+
`;
@@ -46,6 +47,7 @@ exports[`App router component component no network failure snapshot 1`] = `
+
`;
@@ -74,6 +76,7 @@ exports[`App router component component refresh failure snapshot 1`] = `
+
`;
diff --git a/src/components/ZendeskFab/__snapshots__/index.test.jsx.snap b/src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..a663492
--- /dev/null
+++ b/src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ZendeskFab snapshot 1`] = `
+
+`;
diff --git a/src/components/ZendeskFab/index.jsx b/src/components/ZendeskFab/index.jsx
new file mode 100644
index 0000000..294f5db
--- /dev/null
+++ b/src/components/ZendeskFab/index.jsx
@@ -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 (
+
+ );
+};
+
+export default ZendeskFab;
diff --git a/src/components/ZendeskFab/index.test.jsx b/src/components/ZendeskFab/index.test.jsx
new file mode 100644
index 0000000..8c62a4f
--- /dev/null
+++ b/src/components/ZendeskFab/index.test.jsx
@@ -0,0 +1,12 @@
+import { shallow } from 'enzyme';
+
+import ZendeskFab from '.';
+
+jest.mock('react-zendesk', () => 'Zendesk');
+
+describe('ZendeskFab', () => {
+ test('snapshot', () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/src/components/ZendeskFab/messages.js b/src/components/ZendeskFab/messages.js
new file mode 100644
index 0000000..7212bef
--- /dev/null
+++ b/src/components/ZendeskFab/messages.js
@@ -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;
diff --git a/src/config/index.js b/src/config/index.js
index b993d37..98dfc88 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -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 = {};
diff --git a/src/index.jsx b/src/index.jsx
index 423a5a3..9215f16 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -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: [
diff --git a/src/index.test.jsx b/src/index.test.jsx
index 0f96bed..364e278 100644
--- a/src/index.test.jsx
+++ b/src/index.test.jsx
@@ -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);
});
});
diff --git a/src/test/app.test.jsx b/src/test/app.test.jsx
index 5976748..16491e9 100644
--- a/src/test/app.test.jsx
+++ b/src/test/app.test.jsx
@@ -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: () => ({