+ Start a Discussion
ZoomzoomZoomzoom 

Updating Knowledge Articles programmatically

All,

 

Because of a problem in a migration of knowledge base articles from a 3rd party system, we need to update a few thousands articles. Unfortunately, Salesforce doesn't allow to programmatically update the article objects, whether through the API or Apex.

 

The solutions seems to be either update manually the solutions (which is hardly feasible considering the number) or wipe all the solutions and start the migration again (except we've already started using some articles).

 

An alternative I'm investigating is to write a program that would generate a huge Windows PowerShell script that would create an Internet Explorer instance and control it to log in and programmatically modify each article, one at a time. But before going that route I was wondering if people have other suggestions.

 

Thanks,

Laurent

 

francoisLfrancoisL

Hi Zoomzoom,

 

We are working on an Publishing API to allow tu update articles. It is scheduled for next year. 

 

In the meantime, I guess that the easiest solution will be to extract all your exsting articles using Read API in format of article importer. Update the one that you need and re-import everything as a new articles.

 

regards,

shockeereshockeere

What is the status on the release of this tool?

 

We just implemented the "Validation Status" field, and need to update this field on a batch of articles that are already validated. Is the best method still to export the articles, edit the bath with a 3rd party tool, and then import the articles as new ones?

francoisLfrancoisL

Hi, 

 

We deliver a full Read-Write API with Summer'12 release. You can see the documentation about it here: http://www.salesforce.com/docs/developer/knowledge_devpre/index.htm

 

Now, it's not a tools, it's an API. If you want to update all articles and re-publish the, you will need to build the tools using this API.

ZoomzoomZoomzoom

@shokeree,

 

We've been using this API, in particular to update one field for 10,000+ articles.

 

The API is a bit unsettling at first (you first need to call the API to edit an article, then update it, then publish it again) and quickly runs into governor limits (so you will need to quickly use an apex job), but I can send you some sample apex code if you need.

 

Laurent

 

shockeereshockeere
Laurent, That would be wonderful - I would love to see some sample code and try to retrofit it to our org. Michael Hallak Software Development Engineer / Administrator Business Systems Tableau Software
shockeereshockeere
Thanks Francois! I will dive into that document. Michael Hallak Software Development Engineer / Administrator Business Systems Tableau Software
ZoomzoomZoomzoom

Here's the code. It requires 3 passes. In the first one you query and edit each article. The second one requires *another* SOQL query, this time looking for the articles as drafts and updating them. The 3rd one republishes everything.

 

Note also that each call to KbManagement.PublishingService is one DML call.

 

List<Id> articleIDs = new List<Id>();
List<String> articleNumbers = new List<String>();

 

for (Article__kav a : [SELECT Id, ArticleNumber, KnowledgeArticleId

                       FROM Article__kav

                       WHERE PublishStatus = 'online' AND Language = 'en_US' AND ...]) {
    String Id = KbManagement.PublishingService.editOnlineArticle(d.KnowledgeArticleId, true);
    if (Id == null) System.debug('##### ERROR EDITING');
    articleNumbers.add(d.ArticleNumber);
}


List<Article__kav> articles = new List<Article__kav>();

for (Article__kav d : [SELECT Id, KnowledgeArticleId

                                FROM Article__kav

                                WHERE PublishStatus = 'draft' AND Language = 'en_US' AND ArticleNumber IN :articleNumbers]) {

    // perform your updates

    articleIDs.add(d.KnowledgeArticleId);
    articles.add(d);
}
update articles;

 

for (String articleId : articleIds) {

    KbManagement.PublishingService.publishArticle(articleId, true);
}

 

StephenDavidsonStephenDavidson

With your code, tweaked only to add a constructor method, I get this error when saving:

 

  Error: Compile Error: sObject type 'Article__kav' is not supported. If you are attempting to use a custom object, be sure to append the '__c' after the entity name. Please reference your WSDL or the describe call for the appropriate names. at line 7 column 31

 

public class myArticles {

    public updateChannel() {
        List<Id> articleIDs = new List<Id>();
        List<String> articleNumbers = new List<String>();

        for (Article__kav a : [SELECT Id, ArticleNumber, KnowledgeArticleId FROM Article__kav WHERE PublishStatus = 'online']) {
            String Id = KbManagement.PublishingService.editOnlineArticle(d.KnowledgeArticleId, true);
            if (Id == null) System.debug('##### ERROR EDITING');
               articleNumbers.add(d.ArticleNumber);
        }


        List<Article__kav> articles = new List<Article__kav>();
        for (Article__kav d : [SELECT Id, KnowledgeArticleId FROM article__kav WHERE PublishStatus = 'draft' AND Language = 'en_US' AND ArticleNumber IN :articleNumbers]) {

            // perform your updates

            articleIDs.add(d.KnowledgeArticleId);
            articles.add(d);
        }
        update articles;
    
        for (String articleId : articleIds) {
            KbManagement.PublishingService.publishArticle(articleId, true);
        }
    }
}



Do you see why?

ZoomzoomZoomzoom

It looks like your org doesn't recognize the object Article__kav.

 

Do you have an "Article" article type, or is it called something else? If you look at all the objects in your org (using either the Force.com IDE or the apex Data Loader and select all objects), can you see it?

StephenDavidsonStephenDavidson

I have gotten further.  Here is my code now...

 

public class MyArticles {

   public MyArticles() {
        List<Id> articleIDs = new List<Id>();
        List<String> articleNumbers = new List<String>();
  system.debug('I have reached this point in the code !');  
        for (case_summary__kav a : [SELECT Id, ArticleNumber, KnowledgeArticleId FROM case_summary__kav WHERE PublishStatus = 'online' and language = 'en_US' and IsVisibleINPkb = false ]) {
            String Id = KbManagement.PublishingService.editOnlineArticle(a.KnowledgeArticleId, true);
            if (Id == null) System.debug('##### ERROR EDITING');
               articleNumbers.add(a.ArticleNumber);
        }

system.debug('I have reached this point in the code2 !');  
        List<case_summary__kav> articles = new List<case_summary__kav>();
        for (case_summary__kav d : [SELECT Id, KnowledgeArticleId FROM case_summary__kav WHERE PublishStatus = 'draft' AND Language = 'en_US' AND ArticleNumber IN :articleNumbers]) {
system.debug('I have reached this point in the code3 !');  
            // perform your updates

            d.IsVisibleInPkb = true;
system.debug('I have reached this point in the code5 !');  
        //

            articleIDs.add(d.KnowledgeArticleId);
            system.debug('I have reached this point in the code6 !');  
            articles.add(d);
            system.debug('I have reached this point in the code7 !');  
        }
        update articles;
    system.debug('I have reached this point in the code8 !');  
        for (String articleId : articleIds) {
            system.debug('I have reached this point in the code9 !');  
            KbManagement.PublishingService.publishArticle(articleId, true);
            system.debug('I have reached this point in the code10 !');  
        }
    }
}

 

I see that I am making progress, but get an exception on too many DML calls in the first for loop.  As the author pointed out, KbManagement.PublishingService uses one dml call.  SInce I have 205 case_summary__kav articles, it fails.  What is best practice that will allow me to update ALL of a particular article type's articles?

 

Thanks for you help, btw.

 

ZoomzoomZoomzoom

I see that using case_summary__kav did the job.

 

As far as the DML governor limit, unfortunately, the only way to get past that is to use an apex job. The Knowledge API at this point does not allow to process several articles in one single DML operation.

StephenDavidsonStephenDavidson

Yup  -- I put limits on the queries and run it multiple times to update all of the articles.  Not so bad.  I tried parameterizing the article type name this morning and got errors.  I want to pass in the article type and insert it into the code.  But the compiler kicks.  Is it possible to replace the hardcoded article types in the queries with the passed in  parameter?

 

public class MyArticles {
 
   public MyArticles(String theParameter) {
        system.debug('the parameter passed is: ' + theParameter);
        List<Id> articleIDs = new List<Id>();
        List<String> articleNumbers = new List<String>();

        String articleType = theParameter + '__kav';
      
          system.debug('the article type value is :' + articleType);
        for (Best_Practices__kav a : [SELECT Id, ArticleNumber, KnowledgeArticleId FROM Best_Practices__kav WHERE PublishStatus = 'online' and language = 'en_US' and IsVisibleINPkb = false limit 100 ]) {
            String Id = KbManagement.PublishingService.editOnlineArticle(a.KnowledgeArticleId, true);
            if (Id == null) System.debug('##### ERROR EDITING');
               articleNumbers.add(a.ArticleNumber);
        }

        List<Best_Practices__kav> articles = new List<Best_Practices__kav>();
        for (Best_Practices__kav d : [SELECT Id, KnowledgeArticleId FROM Best_Practices__kav WHERE PublishStatus = 'draft' AND Language = 'en_US' AND ArticleNumber IN :articleNumbers limit 100]) {

            // perform your updates

            d.IsVisibleInPkb = true;

            //

            articleIDs.add(d.KnowledgeArticleId);

            articles.add(d);

        }
        update articles;
        
        
        
   

    }
}

 

ZoomzoomZoomzoom

The best way to do that is to use dynamic SOQL, e.g.

 

public MyArticles(String theParameter) {

    String query = 'SELECT Id, ArticleNumber, KnowledgeArticleId FROM '  + theParameter + '__kav WHERE PublishStatus = 'online' and language = 'en_US' and IsVisibleINPkb = false limit 100';

    for (sObject obj : Database.query(query)) {

        ...

    }

 

    query = 'SELECT Id, KnowledgeArticleId FROM ' + theParameter + '__kav WHERE PublishStatus = 'draft' AND Language = 'en_US' AND ArticleNumber IN :articleNumbers limit 100';

    for (sObject obj : Database.query(query)) {

        ...

    }

 

Note that dynamic SOQL does recognize the :parameter notation.

StephenDavidsonStephenDavidson

Nice.  Thanks.  I will try this.  I have another issue but will post separately

Piotr GajekPiotr Gajek

Hi guys, 

I had the same problem. 

More details on my blog here (https://salesforceprofs.com/updating-knowledge-articles-programmatically-by-apex/).

Here you can find a code snippet and example of use:
 

public with sharing class ArticlesUtils {

    @AuraEnabled
    public static List<Knowledge__kav> getAllArticles(){
        return [ SELECT Id, KnowledgeArticleId, Title, UrlName FROM Knowledge__kav ];
    }

    @AuraEnabled
    public static String createNewArticleAsADraft(String title, String urlName) {

        Knowledge__kav newArticle = new Knowledge__kav();
        newArticle.Title = title;
        newArticle.UrlName = urlName;
        insert newArticle;

        return [SELECT KnowledgeArticleId FROM Knowledge__kav WHERE Id =: newArticle.Id].KnowledgeArticleId;
    }

    @AuraEnabled
    public static void publishArticle(String recordId) { //It need to be KnowledgeArticleId
        KbManagement.PublishingService.publishArticle(recordId, true);
    }

    @AuraEnabled
    public static String unPublishArticle(String recordId){ //It need to be KnowledgeArticleId
        String newArticleId = KbManagement.PublishingService.editOnlineArticle(recordId, true); //Method new version id use it to update 
        return [SELECT KnowledgeArticleId FROM Knowledge__kav WHERE Id =: newArticleId].KnowledgeArticleId;
    }

    @AuraEnabled
    public static String updateDraftArticleWithoutPublish(String title, String urlName, Id recordId) {

        Knowledge__kav newArticle = [ SELECT Id, KnowledgeArticleId, Title, UrlName FROM Knowledge__kav WHERE KnowledgeArticleId =: recordId ];   

        newArticle.Title = title;
        newArticle.UrlName = urlName;

        update newArticle;

        return newArticle.KnowledgeArticleId;
   }

    @AuraEnabled
    public static String updatetArticle(String title, String urlName, Id recordId) {

        String newVersionId = unPublishArticle(recordId);

        Knowledge__kav newArticle = [ SELECT Id, KnowledgeArticleId, Title, UrlName FROM Knowledge__kav WHERE KnowledgeArticleId =: newVersionId ];   

        newArticle.Title = title;
        newArticle.UrlName = urlName;

        update newArticle;

        publishArticle(newVersionId);

        return newVersionId;
   }

}
 
List<Knowledge__kav> articles = ArticlesUtils.getAllArticles();
 
String newArticleKnowledgeId = ArticlesUtils.createNewArticleAsADraft('SalesforceProfs', 'salesforce-profs');
 
ArticlesUtils.publishArticle(newArticleKnowledgeId);
 
//unpublish, update, publish > separate actions
String newArticleVersionId = ArticlesUtils.unPublishArticle(newArticleKnowledgeId);
ArticlesUtils.updateDraftArticleWithoutPublish('SalesforceProfs Update', 'salesforce-profs-update', newArticleVersionId);
ArticlesUtils.publishArticle(newArticleVersionId);
 
//update - contain unpublish, update, publish
//ArticlesUtils.updatetArticle('SalesforceProfs Update', 'salesforce-profs-update', newArticleKnowledgeId);

Regards,

Piotrek

Martin TremblayMartin Tremblay
Hi Guys

i have a web service reference to Enterprise wsdl in my .net app (SOAP API), and i cannot update the "PublishStatus" of a knowledge article to "Online". It give me an error about checking that i have read\write access to that field , which we have. Now i saw this post and it give me some hope , but can you please help me by answering the following question:
If i want to be able to use the Apex methods shown above in this post (such as KbManagement.PublishingService.publishArticle(recordId, true);), do i only need to get an Apex wsdl generated fror me by the admin, and then adding a new web service reference(Soap API) to that wsdl, will i be able to access the above methods?? Or is there anything else required to be setup in SF?

thanks in advance for any help
Martin