-
Zendesk 기능 메모DEV/other things 2022. 10. 29. 13:59
const userId = 1234; // 지정한 티켓 번호로 이동. client.invoke('routeTo', 'user', userId);
사용하던 Zendesk 기능 메모.
* 주의점!
1. Sample이라 상황에 맞게 수정하여 사용한다.
2. HelpCenter는 최대한 API를 쓰지 않는것이 좋다!
3. 각 API마다 권한별 결괏값이 다를 수 있다. 권한에 상관 없이 모두 표출되어야 하는 경우 관리자 계정 정보를 사용해야 한다.
일반적인 Zendesk API 요청 axios / fetch 예시
// axios const callApi = async (zendesk_master_emailm zendesk_api_token) => { try { const auth = 'Basic ' + btoa(zendesk_master_email + '/token:' + zendesk_api_token); const headers = { 'Authorization': auth }; const data = { // POST 요청시 필요한 데이터 작성 }; // GET 요청의 경우 data 제외 const res = await axios.post('[Zendesk API URL 작성]', data, { headers }); return user; } catch (error) { throw error; } } // fetch const url = '/api/v2/users/search.json?query=is_verified:false'; const options = { method: 'GET', headers: { 'Content-type': 'application/json', } }; const res = await fetch(url, options); await res.json();
HelpCenter
// account, user 정보를 가지고 있는 Object // helpcenter에서 사용 가능함 HelpCenter
유저정보(id로 조회)
const url = `/api/v2/users/${userId}`; const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(`${ZENDESK_INFO.email}/token:${ZENDESK_INFO.token}`) }, }; try { const response = await fetch(url, options); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); } // or client request 사용(zendesk app 내부) const { user } = await client.request('/api/v2/users/me.json'); console.log(user.external_id);
내 유저정보
const getMyInfo = await fetch(`/api/v2/users/me.json`); try { const response = await getMyInfo.json(); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); }
HelpCenter Sections 조회
// HelpCenter의 경우 다국어를 지원함 // 해당 언어에 따라 url이 변경되므로 참고하자. const url = `/api/v2/help_center/${language}/sections`; const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(`${ZENDESK_INFO.email}/token:${ZENDESK_INFO.token}`) }, }; try { const response = await fetch(url, options); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); }
HelpCenter 특정 Section의 Articles 조회
const url = `/api/v2/help_center/${language}/sections/${section_id}/articles`; const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(`${ZENDESK_INFO.email}/token:${ZENDESK_INFO.token}`) }, }; try { const response = await fetch(url, options); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); }
HelpCenter 특정 Section의 Articles 조회 (100개씩)
// 특정 Section const url = `/api/v2/help_center/${language}/sections/${section_id}/articles.json?per_page=100`; // 모든 Sections const all_section_url = `/api/v2/help_center/${language}/articles.json?per_page=100`; const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(`${ZENDESK_INFO.email}/token:${ZENDESK_INFO.token}`) }, }; try { const response = await fetch(url, options); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); }
HelpCenter Community의 Posts 조회
// 조회 URL const url = `/api/v2/community/posts`; const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(`${ZENDESK_INFO.email}/token:${ZENDESK_INFO.token}`) }, }; try { const response = await fetch(url, options); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); }
HelpCenter Community의 Posts 조회(search query 사용)
// 태그 query 검색 // 참고로 젠데스크 search는 and 조건 불가함 // 대신 검색 결과는 우선순위가 존재함 (직접 필터링해서 AND처럼 사용 가능) const url = `/api/v2/help_center/community_posts/search?query=${query}`; const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(`${ZENDESK_INFO.email}/token:${ZENDESK_INFO.token}`) }, }; try { const response = await fetch(url, options); if(response?.status != 200) { return; } const data = await response.json(); return data; } catch (error) { console.log(error); }
HelpCenter Post 이미지 첨부
// 이미지가 첨부되면 유저 이미지가 젠데스크 서버로 업로드가 되며, <img src={업로드된 경로} /> 사용 // API 계정 정보 const ZD_CONFIG = { EMAIL: "", API_TOKEN: "" } const auth = 'Basic ' + btoa(ZD_CONFIG.EMAIL + '/token:' + ZD_CONFIG.API_TOKEN); /** * https://developer.zendesk.com/api-reference/help_center/help-center-api/user_images/ * 사용자 이미지 API를 사용하여 최종 사용자가 헬프 센터 인스턴스에 이미지를 업로드하도록 할 수 있습니다. * 사용자 이미지 업로드는 3단계 프로세스입니다. * 1. 이미지 업로드 URL 및 토큰 생성을 요청합니다. 이미지 업로드 URL 및 토큰 생성을 참조하십시오 . * 2. 이미지 업로드 URL에 PUT 요청을 하여 이미지를 업로드합니다. 업로드 URL로 이미지 업로드를 참조하십시오 . * 3. 도움말 센터에서 이미지 경로 생성을 요청하세요. 이미지 경로 만들기 를 참조하십시오 . * 게시물 댓글 본문의 경로를 사용하여 이미지를 인라인으로 표시할 수 있습니다. */ // 1. 이미지 업로드 URL 및 토큰 생성 const createAnImageUploadURLAndToken = async (file) => { const response = await fetch('/api/v2/guide/user_images/uploads', { method: 'POST', headers: { 'Authorization': auth, }, body: JSON.stringify({ "content_type": file.type, "file_size": file.size }) }) try { if (!response.ok) throw response; else { const { upload } = await response.json(); return upload; } } catch (error) { console.error(error); alert('이미지 첨부 중 오류가 발생했습니다.(1)') } } // 2. 업로드 URL로 이미지 업로드 const uploadTheImage = async ({ headers, token, url }, fileArrBuf) => { const response = await fetch(url, { method: 'PUT', headers, body: fileArrBuf }) try { if (response.status != 200) throw response; else return token; } catch (error) { console.error(error); alert('이미지 첨부 중 오류가 발생했습니다.(2)') } } // 3. 이미지 경로 생성 const createImagePath = async (token) => { const response = await fetch('/api/v2/guide/user_images', { method: 'POST', headers: { 'Authorization': auth, }, body: JSON.stringify({ "token": token, "brand_id": ${brand_id}, }) }) try { if (!response.ok) throw response; else { const { user_image } = await response.json(); return user_image; } } catch (error) { console.error(error); alert('이미지 첨부 중 오류가 발생했습니다.(3)') } } // 업로드하려는 파일이 2mb가 넘는지 확인 const check_file_size_over_2mb = (file) => { return file.size > 2097152 ? true : false; } // 파일 선택 이벤트 $(document).on("change", "input.formFile", async (event) => { const currentFile = event.target.files[0]; if (!check_file_size_over_2mb(currentFile)) { const resultCreateAnImageUploadURLAndToken = await createAnImageUploadURLAndToken(currentFile); if (resultCreateAnImageUploadURLAndToken) { const fileArrBuf = await currentFile.arrayBuffer(); const token = await uploadTheImage(resultCreateAnImageUploadURLAndToken, fileArrBuf); if (token) { const uploaded = createImagePath(token); console.log('uploaded', uploaded) } } } else alert("파일 용량이 2MB를 초과합니다. 2MB 이하의 파일을 선택 부탁드립니다."); });
ZAFClient
// import const client = ZAFClient.init();
앱 로드 시 발생
/** * - 앱 로드 시 발생 * - 에이전트가 앱을 다시 로드하는 경우에도 실행 */ client.on('app.registered', async function(context) { // ... });
티켓 제출 시 발생
/** * - 제출 버튼을 클릭하는 경우 발생 */ client.on('ticket.save', async () => { // ... });
Instance 예시
getInstanceClient(location) { const { instances } = await client.get('instances'); for (const instanceGuid in instances) { if (instances[instanceGuid].location === location) { return client.instance(instanceGuid); } } }
티켓 불러오기 예시 1
const { ticket } = await client.get('ticket');
티켓 불러오기 예시 2
// ${ticket_id} : ticket의 id const ticket = await client.request(`/api/v2/tickets/${ticket_id}`);
티켓 불러오기(여러개) 예시
// 'ids=' 뒤 숫자는 티켓 번호 예시 const tickets = await client.request(`/api/v2/tickets/show_many?ids=301075,301079`);
티켓의 유저 정보 예시
// 유저(요청자)정보 client.get('ticket.requester'); // 전화번호, 아이디 등등... client.get('ticket.requester.identities');
Search를 이용하여 검색
/** * - status : ticket의 상태를 의미하며 new, solved 등 존재 * - requester_id : 요청자 id * - tags : ticket의 tag * 순서가 중요한듯함. * https://developer.zendesk.com/api-reference/ticketing/ticket-management/search/ * https://developer.zendesk.com/documentation/ticketing/using-the-zendesk-api/searching-with-the-zendesk-api/ */ const _query = `/api/v2/search.json?query=type:ticket requester_id:${ticket.requester_id} status:new tags:${ticket.tag_name}`; const tickets = await client.request(_query); // sample 2 * const query = `/api/v2/search.json?query=type:ticket custom_field_5098259165071:true created>=2023-07-01 created<=2023-07-30`; const searchRes = await client.request(query); // sample 2 const query = `/api/v2/search.json?query=type:ticket created>2023-08-01 created<2023-08-05`; const searchRes = await client.request(query); // sample 3 const searchRes = await client.request('/api/v2/search.json?query=type:ticket&sort_by=updated_at&sort_order=desc'); // user의 특정 user_fields(custom_fields)의 값으로 확인하기 // 값이 null인 field를 찾고 싶으면 "field:none" 사용 // 여집합 검색은 필드 키 값 앞에 - 사용 >> "-user_fields_key:value" query = `/api/v2/search.json?query=type:user user_fields_key:value`; const searchRes = await client.request(query);
앱 내부에서 통신
/** * - [발신단] * - 발신할 .js 내부에 정의, 필요한 이벤트 내에서 발동 * - backgroundClient : background App instance * - trigger() : 발신측 * - function_name : 수신측 (상황에 맞게 이름을 지정하여 사용) * - ticket.id : 전송할 인자 (예시로 ticket의 id를 발송) */ // const backgroundClient = getInstance... (인스턴스 참고) backgroundClient.trigger('function_name', ticket.id); /** * - [수신단] * - 수신한 .js 내부에 정의 * - id : 예시로 ticket id를 수신하여 명칭을 id로 사용, 상황에 맞게 이름을 지정하여 사용 */ client.on('function_name', async (id) => { // ... })
앱 간 통신
/** * - [발신단] * - 발신할 App의 .js 내부에 정의, 필요한 이벤트 내에서 발동 * - appId : 대상(수신) App의 id * - event : 이벤트명 * - body : data.body */ const appId = client._metadata.settings.app_id_sample; const sampleData1 = 'This is sample data1'; const sampleData2 = 'This is sample data2'; // 데이터 let data = { 'app_id' : appId, 'event' : 'event_name', 'body' : { data1 : sampleData1, data2 : sampleData2 } } // 발신 client.request({ url: `/api/v2/apps/notify`, method: 'POST', contentType: "application/json", data: JSON.stringify(data), }); /** * - [수신단] * - 수신할(타 App) .js 내부에 정의 * - data : 예시로 data 수신하여 명칭을 data로 사용, 상황에 맞게 이름을 지정하여 사용 */ client.on('api_notification.event_name', async (data) => { console.log("data 1 : ", data.body.data1); console.log("data 2 : ", data.body.data2); });
앱 사이즈 수정
// width, height를 상황에 맞게 조절 client.invoke('resize', { width: '1400px', height: '85vh' });
티켓 오픈
const ticketId = 1200; // 지정한 티켓 번호로 이동. client.invoke('routeTo', 'ticket', ticketId);
앱 오픈
client.on('pane.activated', function (data) { });
탑바 닫기
// 해당 탑바를 닫음 (탑바만 가능한 것으로 알고 있음) client.invoke('popover', 'hide');
미리 로드
client.invoke('preloadPane');
티켓 모두 닫기 (Console)
for(let ele of document.querySelectorAll('[data-test-id="close-button"]')){ ele.click(); }
티켓 번호 수동으로 긁을때... (크롤링)
for(const ele of document.querySelectorAll('[data-garden-id="tables.row"]')){ console.log(ele.childNodes[3].innerText); } $('.StyledPageBase-sc-lw1w9j-0.StyledPage-sc-1k0een3-0.StyledNavigation-sc-184uuwe-0.idfrwS')[$('.StyledPageBase-sc-lw1w9j-0.StyledPage-sc-1k0een3-0.StyledNavigation-sc-184uuwe-0.idfrwS').length-1].click();
수동으로 API 긁을때...
// url 예시 let urls = [ '/api/v2/search.json?query=type:ticket%20external_id:1234', '/api/v2/tickets/1234', ] for(let url of urls) { let res = await client.request(url); console.log(res); }
수동으로 API 긁을때... (with next_page)
let url = '/api/v2/search.json?query=assignee:시스템%20created>2022-10-07%20tags:migration order_by:created_at sort:asc'; for(let cnt = 0; cnt < 10; cnt++) { let data = await client.request(url); url = data.next_page; }
manifest parameters 사용법
client.metadata().then((metadata) => { console.log(metadata.settings); });
zendesk alert
/** * alert 종류 * 'notice' : green * 'alert' : yellow * 'error' : red * * duration 자리에 { sticky: true } 넣으면 닫기를 눌러야 alert 종료 */ // base client.invoke('notify', message[, kind, duration]); // sample ('notify', '내용', 'alert 종류', 3000 ms) client.invoke('notify', `다시 확인해주세요.`, 'alert', 3000);
티켓 업데이트 / 내부메모 추가
async appendZendeskComment() { // 현재 티켓번호 불러오기 const ticketId = await ticket.getID(); // 내부매모 등록 try { const commentHTML = ` <p>==================================</p> // ... <p>==================================</p>`; await ticket.update(ticketId, { comment: { public: false, html_body: commentHTML } }); } catch (error) { client.invoke('notify', `내부매모 등록에 오류가 발생하였습니다. (error code : ${error})`, 'error', { sticky: true }); throw error; } }, const ticket = { /** * [티켓 번호 가져오기] * !!! 사이드 바에서 다음과 같이 사용(앵간하면 이렇게 사용) !!! * ticketId = client['_context']['ticketId']; */ async getID() { try { const res = await client.get('ticket.id'); if (res['error']) { throw new Error(res['error']); } console.log("[getId] current ticket id : ", res['ticket.id']) return res['ticket.id']; } catch (error) { throw error; } }, /** * [티켓 업데이트] * @param {*} ticketID : 업데이트 티켓 번호 * @param {*} param : 업데이트 티켓 데이터 * @returns */ async update(ticketID, param) { try { const option = { url: `/api/v2/tickets/${ticketID}`, method: 'PUT', contentType: 'application/json', data: JSON.stringify({ ticket: param }) } const res = await client.request(option); return res; } catch (error) { throw error; } } }
루프 예시
// 티켓의 comments 조회로 예시 // 작업의 경우 보통 동일하게 작성 (생각해보면 당연함) // res에 대한 예외처리(실패) 추가하여 사용 async function searchZendesk(ticket_ids) { for(const ticket of tickets_ids) { let target = `/api/v2/tickets/${ticket.id}/comments`; // API 예시 await zendeskApiRequest(target).then(async (res) => { let next_page = res.next_page; while (next_page !== null) { const resMore = await zendeskApiRequest(next_page); next_page = resMore.next_page; // 작업 } // 작업 } } } async function zendeskApiRequest(url) { try { const res = await client.request(url); return res; } catch (error) { return false; } }
팝업창에서 사용
const client = opener.ZAFClient.init(); // 활용하기
Dynamically changeing app icons (탑바 앱 가변 아이콘 샘플 코드) https://github.com/Seokhyeon-Park/icon-counter.git
'DEV > other things' 카테고리의 다른 글
Ubuntu 비밀번호 변경하기 (Ubuntu, Ubuntu server) (0) 2023.01.15 Git 터미널(콘솔) 에서 git pull(update) 받기 (0) 2023.01.10 REST API 정리 (0) 2022.04.07 M1 Mac 젠데스크(Zendesk) 다운로드 (0) 2022.02.23 jQuery 다운로드 및 사용 방법 (0) 2022.02.11