Skip to content

通知のプレースホルダの改修案 ​

第1版作成 最終更新 (日本時間)
確認バージョン1.5.8.1

通知のカスタムフォーマットでできないことのうち、次の 2 つを本体の改修で補う案です。ここにあるのは本体の標準機能ではありません。

  1. リンク先のテーブルの項目([ClassA~1234,ClassB] の形式)を本文・件名に出す
  2. 書式行の Prefix・Delimiter などに書いた {Url}・{UserName} などを展開する

現行の仕組みは 通知のカスタムフォーマットとリマインダーの内部動作 を参照してください。

前提にした現行実装(1.5.8.1) ​

  • 本文は NoticeBody が書式を 1 行ずつ NotificationColumnFormat の JSON として読み、Name から項目が特定できた行は ToNotice で値を出し、それ以外の行だけ ReplacedContextValues で {Url} など 4 つを置換する(ResultModel.cs)
  • 項目の特定は SiteSettings.IncludedColumns で、角括弧の中身と自サイトの Columns の項目名の完全一致(SiteSettings.cs)
  • 件名は ReplacedDisplayValues で [項目名] を表示値に置き換える(ResultModel.cs)
  • NoticeBody・ReplacedDisplayValues・ReplacedContextValues は CodeDefiner のテンプレート(App_Data/Definitions/Definition_Code/ の Model_Notice_Body.txt・Model_ReplacedDisplayValues_Body.txt・Model_ReplacedContextValues_Body.txt)から Issues・Results・Wikis の各モデルに生成される。モデルの .cs を直接直しても CodeDefiner の実行で上書きされる

リンク先の項目を表す ClassA~1234,ClassB の形式は、SiteSettings.GetColumn が , を含む名前を見つけたときに AddJoinedColumn でリンク先の SiteSettings から項目を作る経路で解決されます(SiteSettings.cs)。IncludedColumns はこの経路を通らないため、通知ではリンク先の項目を解決できません。

1. リンク先の項目を出す ​

案の比較 ​

案内容変更範囲性能既存への影響
AIncludedColumns に Context 付きの版を足し、自サイトで見つからず , を含む名前なら GetColumn で解決する小結合項目 1 つごとにリンク先を 1 回読む引数なしの版を残せば通知以外に影響しない
BIncludedColumns は変えず、NoticeBody の中で見つからなかったときだけ GetColumn にフォールバックする中A と同じ件名(ReplacedDisplayValues)にも同じ処理を別に書く必要がある
C書式の結合項目を先に集め、ss.Join() でリンク先を JOIN した SQL で一括取得する大JOIN で 1 回通知の SQL が変わるため回帰のリスクが高い

変更が小さく、件名と本文を同じ入口で扱える 案 A を軸にするのが現実的です。

案 A の変更点 ​

ファイル変更
Libraries/Settings/SiteSettings.csIncludedColumns(Context context, string value, bool labelText = false) を足す。自サイトで見つからず、名前に , を含むときだけ GetColumn(context, columnName) を呼ぶ。既存の IncludedColumns(value, labelText) はこちらを context: null で呼ぶだけにする
Model_Notice_Body.txtss.IncludedColumns(data.Format?.Name) を Context 付きに変え、項目が結合項目(column.Joined)なら専用の処理で値を出す
Model_ReplacedDisplayValues_Body.txt件名の置換も Context 付きの IncludedColumns に変える
IncludedColumns の変更イメージ
csharp
public List<Column> IncludedColumns(
    Context context,
    string value,
    bool labelText = false)
{
    var columns = new List<Column>();
    if (!value.IsNullOrEmpty())
    {
        foreach (Match match in value.RegexMatches(@"(?<=\[).+?(?=\])"))
        {
            var isValue = false;
            var columnName = match.Value;
            if (!labelText && columnName.StartsWith("@"))
            {
                isValue = true;
                columnName = columnName.Substring(1);
            }
            var column = labelText
                ? Columns.FirstOrDefault(o => o.LabelText == columnName)
                : Columns.FirstOrDefault(o => o.ColumnName == columnName);
            // 追加: 自サイトに無く、結合項目の形式なら GetColumn で解決する
            if (column == null && context != null && columnName.Contains(','))
            {
                column = GetColumn(context: context, columnName: columnName);
            }
            if (column != null)
            {
                column.OutputType = isValue
                    ? Column.OutputTypes.Value
                    : Column.OutputTypes.DisplayValue;
                columns.Add(column);
            }
        }
    }
    return columns.DistinctBy(column => column.ColumnName).ToList();
}

結合項目の値の取り方 ​

項目が解決できても、リンク先のレコードの値は通知を出すモデルに読み込まれていません。NoticeBody の項目行の分岐に、結合項目用の処理を足します。

  1. 結合項目の名前(ColumnNameInfo)から、リンク先の SiteSettings(ss.JoinedSsHash)とリンク元の項目(ClassA)を得る
  2. リンク元の項目の値からリンク先のレコード ID を得る。値が空なら何も出さない
  3. リンク先の種類(Issues・Results・Wikis)に応じてモデルを読み、項目の表示値を ToNotice に渡す
値を取る処理のイメージ
csharp
private string GetJoinedColumnNotice(
    Context context,
    SiteSettings ss,
    Column column,
    NotificationColumnFormat notificationColumnFormat,
    bool update)
{
    var columnNameInfo = new ColumnNameInfo(column.ColumnName);
    var joinedSs = ss.JoinedSsHash.Get(columnNameInfo.SiteId);
    if (joinedSs == null) return string.Empty;
    // リンク元の項目(例: ClassA)の値 = リンク先のレコード ID
    var linkColumnName = columnNameInfo.TableAlias
        .Split('-').Last().Split('~').First();
    var linkedRecordId = PropertyValue(
        context: context,
        column: ss.GetColumn(context: context, columnName: linkColumnName))
            ?.ToLong() ?? 0;
    if (linkedRecordId == 0) return string.Empty;
    switch (joinedSs.ReferenceType)
    {
        case "Results":
            var resultModel = new ResultModel(
                context: context,
                ss: joinedSs,
                resultId: linkedRecordId);
            return resultModel.ToDisplay(
                context: context,
                ss: joinedSs,
                column: joinedSs.GetColumn(
                    context: context,
                    columnName: columnNameInfo.Name),
                mine: resultModel.Mine(context: context))
                    .ToNotice(
                        context: context,
                        saved: string.Empty,
                        column: column,
                        notificationColumnFormat: notificationColumnFormat,
                        updated: false,
                        update: update);
        // Issues・Wikis も同様
        default:
            return string.Empty;
    }
}

注意点 ​

観点内容
権限一覧の結合項目はリンク先の閲覧権限を確かめてから出している(GridData.cs)。通知でも同じ確認を入れないと、読めないレコードの値が通知に載る
性能結合項目 1 つごとにリンク先を 1 回読む。同じリンク先の項目が複数あるときは、NoticeBody の先頭で書式を走査してリンク先ごとにまとめて読むとよい
変更前後更新時の「変更前 => 変更後」を出すには、リンク先の変更前の値も要る。まずは現在の値だけを出し、差分は後回しにする
JoinedSsHash通知の時点、特にプロセスの通知で JoinedSsHash が用意されているかを確かめる
多段のリンクClassA~1234-ClassB~5678,ClassC のような多段は、中間のテーブルを順にたどる必要がある。対応するかを決めておく

確認したいケースは、単一のリンク、リンク先が Issues・Results・Wikis のそれぞれ、リンク元が空、リンク先が削除済み、閲覧権限なし、件名での使用、プロセスの通知、更新時の差分表示です。

なお、リンク項目そのもの([ClassA])は、改修しなくても選択肢の表示名(リンク先のタイトル)に変換されて出ます。

2. 書式行の {Url} などを展開する ​

{"Name":"[Title]","Prefix":"{Url} "} の {Url} は、項目行が ReplacedContextValues を通らないため展開されません。1 行に {"Name":"[Title]"}{Url} と続けて書く方法は JSON として読めなくなり、項目行になりません。

案の比較 ​

案内容Prefix などの {Url}同じ行の JSON + 文字列変更量副作用
ANoticeBody の戻り値全体に ReplacedContextValues をかける展開される解決しない(JSON がそのまま出る)テンプレートの 1 行項目の値に {Url} などの文字列が入っていると、それも置換される
B項目行の分岐に入る前に、NotificationColumnFormat の文字列プロパティだけを置換する展開される解決しないNotificationColumnFormat.cs にメソッド 1 つ + テンプレート項目の値には影響しない
C行の先頭の JSON オブジェクトを括弧の対応で切り出し、後ろの文字列を別に置換する展開されない(A か B と併用)解決するテンプレートにパーサーを足す括弧の解析に誤りが入りやすい

案 B を勧めます。置換の対象が書式の設定値だけに限られ、利用者が入力した項目の値は変わりません。Prefix で {Url} が使えるようになれば、1 行に JSON と文字列を続ける書き方は要らなくなります。急ぐ場合は 1 行で済む案 A でもかまいません(項目の値に {Url} などの文字列そのものが入る場面はまれです)。

案 B の変更イメージ
csharp
// NotificationColumnFormat.cs に追加
public void ReplaceVariables(System.Func<string, string> replacer)
{
    if (replacer == null) return;
    Prefix = Prefix != null ? replacer(Prefix) : null;
    Delimiter = Delimiter != null ? replacer(Delimiter) : null;
    Allow = Allow != null ? replacer(Allow) : null;
    StartBracket = StartBracket != null ? replacer(StartBracket) : null;
    EndBracket = EndBracket != null ? replacer(EndBracket) : null;
    DeletePrefixSymbol = DeletePrefixSymbol != null ? replacer(DeletePrefixSymbol) : null;
    DeleteSuffixSymbol = DeleteSuffixSymbol != null ? replacer(DeleteSuffixSymbol) : null;
    AddPrefixSymbol = AddPrefixSymbol != null ? replacer(AddPrefixSymbol) : null;
    AddSuffixSymbol = AddSuffixSymbol != null ? replacer(AddSuffixSymbol) : null;
}
csharp
// Model_Notice_Body.txt の項目行の分岐(else)の先頭に追加
data.Format?.ReplaceVariables(
    v => ReplacedContextValues(context: context, value: v));

案 A の場合は、NoticeBody の最後の return body.ToString(); を return ReplacedContextValues(context: context, value: body.ToString()); に変えます(Model_Notice_Body.txt)。項目行以外の行はすでに置換済みなので、2 回かけても結果は変わりません。

プレフィックス(通知の「プレフィックス」欄)は Notification.Send で件名に連結されるだけなので、ここで {Url} などを使いたい場合は、Send の呼び出し元で件名と同じ置換をかけてから渡す変更が別に要ります。

関連ページ ​

変更履歴

第1版通知とリマインダーの書式・置き換わらない書き方・タイムゾーンの影響、システムログの外部通知の解説と、関連する改修・設計メモを追加