Отчет по панели управления — активность модераторов

Это SQL-версия отчёта панели мониторинга по активности модераторов.

Данный отчёт предоставляет всесторонний обзор деятельности модераторов в рамках указанного временного промежутка на сайте Discourse. Отчёт объединяет несколько аспектов работы модераторов: время, проведённое на платформе, количество рассмотренных флагов, созданных постов, личных сообщений (ЛС), созданных тем и внесённых правок в посты.

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

--[params]
-- date :start_date = 2023-12-01
-- date :end_date = 2024-12-01
-- boolean :include_admins = false


WITH mods AS (
    SELECT
        id AS user_id,
        username_lower AS username,
        uploaded_avatar_id
    FROM users u
    WHERE ((u.moderator = true) OR (:include_admins AND u.admin = true))
    AND u.id > 0
),

time_read AS (
    SELECT SUM(uv.time_read) AS time_read,
        uv.user_id
    FROM mods m
    JOIN user_visits uv ON m.user_id = uv.user_id
    WHERE uv.visited_at >= :start_date
        AND uv.visited_at <= :end_date
    GROUP BY uv.user_id
),
      
flag_count AS (
    WITH period_actions AS (
        SELECT agreed_by_id,
        disagreed_by_id
        FROM post_actions
        WHERE post_action_type_id IN (3,4,8,6,7)
        AND created_at >= :start_date
        AND created_at <= :end_date
    ),

agreed_flags AS (
        SELECT pa.agreed_by_id AS user_id,
        COUNT(*) AS flag_count
        FROM mods m
        JOIN period_actions pa
        ON pa.agreed_by_id = m.user_id
        GROUP BY agreed_by_id
    ),

disagreed_flags AS (
        SELECT pa.disagreed_by_id AS user_id,
        COUNT(*) AS flag_count
        FROM mods m
        JOIN period_actions pa
        ON pa.disagreed_by_id = m.user_id
        GROUP BY disagreed_by_id
    )
    SELECT
    COALESCE(af.user_id, df.user_id) AS user_id,
    COALESCE(af.flag_count, 0) + COALESCE(df.flag_count, 0) AS flag_count
    FROM agreed_flags af
    FULL OUTER JOIN disagreed_flags df
    ON df.user_id = af.user_id
      ),
      
revision_count AS (
      SELECT pr.user_id,
      COUNT(*) AS revision_count
      FROM mods m
      JOIN post_revisions pr
      ON pr.user_id = m.user_id
      JOIN posts p
      ON p.id = pr.post_id
      WHERE pr.created_at >= :start_date
      AND pr.created_at <= :end_date
      AND p.user_id <> pr.user_id
      GROUP BY pr.user_id
),
      
topic_count AS (
      SELECT t.user_id,
        COUNT(*) AS topic_count
      FROM mods m
      JOIN topics t ON t.user_id = m.user_id
      WHERE t.archetype = 'regular'
          AND t.created_at >= :start_date
          AND t.created_at <= :end_date
      GROUP BY t.user_id
),
      
post_count AS (
      SELECT p.user_id,
         COUNT(*) AS post_count
      FROM mods m
      JOIN posts p ON p.user_id = m.user_id
      JOIN topics t ON t.id = p.topic_id
      WHERE t.archetype = 'regular'
          AND p.created_at >= :start_date
          AND p.created_at <= :end_date
      GROUP BY p.user_id
),
      
pm_count AS (
      SELECT p.user_id,
        COUNT(*) AS pm_count
      FROM mods m
      JOIN posts p ON p.user_id = m.user_id
      JOIN topics t ON t.id = p.topic_id
      WHERE t.archetype = 'private_message'
          AND p.created_at >= :start_date
          AND p.created_at <= :end_date
      GROUP BY p.user_id
      )
      
SELECT
    m.user_id,
    m.username,
    fc.flag_count as flags_reviewed,
    ROUND(((tr.time_read) / 3600.00),2) as time_reading_hours,
    tc.topic_count as topics_created,
    pmc.pm_count as PMs_created,
    pc.post_count as posts_created,
    rc.revision_count as revisions
FROM mods m
LEFT JOIN time_read tr ON tr.user_id = m.user_id
LEFT JOIN flag_count fc ON fc.user_id = m.user_id
LEFT JOIN revision_count rc ON rc.user_id = m.user_id
LEFT JOIN topic_count tc ON tc.user_id = m.user_id
LEFT JOIN post_count pc ON pc.user_id = m.user_id
LEFT JOIN pm_count pmc ON pmc.user_id = m.user_id
ORDER BY m.username ASC

Параметры

  • :start_date и :end_date — эти параметры определяют диапазон дат для отчёта. Оба параметра даты принимают формат ГГГГ-ММ-ДД.
  • :include_admins — этот параметр определяет, включать ли администраторов в отчёт вместе с модераторами.

Объяснение SQL-запроса

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

  1. mods: Определяет всех пользователей со статусом модератора или администратора (если включено параметром :include_admins). Выбирает только соответствующие столбцы пользователей для дальнейших запросов.
  2. time_read: Вычисляет общее время (в секундах), которое каждый модератор потратил на чтение контента на платформе между указанными датами начала и конца.
  3. flag_count: Подсчитывает количество флагов, с которыми модераторы согласились или не согласились в течение указанного периода. Учитываются несколько типов флагов, представленных их соответствующими идентификаторами типов действий над постом.
  4. revision_count: Подсчитывает количество правок в постах, внесённых модераторами в чужие посты в течение заданного временного промежутка.
  5. topic_count: Подсчитывает количество обычных тем, созданных модераторами.
  6. post_count: Подсчитывает количество постов, созданных модераторами в обычных темах.
  7. pm_count: Подсчитывает количество личных сообщений, инициированных модераторами.

После сбора данных в CTE основной запрос соединяет их по идентификатору пользователя и формирует итоговый отчёт, отображающий имя пользователя каждого модератора, общее время, проведённое за чтением (преобразованное в часы), количество рассмотренных флагов, созданных тем, личных сообщений, постов и внесённых правок. Результаты сортируются в алфавитном порядке по имени пользователя модератора.

Пример результатов

user username flags_reviewed time_reading_hours topics_created pms_created posts_created revisions
1 moderator1 NULL 36.11 NULL 344 8 15
2 moderator2 46 104.52 2 271 466 363
3 moderator3 NULL 72.15 NULL 418 64 16

Привет, @SaraDev,

Это действительно полезно. Возможно ли добавить в отчёт «Активность модераторов» столбцы «Одобрённые посты» и «Отклонённые посты»?

Спасибо,
T_Disco

Привет, @T_Disco,

Да, чтобы включить результаты «Одобренные сообщения» и «Отклонённые сообщения» в отчёт о деятельности модераторов, мы можем использовать таблицы reviewables и reviewable_scores. В частности, поле status в таблице reviewable_scores позволяет определить, было ли сообщение одобрено (status = 1) или отклонено (status = 2).

Вот как это будет выглядеть в отчёте:

Деятельность модераторов с одобрёнными и отклонёнными сообщениями

-- [params]
-- date :start_date = 2023-12-01
-- date :end_date = 2024-12-01
-- boolean :include_admins = false

WITH mods AS (
    SELECT
        id AS user_id,
        username_lower AS username,
        uploaded_avatar_id
    FROM users u
    WHERE ((u.moderator = true) OR (:include_admins AND u.admin = true))
    AND u.id > 0
),

time_read AS (
    SELECT SUM(uv.time_read) AS time_read,
        uv.user_id
    FROM mods m
    JOIN user_visits uv ON m.user_id = uv.user_id
    WHERE uv.visited_at >= :start_date
        AND uv.visited_at <= :end_date
    GROUP BY uv.user_id
),
      
flag_count AS (
    WITH period_actions AS (
        SELECT agreed_by_id,
        disagreed_by_id
        FROM post_actions
        WHERE post_action_type_id IN (3,4,8,6,7)
        AND created_at >= :start_date
        AND created_at <= :end_date
    ),

agreed_flags AS (
        SELECT pa.agreed_by_id AS user_id,
        COUNT(*) AS flag_count
        FROM mods m
        JOIN period_actions pa
        ON pa.agreed_by_id = m.user_id
        GROUP BY agreed_by_id
    ),

disagreed_flags AS (
        SELECT pa.disagreed_by_id AS user_id,
        COUNT(*) AS flag_count
        FROM mods m
        JOIN period_actions pa
        ON pa.disagreed_by_id = m.user_id
        GROUP BY disagreed_by_id
    )
    SELECT
    COALESCE(af.user_id, df.user_id) AS user_id,
    COALESCE(af.flag_count, 0) + COALESCE(df.flag_count, 0) AS flag_count
    FROM agreed_flags af
    FULL OUTER JOIN disagreed_flags df
    ON df.user_id = af.user_id
      ),
      
revision_count AS (
      SELECT pr.user_id,
      COUNT(*) AS revision_count
      FROM mods m
      JOIN post_revisions pr
      ON pr.user_id = m.user_id
      JOIN posts p
      ON p.id = pr.post_id
      WHERE pr.created_at >= :start_date
      AND pr.created_at <= :end_date
      AND p.user_id <> pr.user_id
      GROUP BY pr.user_id
),
      
topic_count AS (
      SELECT t.user_id,
        COUNT(*) AS topic_count
      FROM mods m
      JOIN topics t ON t.user_id = m.user_id
      WHERE t.archetype = 'regular'
          AND t.created_at >= :start_date
          AND t.created_at <= :end_date
      GROUP BY t.user_id
),
      
post_count AS (
      SELECT p.user_id,
         COUNT(*) AS post_count
      FROM mods m
      JOIN posts p ON p.user_id = m.user_id
      JOIN topics t ON t.id = p.topic_id
      WHERE t.archetype = 'regular'
          AND p.created_at >= :start_date
          AND p.created_at <= :end_date
      GROUP BY p.user_id
),
      
pm_count AS (
      SELECT p.user_id,
        COUNT(*) AS pm_count
      FROM mods m
      JOIN posts p ON p.user_id = m.user_id
      JOIN topics t ON t.id = p.topic_id
      WHERE t.archetype = 'private_message'
          AND p.created_at >= :start_date
          AND p.created_at <= :end_date
      GROUP BY p.user_id
),

reviewable_actions AS (
    SELECT
        rs.reviewed_by_id AS user_id,
        SUM(CASE WHEN rs.status = 1 THEN 1 ELSE 0 END) AS posts_approved,
        SUM(CASE WHEN rs.status = 2 THEN 1 ELSE 0 END) AS posts_rejected
    FROM mods m
    JOIN reviewable_scores rs ON rs.reviewed_by_id = m.user_id
    JOIN reviewables r ON r.id = rs.reviewable_id
    WHERE rs.reviewed_at >= :start_date
      AND rs.reviewed_at <= :end_date
    GROUP BY rs.reviewed_by_id
)

SELECT
    m.user_id,
    m.username,
    fc.flag_count as flags_reviewed,
    ROUND(((tr.time_read) / 3600.00),2) as time_reading_hours,
    tc.topic_count as topics_created,
    pmc.pm_count as PMs_created,
    pc.post_count as posts_created,
    rc.revision_count as revisions,
    ra.posts_approved,
    ra.posts_rejected
FROM mods m
LEFT JOIN time_read tr ON tr.user_id = m.user_id
LEFT JOIN flag_count fc ON fc.user_id = m.user_id
LEFT JOIN revision_count rc ON rc.user_id = m.user_id
LEFT JOIN topic_count tc ON tc.user_id = m.user_id
LEFT JOIN post_count pc ON pc.user_id = m.user_id
LEFT JOIN pm_count pmc ON pmc.user_id = m.user_id
LEFT JOIN reviewable_actions ra ON ra.user_id = m.user_id
ORDER BY m.username ASC

Результаты этого отчёта будут выглядеть следующим образом:

user username flags_reviewed time_reading_hours topics_created pms_created posts_created revisions posts_approved posts_rejected
1 moderator1 NULL 36.11 NULL 344 8 15 10 5
2 moderator2 46 104.52 2 271 466 363 7 3
3 moderator3 NULL 72.15 NULL 418 64 16 NULL NULL

Это отлично, спасибо @SaraDev!

Будут ли эти столбцы добавлены в отчёт о деятельности модераторов на вкладке /admin/dashboard/moderation в ближайшее время?

Спасибо ещё раз :slight_smile:
T_Disco

@SaraDev
Возможно ли выполнить запрос на количество слияний тем, проведённых за определённый период времени?

На данный момент у нас нет планов добавлять эти столбцы в версию отчёта на панели управления, но я могу обсудить это с нашей командой, чтобы посмотреть, можно ли внести улучшения в будущем. :slightly_smiling_face:

Да, чтобы создать запрос, показывающий, когда сообщения были перемещены в другие темы, вы можете отфильтровать таблицу posts для записей, где action_code указывает на перемещение с записью split_topic.

Например:

Действия по перемещению отдельных сообщений

SELECT 
    id AS post_id,
    user_id,
    topic_id,
    post_number,
    created_at::date,
    updated_at::date,
    action_code
FROM 
    posts
WHERE 
    action_code = 'split_topic'
ORDER BY
    created_at DESC

Результаты будут выглядеть примерно так:

post user topic post_number created_at updated_at action_code
Сообщение было объединено с существующей темой:: [Название объединённой темы] USERNAME Original_Topic_Title 3 2024-10-30 2024-10-30 split_topic
2 сообщения были объединены с существующей темой:: [Название объединённой темы] USERNAME Original_Topic_Title 5 2024-10-30 2024-10-30 split_topic
Сообщение было разделено на новую тему: [Название новой темы] USERNAME Original_Topic_Title 2 2024-10-30 2024-10-30 split_topic

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

Количество действий по перемещению сообщений на пользователя

-- [params]
-- date :start_date
-- date :end_date

SELECT 
    user_id,
    COUNT(*) AS move_count
FROM 
    posts
WHERE 
    action_code = 'split_topic'
    AND created_at BETWEEN :start_date AND :end_date
GROUP BY 
    user_id
ORDER BY 
    move_count DESC

Пример результатов будет выглядеть так:

user move_count
Username_1 5
Username_2 2

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

Количество перемещённых сообщений во время каждого действия можно увидеть в запросе Действия по перемещению отдельных сообщений в столбце post в тексте X сообщений были объединены с существующей темой..., однако эта информация отсутствует во втором запросе.

В запросе Действия по перемещению отдельных сообщений вы также можете увидеть сообщения с текстом: Сообщение было разделено на новую тему ..., что указывает на то, что сообщение было разделено на новую тему, а не перемещено в существующую, поскольку Discourse считает оба этих действия действиями split_topic, так как сообщения перемещаются из одной темы в другую.