fix: show the textarea the saved notifier configuration actually uses - #406
Merged
Merged
Conversation
A notifier has two textareas that both take message content, and which one is sent depends on the "disable built-in message" checkbox: unchecked sends the built-in message with `自定义内容` appended, checked sends `自定义消息` and nothing else. The form is supposed to show whichever one applies and hide the other. It showed the wrong one, in two different ways. The markup hardcodes `display: none` on the custom block and nothing read the saved value back, while the script only assigned styles from `onchange`. So every render offered `自定义内容` and hid `自定义消息`, however the notifier had been saved. A notifier saved with the built-in message disabled therefore came back offering the textarea it no longer sends, and edits made there landed in a field nothing reads — `message` stayed empty, the request went out with a blank markdown text, and DingTalk answered `400403 参数 markdown --》 text 缺失`. That is jenkinsci#369. The handler also reached for its blocks with `document.querySelector`, which returns the first match in the document rather than the one belonging to the checkbox that changed. Every configured robot renders this same markup, so with more than one robot the second robot's checkbox toggled the first robot's textareas and left its own alone — the checkbox did nothing observable at all. jenkinsci#239 reported the visible half of this in 2024 and was closed by jenkinsci#240, which replaced `window.addEventListener('load')` plus a single `querySelector` with `Behaviour.specify` so that every checkbox gets a handler. That part was right, but both `document.querySelector` calls moved into the new callback unchanged, and the initial render was never part of it. Before either of them, the de-jQuery-ification in `70215a4` had translated `$('.x').css(...)`, which applies to every match, into `document.querySelector('.x').style`, which applies to the first — that is where addressing the wrong notifier came from. `Behaviour.specify` invokes its callback once per matching element as the form renders, which is exactly where the saved state has to be applied, so the same function now serves both the first paint and later changes. The blocks are resolved with `closest('.repeated-chunk')`, the container core itself reaches for the same way. Which is worth being explicit about, because this view renders the per-item structure of `lib/form/hetero-list` itself rather than using the tag: its items are one per robot, all sharing one descriptor, and the tag builds one prototype per descriptor and caps a `oneEach` list at that many items — so through the tag a job could configure exactly one robot, listed under the descriptor's class name. That copy had drifted, missing the header and content structure core has since grown, so the notifier name was loose text where every other list on the page shows a styled header. It is ported here, without the drag handle: the order comes from the global robot list, so it is not the job's to rearrange. Two tests cover the form. `DingTalkNotifierConfigFormTest` renders a real job configuration page with two robots and asserts that each saved notifier is offered the block it actually sends, and that ticking the second one moves its own blocks and leaves the first one's alone; both fail against the previous script. The second case ticks rather than unticks deliberately — unticking moves a notifier towards the markup's own default, so looking the block up document-wide lands on the same result and the test passes while the bug is present. Removing the initial call fails only the first case and dropping the `onchange` assignment fails only the second, so neither case stands in for the other. `DingTalkJobPropertyFormTest` covers the markup this view owns: that each chunk's header names its robot, and that saving the page unchanged gives back every notifier with its content, message, mentions and mode intact. Signed-off-by: BobDu <i@bobdu.cc>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #369
A notifier has two textareas that both take message content, and which one is sent depends on the "disable built-in message" checkbox: unchecked sends the built-in message with
自定义内容appended, checked sends自定义消息and nothing else. The form is supposed to show whichever one applies and hide the other. It showed the wrong one, in two different ways.The reporter's log says it all
{"raw":true, "content":"## <font color=green>APP意见反馈接口测试</font> ...", "message":""}Their markdown went into
自定义内容, butraw: truesends自定义消息, which was empty — so the request went out with a blank markdown text and DingTalk answered400403 参数 markdown --》 text 缺失.They had not misconfigured anything. The form offered them the textarea that mode does not send.
Two defects
The saved value was never read back. The view hardcodes
display: noneon the custom block and the script only assigned styles fromonchange, so every render offered自定义内容and hid自定义消息however the notifier had been saved. Reopening a notifier saved with the built-in message disabled therefore presented the field it no longer sends, and edits made there landed where nothing reads them.The handler addressed the wrong notifier. It reached for its blocks with
document.querySelector, which returns the first match in the document rather than the one belonging to the checkbox that changed. Every configured robot renders this same markup, so with more than one robot the second robot's checkbox toggled the first robot's textareas and left its own alone — the checkbox did nothing observable at all.How it got here
#239 reported the visible half of this in 2024 and was closed by #240, which replaced
window.addEventListener('load')plus a singlequerySelectorwithBehaviour.specifyso that every checkbox gets a handler. That part was right, but bothdocument.querySelectorcalls moved into the new callback unchanged, and the initial render was never part of it.Before either of them, the de-jQuery-ification in
70215a4had translated$('.x').css(…), which applies to every match, intodocument.querySelector('.x').style, which applies to the first. That is where addressing the wrong notifier came from — with a single robot configured, as in development, all three versions look identical, which is why it survived three years.The fix
Behaviour.specifyinvokes its callback once per matching element as the form renders, which is exactly where the saved state has to be applied, so the same function now serves both the first paint and later changes. The blocks are resolved withclosest('.repeated-chunk')— the container core itself reaches for the same way, inhetero-list.jsand in the help-area lookup inhudson-behavior.js.Why the surrounding markup moves too
This view renders the per-item structure of
lib/form/hetero-listitself rather than using the tag, and the fix depends on that structure, so it is worth saying why: its items are one per robot, all sharing one descriptor. The tag builds one prototype per descriptor andoneEachcaps the list at that count, so through the tag a job could configure exactly one robot, listed under the descriptor's class name rather than the robot's.That hand-rolled copy had drifted, missing the header and content structure core has since grown, so the notifier name sat as loose text where every other list on the page shows a styled header. Both are ported here. The drag handle deliberately is not: the order comes from the global robot list, so it is not the job's to rearrange.
repeated-chunk__header--no-handle, naming the robot.repeated-chunkjenkins-repeated-chunk__contentThe empty
f:entrythat used to carry the robot name goes with it, which is also how core writes this — all eightf:advancedusages in core sit besidef:entry, never inside one, and the tag already emits its own.jenkins-form-item.Tests
Four cases, all against a real job configuration page with two robots.
offersTheBlockEachSavedNotifierActuallySendstogglingOneNotifierLeavesTheOtherAlonenamesEachRobotInItsOwnChunkHeadersavingTheFormUnchangedKeepsEveryNotifierThe first two fail against the previous script. The second one ticks rather than unticks deliberately: unticking moves a notifier towards the markup's own default, so looking the block up document-wide lands on the same result and the test passes while the bug is present. Removing the initial call fails only the first case and dropping the
onchangeassignment fails only the second, so neither case stands in for the other.The round-trip case seeds the two notifiers with different values in every field, because
getNoticeOccasions()falls back to the global default — a set that went missing would come back as all six occasions rather than as null, and identical seeds would hide that.One path is not covered: a notifier added through the "add robot" menu takes the same route, since the hetero-list script applies behaviours to the subtree it inserts, but that menu is a dropdown the test client cannot open. That half was checked by hand and it is noted in the test's javadoc.
Not addressed here
There is extra space under the
Advancedbutton in these chunks, and it changes after the section is expanded and collapsed. That is core's:jenkins-repeated-chunk__content > *:last-of-type { margin-bottom: 0 }keys the trailing margin on:last-of-type, which countsdisplay: nonesiblings of the same tag, andadvanced.jspermanently moves the advanced rows to sit after that block. Core's ownExecute shellchunk behaves identically. Working around it in this plugin would only pin our markup order to a core selector that is likely to change, so it is left alone.