くらめその情シス:Slackから取得した情報で情シス業務を数値化してみた

2021.03.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

情シスやってます、アノテーションの髙嶋です。

みなさんは普段、業務の報告を行う際にどのように報告されいてますか? "報告内容は数値でわかるようにして"と言われることはないでしょうか?

自分が行った業務の情報を手動で集計して数値化するのは大変ですし、それを毎回行うとなったらかなりの時間がかかってしまいます。 今回は情シスが行っている業務の一部を自動で数値化できるようにしてみましたのでご紹介します。

みなさんも自分の業務を自動で数値化(= 見える化)してみませんか?

実際に業務の情報を集計してみた

集計した業務内容

情シス業務の1つとして、PCやシステムなどでトラブルが発生した際のサポートを行っています。

弊社では、社員が何か困った時に"○○○で困ってるから助けて!"というような内容を投稿するためのSlackチャンネルを設けています。

今回はそのSlackチャンネルへの投稿から下記の内容を集計するようにしてみました。

  • 情シスがサポートを行った件数は何件か
  • 何件解決できているか
  • どのくらいの時間で対応を開始できたか
  • 何件を自分たちで解決でき、何件がサポートへの問い合わせが必要だったか

このチャンネルは情シス専用ではなく社内全体で共通に使用しているため、対象となるものを判定しています。

集計手順

集計は下記のように行っています。

  1. SlackのAPIを使用して、指定した期間内の投稿を取得する
  2. 取得した投稿で情シスメンバーが対応した(応答した)ものをマークする
  3. 投稿されてから情シスメンバーが応答するまでの時間を算出する
  4. その投稿が解決済みかどうかを判定する

4の"解決済みかどうか"をどうやって判定するかの条件に悩みましたが、投稿へのリアクションから判定するようにしました。そのため、情シスでは新たに「解決したものには特定のリアクションを設定する」という運用ルールを設定しました。

また、前述した"何件を自分たちで解決でき、何件がサポートへの問い合わせが必要だったか"に関しては自動的に判定していません。これはやり取りしている内容から判定するのは難しいと考え、現時点では集計結果を確認しながら、1件ずつ人が判定するようにしています。

このように、今回の仕組みをつくる際には

  • 何を集計して数値化するか
  • どうやって数値化するか
  • (数値化するための)運用ルールの見直し
  • 自動化する範囲をどうするか

などの検討が必要になります。

集計結果

集計した結果はGoogle スプレッドシートにまとめています。(掲載している値は"それっぽい感じ"に編集しています)

■「取得一覧」シート
Slackから取得したデータをもとに、集計方法で記載した内容を判断できるように値を出力

※色付き行が最初に投稿されたもので、その下がスレッドの投稿

■「集計結果」シート
「取得一覧」シートの内容をスプレッドシートの数式を使用して集計

言葉だけで報告するよりも、こんな感じで数値やグラフがあった方が、よりわかりやすくなると思いませんか?

また、各項目の意味合いは下記のようになります。

  • 集計対象:情シスメンバーが対応したものに"○"
  • リードタイム:最初に投稿されてから最後の応答までの時間(分)
  • ファーストレスポンスタイム:投稿に対して、情シスメンバーが応答した時間(分)
  • 一次回答:情シスメンバーだけで解決できたもの
  • 二次回答:サポートに問い合わせしたもの

集計用プログラム

実際に作成したプログラムも掲載します。これを実行することで、上記で書いた内容を毎回自動で集計してくれるようになります。(ソースをすべて掲載すると長くなるので、やっていることの流れがわかるかな?という程度に削っています。)

また、Slackのワークスペースに対してアプリの登録が必要などもありますが、その辺りの手順は割愛します。

■開発言語
・Google Apps Script(TypeScript)
■Slackアプリに必要な権限
・channels:history (パブリックチャンネルを対象とする場合)
・groups:history (プライベートチャンネルを対象とする場合)

Main.ts

/**
 * Slackデータ収集
 */
class Main {

    /**
     * Slackデータの収集を行う。
     * @param targetMonth 処理対象年月日
     */
    public handler(): void {

        //Slackデータ取得
        let slackAnalyze = new SlackDataAnalyze(
            SpreadsheetApp.getActiveSpreadsheet(),
            [
                '対応済みとして扱うリアクションのID 1',
                '対応済みとして扱うリアクションのID 2',
            ],
            [
                '情シスメンバーのSlack ID 1',
                '情シスメンバーのSlack ID 2',
            ],
        );

        slackAnalyze.exec(
            '情報を取得したいSlackチャンネルのID',
            new Date('2021/02/01 00:00:00'),
            new Date('2021/02/28 23:59:59'),
        );
    }
}

SlackDataAnalyze.ts

/**
 * 
 */
class SlackDataAnalyze {

    /**
     * 出力先シート名
     */
    private readonly SHEET_NAME: string = '一覧';

    private readonly slack: Slack;

    /**
     * コンストラクタ
     * @param targetSpreadsheet 出力先スプレッドシート
     * @param correspondenceCompletedReplies 対応済みとして扱うリアクションのID
     * @param jSysUsers 情シスメンバーのID
     */
    constructor(
        private readonly targetSpreadsheet: GoogleAppsScript.Spreadsheet.Spreadsheet,
        private readonly correspondenceCompletedReplies: string[],
        private readonly jSysUsers: string[],
    ) {
        let token = PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN')

        if (token === null) throw new Error("[プロパティ:SLACK_TOKEN]が設定されていません。");

        this.slack = new Slack(token);
    }

    /**
     * 対象チャンネルからデータを取得し、スプレッドシートへ出力する。
     * @param channelId SlackのチャンネルID
     * @param startDay データ取得範囲(開始)
     * @param endDay データ取得範囲(終了)
     */
    public exec(
        channelId: string,
        startDay: Date,
        endDay: Date,
    ) {

        //メッセージの履歴を取得
        let historyDtos: HistoryDto[] = this.getHistoryData(
            channelId,
            startDay,
            endDay,
        )

        //メッセージに対するリプライを取得
        this.getReplieData(
            channelId,
            historyDtos
        );

        //スプレッドシートへ出力
        this.writeSpreadsheet(
            historyDtos
        );
    }

    /**
     * 指定チャンネルのメッセージの履歴を取得する。
     * @param channelId SlackのチャンネルID
     * @param startDay データ取得範囲(開始)
     * @param endDay データ取得範囲(終了)
     */
    private getHistoryData(
        channelId: string,
        startDay: Date,
        endDay: Date,
    ): HistoryDto[] {

        let historyDtos: HistoryDto[] = []

        //件数が多い場合、1度で取得できないので全件取得するまで繰り返す
        do {

            //メッセージの履歴を取得しDTOへ変換して保持する
            let historyContext = this.slack.getHistory(
                channelId,
                startDay,
                endDay,
                this.slack.nextCursor
            );

            let tmpHistoryDtos: HistoryDto[] = HistoryDto.context2Dto(
                channelId,
                historyContext
            );

            historyDtos = historyDtos.concat(tmpHistoryDtos)

        } while (this.slack.hasNext)

        return historyDtos;
    }

    /**
     * メッセージの履歴に対するリプライを取得する。
     * @param channelId SlackのチャンネルID
     * @param historyDtos 履歴DTO
     */
    private getReplieData(
        channelId: string,
        historyDtos: HistoryDto[],
    ): void {

        //履歴DTO全件に対して処理する
        for (let key in historyDtos) {

            let historyDto = historyDtos[key]

            if (historyDto.hasReplies === true) {

                //メッセージのスレッドをDTOへ変換し、メッセージと紐づけて保持する
                let repliesContext = this.slack.getReplies(
                    channelId,
                    historyDto.ts
                );

                let replieDtos: ReplieDto[] = ReplieDto.context2Dto(
                    channelId,
                    repliesContext
                );

                historyDto.addReplies(replieDtos)
            }
        }
    }

    /**
     * 
     * @param historyDtos 
     */
    private writeSpreadsheet(
        historyDtos: HistoryDto[]
    ): void {
        //取得した結果をスプレッドシートへ出力する。
    }
}

Slack.ts

class Slack {

    //ネクストカーソル
    private _nextCursor: string = "";

    /**
     * コンストラクタ
     *
     * @param apiKey APIキー
     */
    constructor(
        private apiKey: string
    ) {
    }

    /**
     * 指定したチャンネルの履歴を取得する。
     * 繰り返し取得する場合はネクストカーソルの指定が必要。
     * @param plainChannel 取得対象チャンネルID
     * @param rengeStartDay 取得対象日(開始)
     * @param rengeEndDay 取得対象日(終了)
     * @param nextCursor ネクストカーソル
     * @returns コンテキスト
     */
    public getHistory(
        plainChannel: string,
        rengeStartDay: Date,
        rengeEndDay: Date,
        nextCursor: string
    ): string {

        //(https://slack.com/api/conversations.historyを実行してそのコンテキストを返却する)
        return context;
    }

    /**
     * 指定したスレッドのリプライを取得する。
     * @param plainChannel 取得対象チャンネルID
     * @param ts 取得対象スレッドID
     * @returns コンテキスト
     */
    public getReplies(
        plainChannel: string,
        ts: string
    ): string {

        //(https://slack.com/api/conversations.repliesを実行してそのコンテキストを返却する)
        return context;
    }

    /**
     * ネクストカーソルを取得する。
     */
    get nextCursor(): string {
        return this._nextCursor;
    }

    /**
     * 後続データの有無を取得する。
     */
    get hasNext(): boolean {
        return this._nextCursor != "";
    }
}

集計した情報をどうするか

"数値化できた!終わり!"ではなく、せっかく集計した情報なのでその情報を次へどう活かすかが必要になります。 集計・数値化したことにより、現在の状況が把握しやすくなります。

ですので、例えば、

  • 解決率が低い場合は何が原因か(高い場合はどうすれば維持できるか)
  • 問い合わせ内容にどういう傾向があるか

等々の材料とすることができるようになります。

情シスでは集計結果をもとにして定期的に業務の振り返りを行っており、今後の改善点や発生した内容の共有などに使用しています。 他にも"自動でできるならこの情報も数値化したい"というのも今後出てくるかと思っています。

最後に

最後までお読みいただきありがとうございます。

今回は業務でツール化したことを書いてみましたが、いかがでしたでしょうか。

会社ごとに業務内容は異なるでしょうし、同じような業務でもやり方は異なると思います。そのため、今回の集計方法をそのまま使用することはできないと思いますが、少しでもみなさんに"自分もやってみよう"とか"ヒントになる箇所があった"となれば幸いです。

それでは、また次の記事でお会いしましょう。