[A/Bテスト] 実験変数に基づいて親CSSクラスを変更する

既存のテーマを実験的に変更したいと考えています。例えば、トピック一覧の表示スタイルを異なるバージョンでテストするといったケースです。そのために Google Optimize を用いた A/B テストを実施しています。現在、50% のユーザーには変更なしのテーマを、残りの 50% には更新されたテーマを表示する予定です。実験対象が小部分であるため、オリジナルとバリアントは同じテーマ内に定義する必要があります。しかし、親の CSS クラス名が共通しており、特定の値に基づいてそれらの親クラスにスタイルを適用したいという問題に直面しています。同じ親クラス名を持つ異なるテンプレートに対して、どのように個別のスタイルを適用すればよいでしょうか。

以下に、実現しようとしている簡略化されたシナリオを示します。

トピック一覧のスタイルが common/common.scss で次のように定義されているテーマがあると仮定します。

.topic-list {
  tbody {
    border: none;
  }
  tr {
    border: none;
  }
  .example-div {
    border: none;
  }
}

一方、バリアントでは以下のように設定したいと考えています。

.topic-list {
  tbody {
    display: block;
    tr {
      display: block;
    }
  }
  .example-div {
    border: 2px solid black;
  }
  .another-example-div { 
    font-size: 10px;
  }
}

オーバーライドされているハンドルバーファイルは以下の通りです。

javascripts/discourse/templates/list/custom-topic-list-item.hbr

<div class='example-div'>
  Hello
</div>

javascripts/discourse/templates/list/variant-custom-topic-list-item.hbr

<div class='example-div'>
  Hello,
  <div class='another-example-div'>
    Nice to meet you
  </div>
</div>

topic-list クラス名は Discourse コンポーネント内で言及されていますが、レンダリング後の構造は以下のようになります。

<table class="topic-list">
  <tr class="topic-list-item">
   <div class='example-div'>
      Hello
    </div>
  </tr>
</table>

ヘッダーファイルでユーザー ID に応じてこれらのファイルのいずれかをレンダリングしたいと考えています。
common/header.html

const { findRawTemplate } = require("discourse-common/lib/raw-templates");
const user_id = api.getCurrentUser().id
 
api.modifyClass('component:topic-list-item', {
  renderTopicListItem() {
    let template = findRawTemplate("list/custom-topic-list-item");
    if (user_id % 2 === 0)
       template = findRawTemplate("list/variant-custom-topic-list-item");
    if (template) {
      this.set("topicListItemContents", template(this).htmlSafe());
    }
  },
});

よろしくお願いいたします。

「いいね!」 3

これは、ログインしていないユーザーに対して捕捉されていないエラーを引き起こしている可能性があります。以下のように変更することをお勧めします。

const user = api.getCurrentUser();
if (!user) return;

const userId = user.id;

これで問題が解決したので、元の質問に戻りましょう。

CSS とは異なり、SCSS は 親セレクタ をサポートしているため、これを利用できます。

ユーザーの ID が偶数か奇数か(id % 2)に応じてスタイルを適用したい場合、アプリの読み込み時にその判定を行うことができます。例えば、以下のコードを一番上に追加します。

const user = api.getCurrentUser();
const userId = user.id;

if (!userId) return;

const testThemeUser = userId % 2 === 0;

if (testThemeUser) {
  document.body.classList.add("test-theme");
}

その後、JS の条件分岐で testThemeUser を、スタイルでは test-theme クラスを使用できます。

具体的には、以下のように実装します。

JS

let template = findRawTemplate("list/custom-topic-list-item");
if (testThemeUser)
  template = findRawTemplate("list/variant-custom-topic-list-item");
if (template) {
  this.set("topicListItemContents", template(this).htmlSafe());
}

SCSS

.topic-list {
  tbody {
    border: none;
  }
  tr {
    border: none;
  }
  .example-div {
    border: none;
  }
  // テストテーマ用の SCSS
  .test-theme & {
    .example-div {
      border: 2px solid black;
    }
    .another-example-div {
      font-size: 10px;
    }
  }
}
「いいね!」 3

返信ありがとうございます @Johani
現在は topic-list という特定のコンポーネントクラスのみを更新する必要があるため、このアプローチを採用しています。

const useTestTheme = userId % 2 === 0

api.modifyClass('component:topic-list', {
    classNameBindings: ["test"],
    test: useTestTheme,
})
「いいね!」 2