Récupérer des sujets basés sur un champ personnalisé ?

En utilisant le guide très utile de @angus, j’ai pu ajouter des champs personnalisés aux sujets et aux groupes.

Une fois qu’un champ personnalisé a été ajouté avec succès, existe-t-il un moyen (efficace) de récupérer des éléments en fonction de ce champ personnalisé ?

Par exemple, supposons que j’ajoute le champ personnalisé fun_level aux sujets. Il y a donc maintenant le champ topic.fun_level (une chaîne de caractères) ajouté via mon plugin.

Maintenant, je veux récupérer et afficher une liste de sujets contenant tous les sujets où fun_level = « super-duper-fun ».

Comment pourrais-je faire cela ?


Si nous voulons récupérer tous les sujets ayant, par exemple, un certain tag, nous pouvons utiliser ajax('/tags/tag-name.json').then(function(result))..., comme illustré dans ce post explicatif.

Pour un champ personnalisé que nous venons de créer, cela ne serait pas disponible (je pense). Cela nécessiterait en effet de créer un contrôleur pour le champ personnalisé (comme un contrôleur pour fun_level, avec une méthode show qui récupère d’une manière ou d’une autre tous les sujets où fun_level correspond à la valeur du paramètre :fun_level, ou quelque chose de similaire).

Je suppose qu’il existe une méthode plus directe ?

1 « J'aime »

Je pense avoir trouvé une méthode pour récupérer des sujets basés sur un champ personnalisé : les rechercher et récupérer les résultats.

Voici un exemple, en imaginant que vous ayez un bouton avec l’action searchForTopics() quelque part, et que vous cherchiez à obtenir les sujets dont le champ personnalisé fun_level est égal à “super-duper-fun” :

(cela s’intégrerait dans du code JS ES6, comme un initialiseur) :

import Topic from 'discourse/models/topic'; // pas nécessaire selon le code ci-dessous, mais probablement pertinent pour d'autres actions connexes que vous souhaiteriez entreprendre
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 => {
                   // juste pour obtenir une liste des noms de sujets
                    console.log('nom du sujet = ')
                    console.log(topic)
                })
            })
        }
    }
}

Cela fonctionne. Mais est-ce la méthode la plus efficace ?

Par exemple, cette méthode, qui utilise la recherche, est-elle aussi efficace que la méthode utilisée par Discourse pour afficher les sujets correspondant à un certain tag lorsque vous accédez à la page de ce tag ?

1 « J'aime »

La méthode à suivre est la suivante :

  1. Côté client : ajoutez un paramètre de requête topic en utilisant api.addDiscoveryQueryParam

  2. Côté serveur : filtrez les requêtes topic par ce paramètre en utilisant add_custom_filter dans la classe TopicQuery (voir lib/topic_query)

Le callback add_custom_filter ressemblera à ceci :

::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 « J'aime »

MODIF : Après avoir examiné de plus près api.addDiscoveryQueryParam, je pense comprendre l’idée générale :

Je souhaite récupérer programmatiquement tous les sujets avec le champ personnalisé fun_level = super-duper-fun. Je pense qu’une méthode de contrôleur pourrait le faire ? (je suis encore en train de comprendre ça).

Une alternative consiste à effectuer une recherche via ajax("/search") en recherchant tous les sujets basés sur le champ personnalisé fun_level=super-duper-fun. Cependant, créer le champ personnalisé ne suffit pas pour activer cette fonctionnalité. Je dois rendre le champ personnalisé fun_level l’un des champs contre lesquels on peut rechercher (tout comme on peut rechercher dans certaines catégories, tags, etc.), et cela ne se fait pas automatiquement.

D’une certaine manière, api.addDiscoveryQueryParam dans un fichier JS, combiné à TopicQuery dans plugin.rb, est nécessaire pour y parvenir. Mais, honnêtement, je n’ai pas encore réussi à faire fonctionner cela. J’ai vu certains plugins utiliser ces méthodes, mais je n’ai pas réussi à comprendre comment ils « finalisent » le tout. Je pense qu’un code supplémentaire est nécessaire, mais je ne l’ai pas encore trouvé.

Comment passer de ces méthodes à la disponibilité effective du champ personnalisé en tant que terme de recherche ?

Réponse précédente

Merci, @angus. Pour clarifier, l’objectif n’est pas que les utilisateurs saisissent manuellement des valeurs de recherche dans la zone de recherche. L’objectif est de récupérer programmatiquement des sujets basés sur un certain champ personnalisé. Par exemple, l’utilisateur se rendrait sur la page /fun_levels/super-duper-fun pour charger tous les sujets où le champ fun_level = ‘super-duper-fun’.

Est-ce que api.addDiscoveryQueryParam sert à cet effet ?

En examinant des exemples comme ici, je ne suis pas sûr de comprendre comment addDiscoveryQueryParam fonctionne pour récupérer réellement les sujets (je ne pense pas que l’appel de cette méthode retourne des résultats que je puisse analyser).

Peut-être est-ce pour permettre potentiellement à l’utilisateur de rechercher manuellement le terme dans la zone de recherche ? Ce n’est pas la situation que je vise. (Je pourrais tout à fait manquer quelque chose).

J’ai mentionné plus tôt l’utilisation de ajax("/search…") car c’est la meilleure solution à laquelle j’ai pensé jusqu’à présent pour retourner des sujets, mais je me demande s’il existe une méthode plus efficace, y compris éventuellement la mise en place d’un modèle et d’une méthode de contrôleur pour afficher automatiquement les sujets, comme le fait tags/:tag-name (c’est plus complexe, donc j’espère l’éviter, mais si c’est la meilleure solution, je l’envisagerai).

La meilleure approche ici dépend de votre objectif final.

Comment envisagez-vous que cela fonctionne ? Comme une option dans la barre latérale de /search ?

Salut @angus. L’objectif n’est pas d’ajouter une requête dans la barre de recherche latérale (ce serait bien, mais ce n’est pas le but ici). Le but est de charger programmatiquement des sujets basés sur un champ personnalisé dans une liste de sujets lorsque l’utilisateur visite une page. Je pense avoir résolu la partie modèle/composant (c’est-à-dire la vue). Je tente maintenant de comprendre la logique qui chargera les sujets.

La raison pour laquelle je parlais de recherche est que j’ai pensé que lancer ajax("/search") avec custom_field=valeur lors de la visite de la page pourrait être une méthode élégante pour charger les sujets. Mais j’essaie simplement de trouver la solution qui fonctionne le mieux.


Plus de détails :

Dans mon cas, le premier objectif est que l’utilisateur aille vers une nouvelle page de modèle que j’ai créée à un nouveau chemin (/fun_levels/:fun_level), et de charger tous les sujets dont le champ personnalisé fun_level correspond à :fun_level.

J’ai déjà trouvé comment créer le modèle et le charger à ce chemin. Maintenant, je veux charger programmatiquement les sujets correspondants dans le composant topic-list que j’ai sur la page.

Idéalement, j’aimerais éviter de devoir créer un nouveau modèle “fun_level” (ce que je n’ai pas encore fait), simplement pour garder les choses plus simples et plus rapides à mettre en œuvre. Mais je suis ouvert à cette option si cela s’avère inévitablement beaucoup plus performant (c’est une page qui sera beaucoup utilisée).


Savoir comment ajouter la possibilité d’avoir “fun_level” comme option dans la barre de recherche latérale serait également utile, car je pense que je voudrais aussi cette fonctionnalité. Et peut-être que la meilleure façon de charger des sujets basés sur le champ personnalisé est d’ajouter ce champ aux options de recherche, puis d’appeler ajax("/search") avec la requête étant fun_level: super-duper-fun.

Les éléments liés à la recherche pourraient donc être importants ici. Mais la tâche principale pour le moment est de charger des sujets sur une page en fonction d’un champ personnalisé lorsque l’utilisateur visite cette page.

Cette page affichant une liste de sujets doit-elle ressembler aux pages de liste de sujets existantes dans Discourse, ou est-elle substantiellement différente ?

2 « J'aime »

Fondamentalement différent. Mais l’objectif ici est simplement de savoir comment charger des sujets ayant une valeur de champ personnalisé (par exemple, fun_level = ‘super-duper-fun’) dans le composant {{topic-list topics=selectedTopics showPosters=true}} que j’insère dans la page.

Lorsque vous travaillez avec des listes de sujets, la question est de savoir si vous étendez la structure de découverte existante de Discourse ou si vous créez la vôtre, ce qui modifie l’implémentation. Il existe de nombreuses questions connexes à ce que vous cherchez à faire, que nous passons sous silence ici.

Donc, si vous n’étendez pas la structure de découverte, vous ne ferez pas la première partie de ce que j’ai suggéré ci-dessus. Vous devriez toutefois toujours faire la deuxième partie : ajouter le filtre personnalisé à TopicQuery. Vous aurez également besoin d’une route côté client avec un appel AJAX vers un point de terminaison mappé sur list_controller.rb. Vous pouvez trouver les routes du contrôleur de liste dans config/routes.rb en recherchant list#.

Vous devez utiliser le même point de terminaison de liste de sujets que celui utilisé par la découverte de Discourse, car vous obtiendrez ainsi des fonctionnalités telles que la pagination (gérée par le chargement au défilement dans le composant de liste de sujets), la gestion des permissions et bien d’autres choses encore, directement intégrées.

Vous aurez donc besoin de :

  1. plugin.rb contenant le filtre personnalisé
  2. Un fichier de route côté client
  3. Un modèle côté client
1 « J'aime »

Merci pour ces informations.

Je suis tout à fait d’accord pour adopter la méthode la plus simple afin de récupérer les sujets correspondant à la valeur du champ personnalisé. J’ai déjà une route/chemin/modèle fonctionnel en place à l’adresse /fun_levels/:fun_level, qui charge le composant {{topic-list}}. Encore une fois, si cette méthode consiste à effectuer une recherche sur la valeur de ce champ personnalisé (ajax(/search)), cela fonctionne aussi. Je pense de plus en plus que c’est la voie la plus directe. Je n’ai simplement pas encore réussi à faire fonctionner cela.

Pour préciser, ma méthode actuelle consiste à :

  1. Récupérer les sujets via une requête AJAX (je dois juste déterminer quel est le bon point de terminaison et comment le configurer — c’est là que réside la clé),
  2. Analyser le résultat, et
  3. Exécuter component.set('showTopics', parsed-result) pour charger les sujets dans {{topic-list topics=showTopics}}.

Cette approche me semble un peu mystérieuse. Je vois les méthodes dans le list_controller, comme def topics_by, mais comment puis-je prendre l’une de ces méthodes et la modifier pour qu’elle renvoie des sujets basés sur la valeur d’un champ personnalisé ?

Je vous déconseillerais cette approche pour plusieurs raisons. Désolé d’être mystérieux, mais il me faudrait un peu plus de temps que je n’en ai actuellement pour expliquer tout cela en détail.

La méthode la plus simple consiste à utiliser l’un des points de terminaison existants dans le contrôleur de listes. Ils sont déjà configurés pour servir des listes de sujets. Vous pouvez les trouver dans routes.rb, mais en bref, ce sont les filtres /latest, /top, etc. Pour une liste avec un filtre personnalisé, vous voudrez utiliser un paramètre de requête comme ceci :

\n/latest?fun_level=5\n

En utilisant le filtre personnalisé. Vous pouvez suivre la classe TopicQuery depuis list_controller.rb pour voir comment elle fonctionne, par exemple, elle ajoute votre filtre personnalisé en tant que paramètre pris en charge par le contrôleur. La raison pour laquelle cela semble mystérieux est que ce contrôleur et cette classe gèrent pour vous un certain nombre de choses comme la pagination et différents filtres, ce que vous devrez configurer manuellement si vous optez pour une autre méthode.

L’“autre méthode” qui aurait du sens (sans utiliser /search, bien entendu), consisterait à configurer votre propre contrôleur dédié pour la route, qui utilise la classe TopicQuery comme le fait list_controller.rb. Vous devrez créer un contrôleur Rails si vous ajoutez une route complètement nouvelle de toute façon, donc c’est une autre approche plausible, bien que vous devrez gérer des éléments comme la pagination vous-même. Vous devriez toujours utiliser un filtre personnalisé si vous adoptez cette approche.

Je comprends que tout cela puisse sembler opaque, mais vous êtes en train de traiter une fonctionnalité complexe. Pour expliquer cela en détail, il me faudrait rédiger un cours en 10 parties. Ce que je pourrais d’ailleurs faire bientôt :slight_smile:

2 « J'aime »

MISE À JOUR : Je pense que j’ai (presque) réussi (!). Maintenant, cela affichera les sujets les plus récents correspondant à la valeur du champ personnalisé. (la méthode #latest était la plus proche que j’ai pu trouver qui avait du sens dans le fichier config/routes.rb).

Il est important que, en réalité, tous les sujets ayant la valeur pertinente du champ personnalisé fun_level soient chargés sur la page. Y a-t-il autre chose que je dois faire pour que cela se produise ?


Voici le code pour mes propres notes et au cas où cela serait utile à d’autres :

–J’ai créé le champ personnalisé :fun_level. Ensuite :

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 (un fichier JavaScript activé lors de la navigation vers la page concernée. Ainsi, ce JavaScript pourrait se trouver dans un initialiseur ou dans un connecteur reliant un plugin outlet. J’aime utiliser du code associé à un connecteur, alors j’utiliserai ici setupComponent) :

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

export default {
    setupComponent(args, component) {
      let parsedResultArray = []
      var endPoint = '/latest?fun_level=' + funLevel  //funLevel = variable avec la valeur des paramètres   
      ajax(endPoint).then(function (result) {
            console.log('liste des résultats de sujets correspondant à ce niveau de fun = ')
            console.log(result.topic_list.topics)
            //analyser les résultats et les charger dans parsedResultArray
            component.set('showTopics', parsedResultArray        
      })
   }
}

Maintenant, les sujets seront chargés dans {{topic-list topics=showTopics}} que j’ai dans le composant correspondant, lequel est inséré dans le modèle via my-plugin-outlet.

C’est une grande avancée. Merci beaucoup, @angus.

4 « J'aime »

Avec les instructions fournies ci-dessus (grâce à @angus et @JQ331), j’ai pu récupérer avec succès les sujets en donnant une valeur pour le champ de sujet personnalisé en visitant https://domain.com/latest?custom_field=custom_field_value

Cependant, à partir de cette page, si je clique sur le logo du site (ou sur le bouton Latest dans la barre supérieure), le paramètre de requête (custom_field) est supprimé de l’URL, mais les sujets restent filtrés sur le champ personnalisé.

Au rafraîchissement, la page fonctionne comme prévu (c’est-à-dire qu’elle affiche tous les derniers sujets).

Comment puis-je corriger ce comportement ?

1 « J'aime »