<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="generator" content="Asciidoctor 2.0.22"> <meta name="author" content="Perforce Professional Services"> <title>Server Deployment Package (SDP) for Perforce Helix: Workflow Enforcement Triggers</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"> <style> /*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */ /* Uncomment the following line when using as a custom stylesheet */ /* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */ html{font-family:sans-serif;-webkit-text-size-adjust:100%} a{background:none} a:focus{outline:thin dotted} a:active,a:hover{outline:0} h1{font-size:2em;margin:.67em 0} b,strong{font-weight:bold} abbr{font-size:.9em} abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none} dfn{font-style:italic} hr{height:0} mark{background:#ff0;color:#000} code,kbd,pre,samp{font-family:monospace;font-size:1em} pre{white-space:pre-wrap} q{quotes:"\201C" "\201D" "\2018" "\2019"} small{font-size:80%} sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} sup{top:-.5em} sub{bottom:-.25em} img{border:0} svg:not(:root){overflow:hidden} figure{margin:0} audio,video{display:inline-block} audio:not([controls]){display:none;height:0} fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} legend{border:0;padding:0} button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} button,input{line-height:normal} button,select{text-transform:none} button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} button[disabled],html input[disabled]{cursor:default} input[type=checkbox],input[type=radio]{padding:0} button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} textarea{overflow:auto;vertical-align:top} table{border-collapse:collapse;border-spacing:0} *,::before,::after{box-sizing:border-box} html,body{font-size:100%} body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} a:hover{cursor:pointer} img,object,embed{max-width:100%;height:auto} object,embed{height:100%} img{-ms-interpolation-mode:bicubic} .left{float:left!important} .right{float:right!important} .text-left{text-align:left!important} .text-right{text-align:right!important} .text-center{text-align:center!important} .text-justify{text-align:justify!important} .hide{display:none} img,object,svg{display:inline-block;vertical-align:middle} textarea{height:auto;min-height:50px} select{width:100%} .subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0} a{color:#2156a5;text-decoration:underline;line-height:inherit} a:hover,a:focus{color:#1d4b8f} a img{border:0} p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} p aside{font-size:.875em;line-height:1.35;font-style:italic} h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} h1{font-size:2.125em} h2{font-size:1.6875em} h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} h4,h5{font-size:1.125em} h6{font-size:1em} hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em} em,i{font-style:italic;line-height:inherit} strong,b{font-weight:bold;line-height:inherit} small{font-size:60%;line-height:inherit} code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} ul,ol{margin-left:1.5em} ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0} ul.circle{list-style-type:circle} ul.disc{list-style-type:disc} ul.square{list-style-type:square} ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit} ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} dl dt{margin-bottom:.3125em;font-weight:bold} dl dd{margin-bottom:1.25em} blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} @media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} h1{font-size:2.75em} h2{font-size:2.3125em} h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} h4{font-size:1.4375em}} table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal} table thead,table tfoot{background:#f7f8f7} table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} table tr.even,table tr.alt{background:#f8f8f7} table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6} h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} .center{margin-left:auto;margin-right:auto} .stretch{width:100%} .clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} .clearfix::after,.float-group::after{clear:both} :not(pre).nobreak{word-wrap:normal} :not(pre).nowrap{white-space:nowrap} :not(pre).pre-wrap{white-space:pre-wrap} :not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed} pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit} pre>code{display:block} pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal} em em{font-style:normal} strong strong{font-weight:400} .keyseq{color:rgba(51,51,51,.8)} kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} .keyseq kbd:first-child{margin-left:0} .keyseq kbd:last-child{margin-right:0} .menuseq,.menuref{color:#000} .menuseq b:not(.caret),.menuref{font-weight:inherit} .menuseq{word-spacing:-.02em} .menuseq b.caret{font-size:1.25em;line-height:.8} .menuseq i.caret{font-weight:bold;text-align:center;width:.45em} b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} b.button::before{content:"[";padding:0 3px 0 2px} b.button::after{content:"]";padding:0 2px 0 3px} p a>code:hover{color:rgba(0,0,0,.9)} #header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} #header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} #header::after,#content::after,#footnotes::after,#footer::after{clear:both} #content{margin-top:1.25em} #content::before{content:none} #header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} #header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} #header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px} #header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap} #header .details span:first-child{margin-left:-.125em} #header .details span.email a{color:rgba(0,0,0,.85)} #header .details br{display:none} #header .details br+span::before{content:"\00a0\2013\00a0"} #header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} #header .details br+span#revremark::before{content:"\00a0|\00a0"} #header #revnumber{text-transform:capitalize} #header #revnumber::after{content:"\00a0"} #content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} #toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} #toc>ul{margin-left:.125em} #toc ul.sectlevel0>li>a{font-style:italic} #toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} #toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} #toc li{line-height:1.3334;margin-top:.3334em} #toc a{text-decoration:none} #toc a:active{text-decoration:underline} #toctitle{color:#7a2518;font-size:1.2em} @media screen and (min-width:768px){#toctitle{font-size:1.375em} body.toc2{padding-left:15em;padding-right:0} body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} #toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} #toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} #toc.toc2>ul{font-size:.9em;margin-bottom:0} #toc.toc2 ul ul{margin-left:0;padding-left:1em} #toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} body.toc2.toc-right{padding-left:0;padding-right:15em} body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} @media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} #toc.toc2{width:20em} #toc.toc2 #toctitle{font-size:1.375em} #toc.toc2>ul{font-size:.95em} #toc.toc2 ul ul{padding-left:1.25em} body.toc2.toc-right{padding-left:0;padding-right:20em}} #content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px} #content #toc>:first-child{margin-top:0} #content #toc>:last-child{margin-bottom:0} #footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em} #footer-text{color:hsla(0,0%,100%,.8);line-height:1.44} #content{margin-bottom:.625em} .sect1{padding-bottom:.625em} @media screen and (min-width:768px){#content{margin-bottom:1.25em} .sect1{padding-bottom:1.25em}} .sect1:last-child{padding-bottom:0} .sect1+.sect1{border-top:1px solid #e7e7e9} #content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} #content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} #content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} #content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} #content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} details{margin-left:1.25rem} details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent} details>summary::-webkit-details-marker{display:none} details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)} details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)} details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem} .admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} .paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} .admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} .admonitionblock>table td.icon{text-align:center;width:80px} .admonitionblock>table td.icon img{max-width:none} .admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} .admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere} .admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} .exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px} .sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px} .sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} .exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0} .exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} .literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em} @media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}} @media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}} .literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8} .literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)} .listingblock>.content{position:relative} .listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5} .listingblock:hover code[data-lang]::before{display:block} .listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5} .listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} .listingblock pre.highlightjs{padding:0} .listingblock pre.highlightjs>code{padding:1em;border-radius:4px} .listingblock pre.prettyprint{border-width:0} .prettyprint{background:#f7f7f8} pre.prettyprint .linenums{line-height:1.45;margin-left:2em} pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0} pre.prettyprint li code[data-lang]::before{opacity:1} pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none} table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none} table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal} table.linenotable td.code{padding-left:.75em} table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} pre.pygments span.linenos{display:inline-block;margin-right:.75em} .quoteblock{margin:0 1em 1.25em 1.5em;display:table} .quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em} .quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} .quoteblock blockquote{margin:0;padding:0;border:0} .quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} .quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} .quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} .verseblock{margin:0 1em 1.25em} .verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} .verseblock pre strong{font-weight:400} .verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} .quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} .quoteblock .attribution br,.verseblock .attribution br{display:none} .quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} .quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} .quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} .quoteblock.abstract{margin:0 1em 1.25em;display:block} .quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} .quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf} .quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0} .quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} .quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0} p.tableblock:last-child{margin-bottom:0} td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere} td.tableblock>.content>:last-child{margin-bottom:-1.25em} table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} table.grid-all>*>tr>*{border-width:1px} table.grid-cols>*>tr>*{border-width:0 1px} table.grid-rows>*>tr>*{border-width:1px 0} table.frame-all{border-width:1px} table.frame-ends{border-width:1px 0} table.frame-sides{border-width:0 1px} table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0} table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0} table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0} table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0} table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7} th.halign-left,td.halign-left{text-align:left} th.halign-right,td.halign-right{text-align:right} th.halign-center,td.halign-center{text-align:center} th.valign-top,td.valign-top{vertical-align:top} th.valign-bottom,td.valign-bottom{vertical-align:bottom} th.valign-middle,td.valign-middle{vertical-align:middle} table thead th,table tfoot th{font-weight:bold} tbody tr th{background:#f7f8f7} tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} p.tableblock>code:only-child{background:none;padding:0} p.tableblock{font-size:1em} ol{margin-left:1.75em} ul li ol{margin-left:1.5em} dl dd{margin-left:1.125em} dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} ul.unstyled,ol.unstyled{margin-left:0} li>p:empty:only-child::before{content:"";display:inline-block} ul.checklist>li>p:first-child{margin-left:-1em} ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em} ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} ul.inline>li{margin-left:1.25em} .unstyled dl dt{font-weight:400;font-style:normal} ol.arabic{list-style-type:decimal} ol.decimal{list-style-type:decimal-leading-zero} ol.loweralpha{list-style-type:lower-alpha} ol.upperalpha{list-style-type:upper-alpha} ol.lowerroman{list-style-type:lower-roman} ol.upperroman{list-style-type:upper-roman} ol.lowergreek{list-style-type:lower-greek} .hdlist>table,.colist>table{border:0;background:none} .hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} td.hdlist1{font-weight:bold;padding-bottom:1.25em} td.hdlist2{word-wrap:anywhere} .literalblock+.colist,.listingblock+.colist{margin-top:-.5em} .colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} .colist td:not([class]):first-child img{max-width:none} .colist td:not([class]):last-child{padding:.25em 0} .thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd} .imageblock.left{margin:.25em .625em 1.25em 0} .imageblock.right{margin:.25em 0 1.25em .625em} .imageblock>.title{margin-bottom:0} .imageblock.thumb,.imageblock.th{border-width:6px} .imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} .image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} .image.left{margin-right:.625em} .image.right{margin-left:.625em} a.image{text-decoration:none;display:inline-block} a.image object{pointer-events:none} sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} sup.footnote a,sup.footnoteref a{text-decoration:none} sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline} #footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} #footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} #footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} #footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} #footnotes .footnote:last-of-type{margin-bottom:0} #content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} div.unbreakable{page-break-inside:avoid} .big{font-size:larger} .small{font-size:smaller} .underline{text-decoration:underline} .overline{text-decoration:overline} .line-through{text-decoration:line-through} .aqua{color:#00bfbf} .aqua-background{background:#00fafa} .black{color:#000} .black-background{background:#000} .blue{color:#0000bf} .blue-background{background:#0000fa} .fuchsia{color:#bf00bf} .fuchsia-background{background:#fa00fa} .gray{color:#606060} .gray-background{background:#7d7d7d} .green{color:#006000} .green-background{background:#007d00} .lime{color:#00bf00} .lime-background{background:#00fa00} .maroon{color:#600000} .maroon-background{background:#7d0000} .navy{color:#000060} .navy-background{background:#00007d} .olive{color:#606000} .olive-background{background:#7d7d00} .purple{color:#600060} .purple-background{background:#7d007d} .red{color:#bf0000} .red-background{background:#fa0000} .silver{color:#909090} .silver-background{background:#bcbcbc} .teal{color:#006060} .teal-background{background:#007d7d} .white{color:#bfbfbf} .white-background{background:#fafafa} .yellow{color:#bfbf00} .yellow-background{background:#fafa00} span.icon>.fa{cursor:default} a span.icon>.fa{cursor:inherit} .admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} .admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} .admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} .admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} .admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} .admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} .conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} .conum[data-value] *{color:#fff!important} .conum[data-value]+b{display:none} .conum[data-value]::after{content:attr(data-value)} pre .conum[data-value]{position:relative;top:-.125em} b.conum *{color:inherit!important} .conum:not([data-value]):empty{display:none} dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em} p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem} p{margin-bottom:1.25rem} .sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} .exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc} .print-only{display:none!important} @page{margin:1.25cm .75cm} @media print{*{box-shadow:none!important;text-shadow:none!important} html{font-size:80%} a{color:inherit!important;text-decoration:underline!important} a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} abbr[title]{border-bottom:1px dotted} abbr[title]::after{content:" (" attr(title) ")"} pre,blockquote,tr,img,object,svg{page-break-inside:avoid} thead{display:table-header-group} svg{max-width:100%} p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} #header,#content,#footnotes,#footer{max-width:none} #toc,.sidebarblock,.exampleblock>.content{background:none!important} #toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} body.book #header{text-align:center} body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} body.book #header .details{border:0!important;display:block;padding:0!important} body.book #header .details span:first-child{margin-left:0!important} body.book #header .details br{display:block} body.book #header .details br+span::before{content:none!important} body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} .listingblock code[data-lang]::before{display:block} #footer{padding:0 .9375em} .hide-on-print{display:none!important} .print-only{display:block!important} .hide-for-print{display:none!important} .show-for-print{display:inherit!important}} @media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem} .sect1{padding:0!important} .sect1+.sect1{border:0} #footer{background:none} #footer-text{color:rgba(0,0,0,.6);font-size:.9em}} @media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> </head> <body class="book"> <div id="header"> <h1>Server Deployment Package (SDP) for Perforce Helix: Workflow Enforcement Triggers</h1> <div class="details"> <span id="author" class="author">Perforce Professional Services</span><br> <span id="email" class="email"><a href="mailto:consulting@perforce.com">consulting@perforce.com</a></span><br> <span id="revnumber">version v2024.1,</span> <span id="revdate">2024-06-11</span> </div> <div id="toc" class="toc"> <div id="toctitle">Table of Contents</div> <ul class="sectlevel1"> <li><a href="#_introduction">Introduction</a></li> <li><a href="#_outline_design">1. Outline Design</a> <ul class="sectlevel2"> <li><a href="#_workflow_yaml">1.1. Workflow.yaml</a> <ul class="sectlevel3"> <li><a href="#_defining_projects_and_project_specific_configurables">1.1.1. Defining Projects and Project Specific Configurables</a></li> </ul> </li> </ul> </li> <li><a href="#_trigger_details">2. Trigger Details</a> <ul class="sectlevel2"> <li><a href="#_checkfixes_py">2.1. CheckFixes.py</a> <ul class="sectlevel3"> <li><a href="#_configuration_values">2.1.1. Configuration values</a></li> <li><a href="#_trigger_entry">2.1.2. Trigger Entry</a></li> <li><a href="#_notes">2.1.3. Notes</a></li> </ul> </li> <li><a href="#_checkjobedittrigger_py">2.2. CheckJobEditTrigger.py</a> <ul class="sectlevel3"> <li><a href="#_configuration_values_2">2.2.1. Configuration Values</a></li> <li><a href="#_trigger_entry_2">2.2.2. Trigger Entry</a></li> <li><a href="#_notes_2">2.2.3. Notes</a></li> </ul> </li> <li><a href="#_checksubmithasreview_py">2.3. CheckSubmitHasReview.py</a> <ul class="sectlevel3"> <li><a href="#_configuration_values_3">2.3.1. Configuration Values</a></li> <li><a href="#_trigger_entry_3">2.3.2. Trigger Entry</a></li> <li><a href="#_notes_3">2.3.3. Notes</a></li> </ul> </li> <li><a href="#_createswarmreview_py">2.4. CreateSwarmReview.py</a> <ul class="sectlevel3"> <li><a href="#_configuration_values_4">2.4.1. Configuration Values</a></li> <li><a href="#_trigger_entry_4">2.4.2. Trigger Entry</a></li> <li><a href="#_notes_4">2.4.3. Notes</a></li> </ul> </li> <li><a href="#_swarmreviewtemplate_py">2.5. SwarmReviewTemplate.py</a> <ul class="sectlevel3"> <li><a href="#_configuration_values_5">2.5.1. Configuration Values</a></li> <li><a href="#_trigger_entry_5">2.5.2. Trigger Entry</a></li> <li><a href="#_notes_5">2.5.3. Notes</a></li> </ul> </li> </ul> </li> <li><a href="#_test_frameworks">3. Test Frameworks</a> <ul class="sectlevel2"> <li><a href="#_standalone_unit_tests">3.1. Standalone Unit tests</a></li> <li><a href="#_testing_via_p4d_triggers_table">3.2. Testing via P4D Triggers table</a></li> <li><a href="#_mocking_the_swarm_api_interface">3.3. Mocking the Swarm API Interface</a></li> </ul> </li> </ul> </div> </div> <div id="content"> <div class="sect1"> <h2 id="_introduction">Introduction</h2> <div class="sectionbody"> <div class="paragraph"> <p>This document describes various workflows that can be enforced by the use of Helix Triggers and other related scripts in a configurable manner.</p> </div> <div class="paragraph"> <p>Examples include:</p> </div> <div class="ulist"> <ul> <li> <p>Creating default Swarm review descriptions according to a template</p> </li> <li> <p>Requiring a job to be associated with a changelist either when shelving (for review) or when attempting to submit</p> </li> <li> <p>Making most job fields read-only – to enforce good practice when linking with external defect trackers, such as JIRA</p> </li> <li> <p>Automatically submitting reviewed changelists if a Jenkins job succeeds</p> </li> </ul> </div> <div class="paragraph"> <p>Such triggers enhance traceability, ensuring the users associate every changelist in defined projects with a JIRA issue.</p> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <i class="fa icon-note" title="Note"></i> </td> <td class="content"> The triggers are provided in the <code>sdp/Unsupported/Samples/triggers</code> directory of the SDP. They are intended as examples and are Community Supported rather than officially by Perforce Support. </td> </tr> </table> </div> </div> </div> <div class="sect1"> <h2 id="_outline_design">1. Outline Design</h2> <div class="sectionbody"> <div class="paragraph"> <p>The triggers run with a common configuration file (YAML format), which includes things such as messages to be displayed, fields which can be modified in jobs, and the definition of particular projects to which workflow values apply.</p> </div> <div class="sect2"> <h3 id="_workflow_yaml">1.1. Workflow.yaml</h3> <div class="paragraph"> <p>This is a well-commented configuration file that is read by all the workflow triggers to control their actions.</p> </div> <div class="paragraph"> <p>For SDP installs, it should be located in <code>/p4/common/config/Workflow.yaml</code></p> </div> <div class="sect3"> <h4 id="_defining_projects_and_project_specific_configurables">1.1.1. Defining Projects and Project Specific Configurables</h4> <div class="paragraph"> <p>The file contains an array of project entries, and standard flag entries for those projects.</p> </div> <div class="paragraph"> <p>Projects are read from top to bottom, and the first project that matches a changelist file is the one which is used.</p> </div> <div class="paragraph"> <p>Projects - an array of project entries</p> </div> <div class="paragraph"> <p>Each project expected to have entries for specific fields.</p> </div> <div class="paragraph"> <p>depot_paths: an array of values for which to process other fields</p> </div> <div class="paragraph"> <p>Controlling here allows you to not have to update the trigger table entry every time you wish to adjust. Also, it is possible to have this file be updated by ordinary users and not just superusers. You can even auto-sync this file if required via trigger.</p> </div> <div class="paragraph"> <p>Use quotes if spaces in the path, or if you have an exclude mapping ("-//some/path/…​")</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml">projects: - name: ProjectA flag_A: y flag_B: y depot_paths: - //depot/inside/... - "-//depot/inside/....c" - name: ProjectB depot_paths: - //depot/B/... - //depot/C/...</code></pre> </div> </div> </div> </div> </div> </div> <div class="sect1"> <h2 id="_trigger_details">2. Trigger Details</h2> <div class="sectionbody"> <div class="paragraph"> <p>The triggers inherit from <code>P4Triggers.py</code> and <code>WorkflowTriggers.py</code> (in some cases).</p> </div> <div class="sect2"> <h3 id="_checkfixes_py">2.1. CheckFixes.py</h3> <div class="paragraph"> <p>This trigger validates that when users create or delete a fix (which is a link between a changelist and a job), that the job is in an appropriate state.</p> </div> <div class="paragraph"> <p>For example, it will prevent users from associating a changelist with a job where the JiraStatus field has a value of <code>Closed</code>.</p> </div> <div class="sect3"> <h4 id="_configuration_values">2.1.1. Configuration values</h4> <div class="paragraph"> <p>The following global configurables are read by this trigger:</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml"># ------------------------------------------ # For CheckFixes.py # Note the use of fix_allowed_paths per project. # fix_state_field: the name of the field in the Perforce job spec fix_state_field: "JiraStatus" # link_allowed_states: An array of values for fix_state_field in which links are allowed to be created # or deleted between jobs and changelists fix_allowed_states: - "Accepted" - "In Work" # msg_cant_link_jobs: An array of lines for the message # For legibility it is good to have the first line blank msg_cant_link_jobs: - "" - "You are not allowed to link changes to or unlink changes from these jobs" - "because of the state of their associated JIRA issues." - "Please change the state first in JIRA and try again."</code></pre> </div> </div> <div class="paragraph"> <p>Projects are allowed to specify <code>fix_allowed_paths</code>, which is an array like <code>depot_paths</code> of Perforce wildcards.</p> </div> <div class="paragraph"> <p>This ensures that the trigger is strict when changes are made to a _dev codeline, but that copying up of changes to an _int codeline is permitted, even when JiraStatus=Closed or similar.</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml">projects: - name: ProjectA depot_paths: - //depot/inside/... - "-//depot/inside/....c" fix_allowed_paths: - //depot/inside/rel/...</code></pre> </div> </div> </div> <div class="sect3"> <h4 id="_trigger_entry">2.1.2. Trigger Entry</h4> <div class="paragraph"> <p>This is:</p> </div> <div class="literalblock"> <div class="content"> <pre>check-fixes fix-add //... "python /p4/common/bin/triggers/CheckFixes.py %change% %client% %jobs%"</pre> </div> </div> <div class="literalblock"> <div class="content"> <pre>check-fixes fix-delete //... "python /p4/common/bin/triggers/CheckFixes.py --delete %change% %client% %jobs%"</pre> </div> </div> <div class="paragraph"> <p>Note the extra parameter –delete for fix-delete.</p> </div> </div> <div class="sect3"> <h4 id="_notes">2.1.3. Notes</h4> <div class="paragraph"> <p>The deletion of a fix is allowed if the change is pending.</p> </div> </div> </div> <div class="sect2"> <h3 id="_checkjobedittrigger_py">2.2. CheckJobEditTrigger.py</h3> <div class="paragraph"> <p>This trigger only allows the P4DTG user to make changes to most job fields. It has a configurable list of fields which ordinary users are allowed to change – by default:</p> </div> <div class="ulist"> <ul> <li> <p>JobStatus</p> </li> <li> <p>Fixes</p> </li> </ul> </div> <div class="paragraph"> <p>It also does not allow ordinary users to create jobs – instead they are instructed to set the value to do this in JIRA.</p> </div> <div class="paragraph"> <p>All other fields must be changed by editing the JIRA issues and allowing P4DTG replication to propagate those changes into Perforce.</p> </div> <div class="sect3"> <h4 id="_configuration_values_2">2.2.1. Configuration Values</h4> <div class="paragraph"> <p>These are hard-coded in the trigger script.</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-python" data-lang="python"># The error messages we give to the user MSG_CANT_CREATE_JOBS = """ You are not allowed to create new jobs! """ MSG_CANT_CHANGE_FIELDS = """ You have changed one or more read-only job fields: """ # The list of writeable fields that users can change. # Changes to any other fields are rejected. # Please validate this against your jobspec. WRITEABLE_FIELDS = ["Status", "Date"] # Replicator user - this user is allowed to change fields REPLICATOR_USER = "p4dtg" JIRA_USER = "jira"</code></pre> </div> </div> </div> <div class="sect3"> <h4 id="_trigger_entry_2">2.2.2. Trigger Entry</h4> <div class="paragraph"> <p>This is:</p> </div> <div class="literalblock"> <div class="content"> <pre>job_save_check form-in job "python /p4/common/bin/triggers/CheckJobEditTrigger.py %user% %formfile% "</pre> </div> </div> </div> <div class="sect3"> <h4 id="_notes_2">2.2.3. Notes</h4> <div class="paragraph"> <p>The trigger calculates which read-only fields have been updated and adds that to the error message reported to the user.</p> </div> </div> </div> <div class="sect2"> <h3 id="_checksubmithasreview_py">2.3. CheckSubmitHasReview.py</h3> <div class="paragraph"> <p>This trigger is a workflow trigger. When configured to fire, it validates that any attempted change-submit or shelve-submit has a valid Swarm review associated with the change.</p> </div> <div class="sect3"> <h4 id="_configuration_values_3">2.3.1. Configuration Values</h4> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml"># For CheckSubmitHasReview.py # Allow certain users to submit directly without review being required # An array of user IDs submit_without_review_users: - jenkins # msg_submit_requires_review: An array of lines for the message # For legibility it is good to have the first line blank msg_submit_requires_review: - "" - "You are not allowed to submit a change without a review." - "Please shelve the change for Swarm review first." - "Jenkins will build the change and if successful submit on your behalf."</code></pre> </div> </div> <div class="paragraph"> <p>This allows users such as the Jenkins user to submit changes, which might be useful after a successful build.</p> </div> <div class="paragraph"> <p>Project configuration entries – the flag <code>pre_submit_required_review</code> must be set to <code>y</code> for trigger to fire.</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml">projects: - name: ProjectA pre_submit_require_review: y depot_paths: - //depot/inside/... - "-//depot/inside/....c"</code></pre> </div> </div> </div> <div class="sect3"> <h4 id="_trigger_entry_3">2.3.2. Trigger Entry</h4> <div class="literalblock"> <div class="content"> <pre>create_swarm_review change-submit //... "python /p4/common/bin/triggers/CheckSubmitHasReview.py -c /p4/common/config/Workflow.yaml %change% "</pre> </div> </div> <div class="literalblock"> <div class="content"> <pre>create_swarm_review2 shelve-submit //... "python /p4/common/bin/triggers/CheckSubmitHasReview.py -c /p4/common/config/Workflow.yaml %change% "</pre> </div> </div> <div class="paragraph"> <p>There are two entries possible, which allows shelve-submit triggers to also be validated.</p> </div> </div> <div class="sect3"> <h4 id="_notes_3">2.3.3. Notes</h4> <div class="paragraph"> <p>Uses Swarm API to search for reviews based on the change id.</p> </div> </div> </div> <div class="sect2"> <h3 id="_createswarmreview_py">2.4. CreateSwarmReview.py</h3> <div class="paragraph"> <p>This trigger automatically creates a Swarm review for submitted change lists.</p> </div> <div class="paragraph"> <p>It is part of the post-submit build/post-submit review workflow which has been deprecated.</p> </div> <div class="sect3"> <h4 id="_configuration_values_4">2.4.1. Configuration Values</h4> <div class="paragraph"> <p>Project configuration entries – the flag <code>post_submit_create_review</code> must be set to <code>y</code> for trigger to fire. The flag <code>update_review</code> controls whether the trigger will add the changelist to any existing review (via shared jobs), or just create a new review for every changelist.</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml">projects: - name: ProjectA post_submit_create_review: y update_review: y depot_paths: - //depot/inside/... - "-//depot/inside/....c"</code></pre> </div> </div> <div class="paragraph"> <p>The global entry:</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml"># rev__iew_description: The default review description. # Can be a single quoted string (with embedded '\n' for newlines, or # an array of quoted strings. # The values $jobDescription and $changeDescription if found will be # expanded as appropriate. review_description: - "$jobDescription" - "" - "<Please edit these fields as desired>" - "Review type: Delta/Full"</code></pre> </div> </div> </div> <div class="sect3"> <h4 id="_trigger_entry_4">2.4.2. Trigger Entry</h4> <div class="literalblock"> <div class="content"> <pre>create_swarm_review change-commit //... "python /p4/common/bin/triggers/CreateSwarmReview.py -c Workflow.yaml %change% "</pre> </div> </div> </div> <div class="sect3"> <h4 id="_notes_4">2.4.3. Notes</h4> <div class="paragraph"> <p>None</p> </div> </div> </div> <div class="sect2"> <h3 id="_swarmreviewtemplate_py">2.5. SwarmReviewTemplate.py</h3> <div class="paragraph"> <p>This trigger uses the same template value as CreateSwarmReview.py to format review text.</p> </div> <div class="paragraph"> <p>It can be run both as a shelve-commit trigger (when Swarm user copies the user’s shelved changes into its own shadow shelf), and as a form-in trigger.</p> </div> <div class="paragraph"> <p>In both cases, the trigger uses Swarm API to search for existing reviews for the specified change, and to decide whether to update the review description or not. (The trigger does not directly update the Perforce change description that Swarm uses to store the review description).</p> </div> <div class="sect3"> <h4 id="_configuration_values_5">2.5.1. Configuration Values</h4> <div class="paragraph"> <p>Project configuration entries – the flag <code>pre_submit_require_review</code> must be set to <code>y</code> for trigger to fire.</p> </div> <div class="listingblock"> <div class="content"> <pre class="highlight"><code class="language-yaml" data-lang="yaml">projects: - name: ProjectA pre_submit_require_review: y update_review: y depot_paths: - //depot/inside/... - "-//depot/inside/....c"</code></pre> </div> </div> <div class="paragraph"> <p>As above <code>review_description</code>. Also reads <code>swarm_user</code> value and exits if the user is not the specified swarm user.</p> </div> </div> <div class="sect3"> <h4 id="_trigger_entry_5">2.5.2. Trigger Entry</h4> <div class="literalblock"> <div class="content"> <pre>swarm_template_review shelve-commit //... "python /p4/common/bin/triggers/SwarmReviewTemplate.py -c Workflow.yaml %change% "</pre> </div> </div> <div class="literalblock"> <div class="content"> <pre>swarm_template_review2 form-save change "python /p4/common/bin/triggers/SwarmReviewTemplate.py -c Workflow.yaml --user %user% --formfile %formfile% "</pre> </div> </div> </div> <div class="sect3"> <h4 id="_notes_5">2.5.3. Notes</h4> <div class="paragraph"> <p>Some complexity to allow it to be called as both shelve-commit and form-in trigger.</p> </div> </div> </div> </div> </div> <div class="sect1"> <h2 id="_test_frameworks">3. Test Frameworks</h2> <div class="sectionbody"> <div class="paragraph"> <p>It is important that all triggers have a comprehensive test framework so that changes and enhancements can be made with relatively little risk.</p> </div> <div class="paragraph"> <p>All test harnesses are in the tests sub-directory.</p> </div> <div class="sect2"> <h3 id="_standalone_unit_tests">3.1. Standalone Unit tests</h3> <div class="paragraph"> <p>Some tests can directly import the triggers modules and exercise individual classes.</p> </div> </div> <div class="sect2"> <h3 id="_testing_via_p4d_triggers_table">3.2. Testing via P4D Triggers table</h3> <div class="paragraph"> <p>Some tests are installed in test environment where p4d is run using the rsh hack (also known as inetd mode) – this is similar to how P4DVCS works because the server process has no requirement to be run with a port (can be dangerous on machines with live or important Perforce servers).</p> </div> <div class="paragraph"> <p>The test harness directly creates trigger table entries so that the p4d server will execute the trigger as required.</p> </div> <div class="paragraph"> <p>The disadvantage of this test method is that you can’t debug easily across the process boundary, because it is p4d which is directly executing the trigger script, and not the test harness.</p> </div> </div> <div class="sect2"> <h3 id="_mocking_the_swarm_api_interface">3.3. Mocking the Swarm API Interface</h3> <div class="paragraph"> <p>This is done using the Mock framework (installed standalone for Python 2.x, standard with 3.x)</p> </div> <div class="paragraph"> <p>It ensures that the expected Swarm API calls are made, and is used to direct test code paths.</p> </div> <div class="paragraph"> <p>Without this, it would be necessary to have an installation of Swarm.</p> </div> <div class="paragraph"> <p>See TestCheckSubmitHasReview.py for an example.</p> </div> </div> </div> </div> </div> <div id="footer"> <div id="footer-text"> Version v2024.1<br> Last updated 2024-06-11 21:57:43 -0400 </div> </div> </body> </html>
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#5 | 30388 | C. Thomas Tyler |
Released SDP 2024.1.30385 (2024/06/11). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#4 | 30043 | C. Thomas Tyler |
Released SDP 2023.2.30041 (2023/12/22). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#3 | 29612 | C. Thomas Tyler |
Released SDP 2023.1.29610 (2023/05/25). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#2 | 27761 | C. Thomas Tyler |
Released SDP 2020.1.27759 (2021/05/07). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
#1 | 27331 | C. Thomas Tyler |
Released SDP 2020.1.27325 (2021/01/29). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
//guest/perforce_software/sdp/dev/Unsupported/doc/WorkflowEnforcementTriggers.html | |||||
#1 | 26681 | Robert Cowham |
Removing Deprecated folder - if people want it they can look at past history! All functions have been replaced with standard functionality such as built in LDAP, or default change type. Documentation added for the contents of Unsupported folder. Changes to scripts/triggers are usually to insert tags for inclusion in ASCII Doctor docs. |
||
//guest/perforce_software/sdp/dev/doc/WorkflowEnforcementTriggers.html | |||||
#1 | 26655 | Robert Cowham | Convert Workflow triggers doc to AsciiDoctor format and note they are Community Supported. |