Hide widget from Highlights

Created:
Author: Christoph Stoettner
Read in about 6 min · 1091 words

Website Design books

Photo by Alex Chumak | Unsplash

With HCL Connections 6.5 and later, we got the add-on HCL Connections Engagement Center (aka CEC, HCEC, ICEC or XCC) included in a normal HCL Connections deployment.

The HCL Connections license contains the supplement that HCEC can be used within Communities and is the base for the Highlights application. All other options are hidden and could be enabled in LotusConnections-config.xml (set <genericProperty name="icec.light">false</genericProperty>), but then you need to order the HCL Connections Engagement Center license.

Now, the users with the admin role in ICEC (ISC - Enterprise Applications > ICEC > Security role to user/group mapping) are allowed to use the dialog or API to upload a file.

As long as icec.light is set to true, you have no option to add a customized JavaScript file with additional widgets or to hide some of the default widgets of HCEC.

This is documented in “Include custom.js” and “Creating a custom widget with the custom widget API” .

The customization files for HCEC are stored in the XCC database, so there is no option to just upload a file to the customization folder in your shared directory or use the customizer to achieve this. You can download the file from your database, it is stored in a BLOB field in XCC.XCCFILESTORAGE.

From a DevOps perspective, we want to automate as much as possible, so adding this customization manually is boring and could cause errors. Since HCEC is integrated, I wish to build a script to update custom.js, without enabling the full version of HCEC.

In my example, I intend to hide the birthday widget and the new Leap widgets, as we haven’t deployed the Leap application in this environment. To be honest, I would expect that you could select the shown widgets somewhere, but until now, they have just been added, but there is no option to hide some of them.

The used custom.js in the database and the documentation contains some examples for custom widgets, but this is not necessary for the functionality or our goal to hide some widgets.

So I created a new file with this content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(function (W) {
	  XCC.X = XCC.X || {
		init : function() {
      /* Hide widgets */
      ['xccLeapForm', 'xccLeapResults', 'xccPeopleBirthday'].forEach(function removeWidgt(widgetName) {
        XCC.WIDGETS.TYPES[widgetName] = undefined;
      });
		}
	};
}(window));

In lines 4 to 6, we set the list of widgets to undefined, so they do not appear in the list of available widgets.

Example of widget names in browser developer tools

The easiest way to find the widget name is to use the browser developer tools and use the ID of the widget.

Now we need to upload the file to HCEC. The official upload link is only visible for users with the Admin role in ICEC and when icec.light is set to false. I traced the requests and created a script to upload the file from Python.

The requirement is that the used user credentials have the admin role ICEC, but it is not necessary that icec.light is set to false.

"""
Author: Christoph Stoettner
Mail: christoph.stoettner@stoeps.de

Commandline script to upload custom.js to HCL Engagement Center
This even works when icec.light = true
"""

import sys
import requests
import os.path
import urllib3

urllib3.disable_warnings()

if len(sys.argv) != 5:
    raise ValueError(
        "Please provide hostname user password community-uuid and the upload filename.\nExample: %s hostname username password custom.js"
        % sys.argv[0]
    )
else:
    cnx_host = sys.argv[1]
    cnx_user = sys.argv[2]
    cnx_pass = sys.argv[3]
    upload_file_name = sys.argv[4]

    if os.path.exists(upload_file_name):
        pass
    else:
        print("The filename " + upload_file_name + " does not exist.")
        sys.exit()

cnx_root_url = "https://" + cnx_host
headers = {}
login_url = cnx_root_url + "/homepage/j_security_check"
get_csrf_url = cnx_root_url + "/xcc/community"
session = requests.Session()
login_data = {"j_username": cnx_user, "j_password": cnx_pass}
login_response = session.post(login_url, data=login_data, verify=False)
if login_response.status_code == 200:
    # Create a community and delete it after getting the CSRF Token
    create_comm_url = cnx_root_url + "/communities/service/atom/communities/my"
    headers["Content-Type"] = "application/atom+xml"
    comm_name = "Test for Highlights 1293054839840483"
    data = (
        '<?xml version="1.0" encoding="UTF-8"?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns:snx="http://www.ibm.com/xmlns/prod/sn"><title type="text">'
        + comm_name
        + '</title><summary type="text">ignored</summary><content type="html">This should be deleted within one minute</content><published>ignored</published><category term="community" scheme="http://www.ibm.com/xmlns/prod/sn/type"></category><snx:communityType>public</snx:communityType></entry>'
    )
    create_comm_response = session.post(
        create_comm_url, data=data, headers=headers, verify=False
    )
    community_created = create_comm_response.headers["Location"]
    community_created_uuid = community_created.split("=")[1]

    json_data = {}

    if create_comm_response.status_code == 201:
        print("Community successfully created:" + community_created_uuid)

        json_data["commId"] = community_created_uuid

        headers = {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0",
            "Accept": "*/*",
            "Accept-Language": "en-US,en;q=0.5",
            "Accept-Encoding": "gzip, deflate",
            "Content-Type": "application/json; charset=utf-8",
        }

        headers["Origin"] = cnx_root_url
        headers["Referer"] = (
            cnx_root_url
            + "/communities/service/html/communityoverview?communityUuid="
            + community_created_uuid
        )

        get_csrf_response = session.post(
            get_csrf_url, headers=headers, json=json_data, verify=False
        )

        # Store csrf_token_icec for later
        csrf_token = session.cookies.get_dict()["csrf_token_icec"]

        upload_path = "/xcc/rest/files/upload"
        upload_url = cnx_root_url + upload_path

        headers["Accept"] = "application/json, */*; charset=utf8"
        headers["X-Requested-With"] = "XMLHttpRequest"
        headers["X-Token-Icec"] = csrf_token
        # Remove Content-Type (is added during update)
        headers.pop("Content-Type")

        files = {
            "files[]": ("custom.js", open(upload_file_name), "application/x-javascript")
        }

        upload_response = session.post(
            upload_url, files=files, headers=headers, verify=False
        )

        # Handle the response as needed
        if upload_response.status_code == 200:
            print("Uploaded successfully")
        else:
            print("Upload failed")
            print("\n**** request headers ****")
            # print(upload_response.request.headers)
            for key in upload_response.request.headers.keys():
                print(key + ": " + upload_response.request.headers[key])
            print("\n**** request body ****")
            print(upload_response.request.body[:400])
            print("\n**** response content ****")
            print(upload_response.status_code, upload_response.content[:400])

        # Delete community (delete twice to remove from Trash)
        delete_comm_response = session.delete(
            cnx_root_url
            + "/communities/service/atom/community/instance?communityUuid="
            + community_created_uuid
        )
        delete_comm_response2 = session.delete(
            cnx_root_url
            + "/communities/service/atom/community/instance?communityUuid="
            + community_created_uuid
        )
        if delete_comm_response.status_code == 200:
            print("Temporary created community deleted: " + community_created_uuid)
            if delete_comm_response2.status_code == 200:
                print(
                    "Temporary created community removed from trash: "
                    + community_created_uuid
                )
        else:
            print("Temporary community couldn't be deleted: " + community_created)
    else:
        print("Community not created: " + str(create_comm_response.status_code))
else:
    print("Login failed!")
# Close the session
session.close()

For the file upload we need a cookie from HCEC which is later provided as header X-Token-Icec.

The Python script uses requests and urllib3, so please install them before using the script:

pip3 install requests urllib3

I wrote the script to automate the task with Ansible, so I just use the sys.argv variables during the call. When you have the config.js and this script in the same directory, you can call them with:

python3 upload_customjs.py <your connections hostname> <admin user> <admin password> <file to upload>

python3 upload_customjs.py cnx8-db2.stoeps.home stoeps password custom.js

There is no need to name the file custom.js, you can use any name; the script uploads as custom.js.

Example upload.

Update

In the first version of this blog post you had to add a community uuid to do the csrf token call. I changed this and create a community during the upload and delete it immediatly after uploading the custom.js file.

Author
Suggested Reading
Card image cap

Last week, I had three systems with issues displaying the Top Updates in the Orient Me. So I tried to find out which applications and containers are involved in generating the content for this view.

Created: Read in about 4 min
Card image cap

I had one Connections’ environment that I wanted to switch from OpenLDAP to Active Directory LDAP. The old OpenLDAP environment used LDAPS to connect, and so I assumed that the change was done quickly.

The first step was to make a copy of the tdisol folder I used for OpenLDAP and start changing the configuration files for the new LDAP server.

Created: Read in about 4 min
Card image cap

The official documentation, “Migrating data from MongoDB 3 to 5”, wants to dump the MongoDB databases in 3.6 and then restore this data into the newly deployed MongoDB 5.

One issue with this process is that we can’t run the two MongoDB versions in parallel on Kubernetes because the provided helm charts and container for MongoDB 3.6 stop running after Kubernetes 1.21. On the other side, the helm chart providing MongoDB 5 can’t be installed on those old Kubernetes versions. So the process to update is:

Created:
Last Update:
Read in about 7 min