Skip to content

Bug 2059046 - [Perfherder] Display Sherlock's Suggested Culprit in Alerts View - #9818

Open
esanuandra wants to merge 4 commits into
mozilla:masterfrom
esanuandra:display-sherlock-culprit
Open

Bug 2059046 - [Perfherder] Display Sherlock's Suggested Culprit in Alerts View#9818
esanuandra wants to merge 4 commits into
mozilla:masterfrom
esanuandra:display-sherlock-culprit

Conversation

@esanuandra

@esanuandra esanuandra commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Bug 2059046 - [Perfherder] Display Sherlock's Suggested Culprit in Alerts View

It displays suggested culprit revision next to sherlock hourglass in alert's row
image

@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for treeherder ready!

Name Link
🔨 Latest commit 14aa5d4
🔍 Latest deploy log https://app.netlify.com/projects/treeherder/deploys/6a8ef07bc16be40008e7dbf1
😎 Deploy Preview https://deploy-preview-9818--treeherder.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@esanuandra esanuandra added the WIP label Aug 26, 2026
@esanuandra
esanuandra force-pushed the display-sherlock-culprit branch from 233165c to b348258 Compare August 26, 2026 13:30
// ...but the styled detected-push revision span does not
expect(
container.querySelector('.fst-italic.text-muted.small'),
).toBeNull();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a Bootstrap version update or a style change renames those classes, the test will silently pass even if the element is incorrectly rendered.

Suggested change
).toBeNull();
expect(
container.querySelector('[data-testid$="suggested-culprit"]'),
).toBeNull();

});

const revisionEl = await waitFor(() =>
getByText(`Suggested culprit: ${revision.slice(0, 12)}`),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Consider extracting this hardcoded slicing value into a constant (e.g., const REVISION_DISPLAY_LENGTH = 12;). Since it’s used frequently throughout the project, this will help keep it consistent and easier to maintain. This can also be addressed later since it involves other files as well.

Comment thread treeherder/perf/models.py
for entry in reversed(self.get_backfill_logs()):
if entry.get("detected_push_id") is not None:
return {
"detected_push_id": entry.get("detected_push_id"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"detected_push_id": entry.get("detected_push_id"),
"detected_push_id": entry["detected_push_id"],


def get_detected_push_revision(self, obj):
detected = obj.get_latest_detected_push()
return detected["detected_push_revision"] if detected else None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When DRF serializes a BackfillRecord, it calls both get_detected_push_id and get_detected_push_revision. Each calls get_latest_detected_push(), which in turn calls json.loads() on the backfill logs. That means the JSON is parsed twice for every record in the API response.

The following approach computes the result once sets both keys manually. This is the DRF-idiomatic way to customize serialization output.
to_representation is a special method built into DRF that every serializer already has. It's the method DRF calls internally when it converts a model object into a Python dict (before turning it into JSON).
Instead of adding two new fields — you're overriding that built-in method to inject your two values after the normal serialization runs.

Suggested change
return detected["detected_push_revision"] if detected else None
def to_representation(self, instance):
data = super().to_representation(instance) # 1. run normal serialization first
# (all existing fields are in `data`)
detected = instance.get_latest_detected_push() # 2. compute once
data["detected_push_id"] = ... # 3. add field 1 to the dict
data["detected_push_revision"] = ... # 4. add field 2 to the dict
return data # 5. return the final dict

There are multiple ways to compute only once detected_push_id and detected_push_revision. This is one option, but I'm open to any alternative solutions you'd prefer to explore.

</span>
{detectedPushRevision && (
<span className="ms-1 fst-italic text-muted small">
Suggested culprit: {detectedPushRevision.slice(0, 12)}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: As a potential follow-up, we could add a direct link to the Jobs View when clicking on the revision.

@Archaeopteryx Archaeopteryx added the perfherder perfherder related PR for the Perf team to work on label Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

perfherder perfherder related PR for the Perf team to work on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants