RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

ZENDESKのexternal_idをZENDESK上から変更できるアプリをつくる

こんにちは。RITの関です。

ZENDESKのアプリを作成したのでその方法を紹介します。

基本的には下記の公式ドキュメントと
https://developer.zendesk.com/documentation/apps/getting-started/overview/
下記のZENDESK公式ブログ
https://developerblog.zendesk.com/getting-started-zaf-next-js-4d1f83dae815

の通りにそのままやっているだけなのですが、何かの参考になれば幸いです。

完成イメージ

完成イメージ
組織のexternal_idを画面上から更新できるアプリを作成しました
ZAF initして作られるZENDESKアプリ側のコードは殆どいじっておらず、下記の様に作っています。
①ZENDESKアプリの読み込み先をNextjsで書かれたシステムのURLに指定(例: https://test-app/test_zendesk_app )
②Nextjs側でUIを作成(例: \pages\test_zendesk_app.tsx に色々書く)
③Nextjsでボタンを押された際にZENDESK APIにpatchリクエストをしてデータを更新

zat new する

Zendesk Apps Tools (ZAT)はrubyのgemで作られているので、ご自身のPCにrubyZAT gemを入れます Installing and using ZAT | Zendesk Developer Docs

その後に、適宜作業用のディレクトリを作って、その中でzat new --scaffoldを実行します Building your first Support app - Part 1: Laying the groundwork | Zendesk Developer Docs

zat new --scaffold
適宜emailなどを入力するとデフォルトのZENDESKアプリが出来ます。

ZENDESKアプリを動かしてみる

--scaffoldした場合、manifest.jsonは/srcの中に入っています。 なので、cd srcでsrcフォルダに移り、そこでzat serverをするとデフォルトアプリを動かすことが出来ます。

zat server

localhost:4567に接続するよう言われるので接続するとこんなのが出てくる l

localhost:4567

ZENDESKアプリを表示する

今回は下記を参考にZENDESKアプリを作成するので、編集するものはmanifest.jsonのみです。 localhostで作成したページを表示したいので、localhostの該当ページのURLを挿入し、ZENDESKの組織のページに表示したいのでlocationもorganization_sidebarに変更します。

manifest.json

そしてchange_organization_idのURLに該当する箇所に適当なコードを書いてみます

change_organization_id.tsx

import { NextPage } from 'next';
import React from 'react';

const ChangeOrganizationId: NextPage = () => {
    return <div>hogedayo</div>;
};

export default ChangeOrganizationId;

無事組織フィールドに表示されました!

組織フィールドに表示される

ZENDESKアプリからexternal_idを変更できるようにする

先程作成したchange_organization_idの中身を、external_idを差し替えできるようなフォームに書き換えたのが下記コードです

change_organization_id.tsx

import { NextPage } from 'next';
import React, { useEffect, useState } from 'react';
import { useZafClient } from '@/App/Service/ZafClient';
import { Button, Col, Container, Form, Row } from 'react-bootstrap';

interface ZendeskOrganization {
    organization: OrganizationObj;
}

interface OrganizationObj {
    id: number;
    tags: string[];
    name: string;
    domains: string;
    details: string;
    notes: string;
    externalId: string;
    sharedTickets: boolean;
    sharedComments: boolean;
}

const ChangeOrganiztionId: NextPage = () => {
    const client = useZafClient();
    const [newExternalId, setNewExternalId] = useState<string>('');
    const [organization, setOrganization] = useState<OrganizationObj>();

    useEffect(() => {
        if (!client) return;

        client.get('organization').then(function (res: ZendeskOrganization) {
            setOrganization(res.organization);
        });
    }, [client]);

    if (!client) return <p>読込中</p>;
    if (!organization) return <p>organizationがありません</p>;

    const options = {
        url: `/api/v2/organizations/${organization.id}`,
        data: JSON.stringify({
            'organization': {
                'external_id': newExternalId,
            },
        }),
        contentType: 'application/json',
        type: 'PUT',
    };

    const onSubmit: React.FormEventHandler = async e => {
        e.preventDefault();
        await client.request(options);
    };

    return (
        <Container className='bg-white' style={{ height: '220px' }}>
            <Form.Group as={Row} className='pt-3 align-items-center'>
                <Col xs={6}>
                    <h5>現在のexternal_id</h5>
                </Col>
                <Col className='text-right' xs={6}>
                    {organization.externalId}
                </Col>
            </Form.Group>
            <Form.Group as={Row} className='align-items-center'>
                <Col xs={6}>
                    <h5>新しいexternal_id</h5>
                </Col>
                <Col xs={6}>
                    <Form.Control
                        placeholder='123456'
                        className='mr-2'
                        type='text'
                        value={newExternalId}
                        onChange={e => setNewExternalId(e.target.value)}
                    />
                </Col>
            </Form.Group>
            <div className='text-right'>
                <Button variant='primary w-100' onClick={e => onSubmit(e)}>
                    更新する
                </Button>
            </div>
        </Container>
    );
};

export default ChangeOrganiztionId;

コードを貼り付けてハイ終わりだと不明点が多すぎると思うので、上から順番に解説していきます。

const client = useZafClient();は
ZENDESKから組織情報を取得したり、ZENDESKにPUTリクエストをするといった、ZENDESKが用意しているメソッドを使用するために必要なコードです。
ZafClientはScriptタグを読み込む必要があり、同じpage内だとScriptを読み込んでくれないので、app.tsxとかの中に

import Script from 'next/script';

<Script
    type='text/javascript'
    src=’https://static.zdassets.com/zendesk_app_framework_sdk/2.0/zaf_sdk.min.js’
    strategy='beforeInteractive'
/>

を入れることでScriptタグを読み込むようにする必要があります。
そして、ZafClient.tsxの中ではZafClientが読み込めるまで待って、読み込めたらsetClientするといった処理を書いています。

import { useEffect, useState } from 'react';

declare global {
    interface Window {
        ZAFClient: any;
    }
}

let zafClient: any = null;

export function useZafClient() {
    const [client, setClient] = useState(zafClient);

    useEffect(() => {
        if (!client && typeof window.ZAFClient !== 'undefined') {
            zafClient = window.ZAFClient.init();
            if (!zafClient) return;

            setClient(zafClient);
            zafClient.invoke('resize', { height: '220px' });
        }
    }, [client]);

    return client;
}

ZafClientを読み込むことで、下記の箇所でorganizationの情報をセット出来たり

    useEffect(() => {
        if (!client) return;

        client.get('organization').then(function (res: ZendeskOrganization) {
            setOrganization(res.organization);
        });
    }, [client]);

下記の箇所で該当組織へPUTリクエストを送れるようになっています。

    const options = {
        url: `/api/v2/organizations/${organization.id}`,
        data: JSON.stringify({
            'organization': {
                'external_id': newExternalId,
            },
        }),
        contentType: 'application/json',
        type: 'PUT',
    };

    const onSubmit: React.FormEventHandler = async e => {
        e.preventDefault();
        await client.request(options);
    };

仕組みは今まで説明したとおりなので、あとはreturn内に新しいexternal_idを入れるようなフォームを作成すれば完成です!
本当なら送信結果が返ってくるので、成功/失敗のtry catchや、フォームのバリデーションなどを入れるべきですが、今回は簡易な説明をするために省略しています。

改めて完成イメージです。external_idが更新されることを確認いただけると思います。
これにて説明は異常です。ZENDESKアプリを作成するときの役に立てると幸いです。ありがとうございました!

完成イメージ