From 74089504a12a44343a748d363c7552b92f5f33a2 Mon Sep 17 00:00:00 2001 From: Mingfei Shao <2475897+mfshao@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:30:51 -0500 Subject: [PATCH] HP-1646 Feat/zendesk (#1587) * feat migrate from kayako to zendesk * feat code change * update csp * fix fetch * fix content * update --- dev.html | 2 +- docs/portal_config.md | 6 +-- .../GenericAccessRequestForm.tsx | 2 + .../handleRegisterFormSubmission.ts | 27 ++++-------- .../DataDictionarySubmission.tsx | 13 ++---- src/index.ejs | 2 +- src/localconf.js | 8 ++-- src/utils.js | 42 ++++++++++++++----- 8 files changed, 55 insertions(+), 47 deletions(-) diff --git a/dev.html b/dev.html index 7507f1c356..6110ad3b46 100644 --- a/dev.html +++ b/dev.html @@ -11,7 +11,7 @@ - https://atlas.qa-mickey.planx-pla.net for Atlas iframe in VHDC qa-mickey - https://*.quicksight.aws.amazon.com for loading AWS Quicksight dashboards into COVID-19 Home page --> - + diff --git a/docs/portal_config.md b/docs/portal_config.md index f84c7740ce..89d4688f7b 100644 --- a/docs/portal_config.md +++ b/docs/portal_config.md @@ -743,7 +743,7 @@ Below is an example, with inline comments describing what each JSON block config ... ], "stridesPortalURL": "https://strides-admin-portal.org", // optional; If configured, will display a link on the workspace page which can direct user to the STRIDES admin portal, - "registrationConfigs": { // optional; Required when using Kayako integration with Study/Workspace registration + "registrationConfigs": { // optional; Required when using Zendesk integration with Study/Workspace registration "features":{ // Optional; Required when using study/Workspace registration "studyRegistrationConfig": { // optional, config for Study Registration and Study Registration Request Access page. "studyRegistrationTrackingField": "registrant_username", // optional, one of the extra field that is being added to metadata when a study is registered, will be useful in the future. Defaults to "registrant_username" @@ -765,8 +765,8 @@ Below is an example, with inline comments describing what each JSON block config } } }, - "kayakoConfig":{ //Required; if using either of the study/workspace registration feature - "kayakoDepartmentId": 21 // Required; the department ID in the kayako portal. Refer to Ops team to get more info + "zendeskConfig":{ // Optional; add this if you want to customize the subdomain that Zendesk is using in either of the study/workspace registration feature + "zendeskSubdomainName": "projectSupport" // Optional; the subdomain name of the Zendesk server. Refer to User Service team to get more info. If omitted, will default to 'gen3support' } } } diff --git a/src/GenericAccessRequestForm/GenericAccessRequestForm.tsx b/src/GenericAccessRequestForm/GenericAccessRequestForm.tsx index 736697a742..2f3b2fb59b 100644 --- a/src/GenericAccessRequestForm/GenericAccessRequestForm.tsx +++ b/src/GenericAccessRequestForm/GenericAccessRequestForm.tsx @@ -80,6 +80,8 @@ const GenericAccessRequestForm: React.FunctionComponent if (!useArboristUI) { return true; } + // We don't need this method anymore after we migrated to Zendesk, but just an extra layer of check here + // This may get removed in the future return userHasMethodForServiceOnResource( 'create', 'kayako', diff --git a/src/GenericAccessRequestForm/handleRegisterFormSubmission.ts b/src/GenericAccessRequestForm/handleRegisterFormSubmission.ts index 35d9151fc7..7f8bca5883 100644 --- a/src/GenericAccessRequestForm/handleRegisterFormSubmission.ts +++ b/src/GenericAccessRequestForm/handleRegisterFormSubmission.ts @@ -1,11 +1,9 @@ import { doesUserHaveRequestPending } from '../StudyRegistration/utils'; import { - kayakoConfig, hostname, workspaceRegistrationConfig, requestorPath, + zendeskConfig, hostname, workspaceRegistrationConfig, requestorPath, } from '../localconf'; import { fetchWithCreds } from '../actions'; -import { createKayakoTicket } from '../utils'; - -const KAYAKO_MAX_SUBJECT_LENGTH = 255; +import { createZendeskTicket } from '../utils'; const handleRegisterFormSubmission = async ( specificFormInfo, @@ -29,7 +27,7 @@ const handleRegisterFormSubmission = async ( // first, check if there is already a pending request in requestor let userHaveRequestPending : boolean; - let policyID : string; + let policyID : string = ''; try { if (specificFormInfo.name === 'WorkspaceAccessRequest') { policyID = workspaceRegistrationConfig?.workspacePolicyId ? workspaceRegistrationConfig.workspacePolicyId : 'workspace'; @@ -77,20 +75,11 @@ const handleRegisterFormSubmission = async ( .then(({ data, status }) => { if (status === 201) { if (data && data.request_id) { - // request created, now create a kayako ticket + // request created, now create a zendesk ticket const fullName = `${formValues['First Name']} ${formValues['Last Name']}`; const email = formValues['E-mail Address']; - let subject = determineSubjectLine(); - if (subject.length > KAYAKO_MAX_SUBJECT_LENGTH) { - subject = `${subject.substring( - 0, - KAYAKO_MAX_SUBJECT_LENGTH - 3, - )}...`; - } - let contents = `Request ID: ${data.request_id}\n - Grant Number: ${studyNumber}\n - Study Name: ${studyName}\n - Environment: ${hostname}`; + const subject = determineSubjectLine(); + let contents = `Request ID: ${data.request_id}\nGrant Number: ${studyNumber}\nStudy Name: ${studyName}\nEnvironment: ${hostname}`; if (specificFormInfo.name === 'WorkspaceAccessRequest') { contents = `Request ID: ${data.request_id}\nEnvironment: ${hostname}`; } @@ -101,12 +90,12 @@ const handleRegisterFormSubmission = async ( const [key, value] = entry; contents = contents.concat(`\n${key}: ${value}`); }); - createKayakoTicket( + createZendeskTicket( subject, fullName, email, contents, - kayakoConfig?.kayakoDepartmentId, + zendeskConfig?.zendeskSubdomainName, ).then( () => setFormSubmissionStatus({ status: 'success' }), (err) => setFormSubmissionStatus({ diff --git a/src/StudyRegistration/DataDictionarySubmission.tsx b/src/StudyRegistration/DataDictionarySubmission.tsx index b3299b214c..47e6f76906 100644 --- a/src/StudyRegistration/DataDictionarySubmission.tsx +++ b/src/StudyRegistration/DataDictionarySubmission.tsx @@ -24,10 +24,10 @@ import { import './StudyRegistration.css'; import { Link, useLocation } from 'react-router-dom'; import { - hostname, kayakoConfig, studyRegistrationConfig, useArboristUI, + hostname, zendeskConfig, studyRegistrationConfig, useArboristUI, } from '../localconf'; import { cleanUpFileRecord, generatePresignedURL, handleDataDictionaryNameValidation } from './utils'; -import { createKayakoTicket } from '../utils'; +import { createZendeskTicket } from '../utils'; import { userHasMethodForServiceOnResource } from '../authMappingUtils'; import { StudyRegistrationProps } from './StudyRegistration'; @@ -39,8 +39,6 @@ export interface FormSubmissionState { text?: string; } -const KAYAKO_MAX_SUBJECT_LENGTH = 255; - const layout = { labelCol: { span: 8, @@ -165,10 +163,7 @@ const DataDictionarySubmission: React.FunctionComponent uploadToS3(url, fileInfo, setUploadProgress) .then(() => { setFormSubmissionStatus({ status: 'info', text: 'Finishing upload' }); - let subject = `Data dictionary submission for ${studyNumber} ${studyName}`; - if (subject.length > KAYAKO_MAX_SUBJECT_LENGTH) { - subject = `${subject.substring(0, KAYAKO_MAX_SUBJECT_LENGTH - 3)}...`; - } + const subject = `Data dictionary submission for ${studyNumber} ${studyName}`; const fullName = `${formValues['First Name']} ${formValues['Last Name']}`; const email = formValues['E-mail Address']; let contents = `Grant Number: ${studyNumber}\nStudy Name: ${studyName}\nEnvironment: ${hostname}\nStudy UID: ${studyUID}\nData Dictionary GUID: ${guid}`; @@ -179,7 +174,7 @@ const DataDictionarySubmission: React.FunctionComponent // This is the CLI command to kick off the argo wf from AdminVM const cliCmd = `argo submit -n argo --watch HEAL-Workflows/vlmd_submission_workflows/vlmd_submission_wrapper.yaml -p data_dict_guid=${guid} -p dictionary_name="${formValues['Data Dictionary Name']}" -p study_id=${studyUID}`; contents = contents.concat(`\n\nCLI Command: ${cliCmd}`); - createKayakoTicket(subject, fullName, email, contents, kayakoConfig?.kayakoDepartmentId).then(() => setFormSubmissionStatus({ status: 'success' }), + createZendeskTicket(subject, fullName, email, contents, zendeskConfig?.zendeskSubdomainName).then(() => setFormSubmissionStatus({ status: 'success' }), (err) => { cleanUpFileRecord(guid); setFormSubmissionStatus({ status: 'error', text: err.message }); diff --git a/src/index.ejs b/src/index.ejs index ee014ea362..71c073c7b4 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -12,7 +12,7 @@ - https://dap.digitalgov.gov will be inserted by htmlWebpackPlugin if env is configured with DAPTrackingURL. This hostname is needed for fetching and executing the DAP tracking script - https://*.quicksight.aws.amazon.com for loading AWS Quicksight dashboards into COVID-19 Home page --> - + diff --git a/src/localconf.js b/src/localconf.js index e628fcedb2..76a984d02f 100644 --- a/src/localconf.js +++ b/src/localconf.js @@ -334,7 +334,7 @@ function buildConfig(opts) { const { workspacePageDescription } = config; const workspaceRegistrationConfig = (registrationConfigs && registrationConfigs.features) ? registrationConfigs.features.workspaceRegistrationConfig : null; - const kayakoConfig = registrationConfigs ? registrationConfigs.kayakoConfig : null; + const zendeskConfig = registrationConfigs ? registrationConfigs.zendeskConfig : null; const colorsForCharts = { categorical9Colors: components.categorical9Colors ? components.categorical9Colors : [ @@ -471,7 +471,7 @@ function buildConfig(opts) { const aggMDSURL = `${hostname}mds/aggregate`; const aggMDSDataURL = `${aggMDSURL}/metadata`; const cedarWrapperURL = `${hostname}cedar`; - const kayakoWrapperURL = `${hostname}kayako`; + const gen3ZendeskURL = 'https://.zendesk.com'; // Disallow gitops.json configurability of Gen3 Data Commons and CTDS logo alt text. // This allows for one point-of-change in the case of future rebranding. @@ -573,7 +573,7 @@ function buildConfig(opts) { studyViewerConfig, covid19DashboardConfig, discoveryConfig, - kayakoConfig, + zendeskConfig, studyRegistrationConfig, mapboxAPIToken, auspiceUrl, @@ -589,7 +589,7 @@ function buildConfig(opts) { mdsURL, aggMDSDataURL, cedarWrapperURL, - kayakoWrapperURL, + gen3ZendeskURL, commonsWideAltText, ddApplicationId, ddClientToken, diff --git a/src/utils.js b/src/utils.js index ffd720b57b..61d360cbbc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,7 @@ -import { submissionApiPath, kayakoWrapperURL } from './localconf'; +import { submissionApiPath, gen3ZendeskURL } from './localconf'; import { getCategoryColor } from './DataDictionary/NodeCategories/helper'; -import { fetchWithCreds } from './actions'; + +const ZENDESK_MAX_SUBJECT_LENGTH = 255; export const humanFileSize = (size) => { if (typeof size !== 'number') { @@ -233,24 +234,45 @@ export const isFooterHidden = (pathname) => (!!((pathname || pathname.toLowerCase().startsWith('/dd/') )))); -export const createKayakoTicket = async (subject, fullName, email, contents, departmentID) => { +export const createZendeskTicket = async (subject, fullName, email, contents, zendeskSubdomainName) => { try { - const kayakoTicketCreationURL = `${kayakoWrapperURL}/ticket`; - await fetchWithCreds({ - path: kayakoTicketCreationURL, + let zendeskTicketCreationURL = `${gen3ZendeskURL}/api/v2/requests`; + if (zendeskSubdomainName) { + zendeskTicketCreationURL = zendeskTicketCreationURL.replace('', zendeskSubdomainName); + } else { + // This is the default Gen3 helpdesk subdomain + zendeskTicketCreationURL = zendeskTicketCreationURL.replace('', 'gen3support'); + } + let ticketSubject = subject; + if (subject.length > ZENDESK_MAX_SUBJECT_LENGTH) { + ticketSubject = `${subject.substring( + 0, + ZENDESK_MAX_SUBJECT_LENGTH - 3, + )}...`; + } + await fetch(zendeskTicketCreationURL, { method: 'POST', - customHeaders: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - subject, fullname: fullName, email, contents, departmentid: departmentID, + request: { + subject: ticketSubject, + comment: { + body: contents, + }, + requester: { + name: fullName, + email, + }, + }, }), }).then((response) => { if (response.status !== 201) { - throw new Error(`Request for create Kayako ticket failed with status ${response.status}`); + throw new Error(`Request for create Zendesk ticket failed with status ${response.status}`); } return response; }); } catch (err) { - throw new Error(`Request for create Kayako ticket failed: ${err}`); + throw new Error(`Request for create Zendesk ticket failed: ${err}`); } };