User Attributes - reporting descriptions

In a large project, it is easy to generate tens—hundreds—of user attributes … and then forget why you added them when returning later to the project to do new work. In the ideal world we add a description when creating the attribute. But, the Displayed Attributes configurator helpfully makes user attributes for you on the fly—useful, to avoid braking flow—but that bypasses seeing the new attribute in the Inspector.

Opening a research project this AM, I wondered how many user attributes there are and how many lack descriptions. I could have recorded only those attributes for which the attribute(attrName)["description"] was empty but as I wanted a list of descriptions to read, it made sense to do the latter and edit any attributes found with no description text.

In my experiment, I found I had 133(1)m user attributes, 5 without descriptions and 12 with typos in the descriptions. I now have 113 descriptions and no typos. A useful task done!

To do the above, I make a new note and give it this code (I used an edict (a rule is overkill for this):

var:string vOutput=;
var:list vNameList = document["user-attributes"];
$Text = "There are " + vNameList.count + " user attributes.\n\n";
vNameList.each(aName){
   vOutput += aName + "\n";
   vOutput += attribute(aName)["description"] + "\n\n";
};
$Text += vOutput;

… and we get $Text ike so:

To make this more portable you could use a function instead:

function fUserAttributeDescriptions(){
var:string vOutput=;
var:list vNameList = document["user-attributes"];
vOutput = "There are "+vNameList.count+" user attributes.\n\n";
vNameList.each(aName){
   vOutput+=aName+"\n";
   vOutput+=attribute(aName)["description"]+"\n\n";
};
return vOutput;
} // END

called from any note like so:

$Text = fUserAttributeDescriptions();

If writing (exported) reports for others and using attribute names in the report, or the reports describe the data extraction/analysis process done in Tinderbox, being able to export these descriptions might be useful. If the case, then make a variant of the function like so:

function fUserAttributeDescriptionsHTML(){
var:string vOutput=;
var:list vNameList = document["user-attributes"];
vOutput = "<p>There are "+vNameList.count+" user attributes.</p>\n";
vNameList.each(aName){
   vOutput+="<h4>" + aName + "</h4>\n";
   $Text(/log) = attribute(aName)["description"];
   vOutput+=exportedString(/log,"^text^")+"\n";
};
$Text(/log)=;// clear log
return vOutput;
} // END

As exportedString must use a note object as the source data, I’ve added a ‘log’ note to the root of the document’s outline. The description data it written to that note’s $Text and the ^text^ evaluated for the export and at the end the /log file $Text is emptied so as not to leave cruft to build up.

Now your export template simply calls:

^value(fUserAttributeDescriptionsHTML())^

…and your HTML marked-up list data is exported.

6 Likes

Turns out the /log note kludge isn’t needed. The issue we face is an attribute description might per more than one paragraph and we’d like to retain those paragraphs as discrete HTML <p> elements. Also though, it is safe to assume the attribute description:

  • is plain text (you can’t add styled text into the source ‘Description’ box)
  • contains no inline ^export^ code operators

So we can get the String.paragraphList() list of the description and then use the 4-argument format of List.format as in:

$MyString = List/Set.format("listPrefix","itemPrefix","itemSuffix","listSuffix");

But we only need all items in a <p> element so the format argument looks like:

.format("","<p>","<\p>\n","");

The trailing \n on the item close makes a line break in the resulting HTML source code.

Now, we can update our earlier function, as fUserAttributeDescriptionsHTML2():

function fUserAttributeDescriptionsHTML2(){
var:string vOutput=;
var:list vNameList = document["user-attributes"];
vOutput = "<p>There are "+vNameList.count+" user attributes.</p>\n";
vNameList.each(aName){
   vOutput+="<h4>" + aName + "</h4>\n";
   var:string vSource = attribute(aName)["description"];
   vOutput+= vSource.paragraphList().format("","<p>","</p>\n","");

};
return vOutput;
} // END

So if an attribute’s Description had this source text:

This is a note on the attribute.
This is a second paragraph of notes.

The function’s output would be:

<p>This is a note on the attribute.</p>
<p>This is a second paragraph of notes.</p>

Much simpler and no clean-up required.

Note: the reason you only provide two of the four-argument format pattern is because the original purpose of the methods is to make an HTML bulleted list or similar. The first and last tags are only used before the first and after the last list items each of which is already enclosed using arguments #2 and #3. This in much easier than using list.format("<\p>\n<p>") which only adds joins between items and your still need to add a <p> the beginning and a </p>\n to the end of the List.format()'s output.

2 Likes

Thanks for this! I’ve been using a bare bones approach, with the following in the $Rule of a note.

document["user-attributes"].each(x){
	if(!attribute(x)["description"].empty){
		$Text+=x+":\n"+ attribute(x)["description"] +"\n\n";
	}
}
$RuleDisabled=TRUE;

I have $RuleDisabled in the Displayed Attributes for that note. When I need an update I click the checkbox in the Displayed Attributes table. The rule does its thing and then turns itself off.

That self-cancelling rule approach came from @mwra a long time ago in a Tinderbox version far, far away.

Thanks. A quick note on Tinderbox boolean attributes, the correct and case-sensitive values are true and false.

In your rule code you use $RuleDisabled=TRUE;. This works but only because it is a ‘false positive’. Why so? In terms of coercion in action code, only zero (0) or an empty string ("") equate to a valid Boolean false value. Thus values FALSE and TRUE both coerce to Boolean true.

Luckily, you wanted that outcome! But it might be worthwhile to check your rules and other code for incorrect casing of boolean values that might be working against your intent.

More on self-cancelling rules.

1 Like

Ah yes, that link brings back old memories. $Rule=; dates back to before $RuleDisabled (I think).

Rather than having to think about TRUE vs true and FALSE vs false (I also use spreadsheets regularly, which capitalize) I think I’ll avoid hard-coding true and instead use:

$RuleDisabled=!RuleDisabled;

I guess mine is more a “self-toggling rule” than a “self-cancelling rule.” It doesn’t self-cancel and disappear, but remains in place to run again another day. Handier for me in some situations than reaching up to the menu for a stamp.

Thanks for the pointers on user attribute descriptions and on the booleans!

1 Like