Лучшие практики автоматизации заголовков по комбинациям групп и уровней доверия

Здравствуйте,

Я ищу лучший способ автоматически присваивать заголовки пользователям на основе комбинации членства в группе и уровня доверия. Например:

  • Если пользователь состоит в группе A и имеет уровень доверия 1, его заголовок должен быть «A TL1»
  • Если пользователь состоит в группе A и имеет уровень доверия 2, его заголовок должен быть «A TL2»
  • Если пользователь состоит в группе B и имеет уровень доверия 1, его заголовок должен быть «B TL1»
  • …и так далее

Из того, что я пока нашел, основной метод заключается в следующем:

  1. Создать отдельную группу для каждой комбинации «группа + уровень доверия» (например, a-tl1, a-tl2, b-tl1 и т. д.) с помощью плагина Dynamic Groups,
  2. Установить заголовок по умолчанию для каждой из этих групп.

Хотя это, кажется, работает, такой подход требует создания и поддержки большого количества групп, если комбинаций много.

Мои вопросы:

  • Является ли эта настройка (множество динамических групп, каждая со своим заголовком по умолчанию) лучшим или единственным способом автоматически синхронизировать заголовки?
  • Существует ли другой подход (с использованием ядра Discourse, автоматизаций или другого плагина) для динамического присвоения заголовков на основе условий, таких как группа и уровень доверия, без необходимости управлять столькими дополнительными группами?

Буду очень признателен за любые советы или опыт. Заранее спасибо!

Подойдёт ли вам вариант, когда заголовок настраивается в зависимости от принадлежности к группе (A или B), а компонент добавляет заголовок на основе уровня доверия?

Тогда, думаю, вам не понадобятся все эти комбинированные подгруппы :thinking:

Ещё одна идея: возможно, плагин, который автоматически присваивает заголовок в соответствии с уровнем доверия, можно использовать как основу для более сложного обновления заголовка с учётом уровня доверия и членства в группах.

Спасибо за ваши предложения.

К сожалению, у меня самого нет опыта программирования, поэтому я не могу разрабатывать или изменять плагины. Я уже связался с автором плагина, чтобы спросить, можно ли добавить эту функцию (объединение членства в группе и уровня доверия для заголовков), или есть ли у него какие-либо советы.

Если речь идет о основных группах пользователя, то, как вы уже упоминали в другой теме, это значительно упростит задачу.

Тогда вы сможете получить основную группу пользователя через user.primary_group и установить заголовок с помощью user.title.

@joo Я добавил настройку add_primary_group_title (по умолчанию выключена), которая:

  • добавляет основную группу пользователя в качестве заголовка
  • текст после заголовка, например:

                  можно редактировать с помощью настроек, как и раньше.

Использовал немного магии с ask.discourse.com.

П.С. Если вы задаетесь вопросом, как я отступил слово «можно», я использовал  . Извините, ребята.

Привет! Спасибо за обновление и поддержку!

Возможно, я не слишком четко объяснил свои требования ранее, поэтому хочу прояснить их еще раз на простом примере:

Предположим, на нашем форуме есть две основные группы:

  • Группа A: Дизайнеры
  • Группа B: Разработчики

Для каждой группы я хочу, чтобы пользователи с разным уровнем доверия получали совершенно разные названия, например:

Для группы «Дизайнеры»:
Уровень доверия 1 → «Младший дизайнер»
Уровень доверия 2 → «Дизайнер»
Уровень доверия 3 → «Старший дизайнер»
Уровень доверия 4 → «Главный дизайнер»

Для группы «Разработчики»:
Уровень доверия 1 → «Младший разработчик»
Уровень доверия 2 → «Разработчик»
Уровень доверия 3 → «Старший разработчик»
Уровень доверия 4 → «Технический лидер»

Таким образом, если кто-то состоит в группе «Дизайнеры» с уровнем доверия 3, его звание должно быть «Старший дизайнер».
Если он состоит в группе «Разработчики» с уровнем доверия 2, звание должно быть «Разработчик».

Я хотел бы иметь возможность настраивать эти правила для каждой группы и уровня доверия, чтобы звания присваивались автоматически при изменении групп пользователей или их уровня доверия.

Есть ли у вас какие-либо советы, как этого добиться, или вы рассматриваете возможность добавления этой функции в свой плагин?

Большое спасибо!

Также я пытаюсь реализовать это на своем тестовом сайте: http://ask.discourse.com/. У меня нет опыта программирования, поэтому я не уверен, возможно ли реализовать это самостоятельно.

Я могу использовать значение-заполнитель, например {group_nane}, в текстовом поле, чтобы было проще редактировать заголовок. Я посмотрю.

Возможно, стоит ознакомиться с:

Привет!

Я модифицировал плагин под свои нужды и хотел бы поделиться тем, что я сделал, своим текущим рабочим процессом, а также задать несколько вопросов и запросить совет.


1. Мои изменения

Путь к файлу и содержимое:

Файл: app/jobs/scheduled/update-all-titles.rb

# frozen_string_literal: true

module AddTitleBasedOnTrustLevel
  class UpdateTitles < ::Jobs::Scheduled
    every SiteSetting.update_title_frequency.hours

    def execute(args)
      group_titles = JSON.parse(SiteSetting.group_trust_level_titles || "{}")

      User.find_each do |user|
        # Пропускаем администраторов и модераторов, не обновляем их титулы
        next if user.admin? || user.moderator?

        group_id = user.primary_group_id
        next unless group_id

        group = Group.find_by(id: group_id)
        next unless group

        group_key = group.name.downcase
        tl = user.trust_level

        titles = group_titles[group_key]
        next unless titles.is_a?(Array)
        next unless tl >= 1 && tl <= 4

        new_title = titles[tl]
        next unless new_title.present?

        user.update_column(:title, new_title) if user.title != new_title
      end
    end
  end
end

Файл: config/settings.yml

plugins:
  add_title_based_on_trust_level_enabled:
    default: false
    client: true
  group_trust_level_titles:
    default: '{"designers": ["", "Junior Designer", "Designer", "Senior Designer", "Chief Designer"], "developers": ["", "Junior Developer", "Developer", "Senior Developer", "Tech Lead"]}'
    type: string
    client: true
    multiline: true
  update_title_frequency:
    default: 24
    type: integer

2. Как я тестировал

В настоящее время я запускаю эту логику вручную через rails console, чтобы проверить, работает ли функция. Она действительно обновляет титулы пользователей массово в соответствии с моими требованиями.


3. Проблема с кнопкой настроек плагина

На странице /admin/plugins для этого плагина нет кнопки «Настройки». В данный момент, чтобы изменить конфигурацию, мне приходится переходить напрямую по адресу /admin/plugins/add-title-based-on-trust-level/settings.
Есть ли способ сделать так, чтобы кнопка или ссылка настроек отображалась на странице Плагины для более удобного доступа?


4. Мои текущие настройки

Вот моя текущая JSON-конфигурация (я также приложу скриншот):

{
  "designers": ["", "Junior Designer", "Designer", "Senior Designer", "Chief Designer"],
  "developers": ["", "Junior Developer", "Developer", "Senior Developer", "Tech Lead"]
}


5. Вопросы / Возможные улучшения

  • Несет ли этот подход (перебор всех пользователей и обновление их титулов) риск проблем с производительностью, если пользователей очень много? Есть ли лучшая практика для этого?
  • Любые советы по оптимизации, как для запланированной задачи, так и для интерфейса настроек администратора?
  • Есть ли что-то небезопасное или проблемное в моем методе, о чем мне следует знать?

Большое спасибо за вашу отличную работу и этот полезный плагин!
Если у вас есть какие-либо предложения или если вы планируете улучшить конфигурацию титулов на основе группы и уровня доверия в будущем, я с радостью выслушаю ваши мысли.

@joo Отличная идея!

Если посмотреть на плагин сейчас, я думаю, я сделал то же самое (запускал финальные тесты). Я вижу, что вы перебирали каждого пользователя, но в больших форумах это может занять много времени.

Поэтому я сделал SQL UPDATE:

(Я адаптировал запрос от Ask Discourse и немного его изменил)

Таким образом, в настройке tl1_title_on_promotion вы сможете использовать что-то вроде Junior {group_name}, и тогда заголовок станет Junior Developer.

Обратите внимание, что для работы также нужно включить опцию add_primary_group_title.

Спасибо за объяснение, теперь я понимаю, как работает ваш шаблон {group_name}.

Но именно это я и имел в виду, говоря о различии в логике. Моя основная задача — иметь возможность применять совершенно разные структуры заголовков для одного и того же уровня доверия в зависимости от различных групп пользователей.

Например, для уровня доверия 3:

  • Заголовок для группы «Дизайнеры» должен быть «Старший дизайнер».
  • Заголовок для группы «Разработчики» должен быть «Технический лидер».

Эти два заголовка имеют совершенно разную структуру.

Я пытался реализовать эту функцию с помощью ИИ, но у меня не получилось. Подумываю, возможно ли для вас модифицировать плагин, чтобы он поддерживал такую более детальную логику?

А, я думаю, я вас тогда неправильно понял.

Так что, ваш код выше сработал?

Нет, предыдущий код, который я пробовал с помощью ИИ, тоже не сработал. После настройки всего ничего не произошло.

Я хотел показать вам полный код моей последней попытки. Надеюсь, вы сможете увидеть, где я ошибаюсь.

1. plugin.rb

# name: add-title-by-group-trust-level
# about: assign titles via primary group + trust level using SQL
# version: 0.2
# authors: your-name

# Note: I'm not sure if I need a 'require' line here to load the job file.
# I suspect this might be the missing piece.
enabled_site_setting :add_title_by_group_trust_enabled

2. app/jobs/scheduled/update-all-titles.rb

module ::Jobs
  class UpdateTitles < ::Jobs::Scheduled
    every SiteSetting.update_title_frequency.hours

    def execute(args)
      return unless SiteSetting.add_title_by_group_trust_enabled

      mappings = JSON.parse(SiteSetting.group_trust_level_titles || '{}')

      User.transaction do
        mappings.each do |group_key, titles|
          next unless titles.is_a?(Array)

          (1...titles.size).each do |tl|
            title = titles[tl]
            next if title.blank?

            group = Group.find_by("LOWER(name) = ?", group_key.downcase)
            next unless group

            User.where(primary_group_id: group.id, trust_level: tl, admin: false, moderator: false)
                .where.not(title: title)
                .update_all(title: title)
          end
        end
      end
    end
  end
end

3. config/settings.yml

plugins:
  add_title_by_group_trust_enabled:
    default: false
    client: true
  group_trust_level_titles:
    default: '{"designers":["","Junior Designer","Designer","Senior Designer","Chief Designer"],"developers":["","Junior Developer","Developer","Senior Developer","Tech Lead"]}'
    type: string
    client: true
    multiline: true
  update_title_frequency:
    default: 24
    type: integer

Вот как я настроил это в настройках сайта:

Я вставил этот JSON в настройку group_trust_level_titles:

{
  "designers": ["", "Junior Designer", "Designer", "Senior Designer", "Chief Designer"],
  "developers": ["", "Junior Developer", "Developer", "Senior Developer", "Tech Lead"]
}

Проблема:

Задача UpdateTitles не отображается в планировщике Sidekiq (/sidekiq/scheduler).

Однако я протестировал её в консоли Rails согласно методу, который дал мне ИИ, и основная логика работает идеально, корректно выводя всех пользователей, соответствующих условиям.

Вот тестовый скрипт, который я использовал в консоли Rails:

mappings = JSON.parse(SiteSetting.group_trust_level_titles || '{}')

mappings.each do |group_key, titles|
  next unless titles.is_a?(Array)

  (1...titles.size).each do |tl|
    title = titles[tl]
    next if title.blank?

    group = Group.find_by("LOWER(name) = ?", group_key.downcase)
    unless group
      puts "Could not find group: #{group_key}"
      next
    end

    users = User.where(primary_group_id: group.id, trust_level: tl, admin: false, moderator: false)
                .where.not(title: title)
    next if users.empty?

    puts "====== Group: #{group.name} Trust Level: #{tl} New Title: '#{title}' ======"
    users.each do |user|
      puts "User id:#{user.id}, username:#{user.username}, current title:'#{user.title}'"
    end
  end
end

Похоже, что сама логика верна, но Discourse не регистрирует запланированную задачу. В любом случае, чем больше я пытаюсь это изменить, тем запутаннее всё становится… Я не уверен, что это правильный подход, и поскольку я не умею программировать, реализация кажется мне очень сложной. Есть ли у вас лучший метод?

Хм… возможно, вы хотите использовать настройки объекта:

С полями, такими как уровень доверия, группа, заголовок.

Хотел просто сообщить об обновлениях и поблагодарить всех, кто помог.

Благодаря последним изменениям плагин теперь работает именно так, как я планировал! Он корректно присваивает разные заголовки в зависимости от основной группы пользователя и уровня доверия.

Ниже я публикую финальный рабочий код. Он, кажется, верный, но так как я не разработчик, хотел бы узнать, нет ли мест, которые можно улучшить или оптимизировать, и которые я мог упустить.

app/jobs/scheduled/update-all-titles.rb

# frozen_string_literal: true

module ::Jobs
  class AssignGroupBasedTitles < ::Jobs::Scheduled
    every SiteSetting.update_title_frequency.hours

    def execute(args)
      return unless SiteSetting.add_title_by_group_trust_enabled

      rules_json_string = SiteSetting.group_based_title_rules
      return if rules_json_string.blank?

      begin
        # Вручную парсим JSON-строку в массив Ruby.
        rules = JSON.parse(rules_json_string)
      rescue JSON::ParserError
        # Если JSON некорректен, записываем ошибку и завершаем выполнение, чтобы задача не завершилась с ошибкой.
        Rails.logger.error("Плагин заголовков на основе групп: не удалось распарсить JSON group_based_title_rules.")
        return
      end
      
      # Проверка безопасности: убедимся, что распаршенные правила — это непустой массив.
      return unless rules.is_a?(Array) && rules.present?

      User.transaction do
        rules.each do |rule|
          group_id = rule["group_id"]
          trust_level = rule["trust_level"]
          title = rule["title"]

          next if group_id.blank? || trust_level.blank? || title.blank?

          User.where(primary_group_id: group_id, trust_level: trust_level)
              .where.not(title: title)
              .update_all(title: title)
        end
      end
    end
  end
end

config/settings.yml

plugins:
  add_title_by_group_trust_enabled:
    default: true
    client: true

  group_based_title_rules:
    type: objects
    default: []
    schema:
      properties:
        group_id:
          type: integer
          name: "Группа пользователя"
          component: "group-chooser"
        trust_level:
          type: integer
          name: "Уровень доверия"
          min: 1
          max: 4
        title:
          type: string
          name: "Заголовок"
  
  update_title_frequency:
    default: 24
    type: integer

plugin.rb

# frozen_string_literal: true

# name: add-title-by-group-trust-level
# about: Присваивание заголовков на основе основной группы и уровня доверия.
# version: 2.1.0
# authors: Ваше Имя

enabled_site_setting :add_title_by_group_trust_enabled

after_initialize do
  require_relative "./app/jobs/scheduled/update-all-titles.rb"
end