Recuperar Tópicos com base em campo personalizado?

Usando o guia muito útil do @angus, consegui adicionar campos personalizados a tópicos e grupos.

Depois de adicionar com sucesso um campo personalizado, existe uma maneira (eficiente) de recuperar itens com base nesse campo personalizado?

Por exemplo, digamos que eu adicionei o campo personalizado fun_level aos tópicos. Agora, existe o campo topic.fun_level (uma string) adicionado através do meu plugin.

Agora, quero recuperar e exibir uma lista de tópicos com todos os tópicos onde fun_level = “super-duper-fun”.

Como eu faria isso?


Se quisermos recuperar todos os tópicos com, digamos, uma determinada tag, podemos usar ajax('/tags/tag-name.json').then(function(result))..., como usado em este post explicativo.

Para um campo personalizado que acabamos de criar, isso não estaria disponível (eu acho). Isso exigiria a criação de um controlador para o campo personalizado (como um controlador para fun_level, com um método show que de alguma forma recupera todos os tópicos onde fun_level é igual ao valor do parâmetro :fun_level, ou algo assim).

Acho que deve haver uma maneira mais direta?

1 curtida

Acho que descobri um método para recuperar tópicos com base em um campo personalizado: pesquisá-los e obter os resultados.

Aqui está um exemplo, imaginando que você tenha um botão com a ação “searchForTopics()” em algum lugar e esteja tentando obter tópicos com o campo personalizado fun_level igual a “super-duper-fun”:

(isso iria em algum código JS ES6, como um inicializador):


import Topic from 'discourse/models/topic'; //não é necessário com base no código abaixo, mas provavelmente relevante para outras ações relacionadas que você gostaria de realizar
import { ajax } from 'discourse/lib/ajax';

export default {
    actions: {
        checkTopic(){
            let custom_field_value = 'super-duper-fun'
            let searchTerm = 'fun_level:' + custom_field_value
            let args = { q: searchTerm }
            ajax("/search", { data: args }).then(results => {
                let topics = results.topics
                topics.forEach(topic => {
                   //apenas para obter uma lista dos nomes dos tópicos
                    console.log('nome do tópico = ')
                    console.log(topic)
                })
            })
        }
    }
}

Isso funciona. Mas será que essa é a maneira mais eficiente de fazer isso?

Por exemplo, esse método, usando a pesquisa, é tão eficiente quanto o método que o Discourse usa para mostrar tópicos que correspondem a uma determinada tag quando você acessa a página dessa tag?

1 curtida

A maneira de fazer isso é:

  1. No cliente: Adicione um parâmetro de consulta de tópico usando api.addDiscoveryQueryParam

  2. No servidor: Filtre as consultas de tópicos pelo parâmetro usando add_custom_filter na classe TopicQuery (veja lib/topic_query)

O callback add_custom_filter ficará mais ou menos assim:

::TopicQuery.add_custom_filter(:field_name) do |topics, query|
  if query.options[:field_name]
    topics.where("topics.id in (
      SELECT topic_id FROM topic_custom_fields
      WHERE (name = 'field_name')
      AND value = '#{query.options[:field_name]}'
    )")
  else
    topics
  end
end
3 curtidas

EDIT: Tendo investigado mais a fundo api.addDiscoveryQueryParam, acho que agora entendi a ideia geral:

Quero recuperar programáticamente todos os tópicos com o campo personalizado fun_level = super-duper-fun. Acredito que talvez um método de controlador possa fazer isso? (ainda estou descobrindo como).

Uma alternativa é fazer uma busca com ajax("/search"), onde estou pesquisando todos os tópicos com base no campo personalizado fun_level=super-duper-fun. Mas criar o campo personalizado não é suficiente para habilitar isso. Preciso tornar o campo personalizado fun_level um dos campos contra os quais é possível pesquisar (assim como você pode pesquisar em certas categorias, tags, etc.), e isso não é feito automaticamente.

De alguma forma, api.addDiscoveryQueryParam em um arquivo JS, combinado com TopicQuery em plugin.rb, é necessário para fazer isso. Mas, para ser honesto, ainda não consegui fazer funcionar. Já vi alguns plugins que usam esses métodos, mas não consegui entender como eles “concluem o trabalho”. Acredito que algum código adicional seja necessário, mas ainda não o encontrei.

Como se chega desses métodos para ter, na prática, o campo personalizado disponível como termo de busca?

Resposta Anterior

Obrigado, @angus. Para esclarecer, o objetivo não é fazer com que os usuários insiram manualmente valores de busca na caixa de pesquisa. O objetivo é recuperar programáticamente tópicos com base em um determinado campo personalizado. Por exemplo, o usuário acessaria a página /fun_levels/super-duper-fun e carregaria todos os tópicos onde o campo fun_level = ‘super-duper-fun’.

api.addDiscoveryQueryParam serve para esse propósito?

Analisando exemplos como este, não tenho certeza de como addDiscoveryQueryParam funciona para realmente recuperar os tópicos (não acho que chamar esse método retorne resultados que eu possa analisar).

Talvez seja para permitir, potencialmente, que o usuário pesquise manualmente pelo termo na caixa de busca? Essa não é a situação que estou visando. (É perfeitamente possível que eu esteja perdendo algo).

Mencionei anteriormente o uso de ajax("/search..."), pois é o melhor que consegui até agora para retornar tópicos, mas estou me perguntando se existe uma maneira mais eficiente de fazer isso, inclusive configurando um modelo e um método de controlador para exibir tópicos automaticamente, como faz /tags/:tag-name (isso é mais complexo, então espero evitar, mas se for a melhor opção, considerarei).

A melhor abordagem aqui depende do seu objetivo final.

Como você imagina que isso funcionaria? Como uma opção na barra lateral em /search?

Olá @angus. O objetivo não é adicionar uma consulta na barra lateral de pesquisa (isso seria legal, mas não é o objetivo aqui). O objetivo é carregar programaticamente tópicos com base em um campo personalizado em uma lista de tópicos quando o usuário visita uma página. Acredito que já resolvi a parte do modelo/componente (ou seja, a visualização). Agora estou tentando descobrir a lógica que carregará os tópicos.

A razão para falar sobre pesquisa foi minha ideia de que executar ajax("/search") para custom_field=valor ao visitar a página poderia ser uma maneira limpa de carregar os tópicos. Mas estou apenas tentando descobrir qual é a melhor maneira de fazer isso funcionar.


Mais detalhes:

No meu caso, o primeiro objetivo é fazer com que o usuário acesse uma nova página de modelo que criei em um novo caminho (/fun_levels/:fun_level) e carregue todos os tópicos com o campo personalizado fun_level correspondente a :fun_level.

Já descobri separadamente como criar o modelo e carregá-lo no caminho. Agora quero carregar programaticamente os tópicos correspondentes no componente topic-list que tenho na página.

Idealmente, eu evitaria a necessidade de criar um novo modelo “fun_level” (o que ainda não fiz), apenas para manter as coisas mais diretas e mais rápidas de implementar. Mas estou aberto a isso se, inevitavelmente, for significativamente mais performático (esta é uma página que será muito utilizada).


Seria bom saber também como adicionar a capacidade de ter “fun_level” como uma opção na barra lateral de pesquisa, pois espero querer isso também. E talvez a melhor maneira de carregar tópicos com base no campo personalizado seja adicionar o campo personalizado às opções de pesquisa e, em seguida, chamar ajax("/search") com a consulta sendo "fun_level: super-duper-fun".

Então, as coisas relacionadas à pesquisa podem ser importantes aqui. Mas a tarefa principal agora é carregar tópicos em uma página com base em um campo personalizado quando o usuário visita essa página.

Essa página com uma lista de tópicos deve parecer semelhante às páginas de lista de tópicos existentes no Discourse, ou é substancialmente diferente?

2 curtidas

Substancialmente diferente. Mas o foco aqui é apenas como carregar tópicos que possuem um valor de campo personalizado (digamos, fun_level = ‘super-duper-divertido’) no componente {{topic-list topics=selectedTopics showPosters=true}} que estou inserindo na página.

Ao lidar com listas de tópicos, a questão é se você estende a estrutura de descoberta existente do Discourse ou se cria a sua própria, o que altera a implementação. Existem muitas questões relacionadas ao que você está procurando fazer que estamos pulando aqui.

Portanto, se você não estiver estendendo a estrutura de descoberta, não fará a primeira parte do que sugeri acima. No entanto, você ainda deve fazer a segunda parte: adicionar o filtro personalizado ao TopicQuery. Você também precisará de uma rota no lado do cliente com uma chamada AJAX para um ponto de extremidade mapeado no list_controller.rb. Você pode encontrar as rotas do controlador de lista em config/routes.rb procurando por list#.

Você deve usar o mesmo ponto de extremidade de lista de tópicos que o Discourse usa para descoberta, pois assim obterá recursos como paginação (tratada pelo carregamento ao rolar no componente de lista de tópicos), controle de permissões e muitas outras coisas prontas para uso.

Então, você precisará de:

  1. plugin.rb contendo o filtro personalizado
  2. arquivo de rota no lado do cliente
  3. template no lado do cliente
1 curtida

Obrigado pela informação.

Estou feliz em fazer da maneira mais fácil para apenas obter os tópicos que correspondem ao valor do campo personalizado. Já tenho uma rota/caminho/modelo funcionando em /fun_levels/:fun_level, que carrega o componente {{topic-list}}. Novamente, se essa maneira envolver uma busca por esse valor de campo personalizado (ajax(/search)), também funciona. Estou cada vez mais pensando que essa é a maneira mais direta. Só ainda não consegui fazer isso funcionar.

E para esclarecer, meu método atual é:

  1. obter os tópicos via ajax (preciso apenas descobrir qual é o endpoint correto/como configurar esse endpoint—essa é a chave),
  2. analisar o resultado, e
  3. fazer component.set('showTopics', parsed-result), para carregar os tópicos em {{topic-list topics=showTopics}}.

Essa maneira parece um pouco misteriosa para mim. Vejo os métodos no list_controller, como def topics_by, mas como eu pegaria um desses métodos e o modificaria para retornar tópicos com base em um valor de campo personalizado?

Eu aconselharia contra isso por vários motivos. Desculpe ser misterioso, mas explicaria por completo levaria um pouco mais de tempo do que tenho agora.

A maneira mais fácil é usar um dos endpoints existentes no controlador de listas. Eles já estão configurados para servir listas de tópicos. Você pode encontrá-los em routes.rb, mas, em resumo, são os filtros /latest, /top etc. Para uma lista com um filtro personalizado, você usará um parâmetro de consulta assim:

/latest?fun_level=5

Usando o filtro personalizado. Você pode seguir a classe TopicQuery a partir do list_controller.rb para ver como funciona; por exemplo, ela adiciona seu filtro personalizado como um parâmetro suportado ao controlador. A razão pela qual parece misterioso é porque esse controlador e essa classe estão lidando com várias coisas para você, como paginação e filtros diferentes, que você precisaria configurar manualmente se fizer de outra maneira.

A “outra maneira” que faria sentido (não usando /search, note bem), seria configurar seu próprio controlador dedicado para a rota, que usa a classe TopicQuery assim como o list_controller.rb. Você precisará criar um controlador Rails se estiver adicionando uma rota completamente nova de qualquer forma, então essa é outra abordagem plausível, embora você precise lidar com coisas como paginação sozinho. Você ainda deve usar um filtro personalizado se usar essa abordagem.

Entendo que parte disso possa parecer opaco, no entanto você está lidando com funcionalidades complicadas aqui. Para explicar isso por completo, eu precisaria escrever um curso de 10 partes. O que, na verdade, posso fazer em breve :slight_smile:

2 curtidas

ATUALIZAÇÃO: Acredito que consegui (na maior parte) fazer funcionar (!). Agora, isso mostrará os tópicos mais recentes que correspondem ao valor do campo personalizado. (o método #latest foi o mais próximo que encontrei que fazia sentido no arquivo config/routes.rb).

É importante que, de fato, todos os tópicos que possuem o valor relevante do campo personalizado fun_level sejam carregados na página. Há algo mais que eu precise fazer para que isso aconteça?


Aqui está o código para minhas próprias anotações e caso seja útil a outros:

–Criei o campo personalizado :fun_level. Em seguida:

plugin.rb

TopicQuery.add_custom_filter(:fun_level) do |topics, query|
  if query.options[:fun_level]
    topics.where("topics.id in (
      SELECT topic_id FROM topic_custom_fields
      WHERE (name = 'fun_level')
      AND value = '#{query.options[:fun_level]}'
    )")
  else
    topics
  end
end

/connectors/my-plugin-outlet/fun-level.js.es6 (um arquivo JavaScript que é ativado ao acessar a página relevante. Então, esse JavaScript poderia estar em um inicializador ou em um conector ligado a um plugin outlet. Gosto de usar código que acompanha um conector, então usarei um componente de configuração aqui):

const ajax = require('discourse/lib/ajax')

export default {
    setupComponent(args, component) {
      let parsedResultArray = []
      var endPoint = '/latest?fun_level=' + funLevel  //funLevel = variável com valor dos parâmetros   
      ajax(endPoint).then(function (result) {
            console.log('resultado da lista de tópicos para tópicos que correspondem a esse nível de diversão = ')
            console.log(result.topic_list.topics)
            //analisar resultados e carregá-los no parsedResultArray
            component.set('showTopics', parsedResultArray        
      })
   }
}

Agora, os tópicos serão carregados em {{topic-list topics=showTopics}} que tenho no componente correspondente, colocado no template através do my-plugin-outlet.

Este é um grande avanço. Muito obrigado, @angus.

4 curtidas

Com as instruções fornecidas acima (graças a @angus e @JQ331), consegui buscar com sucesso os tópicos dado um valor para o campo de tópico personalizado visitando https://domain.com/latest?custom_field=custom_field_value

No entanto, a partir dessa página, se eu clicar no logotipo do site (ou no botão Mais Recentes na barra superior), ele remove o parâmetro de consulta (custom_field) do URL, mas os tópicos permanecem filtrados no custom_field.

Ao atualizar, a página funciona como esperado (ou seja, exibe todos os tópicos mais recentes).

Como corrijo esse comportamento?

1 curtida