4343 .warn { color : var (--warn ); }
4444 .scroll { overflow-x : auto; }
4545 .outputs { display : grid; grid-template-columns : repeat (auto-fit, minmax (320px , 1fr )); gap : 12px ; margin-top : 16px ; }
46+ .outputs .wide { grid-column : 1 / -1 ; }
4647 .out-head { display : flex; justify-content : space-between; align-items : center; gap : 8px ; flex-wrap : wrap; }
4748 tr .out-of-scope td { opacity : 0.55 ; }
4849 tr .shipped td { background : color-mix (in srgb, var (--accent ) 12% , transparent); }
@@ -84,7 +85,8 @@ <h1 x-text="report.jira_scope.length ? 'Release scope: ' + report.jira_scope.joi
8485 </ template >
8586
8687 < h2 > Services</ h2 >
87- < p x-show ="pickable.length " class ="muted "> Open a service and pick the tag to release: it ships every merge request and commit from its row down to production.</ p >
88+ < p x-show ="pickable.length " class ="muted "> Open a service and pick the tag to release: it ships every merge request and commit from its row down to production. The address keeps the picks, so share it to share them.</ p >
89+ < p x-show ="ignored " class ="warn " x-text ="ignored "> </ p >
8890 < template x-for ="service in deployed " :key ="service.project ">
8991 < details class ="service " name ="services ">
9092 < summary >
@@ -242,6 +244,13 @@ <h3>Compare</h3>
242244 </ ul >
243245 < textarea readonly x-text ="compareMarkdown "> </ textarea >
244246 </ section >
247+ < section class ="wide ">
248+ < div class ="out-head ">
249+ < h3 > Release post</ h3 >
250+ < button type ="button " @click ="copy(releasePost) "> Copy</ button >
251+ </ div >
252+ < textarea readonly rows ="12 " x-text ="releasePost "> </ textarea >
253+ </ section >
245254 </ div >
246255
247256 < p class ="muted "> Legend: ✅ success · ❌ failed · 🔄 running · ⏭ canceled or skipped · ⚠️ no pipeline</ p >
@@ -259,6 +268,7 @@ <h3>Compare</h3>
259268 report : null ,
260269 failure : "" ,
261270 notice : "" ,
271+ ignored : "" ,
262272 picks : { } ,
263273
264274 async init ( ) {
@@ -271,6 +281,8 @@ <h3>Compare</h3>
271281 this . picks [ service . project ] = service . candidates . some ( ( item ) => item . tag . name === tag ) ? tag : "" ;
272282 }
273283 this . report = report ;
284+ this . readHash ( ) ;
285+ window . addEventListener ( "hashchange" , ( ) => this . readHash ( ) ) ;
274286 } catch ( error ) {
275287 this . failure = `Cannot load the report: ${ error . message } .` ;
276288 }
@@ -321,6 +333,16 @@ <h3>Compare</h3>
321333 } )
322334 . join ( "\n" ) ;
323335 } ,
336+ get releasePost ( ) {
337+ const services = this . picked . map ( ( item ) => {
338+ const pipeline = item . candidate . tag . pipeline ;
339+ const build = pipeline ? `[pipeline #${ pipeline . id } ](${ pipeline . url } ) ${ pipeline . status } ` : "no pipeline" ;
340+ const compare = `[${ this . compareLabel ( item ) } ](${ item . candidate . compare_url } )` ;
341+ return `- ${ item . service . project } ${ item . candidate . tag . name } : ${ compare } · ${ build } ` ;
342+ } ) ;
343+ const tasks = this . tasks . map ( ( task ) => `- [${ task . key } ](${ task . url } )${ task . issue ? " " + task . issue . summary : "" } ` ) ;
344+ return [ "**Services**" , ...services , "" , `**Jira tasks (${ tasks . length } )**` , ...( tasks . length ? tasks : [ "- none" ] ) ] . join ( "\n" ) ;
345+ } ,
324346 get compareMarkdown ( ) {
325347 return this . picked
326348 . map ( ( item ) => `- ${ item . service . project } : [${ this . compareLabel ( item ) } ](${ item . candidate . compare_url } )` )
@@ -330,6 +352,36 @@ <h3>Compare</h3>
330352 pick ( service , tag , element ) {
331353 this . picks [ service . project ] = tag ;
332354 element . closest ( "details" ) . open = false ;
355+ this . writeHash ( ) ;
356+ } ,
357+ readHash ( ) {
358+ const decode = ( value ) => {
359+ try {
360+ return decodeURIComponent ( value ) ;
361+ } catch {
362+ return value ;
363+ }
364+ } ;
365+ const wanted = new Map (
366+ location . hash . slice ( 1 ) . split ( "&" ) . filter ( Boolean )
367+ . map ( ( part ) => part . split ( "=" , 2 ) . map ( decode ) ) ,
368+ ) ;
369+ if ( wanted . size ) {
370+ for ( const service of this . pickable ) {
371+ const tag = wanted . get ( service . project ) || "" ;
372+ const known = ! tag || service . candidates . some ( ( item ) => item . tag . name === tag ) ;
373+ this . picks [ service . project ] = known ? tag : "" ;
374+ if ( known ) wanted . delete ( service . project ) ;
375+ }
376+ }
377+ const unknown = [ ...wanted ] . map ( ( [ project , tag ] ) => ( tag ? `${ project } ${ tag } ` : project ) ) ;
378+ this . ignored = unknown . length ? `Ignored picks from the address that are not in this report: ${ unknown . join ( ", " ) } .` : "" ;
379+ this . writeHash ( ) ;
380+ } ,
381+ writeHash ( ) {
382+ const encode = ( value ) => encodeURIComponent ( value ) . replaceAll ( "%2F" , "/" ) ;
383+ const hash = this . picked . map ( ( { service, candidate } ) => `${ encode ( service . project ) } =${ encode ( candidate . tag . name ) } ` ) . join ( "&" ) ;
384+ history . replaceState ( null , "" , hash ? `#${ hash } ` : location . pathname + location . search ) ;
333385 } ,
334386 pickOf ( service ) {
335387 return service . candidates . find ( ( item ) => item . tag . name === this . picks [ service . project ] ) ;
0 commit comments