LoginSignup
16
1

PleasanterのAPI関連メソッドを使いやすくしたい。

Last updated at Posted at 2023-12-04

前置き

Pleasanterには、さまざまAPIが用意されており、単純なAPIだけではなく、内部スクリプトとして$p.apiGetや、items.Getなど様々なメソッドが用意されています。

ただ、公式マニュアルを読んだだけで、実装するのはなかなかハードルが高いと感じます。

そこで、マニュアルを見ずとも各種APIを利用できるよう、独自にスクリプトを組んでみました。

実装の前提

typescriptで実装することでパラメータの型を管理し、扱いやすく・誤りが少なくなるように。また、クラスを活用して、使いまわすことも想定します。(サーバスクリプト等)

実装するテーブルイメージ

AテーブルからBテーブルの情報を、$p.apiGetで取得することを考えます。

Aテーブル/スクリプトでデータを取得するテーブル

特に項目は設定せず、プロセスでスクリプトを実行できるようにします。(「実行」ボタン)

image.png

Onclickに、実装するスクリプトの関数を指定します。

image.png

Bテーブル/データ取得対象のテーブル

確認用にいろいろ項目を並べます。

image.png

結果イメージ

コンソールに表示するだけにします。
image.png

実装スクリプト

ApiGetを実装。

スクリプト-①

リクエストパラメータをenum、interfaceで管理します。

enum ApiDataType {
    KeyValues = "KeyValues",
}

enum ApiColumnKeyDisplayType {
    LabelText = "LabelText",
    ColumnName = "ColumnName",
}

enum ApiColumnValueDisplayType {
    DisplayValue = "DisplayValue",
    Value = "Value",
    Text = "Text",
}

interface ViewOptions {
    Incomplete?: boolean | null;
    Own?: boolean | null;
    NearCompletionTime?: boolean | null;
    Delay?: boolean | null;
    Overdue?: boolean | null;
    Search?: string | null;
    ColumnFilterHash?: Map<string, string> | null;
    ColumnFilterSearchTypes?: Map<string, string> | null;
    ColumnFilterNegatives?: string[] | null;
    ColumnSorterHash?: Map<string, string> | null;
    ApiDataType?: ApiDataType | null;
    ApiColumnKeyDisplayType?: ApiColumnKeyDisplayType | null;
    ApiColumnValueDisplayType?: ApiColumnValueDisplayType | null;
    ApiColumnHash?: Map<string, object> | null;
    GridColumns?: string[] | null;
    MergeSessionViewFilters?: boolean | null;
    MergeSessionViewSorters?: boolean | null;
}

スクリプト-②

リクエストを生成するための、Api・Viewクラス。

パラメータは仕様に従い型を定義。特にKeyValueで指定するものは、Map型を使用。パラメータ設定用のメソッドも用意します。

インスタンス生成時はNullで生成し必要なパラメータを適宜設定します。

class Api {
    constructor(
        public ApiVersion: number | null = 1.1,
        public ApiKey: string | null = null
    ) {}
}

class View extends Api {
    Incomplete: boolean | null;
    Own: boolean | null;
    NearCompletionTime: boolean | null;
    Delay: boolean | null;
    Overdue: boolean | null;
    Search: string | null;
    ColumnFilterHash: Map<string, string> | null;
    ColumnFilterSearchTypes: Map<string, string> | null;
    ColumnFilterNegatives: string[] | null;
    ColumnSorterHash: Map<string, string> | null;
    ApiDataType: ApiDataType | null;
    ApiColumnKeyDisplayType: ApiColumnKeyDisplayType | null;
    ApiColumnValueDisplayType: ApiColumnValueDisplayType | null;
    ApiColumnHash: Map<string, object> | null;
    GridColumns: string[] | null;
    MergeSessionViewFilters: boolean | null;
    MergeSessionViewSorters: boolean | null;

    constructor(options: ViewOptions) {
        super();
        this.Incomplete = options.Incomplete || null;
        this.Own = options.Own || null;
        this.NearCompletionTime = options.NearCompletionTime || null;
        this.Delay = options.Delay || null;
        this.Overdue = options.Overdue || null;
        this.Search = options.Search || null;
        this.ColumnFilterHash = options.ColumnFilterHash || null;
        this.ColumnFilterSearchTypes = options.ColumnFilterSearchTypes || null;
        this.ColumnFilterNegatives = options.ColumnFilterNegatives || null;
        this.ColumnSorterHash = options.ColumnSorterHash || null;
        this.ApiDataType = options.ApiDataType || null;
        this.ApiColumnKeyDisplayType = options.ApiColumnKeyDisplayType || null;
        this.ApiColumnValueDisplayType = options.ApiColumnValueDisplayType || null;
        this.ApiColumnHash = options.ApiColumnHash || null;
        this.GridColumns = options.GridColumns || null;
        this.MergeSessionViewFilters = options.MergeSessionViewFilters || null;
        this.MergeSessionViewSorters = options.MergeSessionViewSorters || null;
    }

    setColumnFilterHash({ key, value }: { key: string; value: string }) {
        if (this.ColumnFilterHash === null) {
            this.ColumnFilterHash = new Map();
        }
        this.ColumnFilterHash.set(key, value);
    }

    setColumnFilterSearchTypes({ key, value }: { key: string; value: string }) {
        if (this.ColumnFilterSearchTypes === null) {
            this.ColumnFilterSearchTypes = new Map();
        }
        this.ColumnFilterSearchTypes.set(key, value);
    }

    setColumnSorterHash({ key, value }: { key: string; value: string }) {
        if (this.ColumnSorterHash === null) {
            this.ColumnSorterHash = new Map();
        }
        this.ColumnSorterHash.set(key, value);
    }

    setColumnFilterNegatives({ value }: { value: string }) {
        if (this.ColumnFilterNegatives === null) {
            this.ColumnFilterNegatives = new Array();
        }
        this.ColumnFilterNegatives.push(value);
    }

    setApiColumnHash({ key, value }: { key: string; value: object }) {
        if (this.ApiColumnHash === null) {
            this.ApiColumnHash = new Map();
        }
        this.ApiColumnHash.set(key, value);
    }

    setGridColumns({ value }: { value: string }) {
        if (this.GridColumns === null) {
            this.GridColumns = new Array();
        }
        this.GridColumns.push(value);
    }

    setGridColumnsByArray({ value }: { value: string[] }) {
        if (this.GridColumns === null) {
            this.GridColumns = new Array();
        }
        this.GridColumns.push(...value);
    }
}

スクリプト-③

Apiリクエストを発行するクラス。まずは、Viewクラスを受け取り、Null以外のパラメータでリクエストを生成し、$p.apiGetを実行。結果を返却します。

class PleasanterApiClient {
    static async apiGetByScript({ id, view }: { id: number; view: View }) {
        let res: any;

        //Pleasnaterのメソッドをtypescriptが解釈できずエラーとなるため
        //エラーチェックをスキップする
        /*@ts-ignore*/
        await $p.apiGet({
            id: id,
            data: PleasanterApiClient.setGetRequest({ view: view }),
            done: function (data: any) {
                res = data;
            },
        });
        return res;
    }

    static setGetRequest({ view }: { view: View }) {
        let data: any = {};

        data.View = {};
        if (view.Incomplete != null) {
            data.View.Incomplete = view.Incomplete;
        }
        if (view.Own != null) {
            data.View.Own = view.Own;
        }
        if (view.NearCompletionTime != null) {
            data.View.NearCompletionTime = view.NearCompletionTime;
        }
        if (view.Delay != null) {
            data.View.Delay = view.Delay;
        }
        if (view.Overdue != null) {
            data.View.Overdue = view.Overdue;
        }
        if (view.Search != null) {
            data.View.Search = view.Search;
        }
        if (view.ColumnFilterHash != null) {
            let columnFilterHash: any = {};
            view.ColumnFilterHash.forEach((value, key) => {
                columnFilterHash[key] = value;
            });
            data.View.ColumnFilterHash = columnFilterHash;
        }
        if (view.ColumnFilterSearchTypes != null) {
            let columnFilterSearchTypes: any = {};
            view.ColumnFilterSearchTypes.forEach((value, key) => {
                columnFilterSearchTypes[key] = value;
            });
            data.View.ColumnFilterHash = columnFilterSearchTypes;
        }
        if (view.ColumnFilterNegatives != null) {
            data.View.ColumnFilterNegatives = view.ColumnFilterNegatives;
        }
        if (view.ColumnSorterHash != null) {
            let columnSorterHash: any = {};
            view.ColumnSorterHash.forEach((value, key) => {
                columnSorterHash[key] = value;
            });
            data.View.ColumnSorterHash = columnSorterHash;
        }
        if (view.ApiDataType != null) {
            data.View.ApiDataType = view.ApiDataType;
        }
        if (view.ApiColumnKeyDisplayType != null) {
            data.View.ApiColumnKeyDisplayType = view.ApiColumnKeyDisplayType;
        }
        if (view.ApiColumnValueDisplayType != null) {
            data.View.ApiColumnValueDisplayType =
                view.ApiColumnValueDisplayType;
        }
        if (view.ApiColumnHash != null) {
            data.View.ApiColumnHash = view.ApiColumnHash;
        }
        if (view.GridColumns != null) {
            data.View.GridColumns = view.GridColumns;
        }
        if (view.MergeSessionViewFilters != null) {
            data.View.MergeSessionViewFilters = view.MergeSessionViewFilters;
        }
        if (view.MergeSessionViewSorters != null) {
            data.View.MergeSessionViewSorters = view.MergeSessionViewSorters;
        }
        return data;
    }
}

スクリプト-④

Pleasanterのプロセスから呼び出される関数。
Viewクラスを生成し、必要なパラメータを設定。その後Apiを実行し結果をコンソールに表示します。

const getRecordsScript = async () => {
    let siteId: number = 11114111;
    let view = new View({});

    let gridColumnHash = [
        "IssueId",
        "ClassA",
        "ClassB",
        "DateA",
        "DateC",
        "NumB",
        "Status",
    ];
    view.setGridColumnsByArray({ value: gridColumnHash });

    let filterStatus = ["300"];
    view.setColumnFilterHash({
        key: "Status",
        value: JSON.stringify(filterStatus),
    });

    view.ApiDataType = ApiDataType.KeyValues;
    view.ApiColumnKeyDisplayType = ApiColumnKeyDisplayType.ColumnName;
    let res = await PleasanterApiClient.apiGetByScript({
        id: siteId,
        view: view,
    });
    console.log(res);
};

補足
コンパイル後のjavascriptをPleasanterに設定します。
image.png

実装してみて

VSCodeで入力候補が出てくるのが、とてもコーディングしやすい。バグも見つけやすい、はず。
image.png

課題など

  • この方式で他に使いまわせるか、いろいろ検証してみたい。(Update、Create、サーバスクリプト、外部からのAPIなどなど)
  • リクエストパラメータなどは、とりあえずpublicとしたが、制限をかけたほうが良いかもしれない(整理できていない)。
  • コンストラクタ回り、Apiリクエスト生成回りの処理をもっと簡略化できないか?
16
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
1