はじめに
プリザンターにはテーブルのロック機能があり、テーブル全体のデータ追加・編集・削除を一時的に禁止できます。ロック中のテーブルを開くと画面上部に赤帯で「読取専用です。」と表示されますが、サイト一覧(フォルダビュー)ではどのテーブルがロックされているのかを判別できません。
テーブルの数が多い環境では、どれがロック中なのかを1つずつ開いて確認するのは手間がかかります。今回は拡張SQLと拡張スクリプトを組み合わせて、サイト一覧でロック中のテーブルを赤く表示する方法を紹介します。
テーブルのロック機能について
テーブルのロック機能は、管理メニューから実行することでテーブル全体を読み取り専用にする機能です。ロック中はレコードの新規作成・編集・削除ができなくなります。
テーブルの管理 > エディタで「テーブルのロックを許可」にチェックを入れると、管理メニューに「テーブルをロック」が表示されます。ロック・ロック解除はロックを実行したユーザーまたは特権管理者のみが操作できます。
データベース上では、SitesテーブルのLockedTime列(ロック日時)とLockedUser列(ロックしたユーザーID)にロック情報が保持されます。ただし、プリザンター本体のロック判定は単純なLockedTime IS NOT NULLではありません。実際には、内部のLockedTable()で「ロック日時が有効な範囲にあり、かつロックユーザーが匿名ユーザーではない」ことを見ています。
public bool LockedTable()
{
return LockedTableTime?.Value.InRange() == true
&& LockedTableUser?.Anonymous() == false;
}
この実装のポイントは、ロック解除時にLockedTimeは現在時刻へ更新されたまま残り、LockedUserだけがnullになることです。さらにLockedUserは、内部では現在テナントのユーザーキャッシュからUserオブジェクトに解決され、存在しないユーザーIDは匿名ユーザー扱いになります。
つまり、厳密なロック判定は最終的にプリザンター本体がSiteSettingsを組み立てた上でLockedTable()を評価しないと確定できません。SQLだけで完全に同じ判定を保証することはできず、ここで紹介するSQLはあくまで「標準設定を前提に、実運用で使いやすい形に寄せた抽出条件」です。
課題
サイト一覧ではテーブルやフォルダがパネルとして並んでいますが、見た目からはどのテーブルがロック中かを判断できません。テーブルを1つずつ開いて確認する必要があります。
今回は拡張機能だけでこの課題を解決してみます。
処理の流れ
全体の処理は次のように流れます。
実装
拡張SQLの作成
まずはロック中とみなしたいサイトを実用上十分な精度で抽出する拡張SQLを作成します。App_Data/Parameters/ExtendedSqls/に配置します。
定義ファイル
{
"Name": "GetLockedSites",
"Api": true,
"CommandText": "-- Write an arbitrary SQL statement."
}
ブラウザから/api/extended/sql経由で呼び出すため、Api: trueが必要です。
SQLファイル
SQL Server
SELECT
S.[SiteId]
FROM
[Sites] AS S
WHERE
S.[TenantId] = @_T
AND S.[ParentId] = @SiteId
AND S.[LockedTime] >= '1900-01-01'
AND S.[LockedTime] <= '2100-01-01'
AND S.[LockedUser] <> 2
AND EXISTS (
SELECT 1
FROM [Users] AS U
WHERE U.[TenantId] = S.[TenantId]
AND U.[UserId] = S.[LockedUser]
)
PostgreSQL
SELECT
S."SiteId"
FROM
"Sites" AS S
WHERE
S."TenantId" = @_T
AND S."ParentId" = @SiteId
AND S."LockedTime" >= TIMESTAMP '1900-01-01 00:00:00'
AND S."LockedTime" <= TIMESTAMP '2100-01-01 00:00:00'
AND S."LockedUser" <> 2
AND EXISTS (
SELECT 1
FROM "Users" AS U
WHERE U."TenantId" = S."TenantId"
AND U."UserId" = S."LockedUser"
)
MySQL
SELECT
S.`SiteId`
FROM
`Sites` AS S
WHERE
S.`TenantId` = @_T
AND S.`ParentId` = @SiteId
AND S.`LockedTime` >= '1900-01-01 00:00:00'
AND S.`LockedTime` <= '2100-01-01 00:00:00'
AND S.`LockedUser` <> 2
AND EXISTS (
SELECT 1
FROM `Users` AS U
WHERE U.`TenantId` = S.`TenantId`
AND U.`UserId` = S.`LockedUser`
)
@_Tは拡張SQLの組み込みパラメータで、現在のテナントIDが自動で設定されます。@SiteIdは拡張スクリプトから渡すカスタムパラメータで、現在開いているサイトIDを指定します。これにより、一覧画面で表示中の親サイト配下だけに対象を絞れます。
ここで重要なのは、LockedTime IS NOT NULLだけではロック中を正しく判定できないことです。プリザンターではロック解除時にLockedTimeは残りますし、LockedUserに数値が残っていても、そのユーザーが現在テナントで解決できなければ本体側では匿名扱いになってロック判定から外れます。そこで今回のSQLでは、少なくとも本体実装から大きく外れないように、次の3点を条件に入れています。
-
LockedTimeが本体の有効日時範囲内にあること -
Usersテーブルに該当ユーザーが存在すること -
LockedUserが匿名ユーザーIDではないこと
プリザンター本体はLockedUserの数値をそのまま見ているのではなく、まずその値を現在テナントのユーザーとして解決できるかを確認しています。そのため、SQL側でもUsersテーブルでの存在確認は必要です。
一方で、この用途ではUsers.Disabled = falseまでは不要です。ユーザーキャッシュ自体がDisabled列を含めて読み込まれており、無効ユーザーを除外していないためです。
また、旧環境ではUsersテーブルにAnonymousユーザーが残っていることがあります。この場合、単にUsersに存在するだけでは本体判定とずれてしまいます。そこで今回のSQLでは、標準環境で匿名ユーザーとして扱われるIDである2も除外しています。
整理すると、今回のSQLは次の考え方です。
-
LockedTimeが有効な日時かを見る -
LockedUserが現在のテナントで解決できるユーザーかを見る - そのユーザーが匿名ユーザーではないようにする
これにより、LockedTime IS NOT NULLだけを見る雑な条件よりは本体実装に寄せられます。ただし、これはあくまで標準設定前提の実用近似であり、プリザンター本体のLockedTable()と完全一致する保証まではありません。
1900-01-01から2100-01-01の範囲は、プリザンター標準のGeneral.jsonに基づく既定値です。また、標準実装では匿名ユーザーIDは 2 を元に設定されています。もしこれらを独自に変更している環境では、SQL側の条件も実環境に合わせて見直してください。完全に本体と同じ判定を求める場合は、SQLだけではなくサーバー側のカスタマイズでLockedTable()相当の処理を実行する方法を検討してください。
このSQLで絞れるのは「現在開いているサイト配下の子サイト」です。画面上でさらに検索条件やフィルタをかけている場合、その結果と完全に一致させたいときは、クライアント側で現在の表示対象IDを取得して別途絞り込む必要があります。
拡張スクリプトの作成
次に、拡張SQLの結果をもとに対象の.nav-siteを赤く塗る拡張スクリプトを作成します。App_Data/Parameters/ExtendedScripts/に配置します。拡張スクリプトはJSON定義ファイルなしで.jsファイル1つだけで動作します。
スクリプトファイル
$(function () {
if ($('.nav-site').length === 0) return;
$p.apiExec(
$('#ApplicationPath').val() + 'api/extended/sql',
{
data: {
Name: 'GetLockedSites',
Params: { SiteId: $p.siteId() }
},
done: function (res) {
if (!res || !res.Response || !res.Response.Data) return;
var tables = res.Response.Data;
var tableKey = Object.keys(tables)[0];
if (!tableKey) return;
var rows = tables[tableKey];
if (!rows || rows.length === 0) return;
for (var i = 0; i < rows.length; i++) {
var siteId = rows[i].SiteId || rows[i].siteId;
if (!siteId) continue;
$('.nav-site[data-value="' + siteId + '"]')
.css({
'background': '#ffd0d0',
'box-shadow': 'inset 0 0 0 3px #e53935'
});
}
}
}
);
});
先頭のif ($('.nav-site').length === 0) return;で、.nav-site要素が存在しないページでは即座に処理を終了させています。拡張スクリプトはすべての画面で読み込まれるため、サイト一覧以外のページで不要なAPIコールが発生しないようにしています。
APIのエンドポイントは/api/extended/sqlです。$('#ApplicationPath').val()でアプリケーションのベースパスを取得し、それに連結します。
リクエストボディのNameに拡張SQL定義の名前を指定し、Paramsに任意のSQLパラメータを渡します。$p.siteId()は現在開いているサイトのIDを整数で返し、SQLの@SiteIdとして渡ります。認証トークンは$p.apiExecが自動で付与します。
APIレスポンスの構造は次のとおりです。
{
"StatusCode": 200,
"Response": {
"Data": {
"Table": [
{ "SiteId": 123 },
{ "SiteId": 456 }
]
}
}
}
res.Response.Dataの最初のキー("Table")が結果セットで、行の配列が格納されています。カラム名はSQLのSELECTで指定したそのままの名前です。
処理の内容は次のとおりです。
-
.nav-site要素が存在しない場合は処理を中断 -
$p.apiExecで拡張SQLをAPIから実行し、ロック中のサイトID一覧を取得 - 取得した各サイトIDに対応する
.nav-site[data-value="..."]に対してjQueryで背景色・内側枠線を直接設定
配置ファイルまとめ
作成するファイルと配置先を整理すると、次のようになります。
| ファイル | 配置先 |
|---|---|
GetLockedSites.json |
App_Data/Parameters/ExtendedSqls/ |
GetLockedSites.json.sql |
App_Data/Parameters/ExtendedSqls/ |
LockedSiteIndicator.js |
App_Data/Parameters/ExtendedScripts/ |
ファイルの配置後、プリザンターを再起動することで拡張機能が読み込まれます。
まとめ
拡張SQLと拡張スクリプトを組み合わせて、サイト一覧画面でロック中のテーブルを見つけやすくする方法を紹介しました。今回のSQLはプリザンター本体のロック判定をそのまま実行しているわけではなく、標準設定を前提にした実用的な抽出条件です。それでも、サイトを1件ずつ開いて確認するよりはかなり扱いやすくなります。完全一致が必要な場合は、サーバー側カスタマイズでLockedTable()相当の処理を呼び出す構成を検討するとよいです。