はじめに
プリザンターの 1.5.8.0 がリリースされました。前回の「プリザンター1.5.7.0の追加機能を詳しくみてみる」では「クラウド運用の足回りが揃った回」と書きましたが、今回は方向性がはっきり変わっています。外部の ID 基盤と AI 基盤にプリザンターをつなぐ回です。
新規追加ファイルは 140 本以上、差分は 270 ファイル・約 3.4 万行の追加という大きめの回になっています。この記事ではソースコードを追いかけながら、1.5.8.0 で何が増えたのかを機能単位で整理してみます。
バージョン 1.5.8.0 を対象にしています
追加機能の全体像
1.5.7.1 と 1.5.8.0 のソースを比較すると、追加された機能は大きく 8 つに分類できます。
| 分類 | 追加された主なもの | ひとことで言うと | ライセンス |
|---|---|---|---|
| SCIM 同期 |
scim/v2 系エンドポイント |
IdP からユーザ・グループを自動同期 | 必要 |
| AI 連携(RAG) |
AiProviders サイト設定 |
レコードを Markdown 化して外部の知識ベースへ送る | 不要(キュー利用時は必要) |
| クロス集計ドリルダウン | crosstabdrilldown.js |
集計セルから絞り込み済みの一覧へ遷移 | 不要 |
| 計算式(拡張) |
$LOGINUSERID() / $LOGINUSERNAME()
|
計算式でログインユーザを参照できる | 不要 |
| 拡張項目の操作 |
users.GetList() ほか |
ユーザ・組織・グループの拡張項目を取得・編集 | 不要 |
| キューイング | BackgroundJobTargetTenants |
バックグラウンドジョブがマルチテナントに対応 | 必要(Queue) |
| 型定義ファイル |
pleasanter-script.d.ts ほか |
スクリプト・サーバスクリプトに TypeScript 型が付いた | 不要 |
| UI テーマ列 |
Tenants.UiType ほか |
テーマ設定用の列だけが先行して追加 | 不要 |
「ライセンス」列が「必要」の機能は、上位ライセンスを当てていない環境では動きません。設定ファイルで有効にしても無視されるか、API が 404 を返します。各機能の節で判定箇所を示します。
ライセンス判定の実装そのものは、別記事「プリザンターのライセンス判定の実装を詳しくみてみる」にまとめています。
前回が「複数インスタンスで動かす」だったのに対し、今回は 「社内の他システムとつなぐ」 方向です。SCIM は ID 基盤、RAG は AI 基盤、どちらも外向きの連携機能です。
この記事では 1.5.7.1 との差分だけを扱います。バックグラウンドジョブのキュー機構そのものは 1.5.7.0 で入った機能なので、1.5.8.0 で変わった部分だけを取り上げます。
SCIM によるユーザ・グループ同期
いちばん大きな追加です。SCIM(System for Cross-domain Identity Management)は ID 基盤側からアプリケーションのユーザとグループを自動でプロビジョニングする標準プロトコルで、Entra ID や Okta が対応しています。これまでプリザンターは SAML でのシングルサインオンには対応していましたが、ユーザの棚卸しは手作業か API での自作同期でした。1.5.8.0 でその部分が標準化されます。
追加されたエンドポイント
Implem.Pleasanter/Controllers/Scim/ に 4 つのコントローラが新設されました。ルートは SCIM 2.0 の規約どおり scim/v2 配下です。
| エンドポイント | メソッド | 用途 |
|---|---|---|
scim/v2/Users |
GET / POST | ユーザの一覧取得・作成 |
scim/v2/Users/{id} |
GET / PUT / PATCH / DELETE | ユーザの取得・置換・部分更新・削除 |
scim/v2/Groups |
GET / POST | グループの一覧取得・作成 |
scim/v2/Groups/{id} |
GET / PUT / PATCH / DELETE | グループの取得・置換・部分更新・削除 |
scim/v2/ServiceProviderConfig |
GET | サーバの対応機能の申告 |
scim/v2/ResourceTypes |
GET | リソース種別の一覧 |
scim/v2/Schemas |
GET | スキーマ定義の一覧 |
PUT による全置換と PATCH による部分更新の両方が実装されているのがポイントです。ScimDirectoryService.Patch.cs が 446 行あり、SCIM の PATCH で使う add / replace / remove の各操作と、members[value eq "123"] のようなフィルタ付きパス指定に対応しています。
有効化とライセンス判定
有効・無効の判定は ScimFeatureUtilities にまとまっています。
internal static bool Enabled() => Parameters.Scim?.Enabled == true
&& Parameters.CommercialLicense() && Parameters.HasScim();
internal static bool SwaggerEnabled() => Enabled()
&& Parameters.Scim?.SwaggerEnabled == true;
設定ファイルの Enabled だけでは動きません。 商用ライセンスであること、かつライセンスに Scim オプションが含まれていることが必要です。設定ファイルは新設の Scim.json です。
{
"Enabled": false,
"SwaggerEnabled": false,
"ExtendedAttributes": {
"Users": [],
"Groups": []
}
}
条件を満たさないと、リクエストは 404 で返されます。403 や 401 ではなく 404 なのは、機能が無効な環境では「エンドポイントが存在しない」ように振る舞うという設計です。
if (!ScimFeatureUtilities.Enabled())
{
filterContext.HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
filterContext.Result = ScimResponse.Error(
statusCode: StatusCodes.Status404NotFound,
detail: "SCIM is disabled");
return;
}
認証は専用トークン
既存の API キーとは別に、SCIM 専用のトークンが導入されました。ScimTokens テーブルが新設され、次の列を持ちます。
| 列 | 用途 |
|---|---|
TokenHash |
トークンの SHA-256 ハッシュ(平文は保存しない) |
TokenPrefix |
画面表示用の先頭部分 |
ExpiresTime |
有効期限 |
LastUsedTime |
最終使用時刻 |
Disabled |
無効化フラグ |
発行時の処理を見ると、平文のトークンは DB に入りません。
var token = GenerateToken();
...
TokenHash = Convert.ToHexStringLower(
inArray: System.Security.Cryptography.SHA256.HashData(
...)),
TokenPrefix = token.Length <= 24
ScimTokenOneTimeMessage という表示定義が追加されていることからも分かるとおり、平文が見えるのは発行直後の一度だけです。検証側は受け取った Bearer トークンをハッシュ化して突き合わせます。
var tokenHash = Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(token)));
var tokens = new ScimTokenCollection(
context: context,
...
where: Rds.ScimTokensWhere()
.Disabled(false)
.TokenHash(tokenHash));
var model = tokens.FirstOrDefault(tokenModel => !TokenExpired(tokenModel.ExpiresTime));
LastUsedTimeUpdateIntervalSeconds = 60 という定数があり、最終使用時刻の更新は 60 秒に 1 回までに間引かれます。プロビジョニングは短時間に大量のリクエストが来るので、毎回 UPDATE を投げない配慮です。
認証の前に IP 制限(Parameters.Security.AllowIpAddresses)のチェックも入るので、IdP の送信元 IP を絞る運用もできます。
同期状態を持つ列が増えた
Users と Groups に SCIM 用の列が 3 つずつ追加されました。
| 列 | 用途 |
|---|---|
ScimId |
SCIM 側のリソース ID |
ScimExternalId |
IdP 側の externalId
|
ScimSync |
SCIM 経由で作られた/管理されているかのフラグ |
ScimSync があることで、画面から手で作ったユーザと IdP から降ってきたユーザを区別できます。同期対象のユーザを画面側で勝手に書き換えられないようにする、といった制御の土台になっています。
拡張項目へのマッピング
プリザンターのユーザには ClassA NumA などの拡張項目があります。SCIM のスキーマ拡張としてこれらを公開する設定が用意されました。Scim.json の ExtendedAttributes に、SCIM 側の属性名とプリザンターの列名の対応を書きます。
{
"Enabled": true,
"ExtendedAttributes": {
"Users": [
{
"Schema": "urn:ietf:params:scim:schemas:extension:pleasanter:2.0:User",
"Name": "employeeNumber",
"ColumnName": "ClassA"
}
],
"Groups": []
}
}
属性名と列名にはバリデーションが掛かっています。
private static readonly Regex AttributeNameRegex = new(
pattern: @"^[A-Za-z][A-Za-z0-9_-]*$",
options: RegexOptions.Compiled);
private static readonly Regex ColumnNameRegex = new(
pattern: @"^[A-Za-z0-9]+$",
options: RegexOptions.Compiled);
Schema を省略した場合は既定のスキーマ URN が使われます。ここで宣言した属性は scim/v2/Schemas のレスポンスにも反映されるので、IdP 側の属性マッピング画面にそのまま出てくるという流れです。
Swagger UI も付いてくる
SwaggerEnabled を true にすると、SCIM API の OpenAPI ドキュメントと Swagger UI が公開されます。
const string scimSwaggerJsonPath = "/scim/swagger/v1/swagger.json";
...
app.UseSwaggerUI(options =>
{
options.RoutePrefix = "scim/swagger";
options.SwaggerEndpoint("v1/swagger.json", "Pleasanter SCIM API v1");
options.DocumentTitle = "Pleasanter SCIM API";
options.DisplayRequestDuration();
ScimOpenApiDocument.cs が 564 行かけてドキュメントを組み立てています。IdP 側の設定を作る前に /scim/swagger で疎通確認できるので、導入時の切り分けが楽になりそうです。
SwaggerEnabled は API の構造を公開する設定です。本番では無効のままにして、検証環境でだけ有効にする運用をおすすめします。
AI 連携(RAG)
もう一つの大きな追加です。レコードを Markdown に整形して外部の知識ベースへ送り込む機能で、Implem.Pleasanter/Libraries/AiConnect/ に 18 ファイルが新設されました。
プロバイダは 3 種類
送信先は 3 つから選べます。
public static class Types
{
public const string MarkdownFile = "MarkdownFile";
public const string Dify = "Dify";
public const string OpenAi = "OpenAi";
}
public const string DefaultProviderType = Types.MarkdownFile;
MarkdownFile はローカルのファイルに書き出すだけのプロバイダです。既定値がこれになっているのは、まずファイルで中身を確認してから外部送信に切り替えるという導線を想定しているのでしょう。
接続設定はプロバイダごとに必要なキーが決まっています。
case AiProvider.Types.Dify:
return new List<ConnectionSettingKeyDefinition>
{
new() { Key = "Endpoint" },
new() { Key = "DatasetId" },
new() { Key = "ApiKey", Secret = true }
};
case AiProvider.Types.OpenAi:
return new List<ConnectionSettingKeyDefinition>
{
new() { Key = "Endpoint" },
new() { Key = "VectorStoreId" },
new() { Key = "ApiKey", Secret = true }
};
Secret = true が付いたキーは画面表示時にマスクされます(ConnectionSettingMask = "********")。編集時にマスクされた値が送られてきたら、保存済みの値をそのまま使う ResolveConnectionSetting という処理も入っています。画面を開いて保存し直しただけで API キーが消える、という事故を防ぐ仕組みです。
設定はサイトごと
AiProviders は SiteSettings に追加されているので、テーブルの管理から設定するサイト単位の設定です。加えて AiProvidersAllDisabled という一括停止フラグがあります。
public SettingList<AiProvider> AiProviders;
public bool? AiProvidersAllDisabled;
サイト単位なので、同期したいテーブルだけを選んで有効化できます。全社のレコードを丸ごと AI に流すのではなく、ナレッジ寄りのテーブルだけを対象にする、といった使い分けができます。
送信のトリガー
AiConnectUtilities.Sync() の呼び出し元を追うと、期限付きテーブルと記録テーブルの作成・更新・一括更新の 6 箇所です。
Implem.Pleasanter/Models/Issues/IssueModel.cs
Implem.Pleasanter/Models/Issues/IssueUtilities.cs
Implem.Pleasanter/Models/Results/ResultModel.cs
Implem.Pleasanter/Models/Results/ResultUtilities.cs
Sync() は 3 段階でガードしてから動きます。
if (Parameters.AiConnect?.Rag.Enabled != true)
{
return;
}
if (ss?.AiProviders?.Any() != true)
{
return;
}
if (ss.AiProvidersAllDisabled == true)
{
return;
}
パラメータで全体を止められ、サイト設定で個別に止められ、一括フラグでまとめて止められる、という三重構造です。
キューが使えるなら非同期、なければ即時
送信はキューがあるかどうかで挙動が変わります。
public static bool ShouldQueue()
{
return BackgroundJobQueue.BackgroundQueueEnabled()
&& Parameters.BackgroundJobs?.OutputFilePath.IsNullOrEmpty() == false;
}
キューが使える環境ではジョブとして積まれ、レコードの保存はすぐ返ります。使えない環境では RunImmediate で同期的に送信されます。ただし即時実行にも SemaphoreSlim による同時実行制御と、レコード単位のチェーン化(immediateOperationChains)が入っていて、同じレコードへの連続更新が並行して送信されないようになっています。
バックグラウンドキューは Queue ライセンスオプションが必要です。ライセンスが無い環境では即時送信になるため、レコードの保存が外部 API のレスポンス待ちで遅くなります。本番で RAG を使うならキューの利用を前提にしたほうがよさそうです。
送信するのは Markdown
送信内容は Rag.FormatTemplate で決まります。既定値がそのままテンプレートの見本になっています。
"FormatTemplate": [
"# [Title]",
"",
"| 項目 | 値 |",
"|------|----|",
"| 状況 | [Status] |",
"| 管理者 | [Manager] |",
"| 担当者 | [Owner] |",
"| 作成者 | [Creator] |",
"| 更新者 | [Updator] |",
"| 更新日時 | [UpdatedTime] |",
"",
"## 内容",
"",
"[Body]",
"",
"## コメント",
"",
"[Comments]",
"",
"---",
"",
"URL: {Url}",
""
]
[Title] のような角括弧はプリザンターの項目置換、{Url} はレコードの URL です。通知メールの本文テンプレートと同じ書き方なので、覚え直すことはありません。
ファイル名は決まった規則で生成されます。
return FileNamePrefix(
context: context,
ss: ss,
aiProvider: aiProvider)
+ $"{ReferenceTypeName(referenceType: referenceType)}-{referenceId}.md";
issue-123.md result-456.md のような形になるので、送信先で同じレコードを上書きできるという設計です。
リトライとインデックス確認
ジョブハンドラは 4 種類あります。
| ハンドラ | 役割 |
|---|---|
AiConnectSyncJobHandler |
送信 |
AiConnectIndexCheckJobHandler |
送信先でのインデックス完了確認 |
AiConnectResyncJobHandler |
サイト全体の再同期 |
AiConnectDeleteJobHandler |
削除 |
面白いのは AiConnectIndexCheckJobHandler です。Dify や OpenAI のベクトルストアは、ファイルを投げてから検索可能になるまで時間が掛かります。そこで送信後に別ジョブでインデックス状態をポーリングするという作りになっています。
"SyncRetryCount": 2,
"IndexCheckMaxAttempts": 10,
"DeleteRetryCount": 2,
"RetryAfterMaxSeconds": 60
RetryAfterMaxSeconds があるとおり、送信先が返した Retry-After ヘッダも見ています。ヘッダが無ければ 5 秒、あってもこの上限(既定 60 秒)で頭打ちにする作りです。
private const int DefaultRetryAfterSeconds = 5;
private const int DefaultRetryAfterMaxSeconds = 60;
...
var seconds = response.RetryAfterSeconds ?? DefaultRetryAfterSeconds;
レート制限に当たったら送信先の指示どおり待つ、ただし言われるままに何時間も待たされはしない、という真っ当な実装です。
エンドポイントには制限も掛かります。
public static bool AllowedEndpoint(string endpoint)
Parameters.AiConnect.ProviderEndpoints に許可するエンドポイントを列挙しておく方式で、AllowInsecureLoopbackEndpoint を明示的に立てない限り localhost への平文通信も弾かれます。サイト管理者が接続設定を触れる機能なので、送信先をパラメータ側で縛れるという構造です。
クロス集計からのドリルダウン
クロス集計のセルをクリックすると、そのセルの条件で絞り込んだ一覧に飛べるようになりました。地味ですが、日常的にいちばん効く追加かもしれません。
サーバ側は絞り込み用のビューを組み立てて hidden に埋めています。
private static View DrillDownView(
Context context,
SiteSettings ss,
View view,
Column groupByX,
Column groupByY)
{
var drillDownView = view.GetRecordingData(
context: context,
ss: ss);
new[] { groupByX, groupByY }
.Where(o => o != null)
.ForEach(o => drillDownView.AddColumnFilterSearchTypes(
columnName: o.ColumnName,
searchType: Column.SearchTypes.ExactMatch));
drillDownView.CrosstabGroupByX = null;
drillDownView.CrosstabGroupByY = null;
...
return drillDownView;
}
X 軸と Y 軸の列に完全一致の検索方法を設定し、クロス集計用の設定項目を null に落として一覧用のビューに変換しています。部分一致だと「東京」で「東京都」まで拾ってしまうので、ここは完全一致でなければ数が合いません。
クライアント側は絞り込み条件を足して遷移します。
$p.crosstabDrillDown = function ($control) {
var url = $('#CrosstabDrillDownUrl').val();
var json = $('#CrosstabDrillDownView').val();
if (!url || !json) {
return;
}
var view = JSON.parse(json);
view.ColumnFilterHash = view.ColumnFilterHash || {};
addFilter($control.attr('data-crosstab-x-column'), $control.attr('data-crosstab-x-filter'));
addFilter($control.attr('data-crosstab-y-column'), $control.attr('data-crosstab-y-filter'));
「戻る」への配慮も入っています。ドリルダウンの絞り込みはセッションに保存されるため、素朴に戻ると絞り込み済みのクロス集計が表示されてしまいます。そこで遷移前のビューを sessionStorage に退避しておき、戻り先の URL を書き換えています。
//ドリルダウンで遷移してきた場合に、戻る先を絞り込み前のクロス集計にする。
$p.setCrosstabDrillDownReturn = function () {
var $backUrl = $('#BackUrl');
キーボード操作にも対応していて、Enter と Space でもドリルダウンできます。集計値が 0 のセル(hasRecords が false)にはリンクの属性が付かないので、空のセルをクリックして空の一覧に飛ぶことはありません。
計算式でログインユーザを参照できる
計算式(拡張)に 2 つの関数が増えました。
| 関数 | 戻り値 |
|---|---|
$LOGINUSERID() |
ログインユーザの ID |
$LOGINUSERNAME() |
ログインユーザの名前 |
実装は素朴で、スクリプトを組み立てる時点で値を埋め込んでいます。
private static string GetLoginUserIdScript(Context context)
{
return @"
function $LOGINUSERID()
{
return " + context.UserId + @";
}";
}
private static string GetLoginUserNameScript(Context context)
{
return @"
function $LOGINUSERNAME()
{
return " + SiteInfo.UserName(
context: context,
userId: context.UserId,
notSet: false).ToJson() + @";
}";
}
既存の $NOW() や $DATETIME() と同じ仕組みです。大文字小文字を問わず書けるよう、$loginuserid( を $LOGINUSERID( に置換する処理も追加されています。
値は計算式が実行された時点のログインユーザです。バックグラウンドサーバスクリプトや API 経由の更新では、その実行主体のユーザになります。「レコードを最後に触った人」を残す用途なら、既存の Updator 項目を使うほうが確実です。
ユーザ・組織・グループの拡張項目を扱えるようになった
サーバスクリプトの users depts groups オブジェクトに、取得・作成・更新のメソッドが揃いました。
| オブジェクト | 1.5.7.1 まで | 1.5.8.0 |
|---|---|---|
users |
Get |
Get / GetList / Create / Update
|
depts |
Get |
Get / Create / Update
|
groups |
Get / Update
|
Get / Create / Update
|
さらに、取得したオブジェクトから拡張項目が読めるようになりました。従来はキャッシュ(SiteInfo.User)から取っていたところが、拡張項目付きの取得に切り替わっています。
var user = User.GetWithExtras(
context: Context,
tenantId: Context.TenantId,
userId: id.ToInt());
拡張項目は API と同じ ClassHash NumHash のような形にまとめられて渡されます。
foreach (var kv in hashes)
{
mapped[kv.Key + "Hash"] = kv.Value;
}
Def.GetExtendedColumnDefinitions() が ConcurrentDictionary でキャッシュされているので、レコードごとに定義を走査し直すことはありません。
これで 「ユーザの拡張項目に部署コードを持たせて、サーバスクリプトから参照する」 という運用がまともにできます。これまでは拡張 SQL で直接引くしかなかった部分です。
キューイングがマルチテナントに対応
1.5.7.0 で入ったバックグラウンドジョブのキューは、単一テナント前提の作りでした。1.5.8.0 で BackgroundJobTargetTenants が新設され、対象テナントの決定とワーカー数の算出がここに集約されています。
public static bool ProcessingEnabled =>
s_timerEnabled
&& s_queueEnabled
&& (s_multiTenant || s_defaultTenantId > 0);
public static int ScopedTenantId => s_multiTenant ? 0 : s_defaultTenantId;
マルチテナントライセンスがあれば全テナントを対象にし、無ければ MultiTenant.DefaultTenantId のテナントだけを処理します。DefaultTenantId が未設定のまま単一テナントライセンスで動かすと、ジョブが 1 件も処理されません。この状態は起動時に警告としてシステムログに出ます。
message: $"BackgroundJobs are not processed:"
+ $" MultiTenant is unavailable and the default tenant is invalid"
+ $" (must be 1 or greater)."
+ $" MultiTenant.DefaultTenantId={s_defaultTenantId}.",
sysLogType: SysLogModel.SysLogTypes.Warning);
ワーカー数の扱いも整理されました。
s_workerCountIgnored = s_multiTenant == false && verified >= 2;
s_effectiveWorkerCount = s_multiTenant
? verified
: 1;
単一テナントライセンスでは WorkerCount を何に設定しても 1 に固定されます。無視されたことはログに出ますが、設定値が効いていると思い込んでいると気付きにくいところです。
上限は Quartz のスレッドプールサイズです。クラスタリングが無効なら 10 固定で、設定では上げられません。
private static int ResolveWorkerCountLimit()
{
var clustering = Parameters.Quartz?.Clustering;
if (clustering?.Enabled != true)
{
return DefaultThreadPoolSize;
}
return clustering.MaxConcurrency > 0
? clustering.MaxConcurrency
: DefaultThreadPoolSize;
}
さらに、ワーカーと他のタイマーがスレッドプールを共有していて発火が遅れそうな場合にも警告が出ます(WriteThreadPoolSharingNotice)。キューを本格的に使うならクラスタリングを有効にして MaxConcurrency を上げる、という指針が実装から読み取れます。
TypeScript の型定義ファイルが入った
スクリプトとサーバスクリプト向けの型定義が追加されました。
Implem.PleasanterFrontend/wwwroot/src/types/pleasanter-script.d.ts (680 行)
Implem.PleasanterFrontend/wwwroot/src/types/pleasanter-server-script.d.ts(2084 行)
中身は JSDoc 付きの日本語コメントとマニュアルへのリンクが揃っていて、そのまま補完に出せる品質です。
/** 現在の要求、ユーザ、サイト、メッセージ、レスポンスに関する情報を扱います。 */
const context: PleasanterServerScriptContext;
/** コンソールおよびシステムログを出力します。 */
const logs: PleasanterServerScriptLogs;
/** 現在のサイト設定を参照または変更します。 */
const siteSettings: PleasanterServerScriptSiteSettings;
.NET の配列やリストがそのまま渡ってくる点まで型で表現されています。
/**
* .NETの配列です。インデックスアクセス、Length、for...ofを使用できます。
* JavaScriptの配列メソッドを使用する場合はArray.fromで変換してください。
*/
interface PleasanterHostArray<T> extends Iterable<T> {
readonly [index: number]: T;
readonly Length: number;
}
Length であって length ではない、map や filter は生えていない、というサーバスクリプトでいちばん引っかかるところがコメントで明示されています。これをエディタに読み込ませておけば、Array.from を忘れて実行時エラーになる事故が減ります。
この型定義は C# の実装より少し遅れています。たとえば今回追加された users.GetList() users.Create() users.Update() は、.d.ts にはまだ載っていません(users は Get のみ)。型が無いから使えない、ではないので注意してください。
先行して入った UI テーマ列
Tenants と Users に、テーマ設定らしい列が 5 つずつ追加されています。
| 列 | ラベル |
|---|---|
UiType |
UI タイプ |
UiColorScheme |
カラーモード |
UiMainColor |
メインカラー |
UiSubColor |
サブカラー |
UiBackgroundColor |
背景色 |
TenantApiModel UserApiModel にも載っているので API からは読み書きできます。
public string UiType { get; set; }
public string UiColorScheme { get; set; }
public string UiMainColor { get; set; }
public string UiSubColor { get; set; }
public string UiBackgroundColor { get; set; }
ただし Implem.Pleasanter/Libraries/HtmlParts/ を検索しても、これらの値を使って画面を描いている箇所は見つかりません。列とモデルと API だけが先に入って、描画側は未実装の状態です。
リリースノートにも載っていないので、次のバージョンで UI テーマ機能として出てくる準備と見るのが自然でしょう。現時点で値を入れても見た目は変わりません。
注意したい既定値の変更
バックグラウンドサービスの設定で、既定値が 1 つ変わっています。
- "DeleteTenant": true,
+ "DeleteTenant": false,
1.5.7.0 で入ったテナントの物理削除ジョブが、既定で無効になりました。マルチテナント管理 API でテナントを論理削除しても、放っておくだけでは物理削除されません。
意図した挙動としては安全側への変更ですが、1.5.7.x で物理削除が走る前提で運用していた環境はデータが溜まり続けます。バージョンアップ時に DeleteTenant を明示的に true へ戻す必要があります。
既定値の変更は、設定ファイルに DeleteTenant を書いていない環境にだけ影響します。明示的に true と書いてあれば従来どおり動きます。
まとめ
1.5.7.1 から 1.5.8.0 で追加された機能を見てきました。
-
SCIM 同期:
scim/v2の Users / Groups エンドポイントを新設。PATCHのフィルタ付きパスまで対応し、専用トークンは SHA-256 ハッシュのみ保存。商用ライセンス+Scimオプションが必要で、無効時は 404 を返す -
拡張項目のスキーマ拡張:
Scim.jsonで SCIM 属性名とプリザンターの列名を対応付け、scim/v2/Schemasに反映 -
AI 連携(RAG):
MarkdownFile/Dify/OpenAiの 3 プロバイダ。サイト単位の設定で、レコードを Markdown 化して送信。既定は書き出しのみのMarkdownFile -
RAG のジョブ設計: 送信・インデックス確認・再同期・削除の 4 ハンドラ。キューがあれば非同期、なければ同期送信。
Retry-Afterも見る - クロス集計ドリルダウン: セルから完全一致で絞り込んだ一覧へ遷移。空セルはリンクにならず、戻る操作も絞り込み前に復元
-
計算式(拡張):
$LOGINUSERID()/$LOGINUSERNAME()を追加 -
拡張項目の操作: サーバスクリプトの
users/depts/groupsにGetList/Create/Updateが揃い、拡張項目もClassHash形式で読める -
キューのマルチテナント対応: 対象テナントとワーカー数を
BackgroundJobTargetTenantsに集約。単一テナントライセンスではWorkerCountが 1 に固定 -
TypeScript 型定義: スクリプト・サーバスクリプト用の
.d.tsを同梱。ただし C# の実装より少し遅れている - UI テーマ列: 列・モデル・API だけが先行追加。描画は未実装
前回の 1.5.7.0 が「クラウドで動かすための足回り」だったのに対し、1.5.8.0 は 「社内の他システムとつなぐための入口」 が揃った回です。SCIM でユーザ管理を IdP に寄せ、RAG でレコードを AI の知識ベースに流す、という二本柱がはっきりしています。
一方で、クロス集計のドリルダウンと計算式のログインユーザ関数は、ライセンスも構成も問わず今日から効く改善です。TypeScript の型定義も、スクリプトを書く機会が多い環境なら真っ先に恩恵があります。バージョンアップの際は、このあたりから触ってみるのがよさそうです。
DeleteTenant の既定値変更だけは、1.5.7.x でマルチテナント管理 API を使っていた環境では見落とすと効いてきます。設定ファイルを確認しておいてください。