Relatório do Painel - Atividades do Moderador

Este é um relatório de painel em versão SQL para atividade de moderador.

Este relatório fornece uma visão geral abrangente das atividades realizadas pelos moderadores dentro de um período de tempo especificado em um site Discourse. O relatório combina múltiplos aspectos da atividade do moderador: tempo gasto na plataforma, número de sinalizações revisadas, postagens criadas, mensagens pessoais (PMs) criadas, tópicos criados e revisões de postagens feitas.

Este relatório de painel é uma ferramenta valiosa para administradores que buscam medir a eficácia e o engajamento de sua equipe de moderação, fornecendo uma visão detalhada de suas atividades e contribuições. As informações fornecidas pelo relatório podem informar decisões sobre treinamento, reconhecimento e recrutamento de moderadores, garantindo que a equipe de moderação seja equilibrada e eficaz na manutenção dos padrões da comunidade.

--[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

Parâmetros

  • :start_date e :end_date - Estes parâmetros definem o intervalo de datas para o relatório. Ambos os parâmetros de data aceitam o formato de data AAAA-MM-DD.
  • :include_admins - Este parâmetro determina se os administradores devem ser incluídos no relatório juntamente com os moderadores.

Explicação da Consulta SQL

O relatório é estruturado usando expressões de tabela comuns (CTEs) para segmentar o processamento de dados em seções gerenciáveis e lógicas. Veja o que acontece em cada CTE:

  1. mods: Identifica todos os usuários com status de moderador ou status de administrador (se incluído pelo parâmetro :include_admins). Ele seleciona apenas as colunas de usuário relevantes para consultas posteriores.
  2. time_read: Calcula o tempo total (em segundos) que cada moderador passou lendo conteúdo na plataforma entre as datas de início e fim fornecidas.
  3. flag_count: Conta o número de sinalizações que os moderadores concordaram ou discordaram durante o período especificado. Leva em consideração múltiplos tipos de sinalização representados por seus respectivos IDs de tipo de ação de postagem.
  4. revision_count: Conta o número de revisões de postagens feitas por moderadores em postagens de outros usuários dentro do período de tempo fornecido.
  5. topic_count: Conta o número de tópicos regulares criados por moderadores.
  6. post_count: Conta o número de postagens criadas por moderadores em tópicos regulares.
  7. pm_count: Conta o número de mensagens privadas iniciadas por moderadores.

Após coletar os dados nas CTEs, a consulta principal as une com base no ID do usuário e compila o relatório final exibindo o nome de usuário de cada moderador, o tempo total gasto lendo (convertido para horas), o número de sinalizações revisadas, tópicos criados, mensagens pessoais criadas, postagens criadas e revisões feitas. Os resultados são ordenados alfabeticamente pelo nome de usuário do moderador.

Resultados de Exemplo

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
7 curtidas

Olá @SaraDev,

Isso é muito útil. Seria possível adicionar colunas de ‘Posts aprovados’ e ‘Posts rejeitados’ ao relatório de Atividade do Moderador?

Obrigado,
T_Disco

3 curtidas

Olá @T_Disco,

Sim, para adicionar os resultados de Posts aprovados e Posts rejeitados ao relatório de Atividade do Moderador, podemos utilizar as tabelas reviewables e reviewable_scores. Especificamente, o campo status em reviewable_scores pode ser usado para determinar se um post foi aprovado (status = 1) ou rejeitado (status = 2).

Veja como isso ficaria adicionado ao relatório:

Atividade do Moderador com Posts Aprovados e Posts Rejeitados

-- [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

Onde os resultados para este relatório ficariam assim:

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
1 curtida

Isso é brilhante, obrigado @SaraDev!

Essas colunas serão adicionadas ao relatório de Atividade do Moderador na aba /admin/dashboard/moderation em algum momento?

Obrigado novamente :slight_smile:
T_Disco

1 curtida

@SaraDev
É possível uma consulta para o número de fusões de tópicos realizadas em um determinado período?

2 curtidas

Atualmente, não temos planos de adicionar essas colunas à versão do relatório no painel, mas posso discutir isso com nossa equipe para ver se podemos fazer algumas melhorias no futuro. :slightly_smiling_face:

Sim, para criar uma consulta que mostre quando as postagens foram movidas para outros tópicos, você pode filtrar a tabela posts por entradas onde o action_code indica uma movimentação com uma entrada split_topic.

Por exemplo:

Ações Individuais de Movimentação de Postagens

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

Mostraria resultados como:

post user topic post_number created_at updated_at action_code
Uma postagem foi mesclada a um tópico existente:: [Título do Tópico Mesclado] USERNAME Título do Tópico Original 3 2024-10-30 2024-10-30 split_topic
2 postagens foram mescladas a um tópico existente:: [Título do Tópico Mesclado] USERNAME Título do Tópico Original 5 2024-10-30 2024-10-30 split_topic
Uma postagem foi dividida em um novo tópico: [Título do Tópico Dividido] USERNAME Título do Tópico Original 2 2024-10-30 2024-10-30 split_topic

Se você quisesse mostrar uma contagem total de movimentações de postagens para cada usuário em um site e adicionar alguns parâmetros para filtrar as ações de movimentação por data, você também poderia usar uma consulta como a seguinte:

Número de Ações de Movimentação de Postagens por Usuário

-- [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

Onde os resultados de exemplo seriam assim:

user move_count
Username_1 5
Username_2 2

Note que com ambas as consultas, a movimentação de qualquer número de postagens de um tópico para outro é contada como uma ação apenas, independentemente do número de postagens movidas. A movimentação do conteúdo de um tópico inteiro para outro tópico também é contada como uma ação apenas.

O número de postagens movidas durante cada ação pode ser visto na consulta Ações Individuais de Movimentação de Postagens na coluna post com o texto X posts were merged into an existing topic..., no entanto, essa informação não está presente na segunda consulta.

Na consulta Ações Individuais de Movimentação de Postagens, você também pode ver postagens com o texto: A post was split to a new topic ..., que indicam que a postagem foi dividida em um novo tópico em vez de ser movida para um tópico existente, já que o Discourse considera ambas as ações como split_topic, pois postagens estão sendo movidas de um tópico para outro.

2 curtidas

5 posts foram movidos para um novo tópico: Métricas da Página do Usuário