Skip to content

fix: deliver the configured mentions in every notification mode - #404

Merged
BobDu merged 1 commit into
jenkinsci:mainfrom
BobDu:fix/at-mention-path
Jul 29, 2026
Merged

fix: deliver the configured mentions in every notification mode#404
BobDu merged 1 commit into
jenkinsci:mainfrom
BobDu:fix/at-mention-path

Conversation

@BobDu

@BobDu BobDu commented Jul 29, 2026

Copy link
Copy Markdown
Member

Fixes #279
Fixes #305

Also addresses item 2 of #282 (see below), and re-opens the question closed by #275.

The mechanism, because the bugs only make sense against it

A DingTalk mention has two halves: the at object decides who gets notified, and a
@<mobile> token in the message body decides where the mention is rendered. Which half you
have to supply depends on the message type, and the plugin treated them as if it did not:

msgtype what the plugin has to do
markdown / actionCard append the token. A mobile is never resolved without it, and actionCard needs one for isAtAll too.
text nothing. DingTalk renders the mention from the at object itself, and appends its own copy even when the body already has one.
link nothing. DingTalk documents this type as not supporting mentions at all.

Six defects on that path

  1. The raw ("disable built-in message") branch never sent at. Its MessageModel was built
    without atAll/atMobiles since raw mode was introduced in c66a0df, so ticking "disable
    built-in message" silently dropped every mention — 禁用内置消息 希望可以有@别人的功能 #279, and item 2 of 希望markdown支持模板语法 #282.

  2. "Notify everyone" did nothing on the built-in notification. The built-in message is an
    actionCard, which needs an @所有人 token in the body for isAtAll to take effect, and none was
    ever written. It only worked by accident, when the custom content happened to contain some other
    @ — which is why it behaved intermittently and was never reported on its own.

  3. The mention was appended unconditionally, so a template that already mentioned someone got a
    second copy — DingTalk renders both. That is 这个插件发起的通知里面可以把 @被通知人 插入消息中间,而不是结尾吗? #305.

  4. Mobiles were appended even when everyone was being mentioned. DingTalk stops resolving
    individual mobiles once isAtAll is set, so those tokens stayed unresolved: a bare phone number
    sitting in the group message where a name should have been, notifying nobody who was not already
    covered.

  5. Text messages got a mention appended as well, which DingTalk renders in addition to the one
    it adds itself.

  6. Link messages were given both the at object and an appended @<mobile>. Neither does
    anything for that type — the token is never resolved, so it only left a bare phone number behind.

The mention fields also lived inside the block the UI hides when the built-in message is disabled,
so there was no way to configure them in the mode that now honours them. They move out; the
JavaScript that hides the block is unchanged.

At.isAtAll becomes a primitive so reading it needs no null handling. It is a request object that is
never persisted, and Gson keys off the field name, so the wire format is unchanged.

What changes for users

  • Ticking Notify everyone now produces an @所有人 line where there was none, and on the
    built-in notification it notifies reliably instead of occasionally.
  • If you ticked Notify everyone and filled in mobile numbers, those numbers no longer appear
    in the message. They were never resolved into names and never notified anyone extra.
  • A text message's mention moves onto its own line — that is where DingTalk puts the one it renders.
  • Mentions configured for a raw/custom message now work, and the fields to configure them are
    visible in that mode.

Documentation

Mentions in markdown and actionCard messages are not tappable on the DingTalk mobile client.
DingTalk documents the rendering half of this — "机器人发送 Markdown 消息类型@人员,不支持高亮显示",
with a per-type table marking Text 是 / Markdown 否 / ActionCard 否
(robot-message-type-staff-information-in-an-enterprise) —
and testing against a live robot confirms the mention is also not tappable there, while a TEXT
message's mention is tappable on both clients. Using atUserIds instead of a mobile does not change
this: the message type is what decides it, not the identifier.

That is almost certainly what #218 reports. It cannot be fixed inside an actionCard, so this PR
documents it instead of changing behaviour: a new @ 人 page covers how mentions work and what
each message type can do, and the field's own help text plus the two pages that used to promise a
mention without qualifying it now point at it.

Testing done

mvn clean verify passes: 67 tests, plus spotless, access-modifier-checker, the enforcer import
rules and spotbugs (0 findings).

The new tests assert on the JSON actually posted, by pointing the robot's webhook at a local
HttpServer — the first payload-level coverage in this repository:

  • DingTalkSenderAtTest covers the token rules — appending, not repeating a token the body already
    has, leaving mobiles out when everyone is mentioned, adding nothing to text or link messages.
  • DingTalkRunListenerAtTest runs a real build through JenkinsRule for both notification modes and
    checks the payload carries the configured mention.

Each of the six fixes was checked by mutation: reverting it on its own makes the corresponding test
fail, so the tests reproduce the defects rather than merely exercising the lines.

The behaviour the fixes rely on was established against a live robot rather than inferred — which
message types resolve a mention, whether a token is required, what happens when isAtAll and
atMobiles are combined, and whether wrapping the token in <font> matters (it does not).

Related issues

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

Which half of a mention has to be supplied depends on the message type. For markdown and
actionCard the body has to carry the `@<mobile>` token or the mobile is never resolved, and
actionCard needs one for `isAtAll` too; for text DingTalk renders the mention from the `at`
object itself, and adding one to the body only gets it rendered twice; the link type supports
no mention at all. Six places got that wrong:

* the raw ("disable built-in message") branch built its MessageModel without atAll or
  atMobiles, so disabling the built-in message silently dropped every mention;
* actionCard, which the built-in notification uses, needs a token in the body for
  `isAtAll` too, and none was ever written — so "notify everyone" only worked by accident,
  when the custom content happened to contain some other `@`;
* the mention was appended unconditionally, so a template that already mentioned someone
  got a second copy of the same mention, which DingTalk renders twice;
* mobiles were appended even when everyone was being mentioned, but DingTalk stops
  resolving individual mobiles in that case, leaving bare phone numbers in the message
  body where a name should have appeared — and notifying nobody who was not already
  covered by mentioning everyone;
* text messages had a mention appended as well, which DingTalk renders in addition to the
  one it adds itself;
* link messages, which DingTalk documents as not supporting mentions at all, were given
  both the at object and an appended `@<mobile>` that can only ever look like a mention.

The mention fields also lived inside the block the UI hides when the built-in message is
disabled, so there was no way to configure them in the mode that now honours them.

`At.isAtAll` becomes a primitive so that reading it needs no null handling; it is a
request object that is never persisted, and Gson keys off the field name, so the wire
format is unchanged.

A mention in a markdown or actionCard message is not tappable on the mobile client — that is
DingTalk's own documented behaviour, not something this change can fix — so the docs now say
so where people configure mentions: a new "@ 人" page, the field's own help text, and the two
pages that used to promise a mention without qualifying it.

Users who ticked "notify everyone" will see an `@所有人` line where they saw none before,
will no longer see the phone numbers that used to be appended next to it, and on the
built-in notification it now notifies reliably rather than occasionally. A text message's
mention moves onto its own line, which is where DingTalk puts the one it renders.

Signed-off-by: BobDu <i@bobdu.cc>
@BobDu
BobDu merged commit d5c898b into jenkinsci:main Jul 29, 2026
16 checks passed
@BobDu
BobDu deleted the fix/at-mention-path branch July 29, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant