Amazon Connectの機能拡張(オペレーション時間の設定)をAWS CDKで簡単にセットアップできるようにしてみました

1 はじめに

CX事業本部の平内(SIN)です。

Amazon Connect(以下、Connect)では、オペレーション時間を設定し、営業時間内・外で処理を分けることが可能です。しかし、設定は、曜日と時間しかないため、祝日などに対応するには、別の仕組みを用意する必要あります。

今回は、この仕組みをConnectの機能拡張と位置づけ、AWS CDK(Cloud Development Kit)を使い、簡単にセットアップできるようにしてみました。

機能としては、S3上の設定ファイルを編集して上書きすることで、オペレーション時間が変更できるものです。

2 セットアップ手順

セットアップの手順は以下のとおりです。

  • ダウンロード
  • バケット名指定
  • ビルド・デプロイ
  • 設定ファイルのアップロード
  • 問い合わせフローの設定

(1) ダウンロード

プロジェクトをgitからcloneします。

$ git clone https://github.com/furuya02/AmazonConnect-Extended-OperationTime3.git
$ cd AmazonConnect-Extended-OperationTime3

(2) バケット名指定

設定ファイルを保存するバケット名を指定してください。 (デフォルトは、connect-ex-opetimesetting-bucketとなっていますが、重複できないため、オリジナルの名前をつける必要があります)

lib/amazon_connect-extended-operation_time-stack.ts

(3) ビルド・デプロイ

yarnで必要モジュールをインストールします。

$ yarn
$ cd src/lambda && yarn && cd ../..

次のコマンドでビルド及び、デプロイします。

$ npm run build
$ cdk deploy

デプロイ終了時に表示される、Bucket名(SettingBucket)とLambdaのARN(LambdaFunction)をコピーしておいて下さい。

  • バケット名は設定ファイルの保存先です
  • LambdaのARNは、問い合わせフローの設定に使用します。

(4) 設定ファイルのアップロード

サンプルで用意されている設定ファイルを、S3にアップロードします。

$ aws s3 cp sample/OperationTime.txt s3://connect-ex-opetime-setting-bucket 

(5) 問い合わせフローの設定

sample/OperationTimeFlowをインポートします。

AWS Lambda関数を呼び出すブロックを開いて、LambdaのARNを設定します。

3 設定ファイル

設定ファイルは、エンジニア以外でも編集しやすいようにと考え、あえてJSONを避けています。

設定は、曜日ごとの営業時間及び、休日の2種類あります。

(1) 曜日ごとの営業時間

カンマ区切りで下記のとおり

曜日,始業時間,就業時間

(2) 休日

1行に1件で設定します。年を省略した場合は、年に関係なく祝日と判定されます。

月/日
年/月/日

なお、空行や、#以降(コメント)は、無視されます。

OperationTime.txt(例)

月,09:00,19:00
火,09:00,19:00
水,09:00,19:00
木,09:00,19:00
金,09:00,19:00


1/1 #元旦
2019/1/8 #成人の日

1/13 # 会社の創立記念日

# 2/11 #建国記念日
2019/2/12 #建国記念日(振替)

2019/3/21 #春分の日

# 4/29 #昭和の日
2019/4/30 #昭和の日(振替)

5/3 #憲法記念日
5/4 #みどりの日 
5/5 #こどもの日

2019/7/16 #海の日

8/11 #山の日

2019/9/17 #敬老の日(9月第三月曜日)
# 9/23 #秋分の日
2019/9/24 #秋分の日(振替)

2019/10/8 #体育の日(10月の第2日曜日)

11/3 #文化の日
11/23 #勤労感謝の日
# 12/23 #天皇誕生日
2019/12/24 #天皇誕生日(振替)

4 参考

参考までに、今回作成したAWS CDK及び、Lambdaのコードを少し紹介させて下さい。

(1) AWS CDK

作成しているリソースは、S3のバケットとLambdaファンクションのみです。

import cdk = require('@aws-cdk/core');
import * as lambda  from '@aws-cdk/aws-lambda';
import * as s3  from '@aws-cdk/aws-s3';
import * as iam from '@aws-cdk/aws-iam';

// 識別するためのタグ
const tag = "connect-ex-opetime";
const settingFile = "OperationTime.txt";
// 設定ファイルを保存するバケット名(変更の必要があります)
const bucketName = tag + "setting-bucket";
// Lambdaのタイムゾーン
const timeZone = 'Asia/Tokyo';

export class AmazonConnectExtendedOperationTimeStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    //設定ファイル保存バケット
    const settingBucket = new s3.Bucket(this, tag + '_settingBucket', {
      bucketName: bucketName
    } )

    // 営業時間判定ファンクション
    const opeTimeFunction = new lambda.Function(this, tag + '_opeTimeFunction', {
      functionName: tag + "-opeTimeFunction",
      code: lambda.Code.asset('src/lambda'),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_10_X,
      timeout: cdk.Duration.seconds(3),
      environment: {
          SETTING_BUCKET: settingBucket.bucketName,
          SETTING_FILE: settingFile,
          TZ: timeZone
      }
    });

    // 設定ファイルのRead権限追加
    opeTimeFunction.addToRolePolicy(new iam.PolicyStatement({
      resources: [settingBucket.bucketArn + "/" + settingFile],
      actions: ['s3:GetObject'] }
    ));

    // 出力に設定ファイル用バケット名を表示
    new cdk.CfnOutput(this, "SettingBucket", {
      value: settingBucket.bucketName
    });
    new cdk.CfnOutput(this, "LambdaFunction", {
      value: opeTimeFunction.functionArn
    });
  }
}

(2) Lambda

設定ファイルを読み込んで、現在時間から営業時間内の場合、{ inTime: true }、外の場合、{ inTime: false }を返します。

import AWS = require("aws-sdk");
const s3 = new AWS.S3();
const settingBucket = process.env.SETTING_BUCKET!;
const settingFile = process.env.SETTING_FILE!;

exports.handler = async (event: any) => {
  console.log(JSON.stringify(event));
    // オペレーション時間の取得
    const data = await s3.getObject( {
            Bucket: settingBucket,
            Key: settingFile
        }).promise();

    if (data == undefined || data.Body == undefined) {
        throw new Error("Read error " + settingFile);
    }
    const operationTime = data.Body.toString();
    let lines = operationTime.split('\n');

    // コメント削除及び、余分な空白削除
    lines = lines.map( line => {
      return line.replace(/#.*$/, '').replace(/\s+$/, '');
    });
    // 無効(空白)行の削除
    lines = lines.filter( line => {
      return line != '';
    });

    // 時間内かどうかのチェック
    const inTime = CheckInTime(lines);

    return { inTime: inTime };
  }

  function CheckInTime(lines: string[]) {
    // 現在時間
    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth() + 1;
    const day = now.getDate();
    const week = now.getDay();
    const hour = now.getHours();
    const miniute = now.getMinutes();

    const weekdays = ["日", "月", "火", "水", "木", "金", "土"];

    // 曜日指定の抽出
    const weeks = lines.filter(line => {
      return 0 < weekdays.indexOf(line.split(',')[0]);
    });

    // 祝日指定の抽出
    // (1) yyyy/mm/dd指定抽出
    let holidays = lines.filter(line => {
      return line.split(',')[0].split('/').length == 3;
    });
    // (2) mm/dd指定抽出
    holidays = holidays.concat(lines.filter(line => {
      return line.split(',')[0].split('/').length == 2;
    }).map(date => { return year + '/' + date }));


    // 曜日チェック
    let flg = false; // デフォルトで時間外(設定がない場合時間外となるため)
    weeks.forEach( line => {
      const tmp = line.split(',');
      const w = weekdays.indexOf(tmp[0]);
      if(week == w) { // 当該曜日の設定
        // 始業時間以降かどうかのチェック
        const t = tmp[1].split(':');
        if(t.length == 2) {
          if( Number(t[0]) * 60 + Number(t[1]) <= (hour * 60 + miniute )){
            // 終業時間前かどうかのチェック
            t = tmp[2].split(':');
            if(t.length == 2) {
              if((hour * 60 + miniute ) <= (Number(t[0]) * 60 + Number(t[1]))){
                flg = true;
              }
            }
          }
        }
      }
    });

    // 曜日指定で時間外の場合は、祝日に関係なく時間外となる
    if(!flg){
      return false;
    }

    // 祝日のチェック
    flg = true; // デフォルトで時間内(設定がない場合時間内となるため)
    holidays.forEach( line => {
      const date = line.split('/');
      if(date.length == 3){
        if(year == Number(date[0]) && month == Number(date[1]) && day == Number(date[2])) {
          flg = false;
        }
      }
    })
    return flg;
}

5 最後に

今回は、オペレーション時間の設定を独自の設定ファイルで自由に変更できるような拡張機能を作成してみました。

Connectは、CFnで設定できないため限界がありますが、AWS CDK(Cloud Development Kit)と組み合わせることで、比較的再利用しやすい機能拡張のセットが作れそうな気がします。

コメントは受け付けていません。