Skip to content

Issue 70 Renaming an Impact Level

Ed Mozley edited this page Aug 17, 2026 · 1 revision

Renaming an impact level broke the status board (issue #70)

Rename Operational under Service status β†’ Settings β†’ Impact levels and the board stopped agreeing with itself. Some services showed the new name; the rest carried on showing the old one, and their badges lost their colour.

Reported in issue #70, following on from discussion #59.

Fixed in ee171d07, released as update #1103.

The general guide to the module is Service Status.


1. What you saw

Rename the level to anything at all β€” the reporter's example, and the one used to reproduce it here, was appending a single letter β€” and the board looked like this:

Service Impact level shown Badge colour
File Services Operationalx βœ… the new name green βœ…
VPN Operational ❌ the old name grey ❌
Internet Operational ❌ grey ❌
Printing Operational ❌ grey ❌

One level, two different names, on the same screen. And the ones showing the stale name were the ones that lost their colour.

The pattern looks arbitrary until you know which services were which. File Services was the only one caught up in an open incident. Every service showing the wrong name was a healthy one.


2. Why "healthy" is the interesting case

This is the whole bug, and it is worth understanding because the same shape of mistake can happen anywhere.

When a service is affected by an incident, FreeITSM writes down which impact level it is at β€” a row in status_incident_services saying "service 4 is at level 2". That is a real, stored fact. Rename level 2 and the row still points at level 2, so the board reads the new name straight out of the table. File Services was correct because its status was recorded.

A healthy service has no such row. There is nothing to record: it isn't in an incident, so nothing links it to any level. Its status doesn't exist anywhere in the database β€” it is worked out at the moment the page is drawn, from the absence of any incident.

So the code has to answer the question "what do we call a service that nothing is wrong with?" β€” and six different places in the codebase answered it by writing the word Operational into the reply as a piece of text:

COALESCE(
    (SELECT il.name FROM status_incident_services sis ... ),
    'Operational'          -- <- the entire bug
) AS current_status

That literal was correct the day it was written and wrong the moment anybody renamed the level.

And that also explains the missing colour

The badge is not coloured by a stylesheet rule per level β€” that could never work, because you can add your own levels and pick your own colours. The page looks the name up in the list of impact levels and uses whatever colour it finds.

Once the level was called Operationalx, the string Operational matched nothing. No match, no colour, grey badge. The lost colour and the stale name were the same fault twice.

The general lesson: wherever a value means "the absence of a record", something has to invent it at read time. That invented value is where a hardcoded assumption hides, and it will not show up in any table you can inspect.


3. The reported symptom was the smallest of five

The board was simply the most visible face of the same literal. Four more places were broken by the same rename, and two of them were worse than the thing that got reported.

3a. The portal's "All systems operational" banner could never come back

The self-service portal shows a green banner when nothing is wrong. It decided that by checking whether every service's status was the text Operational.

After a rename, no service ever matched that word β€” including services that were perfectly healthy. The banner did not flicker or misfire; it silently retired for good. Nothing looked broken, because a missing banner looks exactly like a busy day.

3b. Watchtower would have listed every healthy service as affected

Watchtower's list of services currently in trouble worked by excluding the healthy ones by name:

AND il.name <> 'Operational'

Rename the level and that clause stops excluding anything. The "services affected right now" panel would have filled up with every service you own, all of them fine. Nobody had hit this yet only because it needs both a rename and an incident open at the renamed level.

3c. The REST API refused to create incidents

Creating an incident through the API without naming an impact level fell back to the same literal, then looked it up, then failed to find it:

422  Unknown or inactive impact level

This is the one that would waste the most time, because the error names a field the caller never sent.

3d. Custom levels were shown in the portal in Operational green

Separate cause, same family. The portal picked a badge style from a hardcoded list of five level names. Any level you added yourself matched nothing and fell through to the default branch β€” which was impact-operational, i.e. green.

So a level called Degraded Performance, which is a warning, was displayed to your users in the colour that means fine. This one needed no rename at all: it was wrong for anybody who had ever added a level.


4. How it was fixed

There is already a flag in the database that means exactly "this is the level that means nothing is wrong" β€” service_impact_levels.is_default. It was being used for other things and had a good claim to being authoritative:

  • saving a level as the default clears the flag on every other level, so there is only ever one;
  • deleting the default is refused outright, so it cannot vanish;
  • an installation that somehow ends up with none promotes the least severe level automatically.

That is a rule the database already enforces, so the fix is to ask the flag instead of asserting a name. One small helper does it, and every one of the six places calls it:

// includes/service_impact_levels.php
function defaultImpactLevel(PDO $conn): array   // β†’ ['id' =>, 'name' =>, 'colour' =>]

It deliberately has no require statements of its own. The portal dashboard needs it, and making the portal load the workflow engine to find out what "healthy" is called would be a poor trade.

The colour is now sent with the name

Both dashboards now return current_status_colour alongside current_status, rather than leaving the browser to look the colour up by name.

That is not tidiness β€” it fixes a second case nobody had reported. The browser's list of levels is filtered to active ones, so a level that was switched off while an incident was still open at it also lost its badge colour. Sending the colour from the row that was actually resolved makes a renamed or a deactivated level paint correctly.

One more of the same kind, found on the way

The Add affected service row in the incident dialogue defaulted its impact by naming 'Degraded' literally. Rename that level and a new row silently fell back to the baseline β€” an incident affecting a service at the level that means nothing is wrong.

It now asks by meaning rather than by name: the mildest level that still counts as downtime. On the stock lookup that is Degraded, and it stays right whatever the levels are called.


5. πŸ“ The files involved

🟒 The new single source of truth

File Role
includes/service_impact_levels.php New. defaultImpactLevel() β€” reads the is_default row, falls back to the least severe active level, then to a literal only if the table is empty. Dependency-free by design

πŸ”΅ The two dashboards

File Role
api/service-status/get_dashboard.php Analyst board. Fallback name is now bound from the default level; returns current_status_colour and default_impact
api/self-service/get_dashboard.php Portal dashboard. Same change, so both surfaces derive status identically
service-status/index.php Board rendering β€” badge colour now comes from the resolved row; the incident dialogue's default impact asks by meaning
self-service/index.php Portal rendering β€” "all clear" banner compares against the default level; badge style comes from the level's own colour

🟠 Everywhere else the literal had spread

File Role
includes/watchtower_queries.php The affected-services panel excludes the healthy level with il.is_default = 0 instead of by name
includes/services/service_status.php An incident write with no impact named falls back to the default level, not to the word
api/v1/resources/service_status.php The REST derived-status subquery and serialiser

βšͺ Documentation kept in step

File Role
api/v1/spec.json Endpoint descriptions no longer promise "else Operational"
api/v1/lib/openapi_schemas.php The current_status field description

6. How it was verified

Run against a database left in the broken state, with the level genuinely renamed to Operationalx β€” not simulated.

What was checked Result
Analyst board (get_dashboard.php) All six services report Operationalx, all green
Portal dashboard (self-service/get_dashboard.php) Same, and Degraded Performance now shows in its own colour rather than green
GET /api/v1/service-status/services Same derived status as both dashboards
Watchtower affected-services query Two genuinely affected services; no healthy ones
Both pages driven in a real browser Labels and computed badge colours read back from the rendered page, not from the source

Renaming the level again mid-test, to All Good Mate with a purple colour, moved the name and the colour on every healthy service β€” proving it follows the flag rather than any particular word.

The positive control matters more than any of the above. With both incidents temporarily resolved, the portal's All systems operational banner reappeared. That banner is the one thing the old code could not possibly produce once the level had been renamed, so seeing it is proof the check is genuinely dynamic β€” and not merely that nothing crashed.


7. What this means for you

  • You can rename the default impact level to whatever you like. "Operational", "Available", "Normal", something in your own language β€” the board, the portal, Watchtower and the API all follow it.
  • You can change its colour, and healthy services will use it.
  • Custom levels of your own now show in their own colour in the portal, instead of everything unfamiliar coming out green.
  • Which level counts as "nothing is wrong" is the one marked as the default on Service status β†’ Settings β†’ Impact levels. If you want a different level to be the baseline, mark that one as default and everything follows it.
  • Nothing needs migrating and no setting needs changing. An installation that never renamed anything behaves exactly as before, because the default level is still called Operational.

Related pages

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally