0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プリザンターの拡張サーバスクリプトとバックグラウンドサーバスクリプトで履歴を自動削除してみる

0
Posted at

はじめに

プリザンターはレコードを更新するたびに変更履歴をバージョンとして保持します。バージョン番号(Ver)は更新ごとにインクリメントされるため、Ver数はそのレコードの履歴件数とほぼ一致します。長期運用やインポート・一括更新を繰り返すと履歴が膨れ上がり、データベースサイズの増大や履歴一覧の視認性低下を招くことがあります。

手動で履歴を削除するには、編集画面の「変更履歴の一覧」タブから1件ずつ選択して削除する必要があり、レコード数が多い場合は現実的ではありません。

今回は、拡張サーバスクリプトバックグラウンドサーバスクリプトを併用して、履歴を「バージョン数」と「経過日数」の2つの基準で削除する機能を作ってみます。拡張サーバスクリプトで編集画面にクリーンアップボタンを追加し、バックグラウンドサーバスクリプトでスケジュール実行による自動クリーンアップを実現します。

処理の全体像

手動実行と自動実行の2つの経路がありますが、どちらも同じ流れで処理されます。

前提条件

History.json

履歴の物理削除を有効にするために、App_Data/Parameters/History.jsonPhysicalDeletetrueに設定します。falseの場合は「変更履歴を削除」機能自体が無効化されます。

App_Data/Parameters/History.json
{
    "Restore": true,
    "PhysicalDelete": true
}

Script.json

バックグラウンドサーバスクリプトを有効にするために、App_Data/Parameters/Script.jsonBackgroundServerScripttrueに設定します。

App_Data/Parameters/Script.json
{
    "ServerScript": true,
    "BackgroundServerScript": true
}

APIキー

履歴の取得と削除にはAPIキーが必要です。テナント管理の権限を持つユーザのAPIキーを作成しておきます。

パラメータファイルの変更後はプリザンターの再起動が必要です。パラメータ再読み込み機能を使う場合は、特権ユーザでログインして実行してください。

拡張サーバスクリプト(手動クリーンアップ)

編集画面にクリーンアップボタンを追加し、ボタンをクリックすると対象レコードの履歴を削除する機能を実装します。拡張サーバスクリプトは2つのファイルに分けます。

ボタンの追加(BeforeOpeningPage)

編集画面にクリーンアップボタンとダイアログを追加する拡張サーバスクリプトです。App_Data/Parameters/ExtendedServerScripts/に配置します。

ExtendedServerScripts/HistoryCleanupButton.json
{
    "BeforeOpeningPage": true,
    "Actions": ["edit"],
    "TryCatch": true,
    "Body": "-- loaded from .json.js"
}

Actionseditに限定しているため、編集画面でのみボタンが表示されます。

ExtendedServerScripts/HistoryCleanupButton.json.js
context.AddResponse('Append', '#MainCommands',
    '<button id="historyCleanup" class="button button-icon" type="button" onclick="hc_showDialog()">'
    + '<span class="ui-icon ui-icon-clock"></span>'
    + '<span>履歴クリーンアップ</span>'
    + '</button>'
);

context.AddResponse('Append', 'body',
    '<script>'
    + 'function hc_showDialog() {'
    + '  var dialog = document.createElement("dialog");'
    + '  dialog.style.cssText = "padding:20px;border:1px solid #ccc;border-radius:8px;min-width:320px";'
    + '  dialog.innerHTML = \'<form method="dialog">\''
    + '    + \'<h3 style="margin-top:0">履歴クリーンアップ</h3>\''
    + '    + \'<div style="margin-bottom:12px"><label>保持するバージョン数(0で無制限):</label><br>\''
    + '    + \'<input type="number" id="hc-maxVer" value="10" min="0" style="width:100%;padding:4px"></div>\''
    + '    + \'<div style="margin-bottom:16px"><label>保持する日数(0で無制限):</label><br>\''
    + '    + \'<input type="number" id="hc-maxDays" value="90" min="0" style="width:100%;padding:4px"></div>\''
    + '    + \'<div style="text-align:right">\''
    + '    + \'<button type="button" onclick="this.closest(\\\'dialog\\\').close()" style="margin-right:8px">キャンセル</button>\''
    + '    + \'<button type="submit" value="ok">実行</button></div></form>\';'
    + '  document.body.appendChild(dialog);'
    + '  dialog.showModal();'
    + '  dialog.addEventListener("close", function() {'
    + '    var ok = dialog.returnValue === "ok";'
    + '    var maxVer = dialog.querySelector("#hc-maxVer").value;'
    + '    var maxDays = dialog.querySelector("#hc-maxDays").value;'
    + '    dialog.remove();'
    + '    if (!ok) return;'
    + '    var url = $p.apiUrl($p.getId(), "update")'
    + '      + "?cleanup=1&maxVer=" + maxVer + "&maxDays=" + maxDays;'
    + '    $.ajax({'
    + '      url: url, method: "PUT",'
    + '      contentType: "application/json", data: "{}",'
    + '      success: function() {'
    + '        $p.message("#Message", { Text: "履歴のクリーンアップが完了しました", Css: "alert-success" });'
    + '      },'
    + '      error: function() {'
    + '        $p.message("#Message", { Text: "クリーンアップに失敗しました", Css: "alert-error" });'
    + '      }'
    + '    });'
    + '  });'
    + '}'
    + '\x3c/script>'
);

HTML <dialog> でバージョン数と日数を入力するダイアログを表示し、実行ボタンをクリックすると更新APIにクエリパラメータ ?cleanup=1 を付けて呼び出します。API URLの組み立てには $p.apiUrl() を使用しているため、サブディレクトリ配置でも正しいパスが生成されます。

更新APIを呼び出すため、クリーンアップの実行時に新しいバージョンが1つ作成されます。定期的なクリーンアップには後述のバックグラウンドサーバスクリプトの使用をおすすめします。

クリーンアップ処理(AfterUpdate)

ボタンからの更新APIリクエストを受けて、実際に履歴の取得・判定・削除を行う拡張サーバスクリプトです。

ExtendedServerScripts/HistoryCleanup.json
{
    "AfterUpdate": true,
    "TryCatch": true,
    "Body": "-- loaded from .json.js"
}
ExtendedServerScripts/HistoryCleanup.json.js
// ?cleanup=1 のときだけ処理
if (context.QueryStrings.Data('cleanup') !== '1') return;

// --- 設定 ---
var apiKey = 'YOUR_API_KEY';
var baseUrl = 'http://localhost/';  // プリザンターのURL(末尾/必須)
// --- 設定ここまで ---

var maxVersions = parseInt(context.QueryStrings.Data('maxVer') || '0', 10);
var maxDays = parseInt(context.QueryStrings.Data('maxDays') || '0', 10);

if (maxVersions <= 0 && maxDays <= 0) return;

// 履歴を取得
httpClient.ResponseHeaders.Clear();
httpClient.RequestUri = baseUrl + 'api/items/' + context.Id + '/get';
httpClient.Content = JSON.stringify({
    ApiKey: apiKey,
    TableType: 'History'
});
httpClient.MediaType = 'application/json';
var response = httpClient.Post();

if (!httpClient.IsSuccess) {
    context.Log('履歴取得に失敗: RecordId=' + context.Id);
    return;
}

var result = JSON.parse(response);
var histories = result.Response.Data;

if (!histories || histories.length <= 1) return;

// Ver降順でソート
histories.sort(function (a, b) { return b.Ver - a.Ver; });

// 削除対象のバージョンを判定
var versionsToDelete = [];
var now = new Date();

for (var i = 1; i < histories.length; i++) {
    var h = histories[i];
    var shouldDelete = false;

    // バージョン数による判定
    if (maxVersions > 0 && i >= maxVersions) {
        shouldDelete = true;
    }

    // 経過日数による判定
    if (maxDays > 0) {
        var updatedTime = new Date(h.UpdatedTime);
        var diffDays = (now - updatedTime) / (1000 * 60 * 60 * 24);
        if (diffDays > maxDays) {
            shouldDelete = true;
        }
    }

    if (shouldDelete) {
        versionsToDelete.push(h.Ver);
    }
}

if (versionsToDelete.length === 0) return;

// 履歴を削除
httpClient.ResponseHeaders.Clear();
httpClient.RequestUri = baseUrl + 'api/items/' + context.Id + '/deletehistory';
httpClient.Content = JSON.stringify({
    ApiKey: apiKey,
    Selected: versionsToDelete
});
httpClient.MediaType = 'application/json';
httpClient.Post();

context.Log(
    '履歴クリーンアップ完了: RecordId=' + context.Id
    + ', 削除件数=' + versionsToDelete.length
);

処理の流れは以下のとおりです。

  1. context.QueryStrings.Data('cleanup') でクエリパラメータを確認し、?cleanup=1 でない場合は何もしない
  2. httpClient で対象レコードの履歴を TableType: 'History' で取得する
  3. Ver の降順にソートし、インデックス0(最新バージョン=現在のレコード)をスキップする
  4. 各バージョンについて、バージョン数超過(i >= maxVersions)または経過日数超過(diffDays > maxDays)のいずれかに該当すれば削除対象とする
  5. 削除対象のバージョン番号を配列にまとめて deletehistory で一括削除する

httpClient.ResponseHeaders.Clear() はリクエストの前に必ず呼び出してください。詳しくは「プリザンターのサーバスクリプトでhttpClientを使うときのお約束」を参照してください。

バックグラウンドサーバスクリプト(スケジュール実行)

テナント管理画面からバックグラウンドサーバスクリプトを登録し、スケジュール実行で対象サイトの全レコードを定期的にクリーンアップします。

登録手順

  1. 特権ユーザでログイン
  2. 「テナント管理」→「サーバスクリプト」タブを開く
  3. 「新規作成」をクリック
  4. 以下を設定して「追加」→「更新」
項目 設定値
タイトル 履歴自動クリーンアップ
条件 バックグラウンドサーバスクリプト
スケジュール 毎日 02:00(任意)
無効 チェックなし
サーバスクリプト 下記参照

スクリプト

// --- 設定 ---
var apiKey = 'YOUR_API_KEY';
var baseUrl = 'http://localhost/';    // プリザンターのURL(末尾/必須)
var targetSiteIds = [12345, 67890];   // クリーンアップ対象のサイトID
var maxVersions = 10;                 // 保持するバージョン数(0で無制限)
var maxDays = 90;                     // 保持する日数(0で無制限)
// --- 設定ここまで ---

targetSiteIds.forEach(function (siteId) {
    // サイト内のレコード一覧を取得
    httpClient.ResponseHeaders.Clear();
    httpClient.RequestUri = baseUrl + 'api/items/' + siteId + '/get';
    httpClient.Content = JSON.stringify({ ApiKey: apiKey });
    httpClient.MediaType = 'application/json';
    var response = httpClient.Post();

    if (!httpClient.IsSuccess) {
        context.Log('レコード一覧取得に失敗: SiteId=' + siteId);
        return;
    }

    var result = JSON.parse(response);
    var records = result.Response.Data;

    if (!records || records.length === 0) return;

    var totalDeleted = 0;

    records.forEach(function (record) {
        var recordId = record.ResultId || record.IssueId;
        if (!recordId) return;

        var deleted = cleanupHistory(apiKey, baseUrl, recordId, maxVersions, maxDays);
        totalDeleted += deleted;
    });

    context.Log(
        '履歴クリーンアップ完了: SiteId=' + siteId
        + ', レコード数=' + records.length
        + ', 削除履歴数=' + totalDeleted
    );
});

function cleanupHistory(apiKey, baseUrl, recordId, maxVersions, maxDays) {
    // 履歴を取得
    httpClient.ResponseHeaders.Clear();
    httpClient.RequestUri = baseUrl + 'api/items/' + recordId + '/get';
    httpClient.Content = JSON.stringify({
        ApiKey: apiKey,
        TableType: 'History'
    });
    httpClient.MediaType = 'application/json';
    var response = httpClient.Post();

    if (!httpClient.IsSuccess) return 0;

    var result = JSON.parse(response);
    var histories = result.Response.Data;

    if (!histories || histories.length <= 1) return 0;

    // Ver降順でソート
    histories.sort(function (a, b) { return b.Ver - a.Ver; });

    // 削除対象のバージョンを判定
    var versionsToDelete = [];
    var now = new Date();

    for (var i = 1; i < histories.length; i++) {
        var h = histories[i];
        var shouldDelete = false;

        if (maxVersions > 0 && i >= maxVersions) {
            shouldDelete = true;
        }

        if (maxDays > 0) {
            var updatedTime = new Date(h.UpdatedTime);
            var diffDays = (now - updatedTime) / (1000 * 60 * 60 * 24);
            if (diffDays > maxDays) {
                shouldDelete = true;
            }
        }

        if (shouldDelete) {
            versionsToDelete.push(h.Ver);
        }
    }

    if (versionsToDelete.length === 0) return 0;

    // 履歴を削除
    httpClient.ResponseHeaders.Clear();
    httpClient.RequestUri = baseUrl + 'api/items/' + recordId + '/deletehistory';
    httpClient.Content = JSON.stringify({
        ApiKey: apiKey,
        Selected: versionsToDelete
    });
    httpClient.MediaType = 'application/json';
    httpClient.Post();

    return versionsToDelete.length;
}

スクリプトの解説

スクリプト先頭の設定セクションで、対象サイトIDとクリーンアップ条件を指定します。

設定項目 説明
apiKey テナント管理権限を持つユーザのAPIキー
baseUrl プリザンターのベースURL(サブディレクトリ配置の場合は http://localhost/pleasanter/ のように指定)
targetSiteIds クリーンアップ対象のサイトIDを配列で指定
maxVersions 保持するバージョン数。0で無制限。10を指定すると最新10バージョン以外を削除
maxDays 保持する日数。0で無制限。90を指定すると90日より前の履歴を削除

maxVersionsmaxDays の両方を指定した場合、いずれかの条件に該当した履歴が削除対象になります(OR条件)。最新バージョン(現在のレコード)は常に保持されます。

cleanupHistory 関数がレコード単位のクリーンアップ処理を行います。処理の流れは拡張サーバスクリプト版と同じです。

  1. 対象レコードの履歴を TableType: 'History' で取得
  2. Ver 降順でソートし、最新バージョンをスキップ
  3. バージョン数超過または経過日数超過の履歴を削除対象に追加
  4. deletehistory で一括削除

対象サイトのレコード数が多い場合、APIのデフォルトのレスポンス件数上限に注意してください。上限を超える場合は Offset パラメータでページネーションするか、ApiGetPageSize の設定を確認してください。

レコード数が多い場合の対応

デフォルトではAPIの1回のリクエストで取得できるレコード数に上限があります。対象サイトのレコード数が多い場合は、以下のようにページネーションで全件を取得します。

function getAllRecords(apiKey, baseUrl, siteId) {
    var allRecords = [];
    var offset = 0;
    var pageSize = 200;

    while (true) {
        httpClient.ResponseHeaders.Clear();
        httpClient.RequestUri = baseUrl + 'api/items/' + siteId + '/get';
        httpClient.Content = JSON.stringify({
            ApiKey: apiKey,
            Offset: offset,
            ApiGetPageSize: pageSize
        });
        httpClient.MediaType = 'application/json';
        var response = httpClient.Post();

        if (!httpClient.IsSuccess) break;

        var result = JSON.parse(response);
        var records = result.Response.Data;

        if (!records || records.length === 0) break;

        allRecords = allRecords.concat(records);

        if (records.length < pageSize) break;
        offset += pageSize;
    }

    return allRecords;
}

削除基準の考え方

バージョン数で削除

maxVersions はレコードごとに保持するバージョン数の上限です。履歴を Ver 降順にソートし、先頭(最新バージョン)から数えて maxVersions 番目以降のバージョンを削除します。

例えば maxVersions = 5 でVer 1〜20の履歴がある場合、Ver 20〜16が保持されVer 15〜1が削除されます。

経過日数で削除

maxDays は履歴の保持期間(日数)です。UpdatedTime が現在日時から maxDays 日以上前の履歴を削除します。

例えば maxDays = 90 の場合、90日より前に更新された履歴が削除されます。

両方指定した場合

両方を指定した場合はOR条件で動作します。バージョン数の上限を超えた履歴、または経過日数を超えた履歴のどちらかに該当すれば削除対象になります。

例: maxVersions=10, maxDays=90

Ver 20 (1日前)   → 保持(最新)
Ver 19 (3日前)   → 保持(10件以内)
...
Ver 11 (30日前)  → 保持(10件以内)
Ver 10 (60日前)  → 削除(11件目以降 → バージョン数超過)
Ver  9 (80日前)  → 削除(バージョン数超過)
Ver  8 (95日前)  → 削除(バージョン数超過 + 日数超過)
...
Ver  1 (365日前) → 削除(バージョン数超過 + 日数超過)

まとめ

拡張サーバスクリプトとバックグラウンドサーバスクリプトを併用して、履歴をバージョン数と経過日数で自動削除する機能を作成しました。

  • 拡張サーバスクリプトで編集画面にクリーンアップボタンを追加し、ダイアログで条件を指定して手動実行できるようにした
  • バックグラウンドサーバスクリプトでスケジュール実行(毎日深夜など)による自動クリーンアップを設定した
  • 履歴の取得には TableType: 'History' を指定したAPI呼び出しを使用し、削除には deletehistory APIを使用した
  • 削除判定は Ver 降順ソート後にバージョン数超過または経過日数超過のOR条件で行い、最新バージョンは常に保持する
  • History.jsonPhysicalDelete: trueScript.jsonBackgroundServerScript: true が前提条件
  • レコード数が多い場合は Offset パラメータによるページネーションで対応できる
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?