How We Built the KB Audit Flow Part 2: Audit Checkbox (Updated)
The KB Audit Flow requires two components to work. The first is an audit checkbox that enables members with specific roles to mark a KB article as "audited" from the front end. This checkbox includes the initial check and confirmation.
The second component is the 'Last Reviewed' Information component which displays the date and time of the last audit of the KB article. That component is displayed for all visitors, regardless of their role(s). It does, however, check the member's roles to determine whether or not to additionally display the audit checkbox featured in this post.
In this post, we will create a new audit checkbox component.
Note: These instructions cover the method for creating the component using the Community Plugin SDK. For an easier UI-based approach, we recommend using Studio to create the component. The content of the file below remains the same.
We highly recommend reading the other two posts in this series to get a better understanding of the other components and prerequisites required for this component to function properly.
Create a Custom Endpoint
A custom endpoint enables the audit checkbox component to send the "last reviewed" timestamp to Khoros, updating the date and time the article was last audited.
To create this custom endpoint:
- Navigate to Studio > Endpoints
- Select the New Endpoint button
- Enter last-reviewed-date in the title field
- Select Save
- Enter the following in the View Content field:
<#assign msg_id = http.request.parameters.name.get("msg_id", "")?string />
<#assign date_time = http.request.parameters.name.get("date_time", "")?string />
<#if msg_id != "">
<#assign lastReviewedDateResponse = restadmin("/messages/id/${msg_id}/metadata/key/custom.message_last_reviewed_date/set?value=${date_time}") />
</#if>
- Select Save
This creates a new custom endpoint which passes the message identifier and datetime to Khoros. It is utilized by the Audit checkbox component we are creating next.
Create LastReviewed-Audited-checkbox.ftl file
The process for creating the checkbox component is pretty straightforward. To start, create the LastReviewed-Audited-checkbox.ftl file as a component. For example: /res/components/LastReviewed-Audited-checkbox.ftl.
Here are the contents of that file:
<style>
.audited-component-checkbox{
margin-top: -20px;
margin-bottom: 35px;
}
label.lia-form-label.audited-label-set {
padding-top: 4px;
font-weight: bold !important;
float:left;
}
.litho-audited-checkbox-style{
float: left;
}
.lia-form-label-wrapper.audited-label-float{
float: left;
}
.lia-inline-confirm.confirm-label-float{
float: right;
margin-top: 4px;
margin-left: 12px;
}
a.litho-tkb-audited-deny {
cursor: pointer;
}
a.litho-tkb-audited-approve {
cursor: pointer;
}
.hide-article-component-here{
display:none;
}
</style>
<#-- <#import "article" as com> -->
<div class="hide-article-component-here">
<@component id="article"/>
</div>
<#assign message_uniqueId = -1 />
<#if env.context.message??>
<#assign message_uniqueId = env.context.message.uniqueId />
<#assign lengthOfDomain = env.context.message.webUi.url?index_of("/t5") />
<#assign communityDomain = env.context.message.webUi.url?substring(0,lengthOfDomain) />
</#if>
<#assign communityId = community.id />
<#-- REST call to get the user's roles -->
<#list restadmin("/users/id/${user.id?c}/roles").roles.role as role>
<#-- Look for the role name you want to display content for -->
<#if role.name?? && ( (role.name == "Administrator") || (role.name == "Documentation") )>
<div class="audited-component-checkbox">
<div class="lia-quilt-row lia-quilt-row-standard lia-quilt-row-first lia-quilt-row-last">
<div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-input-edit-form-column">
<div class="lia-quilt-column-alley lia-quilt-column-alley-single">
<div class="lia-form-row lia-form-auto-subscribe-to-thread-entry lia-form-row-reverse-label-input lia-form-row-checkbox litho-audited-checkbox-content">
<div class="lia-quilt-row lia-quilt-row-standard litho-audited-checkbox-style">
<div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single">
<div class="lia-quilt-column-alley lia-quilt-column-alley-single">
<div class="lia-form-label-wrapper">
<input class="lia-form-auto-subscribe-to-thread-input audited-checkbox" id="LastReviewAudited" name="auditedCheckbox" type="checkbox">
</input>
</div>
</div>
</div>
</div>
<div class="lia-form-label-wrapper audited-label-float">
<label for="LastReviewAudited" class="lia-form-label audited-label-set">
Audited
</label>
</div>
<div class="lia-inline-confirm confirm-label-float confirm-label" style="display:none;">
Confirm?
<a type="submit" class="litho-tkb-audited-approve">
Yes
</a> /
<a type="submit" class="litho-tkb-audited-deny">
No
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<#break>
</#if>
</#list>
<@liaAddScript>
;(function($){
$(".audited-checkbox").click(function(){
$('.confirm-label').toggle();
});
$(".litho-tkb-audited-approve").click(function(){
$(".audited-checkbox").attr("disabled", true);
<#if message_uniqueId != -1 >
<#assign aDateTime = .now?iso_utc>
$.ajax({
type:"POST",
url:'${communityDomain}/plugins/custom/lithium/${communityId}/last-reviewed-date?date_time=${aDateTime}&msg_id=${message_uniqueId}',
contentType: 'application/json',
success: function(res) {
console.log(res);
console.log("Added");
location.reload(true);
}.bind(this),
error: function(xhr, status, err) {
console.error(xhr, status, err.toString());
console.log("unable to add last_reviewed_date field into DB");
}.bind(this)
});
</#if>
$(".audited-checkbox").prop("checked", false);
$('.confirm-label').hide();
});
$(".litho-tkb-audited-deny").click(function(){
$(".audited-checkbox").prop("checked", false);
$('.confirm-label').hide();
});
})(LITHIUM.jQuery);
</@liaAddScript>
Code Breakdown
In this section, we will examine some of the parts of the component's code to better understand how the component works.
<style>
...
</style>
This section of the file sets the CSS styling for elements within the component.
<div class="hide-article-component-here">
<@component id="article"/>
</div>
This section loads the current article in this component to get the message information, including the message's unique identifier.
<#assign message_uniqueId = -1 />
<#if env.context.message??>
<#assign message_uniqueId = env.context.message.uniqueId />
<#assign lengthOfDomain = env.context.message.webUi.url?index_of("/t5") />
<#assign communityDomain = env.context.message.webUi.url?substring(0,lengthOfDomain) />
</#if>
<#assign communityId = community.id />
This section extracts the message's uniqueId, enabling the component to fetch information for the specific article. It also references the community ID, which we use later in a POST call to update the last reviewed timestamp.
<#list restadmin("/users/id/${user.id?c}/roles").roles.role as role>
This section initiates a REST API call to retrieve the member's role.
<#if role.name?? && ( (role.name == "Administrator") || (role.name == "Documentation") )>
This section compares the retrieved role name to pre-defined names of roles that we want to enable access to the Audit checkbox.
<input class="lia-form-auto-subscribe-to-thread-input audited-checkbox" id="LastReviewAudited" name="auditedCheckbox" type="checkbox">
...
</input>
Here we create the checkbox with the audited-checkbox class which is referenced in the liaaddscript Freemarker section at the bottom of the file.
<#if message_uniqueId != -1 >
<#assign aDateTime = .now?iso_utc>
$.ajax({
type:"POST",
url:'${communityDomain}/plugins/custom/lithium/${communityId}/last-reviewed-date?date_time=${aDateTime}&msg_id=${message_uniqueId}',
contentType: 'application/json',
success: function(res) {
console.log(res);
console.log("Added");
location.reload(true);
}.bind(this),
error: function(xhr, status, err) {
console.error(xhr, status, err.toString());
console.log("unable to add last_reviewed_date field into DB");
}.bind(this)
});
</#if>
Here, we are creating a variable containing the ISO UTC date and time and applying it to an endpoint dedicated to updating the last reviewed date through a POST request to a custom endpoint we configured in Studio.
Note: This post's script examples and descriptions have been updated to the latest method used by our Atlas team. The new method uses a custom endpoint to handle the data transfer.