Secret Ninja Blog

Customer Experience Senior Directorしてます

Zendesk Guideの記事フィードバックでSlackを使ってコメントを収集できるようにする

Zendesk Guideでは記事のフィードバックをYes / Noの取得機能しかない。そこで、Guideのテーマを編集し、Yes / Noを入力した後にフィードバックのコメントを入れられるようにする。また、このフォームから送信されるデータをZendeskには貯められないので、SlackのAPIを使って一旦受け取る。

最初は、Zapierで受け取ればGoogle Sheet / Slackに書き出すといったことも可能になるかなと思ったが、PremiumプランだったのでSlackに直接書き出す。Zapierでも基本的にはやり方はどうようになる。

Slackの設定

下記記事に従ってIncoming Webhookが取得できるのでそれをメモしておき、下記のZendesk側で設定する。

Slack API を使用してメッセージを投稿する

Zendesk Guideの設定

下記を編集する。

  • article_page.hbs
  • script.js
  • script.css

article_page.hbs

article_page.hbsの編集前の下記箇所を

          <div class="article-votes">
            <span class="article-votes-question">{{t 'was_this_article_helpful'}}</span>
            <div class="article-votes-controls" role='radiogroup'>
              {{vote 'up' role='radio' class='button article-vote article-vote-up'}}
              {{vote 'down' role='radio' class='button article-vote article-vote-down'}}
            </div>
            <small class="article-votes-count">
              {{vote 'label' class='article-vote-label'}}
            </small>
          </div>

下記で上書きする。ホームページ内で変更したい箇所は直接編集する。特に変更した方がよさそうな場所にはコード内でコメントしておく

<div class="article-votes">
          <div class="article-votes-title">Was this article helpful?</div>
          <div class="article-votes-btns-wrap">
# 役に立った。などに変えても良い
            <span class="article-votes-btn article-votes-btn-yes js--article-votes-btn" data-type="yes">Yes, thanks</span>
            <span class="article-votes-btn article-votes-btn-no js--article-votes-btn" data-type="no">Not really</span>
          </div>
          <form class="article-votes-form-yes js--form-yes">
            <textarea class="article-votes-textarea"></textarea>
# 特にどのようなところが参考になりましたか?など
            <label class="article-votes-textarea-label">What did you like about this article? (optional)</label>
            <button class="article-votes-button js--btn-send-yes" type="submit">Submit feedback</button>
          </form>
          <form class="article-votes-form-no js--form-no">
            <div class="article-votes-form-sorry-text">
                Sorry about that! Why wasn't the article helpful? 
            </div>
            <div class="article-votes-labels">
# どのようなところが参考になりませんでしたか?など。選択肢は追加しても良い
              <label><input type="radio" name="feedback-selection" value="This article didn't answer my questions or solve my problem" checked>
                This article didn't answer my questions or solve my problem
              </label>
              <label><input type="radio" name="feedback-selection" value="I found this article confusing or difficult to read">
                I found this article confusing or difficult to read
              </label>
              <label><input type="radio" name="feedback-selection" value="I don't like how the feature works">
                I don't like how the feature works
              </label>
              <label><input type="radio" name="feedback-selection" value="Other">
                Other
              </label>
            </div>
            <div class="article-votes-form-no-textarea-wrap">
              <div class="article-votes-form-no-textarea">
                <textarea class="article-votes-textarea article-votes-textarea-no" placeholder="Your comments will help us to improve the article!"></textarea>
                <label class="article-votes-form-no-textarea-label"></label>
              </div>
            </div>
            <button class="article-votes-button js--btn-send-no" type="submit">Submit feedback</button>

# 自社のヘルプセンターのリンクに変える
            <div class="article-more-questions">
                If you would like to get a reply from the Support team, please create a ticket <a href="https://help.xxxx.com/hc/en-us/requests/new?referer=help-center-article">here</a>
        </div>
          </form>
          <div class="article-votes-feedback-success">Thank you for your feedback!</div>
          <div class="article-votes-feedback-fail">Error! Please try later...</div>
        </div>

syle.css

syle.cssの末尾に下記を追加

.article-vote-down-explanation {
  display: none;
}

 .article-votes-form-yes {
  display: none;
}

.article-votes-form-yes.visible {
  display: block;
}

.article-votes-form-no {
  display: none;
}

.article-votes-form-no.visible {
  display: block;
}

.article-votes-btn {
  cursor: pointer;
  border: 1px solid rgba(5, 0, 56, 1);
  color: rgba(5, 0, 56, 1);
  border-radius: 4px;
  padding: 6px 20px;
  transition: background-color .12s ease-in-out, border-color .12s ease-in-out, color .15s ease-in-out;
  font-size: 12px;
}

.article-votes-btn.active {
  background: rgba(5, 0, 56, 1);;
  color: #fff;
}

.article-votes-btn-yes {
  margin-right: 10px;
  padding: 6px 16px;
}

.article-votes-title {
  margin-bottom: 14px;
}

.article-votes-btns-wrap {
  margin-bottom: 40px;
}

.article-votes-textarea {
  height: 85px;
  resize: none;
  margin-bottom: 14px;
}

.article-votes-textarea.validationError::-webkit-input-placeholder { /* Chrome */
  color: #f2545b;
}

.article-votes-textarea.validationError:-ms-input-placeholder { /* IE 10+ */
  color: #f2545b;
}

.article-votes-textarea.validationError::-moz-placeholder { /* Firefox 19+ */
  color: #f2545b;
  opacity: 1;
}

.article-votes-textarea.validationError:-moz-placeholder { /* Firefox 4 - 18 */
  color: #f2545b;
  opacity: 1;
}

.article-votes-form-yes {
  position: relative;
  padding: 21px 0 0;
}

.article-votes-textarea-label {
  font-size: 12px;
  position: absolute;
  top: 0;
  left: 10px;
}

.article-votes-form-no-textarea-wrap {
  position: relative;
  padding: 21px 0 0;
}

.article-votes-form-no-textarea-label {
  font-size: 12px;
  position: absolute;
  top: 0;
  left: 10px;
}


.article-votes-labels {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.article-votes-labels label {
  margin: 3px 0;
}

.article-votes-labels label input {
  margin-right: 5px;
}

.article-votes-button {
  background-color: rgba(5, 0, 56, 1);
  border: none;
  color: #fff;
  padding: 8px 16px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 12px;
  border-radius: 4px;
  cursor: pointer;
}

.article-votes-button:focus {
  border: none;
}

.article-votes-btns-wrap.disabled .article-votes-btn {
  opacity: 0.6;
  pointer-events: none;
  cursor: default;
}

.article-votes-form-yes.disabled .article-votes-button {
  opacity: 0.6;
  pointer-events: none;
  cursor: default;
}

.article-votes-form-yes.disabled .article-votes-textarea {
  opacity: 0.6;
  pointer-events: none;
  cursor: default;
}

.article-votes-form-no.disabled .article-votes-button {
  opacity: 0.6;
  pointer-events: none;
  cursor: default;
}

.article-votes-form-no.disabled .article-votes-textarea {
  opacity: 0.6;
  pointer-events: none;
  cursor: default;
}

.article-votes-feedback-success {
  display: none;
}

.article-votes-feedback-success.active {
  display: block;
}

.article-votes-feedback-fail {
  display: none;
}

.article-votes-feedback-fail.active {
  display: block;
}

.article-votes-form-sorry-text {
  font-weight: 700;
  margin-bottom: 10px;
}

.article-votes-form-no .article-votes-button {
  margin-bottom: 20px;
}

script.js

script.jsのdocument.addEventListener('DOMContentLoaded', function() {}内の最後に入れる。 また、Zapierで取得したWebHookの設定をurlの変数で設定すること

//feedback form help center
  var articleVotesBtnsBlock = document.querySelector(".article-votes-btns-wrap");
  var articleVotesBtns = document.querySelectorAll(".js--article-votes-btn");
  var formYes = document.querySelector(".js--form-yes");
  var formNo = document.querySelector(".js--form-no");
  
  if(formNo) {
    var radioBtns = document.querySelectorAll("input[type='radio'][name='feedback-selection']");
    var labvarextareaNo = formNo.querySelector(".article-votes-form-no-textarea-label");
    var btnSendYes = document.querySelector(".js--btn-send-yes");
    var btnSendNo = document.querySelector(".js--btn-send-no");
    var userResponse = {
      subject: 'Help center feedback',
      url: document.location.href,
      responseType: 'yes',
      radioResponse: '-',
      description: '-',
    };
    var itemSucces = document.querySelector(".article-votes-feedback-success");
    var itemFail = document.querySelector(".article-votes-feedback-fail");
    var date = new Date();
    var commentPlaceholderText = 'Your comments will help us to improve the article!';
    var commentPlaceholderErrorText = 'Please let us know how we can improve the article - leave your comment here!';

    for (var i = 0; i < articleVotesBtns.length; i++) {
      articleVotesBtns[i].addEventListener('click', function(e){
        e.preventDefault();

        if(this.getAttribute('data-type') === 'yes') {
          setActiveBtn();
          clearForm();

          this.classList.add("active");
          formNo.classList.remove("visible");
          formYes.classList.add("visible");
        }

        if(this.getAttribute('data-type') === 'no') {
          setActiveBtn();
          clearForm();

          this.classList.add("active");
          formYes.classList.remove("visible");
          formNo.classList.add("visible");
        }
      });
    }

    //submit
    btnSendYes.addEventListener('click', function(e){
      e.preventDefault();
      userResponse.description = formYes.querySelector(".article-votes-textarea").value;

      sendRequest(userResponse);

    });

    btnSendNo.addEventListener('click', function(e){
      e.preventDefault();
      // userResponse

      for (var i = 0; i < radioBtns.length; i++) {
        userResponse.responseType = 'no';
        if(radioBtns[i].checked){
          userResponse.radioResponse = radioBtns[i].value;
        }
        userResponse.description = formNo.querySelector(".article-votes-textarea").value;
      }
      
      //user comment requaered validation
      if(userResponse.description.length > 0) {
        sendRequest(userResponse);
      } else {
        showValidationError();
      }
    });
    
    function showValidationError(){
      //change placeholder for comment textarea
      var commentTextarea = formNo.querySelector(".article-votes-textarea");
      if(commentTextarea !== undefined) {
        commentTextarea.placeholder = commentPlaceholderErrorText;
        //change placeholder color -> red
        commentTextarea.classList.add('validationError');
      }
    }

function sendRequest(userResponse) {
      var result = false;

      //block form
      setDisabledForm();

      var request = new XMLHttpRequest();
# Webhookに書き換える
      var url = 'https://hooks.slack.com/services/xxx/xxx/xxxx'

# 整形したかったらこちら
      var data = {
        text: date + '\n' +  userResponse.responseType + '\n' + userResponse.radioResponse + '\n' + userResponse.description + '\n' + userResponse.url
      }
      
      request.open('POST', url);
      request.withCredentials = false;
      request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      request.addEventListener("readystatechange", function () {
        if (request.status === 200) {
          setCloseForm();
          clearForm();

          itemSucces.classList.add("active");
        } else {
          setCloseForm();
          clearForm();
        }
      });

      request.send('payload=' + JSON.stringify(data));
    }

    function clearForm(){
      formYes.querySelector(".article-votes-textarea").value = '';

      for (var i = 0; i < radioBtns.length; i++) {
        if(i === 0 && radioBtns[i] !== undefined){
          radioBtns[i].checked = true;
        } else {
          radioBtns[i].checked = false;
        }
      }
      
      var commentTextarea = formNo.querySelector(".article-votes-textarea");
      commentTextarea.placeholder = commentPlaceholderText;
      //change placeholder color -> red
      commentTextarea.classList.remove('validationError');
            commentTextarea.value = '';
      labvarextareaNo.innerText = '';
    }

    function setActiveBtn(){
      for (var i = 0; i < articleVotesBtns.length; i++) {
        articleVotesBtns[i].classList.remove("active");
      }
    }

    function setDisabledForm(){
      articleVotesBtnsBlock.classList.add("disabled");
      formYes.classList.add("disabled");
      formNo.classList.add("disabled");
    }

    function setCloseForm(){
      formYes.classList.remove("visible");
      formNo.classList.remove("visible");
    }
  }
});

結果

謝辞

上記スクリプトはhttps://help.miro.com/hc/en-us/を参考に作成しています。