The below custom widget displays knowledge articles in the same topic as the current knowledge article. This widget is for the esc_kb_article_view page.

HTML Template
<div ng-if="data.populatedTopics > 0" class="panel b">
<div class="panel panel-default b panel-wrapper">
<div class="panel-heading b-b">
<div class="panel-title">Articles in same topic</div>
</div>
</div>
<div class="panel-body b-b">
<div class="topic-related-articles">
<div class="topic-container" ng-repeat="topic in data.topics">
<div ng-if="topic.articles" class="topic-title">
<span><strong>{{topic.topic_name}}</strong></span><br>
</div>
<div class="topic-articles" ng-repeat="article in topic.articles">
<div ng-if="article.current == 0">
<a href="{{data.url}}{{article.number}}" target="_blank">{{article.title}}</a><br>
</div>
<div ng-if="article.current == 1">
<span>{{article.title}}</span><br>
</div>
</div>
</div>
</div>
</div>
CSS
.topic-articles {
padding-left: 10px;
padding-bottom:15px;
}
.topic-title{
padding-bottom:15px;
}
Server Script
(function() {
/* populate the 'data' object */
/* e.g., data.table = $sp.getValue('table'); */
//get the article number from the URL parameter
var kbNumber = $sp.getParameter('sysparm_article');
//get the article gliderecord
var kbGR = new GlideRecord('kb_knowledge');
kbGR.get('number',kbNumber);
//get the portal's sys_id
var portalGR = $sp.getPortalRecord();
var portalSysId = '70cd9f3b734b13001fdae9c54cf6a72f' //portalGR.getUniqueValue();
//get the taxonomy for the portal
var taxonomyGR = new GlideRecord('m2m_sp_portal_taxonomy');
taxonomyGR.addQuery('sp_portal',portalSysId);
taxonomyGR.addActiveQuery();
taxonomyGR.query();
if(taxonomyGR.next()){
var taxonomySysId = taxonomyGR.taxonomy.sys_id.getDisplayValue();
}
else {
return;
}
//get all the active topics for the taxonomy
var allTopicsArr = [];
var topicGR = new GlideRecord('topic');
topicGR.addActiveQuery();
topicGR.addQuery('taxonomy',taxonomySysId);
topicGR.query();
while(topicGR.next()){
allTopicsArr.push(topicGR.getUniqueValue());
}
// get all the topics for the current article from the connected content
var articlesMap = {};
var ccGR = new GlideRecord('m2m_connected_content');
ccGR.addQuery('topic','IN',allTopicsArr.join(','));
ccGR.addQuery('knowledge',kbGR.getUniqueValue());
ccGR.query();
while(ccGR.next()){
var topicSysId = ccGR.topic.sys_id.getDisplayValue();
if(!articlesMap[topicSysId]){
var topic = {}
topic.topic_name = ccGR.topic.name.getDisplayValue();
topic.articles = getArticlesInTopic(topicSysId,kbGR.getUniqueValue());
articlesMap[topicSysId] = topic;
}
}
function getArticlesInTopic(topicSysId,currentArticleSysId){
var articles = [];
var articleCount = 0;
var ccArtGR = new GlideRecord('m2m_connected_content');
ccArtGR.addActiveQuery();
ccArtGR.addQuery('topic',topicSysId);
ccArtGR.addQuery('content_type','4c32a92153622010069addeeff7b12a3'); //knowledge
ccArtGR.orderBy('order')
ccArtGR.query();
while(ccArtGR.next()){
var article = {};
article.sys_id = ccArtGR.knowledge.sys_id.getDisplayValue();
article.title = ccArtGR.knowledge.short_description.getDisplayValue();
article.number = ccArtGR.knowledge.number.getDisplayValue();
article.current = ccArtGR.knowledge.sys_id.getDisplayValue() == currentArticleSysId ? 1:0;
articles.push(article);
if(!article.current){
articleCount++;
}
}
if (articleCount > 0){
return articles;
}
else{
return 0;
}
}
//build the url
var url = "/"
url += portalGR.url_suffix.getDisplayValue();
url += "?id=kb_article&sysparm_article=";
//check each topic to make sure there are articles
var populatedTopics = 0;
for (var i in articlesMap){
if(articlesMap[i]['articles']){
populatedTopics++;
}
}
data.topics = articlesMap;
data.url = url;
data.populatedTopics = populatedTopics;
})();
