We're changing the labels on our Ideas Exchange, and would like to find a way to bulk edit the already existing threads to the new labels.
For example, there are a lot of threads that have the label "Company." This label will be changed to "Companies." Is there a way to edit all of the threads that have the "Company" labels to be "Companies"?
thanks!
Solved! Go to Solution.
You can edit labels in Community Admin>Features>Labels>Edit Labels.
Changes made here will update all usage of the label across the community. It can take up to 10 minutes to see all the UI update to reflect this change.
This feature is only available at the community level and can not be used to make changes on a specific node only.
Hi Pam!
Thanks for that! Do you know if there's a way to bulk edit the labels that are on the already existing threads?
For example, there are a bunch of threads with the label "dog". If I change the label "dog" to "cat" in the settings, is there a way to edit all the threads that had the "dog" label to now have the "cat" label?
Thanks!
Changing the label in admin should change the usage of label everywhere on the community. If you change dog to cat, then it will be updated in all existing threads that use it.
Hope this helps.
@JoeMayall if you want to change labels just in a specific idea exchange (or forum, or tkb, or blog) you do that using APIs. I have a script I use quite often for bulk changes to labels in just one node.
Hi! Is there anywhere I can learn more about this?
Message me your email address and I'll send you the script or I've included the code at the bottom of this post. FYI since the 20.5 update, the script has had problems running and I've got a support case open on it since it is a Khoros created script. They're looking into it. If you wanna try it, here you go go.
The script requires Ruby 1,9+ and Curl be installed on your machine.
1. Make sure restapi.enableRefererCheck set to false on the community instance you want to run the script against. Without this check being disabled, you can't authenticate the script using cookie from another session. This can be requested through support temporarily (until the next restart of the instance) by explicitly requesting VAR change (in memory) and not config change. Config change would make the setting disabled permanently or until the config is updated again.
2. Login to your community with an Admin account
3. Copy the session ID from the 'LiSESSIONID' cookie
4. Run reports and/or browse to the topics you want to add or remove labels (or obtain the topic IDs and labels using other means)
5. Prepare the CSV file with the topic ID, action and label - each row will contain one community root (topic) message ID, the action type and the label, separated by commas. For example:
1604938,ADD,label1
1609722,REMOVE,label2
6. Create a command line with this type of format. I keep them in a text file. The benefit of doing that is that if something goes wrong, you can easily switch things around and reverse the script to undo any changes you make:
***Sample Command line:
-------always the same---| URL OF THE COMMUNITY----------|----| Lithium Session ID cookie: LiSESSIONID | Input File(and dir) | Output log File and Directory
7. Run the script against the instance from CMD like these examples formatted like the above:
Copy this code to a whatever.rb file:
# encoding: UTF-8
require 'rexml/document'
require 'logger'
require 'time'
# Ruby script for adding and removing labels inside messages
# Requirements: Ruby, Curl, restapi.enableRefererCheck set to false on the Lithium instance (in order to authenticate with another session ID)
# Usage: ruby update-labels.rb -h <hostname> -s <session ID> <CSV-filename> [> <logfile>]
# Tested with ruby 1.9.3p551(centos),2.2.6p396(win) and curl 7.39.0(centos),7.50.0(win)
# Author: Lithium migration team, Boyana Dobrinska (boyanna.dobrinska@lithium.com)
$log = Logger.new(STDOUT)
#function to output log data on both screen and file if redirected by command line
def echo(output_text, fileonly=0)
if $stdout.tty? && (fileonly==1) then return end
$log.info output_text
if !$stdout.tty? && (fileonly!=1) then
$stderr.puts output_text
end
end
$hostname = "" #full instance hostname URL eg. http://migration3.stage.lithium.com/
$sessionid = "" #Lithium cookie session ID
$filename = "" #CSV filename with the source, target node movement pairs
#checking command line agruments
ARGV.each do|a|
if $hostname == "?" then
$hostname = a
next
end
if $sessionid == "?" then
$sessionid = a
next
end
if a == "-h" then
$hostname = "?"
elsif a == "-s" then
$sessionid = "?"
else
$filename = a.encode('utf-8','windows-1252')
end
end
if $filename.empty? || $hostname.length<4 || $sessionid.length<4 then
$stderr.puts "Usage: ruby #{$PROGRAM_NAME} -h <hostname> -s <session ID> <CSV-filename> [> <logfile>]"
$stderr.puts "Example: ruby #{$PROGRAM_NAME} -h http://migration3.stage.lithium.com/ -s E5F00635A34824446A2854A9D6B1D117 update-labels-test.csv > todays-test.log"
exit
end
#creating the rest urls from the hostname url
REST_URL = "#{$hostname}#{$hostname[-1]!="/" ? "/" : ""}restapi/vc" #ADDING THE REST PART TO THE BASE URL
count_actions = 0
count_errors = 0
start_time = Time.now
begin
echo "STARTING THE PROCESS"
echo "*** VERBOSE MODE *** NO CHANGES WILL BE WRITTEN TO THE COMMUNITY" if $verbose == 1
echo " INSTANCE: #{$hostname}"
echo " SESSIONID: #{$sessionid}"
echo "SOURCE FILE: #{$filename}"
#check file exists
if !File.exist?($filename) then
echo "WARN: FILE '#{$filename}' DOESN'T EXIST."
count_errors += 1
exit
end
#check for authenticated admin
restcall="curl -s -S --retry 15 -k " \
+" --cookie \"LiSESSIONID=#{$sessionid}\" " \
+" #{REST_URL}/authentication/sessions/current/user/roles"
#puts restcall
curl = %x[#{restcall}]
#puts curl
if $?.exitstatus !=0 then
echo "WARN: HTTP communication failed for rest call: #{restcall}. Exit status: #{$?.exitstatus}."
count_errors += 1
exit
end
begin
doc = REXML::Document.new(curl)
rescue REXML::ParseException => msg
echo "WARN: XML Parsing Failed: #{msg.message} (Line:#{msg.backtrace.inspect.match(/(.rb:)(\d+):/)[2]})"
echo "WARN: XML for parsing:\n#{curl}"
count_errors += 1
exit
end
if doc.elements["response"].nil? || (doc.elements["response"].attributes["status"] != "success") then
echo "WARN: REST call failed."
echo "WARN: REST response:\n#{curl}"
count_errors += 1
exit
end
if !/<name type="string">Administrator<\/name>/.match(curl)
echo "WARN: The user doesn't have the Administrator role. Exiting."
count_errors += 1
exit
end
echo "Authentication success."
REGPAT = /^\s*([0-9]+?)\s*,\s*(ADD|REMOVE)\s*,\s*([^,]+)/i #regular expression to match a proper CSV file record
count_lines = 0
#reading the csv
file = File.new($filename, "r:UTF-8")
while (line = file.gets)
count_lines+=1
if match = line.match(REGPAT) then
topicid, action, label = match.captures
else
echo "WARN: LINE NOT MATCHING REGEXP: line #{count_lines} content: #{line}"
break if count_lines > 1 #only one bad line is allowed, usually the last line in the file may be empty
next
end
topicid = topicid.strip
action = action.strip.downcase
label = label.strip
echo "line: #{count_lines}, topic '#{topicid}': #{action} '#{label}'"
#check the message
restcall="curl -s -S --retry 15 -k " \
+" --cookie \"LiSESSIONID=#{$sessionid}\" "\
+" #{REST_URL}/messages/id/#{topicid}"
#echo restcall
curl = %x[#{restcall}]
#echo curl
if $?.exitstatus !=0 then
echo "WARN: HTTP communication failed for message #{rootmsg}. Rest call: #{restcall}. Exit status: #{$?.exitstatus}."
count_errors += 1
next #change to exit if you need hard stop on such error
end
begin
doc = REXML::Document.new(curl)
rescue REXML::ParseException => msg
echo "WARN: XML Parsing Failed: #{msg.message} (Line:#{msg.backtrace.inspect.match(/(.rb:)(\d+):/)[2]})"
echo "WARN: XML for parsing:\n#{curl}"
count_errors += 1
next #change to exit if you need hard stop on such error
end
if doc.elements["response"].nil? || (doc.elements["response"].attributes["status"] != "success") then
echo "WARN: REST call failed."
echo "WARN: REST response:\n#{curl}"
count_errors += 1
next #change to exit if you need hard stop on such error
end
author = doc.elements["response/message/author"].attributes["href"].to_s.strip
labels, newlabels = ""
labelfound = false
doc.elements.each("response/message/labels/label") do |labelitem|
labelfound = true if labelitem.elements["text"].text == label
labels += (", " + labelitem.elements["text"].text)
end
labels[0..1] = "" if labels[0] = ","
#echo labels
case action
when 'remove'
if not labelfound then
echo "WARN: Label '#{label}' not found inside the existing '#{labels}'."
count_errors += 1
next #change to exit if you need hard stop on such error
end
newlabels = labels.gsub(label,"")
when 'add'
if labelfound then
echo "WARN: Label '#{label}' already present."
count_errors += 1
next #change to exit if you need hard stop on such error
end
newlabels = labels + (labels == "" ? "" : ",") + label
else
echo "WARN: Unknown action type. This should never happen."
exit
end
#echo newlabels
#updating the message
restcall="curl -s -S --retry 15 -k " \
+" #{REST_URL}/messages/id/#{topicid}/edit "\
+" --cookie \"LiSESSIONID=#{$sessionid}\" "\
+" --form \"restapi.response_style=view\" "\
+ (author != "" ? " --form \"credentials.identity_user=#{author}\" " : "") \
+" --form \"label.labels=#{newlabels.gsub(/(["`$])/, '\\\\\1')}\" "
#echo restcall
curl = %x[#{restcall}]
#echo curl
if $?.exitstatus !=0 then
echo "WARN: HTTP communication failed for message #{rootmsg}. Rest call: #{restcall}. Exit status: #{$?.exitstatus}."
count_errors += 1
next #change to exit if you need hard stop on such error
end
begin
doc = REXML::Document.new(curl)
rescue REXML::ParseException => msg
echo "WARN: XML Parsing Failed: #{msg.message} (Line:#{msg.backtrace.inspect.match(/(.rb:)(\d+):/)[2]})"
echo "WARN: XML for parsing:\n#{curl}"
count_errors += 1
next #change to exit if you need hard stop on such error
end
if doc.elements["response"].nil? || (doc.elements["response"].attributes["status"] != "success") then
echo "WARN: REST call failed."
echo "WARN: REST response:\n#{curl}"
count_errors += 1
next #change to exit if you need hard stop on such error
end
echo "Updated #{doc.elements["response/message"].attributes["view_href"].to_s.strip}."
count_actions += 1
end
file.close
echo "TOTAL OF #{count_actions} ACTIONS PERFORMED, #{count_errors} ERRORS ENCOUNTERED."
echo "THE PROCESS TOOK #{Time.at(Time.now - start_time).gmtime.strftime('%R:%S')} HOURS."
echo "PROCESS COMPLETED."
end
@kgroneman Curious - What problems you having with it? We just built a similar script that best I can tell accomplishes the same thing, but upon running it, it nukes our cover photos/authors on the article, completely randomly. (Been waiting a month myself on Support, thus my curiosity).
We also have a bug open about the Admin->Edit labels not always working, so we're currently in a bind of having no options available to us 😞
@StanGromer the problem is that when I run it, sometimes it works, but most of the time it will throw strange errors on specific posts, like I don't have permission or the post doesn't exist, etc. The errors don't make sense. I've not seen it do what you describe. Just 30 minutes ago I was on a call with Khoros and ran a test while they did some logging. Waiting for the results.
I now have working scripts to change labels in bulk and to move posts in bulk if anyone is interested. Message me with your email address.
Welcome to the Technology board!
Curious about our platform? Looking to connect on social technology? You've come to the right place!