Public
New Commentator

Can we bulk edit labels on Idea Exchange threads??

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!

2 Replies 2
Honored Contributor

Yes, I do it all the time using the API in a script.  Put the following code in a .rb file and run the script according to the instructions in the code.  If you have questions, feel free to contact me kim.groneman@microfocus.com  

# 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
--
Community manager in the Micro Focus Community. My computer always used to beat me at chess, but it is no match for me now I changed the competition to kick boxing.
Honored Contributor

The input csv file looks like this:

post ID, add,label

postID,remove,label

I always set the following before running the script otherwise errors will happen and/or you'll tick a lot of people off with a flurry of subscription emails:

  • ADMIN > FEATURES > LABELS set to not require labels on posts and use both user and predefined labels
  • ADMIN > SYSTEM > SUBSCRIPTIONS turned off so you don't spam people
  • ADMIN > MOD TOOLS > MODERATION turn spam management off or the quick change in so many posts will trigger the spam filter on them.
--
Community manager in the Micro Focus Community. My computer always used to beat me at chess, but it is no match for me now I changed the competition to kick boxing.

Welcome to the Technology board!

Curious about our platform? Looking to connect on social technology? You've come to the right place!

Are you a Khoros customer? For direct assistance from our Support team, please visit the Support Forum.