Report Components: Best Practices
There are several practices you can employ to help develop high-quality Report Components. These become much more important if you are planning on building something that you will then share with other people.
Optimize Performance
You may find yourself occasionally running into a "Script used more than allowed CPU time" error. This indicates that your script is simply doing too much work and is hitting the safeguards that Warcraft Logs Classic has put in place to ensure our servers don't get overloaded.
But don't worry! The limits are very generous, and usually you can refactor your code to be more performant. Here are a couple of tips for common pitfalls:
Understand how large your arrays are! The JavaScript array methods like
filter
&map
are really helpful but have a performance cost (they copy the entire array each time). They are typically okay to use when dealing with arrays of hundreds of elements but should be avoided for arrays of thousands or more elements (such as all damage events for a player).For larger array manipulation, consider using
reduce
(which still copies the array but lets you combine multiplefilter
&map
operations) or a single for loop.Note that it's usually perfectly fine to use
filter
&map
when dealing with small arrays (for things such as players, abilities, deaths etc).The helper methods are usually cached, so are more performant and should be used as much as possible. These are things like
fight.eventsByCategoryAndDisposition
,fight.combatantInfoEvents
,fight.friendlyDeathEvents
etc. These are faster and usually more convenient than their alternatives.
Include a Title
Adding a title to your component is optional, but may be helpful when revisiting a report or sharing the component with another user. The different types of Report Components implement titles slightly differently:
Enhanced Markdown
The typical way to add a title to an EnhancedMarkdown
component is to include a markdown heading in the content
prop, such as:
return {
component: 'EnhancedMarkdown',
props: {
content: `
# Title of the Component
Content of the component.
`
}
}
Table
For Table
, you can add a title by adding a header group for your columns:
return {
component: 'Table',
props: {
columns: {
title: {
header: '**Title of the Component**',
columns: {
columnA: {
header: 'Column A'
},
columnB: {
header: 'Column B'
}
}
}
},
data: [{
columnA: 'Cell A',
columnB: 'Cell B'
}]
}
}
Chart
As Chart
uses the Highcharts API for its props, we can specify title and subtitle like so:
return {
component: 'Chart',
props: {
title: {
text: 'Title of the Component'
},
subtitle: {
text: 'Subtitle of the Component'
}
}
}
Add Styling, Icons, and Tooltips
The Warcraft Logs Classic UI uses various styling, icons, and tooltips to ensure it is provided quick context to its analysis. Most users can identify a player's class or an ability's school by color, and being able to hover over items and abilities to see tooltips with their in-game description can save a lot of time compared to having to lookup the ability separately.
Luckily, Enhanced Markdown includes lots of helper components for building these bits of UI:
- The ActorIcon component
- The AbilityIcon component
- The ItemIcon component
- The EncounterIcon component
- The Bar component
- The Styled component
Enhanced Markdown can be used in both the EnhancedMarkdown
and Table
Report Components. Chart
does not currently support Enhanced Markdown, but the relevant colors for actors/abilities can be found using:
- Actors:
styles.getColorForActorType(actor.subType)
- Abilities:
styles.getColorForAbilityType(ability.type)
You can read their full documentation.
Check out the examples in the previous articles to see how the UI can be polished with these techniques.
Guard Against Unintended Use
You might design a component to do analysis for a particular encounter or to require a single player to be selected. In these scenarios, it's useful to guard against unintended use, especially if you plan on sharing the component.
For example, to ensure that every fight selected is for a particular encounter (in this case, Halondrus):
getComponent = () => {
const halondrusEncounterId = 2529;
const isHalondrus = reportGroup.fights
.every(fight => fight.encounterId === halondrusEncounterId);
if (!isHalondrus) {
return {
component: 'EnhancedMarkdown',
props: {
content: `This component only works for <EncounterIcon id="${halondrusEncounterId}">Halondrus</EncounterIcon>.`
}
}
}
return 'Not yet implemented.';
}
Or to ensure that only a single fight is picked and only a single player is filtered to:
getComponent = () => {
const onlyOneFightSelected = reportGroup.fights.length === 1;
const onlyOneCombatantInfoEvent =
reportGroup.fights[0].combatantInfoEvents.length === 1;
if (!onlyOneFightSelected || !onlyOneCombatantInfoEvent) {
return {
component: 'EnhancedMarkdown',
props: {
content: 'Please select a single fight and player to check enchants for.'
}
}
}
return 'Not yet implemented';
}