2 | This plugin renders upstream and downstream connected jobs that typically form a build pipeline. In addition, it offers the ability to define manual triggers for jobs that require intervention prior to execution, e.g. an approval process outside of Jenkins.
3 |
2 | Select this option if you want to be able to execute again a successful pipeline step. If the build is parameterized, this will re-execute the step using the same parameter values that were used when it was previously executed.
3 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildGrid.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | /**
4 | * {@link Grid} of {@link BuildForm}s, which represents one instance
5 | * of a pipeline execution.
6 | *
7 | * @author Kohsuke Kawaguchi
8 | */
9 | public abstract class BuildGrid extends Grid {
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildPipelineView/help-showPipelineDefinitionHeader.html:
--------------------------------------------------------------------------------
1 |
2 | Select this option if you want to show the pipeline definition header in the pipeline view. If this option is not selected, then a pipeline that has never been run will not show any details about its jobs and appear like a blank form. Job details will only appear after the pipeline has been run at least once.
3 |
2 | The Build Pipeline View plugin uses the relationship between upstream and downstream projects to map out a build pipeline.
3 | As default, the downstream project requires a manual trigger when the upstream job is completed.
4 | Specify the Downstream triggered projects in the "Downstream Project Name" field. Multiple projects can be specified by using comma, like "abc, def".
5 |
2 | Select this option to restrict the display of a Trigger button to only the most recent successful build pipelines.
3 |
4 | This option will also limit retries to just unsuccessful builds of the most recent build pipelines.
5 |
6 | Yes: Only the most recent successful builds displayed on the view will have
7 | a manual trigger button for the next build in the pipeline.
8 | No: All successful builds displayed on the view will have a manual trigger
9 | button for the next build in the pipeline.
10 |
--------------------------------------------------------------------------------
/checkstyle_suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/webapp/css/PIE.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/messages.properties:
--------------------------------------------------------------------------------
1 | BuildPipelineTrigger.DisplayText=Build other projects (manual step)
2 | BuildPipelineTrigger.FailedPersistDuringRemoval=Failed to persist project BuildPipelineTrigger setting during removal of
3 | BuildPipelineTrigger.FailedPersistDuringRename_FMT=Failed to persist project BuildPipelineTrigger setting during rename from $1%s to $2%s
4 | BuildPipelineView.DisplayText=Build Pipeline View
5 |
6 | PipelineBuild.PendingBuildOfProject=Pending build of project:
7 | PipelineBuild.ToString_FMT=Project: %s : Build: %s
8 | PipelineBuild.RevisionNotAvailable=Revision not available
9 |
10 | Portlet.BuildPipelineDashboardDescriptor=Build Pipeline Dashboard View
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/ProjectGrid.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | /**
4 | * Two-dimensional placement of {@link ProjectForm}s into a grid/matrix layout.
5 | *
6 | * This class is also responsible for producing a sequence of {@link BuildGrid}s
7 | * that are the instances of the pipelines.
8 | *
9 | * @author Kohsuke Kawaguchi
10 | */
11 | public abstract class ProjectGrid extends Grid {
12 | /**
13 | * Iterates instances of the pipeline grid view from this project layout.
14 | *
15 | * The caller is only going to iterate {@link BuildGrid}s up to a certain number
16 | * that the user has configured.
17 | *
18 | *
19 | * @return never null.
20 | */
21 | public abstract Iterable builds();
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildPipelineView/main_dashboard.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/src/main/groovy/au/com/centrumsystems/hudson/plugin/buildpipeline/ProjectJSONBuilder.groovy:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline
2 |
3 | import groovy.json.JsonBuilder
4 |
5 | class ProjectJSONBuilder {
6 |
7 | static String asJSON(ProjectForm projectForm) {
8 | def entries = new ArrayList()
9 | projectForm.lastSuccessfulBuildParams?.each() { key, value ->
10 | entries.add({
11 | paramName(key)
12 | paramValue(value)
13 | })
14 | }
15 |
16 | def builder = new JsonBuilder()
17 | def root = builder {
18 | id(projectForm.id)
19 | name(projectForm.name)
20 | health(projectForm.health)
21 | url(projectForm.url)
22 | lastSuccessfulBuildNumber(projectForm.lastSuccessfulBuildNumber)
23 | lastSuccessfulBuildParams(entries.toArray())
24 | }
25 | return builder.toString()
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/trigger/BuildPipelineTrigger/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/DownstreamProjectGridBuilderTest.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import org.jvnet.hudson.test.HudsonTestCase;
4 |
5 | /**
6 | * @author Kohsuke Kawaguchi
7 | */
8 | public class DownstreamProjectGridBuilderTest extends HudsonTestCase {
9 | /**
10 | * Makes sure that the config form will keep the settings intact.
11 | */
12 | public void testConfigRoundtrip() throws Exception {
13 | DownstreamProjectGridBuilder gridBuilder = new DownstreamProjectGridBuilder("something");
14 | BuildPipelineView v = new BuildPipelineView("foo","Title", gridBuilder, "5", true, null);
15 | jenkins.addView(v);
16 | configRoundtrip(v);
17 | BuildPipelineView av = (BuildPipelineView)jenkins.getView(v.getViewName());
18 | assertSame(v,av);
19 | // assertNotSame(gridBuilder,(DownstreamProjectGridBuilder)av.getGridBuilder()); //FIXME: this is making the test fail, and it's not obvious why this should be true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/dashboard/BuildPipelineDashboard/portlet.jelly:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-Present, Centrum Systems Pty Ltd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/Strings.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import java.util.MissingResourceException;
4 | import java.util.ResourceBundle;
5 |
6 | /**
7 | * Message resource bundle utility
8 | *
9 | * @author Unknown
10 | */
11 | public final class Strings {
12 | /**
13 | * bundle name
14 | */
15 | private static final String BUNDLE_NAME = "au.com.centrumsystems.hudson.plugin.buildpipeline.messages"; //$NON-NLS-1$
16 |
17 | /**
18 | * message resource bundle
19 | */
20 | private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
21 |
22 | /**
23 | *
24 | */
25 | private Strings() {
26 | }
27 |
28 | /**
29 | *
30 | * @param key
31 | * key to resource bundle
32 | * @return resource of the key.
33 | */
34 | public static String getString(final String key) {
35 | try {
36 | return RESOURCE_BUNDLE.getString(key);
37 | } catch (final MissingResourceException e) {
38 | return '!' + key + '!';
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Build Pipeline Plugin
2 | =====================
3 | * [Wiki][wiki]
4 | * [Issue Tracking][issues]
5 | * [How to Contribute][contributing]
6 |
7 | Building the Project
8 | --------------------
9 |
10 | ### Dependencies
11 | * [Apache Maven][maven] 3.0.4 or later
12 |
13 | ### Targets
14 | ```shell
15 | $ mvn clean install
16 | ```
17 |
18 | Installing Plugin Locally
19 | -------------------------
20 | 1. Build the project to produce `target/build-pipeline-plugin.hpi`
21 | 2. Remove any installation of the build-pipeline-plugin in `$user.home/.jenkins/plugins/`
22 | 3. Copy `target/build-pipeline-plugin.hpi` to `$user.home/.jenkins/plugins/`
23 | 4. Start/Restart Jenkins
24 |
25 |
26 | Continuous Integration
27 | ----------------------
28 | After a pull request is accepted, it is run through a [Jenkins job][job] hosted on CloudBees.
29 |
30 |
31 | [wiki]: https://wiki.jenkins-ci.org/display/JENKINS/Build+Pipeline+Plugin
32 | [issues]: http://issues.jenkins-ci.org/secure/IssueNavigator.jspa?mode=hide&reset=true&jqlQuery=project+%3D+JENKINS+AND+status+in+%28Open%2C+%22In+Progress%22%2C+Reopened%29+AND+component+%3D+%27build-pipeline%27
33 | [contributing]: https://wiki.jenkins-ci.org/display/JENKINS/Build+Pipeline+Plugin+-+How+to+Contribute
34 | [maven]: https://maven.apache.org/
35 | [job]: https://jenkins.ci.cloudbees.com/job/plugins/job/build-pipeline-plugin/
36 |
37 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/ProjectGridBuilderDescriptor.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import hudson.DescriptorExtensionList;
4 | import hudson.model.Descriptor;
5 | import jenkins.model.Jenkins;
6 |
7 | /**
8 | * {@link Descriptor} for {@link ProjectGridBuilder}.
9 | *
10 | * @author Kohsuke Kawaguchi
11 | */
12 | public abstract class ProjectGridBuilderDescriptor extends Descriptor {
13 | /**
14 | * For {@link Descriptor}s that explicitly specify {@link ProjectGridBuilder}
15 | *
16 | * @param clazz
17 | * The type of the {@link ProjectGridBuilder}.
18 | */
19 | public ProjectGridBuilderDescriptor(Class extends ProjectGridBuilder> clazz) {
20 | super(clazz);
21 | }
22 |
23 | /**
24 | * For {@link Descriptor}s that are enclosed in their {@link ProjectGridBuilder}s.
25 | */
26 | public ProjectGridBuilderDescriptor() {
27 | }
28 |
29 | /**
30 | * Returns all the registered {@link ProjectGridBuilder} descriptors.
31 | *
32 | * @return
33 | * always non-null
34 | */
35 | public static DescriptorExtensionList all() {
36 | return Jenkins.getInstance().getDescriptorList(ProjectGridBuilder.class);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/groovy/au/com/centrumsystems/hudson/plugin/buildpipeline/dashboard/BuildPipelineDashboardTest.groovy:
--------------------------------------------------------------------------------
1 | import au.com.centrumsystems.hudson.plugin.buildpipeline.DownstreamProjectGridBuilder
2 | import spock.lang.*
3 |
4 | import au.com.centrumsystems.hudson.plugin.buildpipeline.dashboard.BuildPipelineDashboard
5 | import au.com.centrumsystems.hudson.plugin.buildpipeline.dashboard.ReadOnlyBuildPipelineView
6 |
7 | class BuildPipelineDashboardTest extends Specification {
8 | def cut
9 | def setup() {
10 | cut = new BuildPipelineDashboard('TestProject', 'Test Description', new DownstreamProjectGridBuilder('Job10'), '5')
11 | }
12 |
13 | def "should return a new BuildPipelineView"() {
14 | def bpv = cut.getBuildPipelineView()
15 |
16 | expect:
17 | bpv != null
18 | bpv instanceof ReadOnlyBuildPipelineView
19 | bpv.getGridBuilder().getFirstJob() == 'Job10'
20 | bpv.getNoOfDisplayedBuilds() == '5'
21 | bpv.getBuildViewTitle() == 'TestProject'
22 | }
23 |
24 | def "should not have build permissions"() {
25 | def bpv = cut.getBuildPipelineView()
26 |
27 | expect:
28 | !bpv.hasBuildPermission()
29 | }
30 |
31 | def "should not have any permission"() {
32 | def bpv = cut.getBuildPipelineView()
33 |
34 | expect:
35 | !bpv.hasPermission(null)
36 | }
37 | }
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/dashboard/BuildPipelineDashboard/config.jelly:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/util/HudsonResult.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2011, Centrum Systems Pty Ltd
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 | package au.com.centrumsystems.hudson.plugin.util;
26 |
27 | /**
28 | * Hudson Result
29 | *
30 | * @author Centrum Systems
31 | */
32 | public enum HudsonResult {
33 | /**
34 | * Hudson Result
35 | */
36 | SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORT, BUILDING, PENDING, MANUAL
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/DefaultBuildGridImpl.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * {@link BuildGrid} implementation backed by a sparse array.
8 | *
9 | * @author Kohsuke Kawaguchi
10 | */
11 | public class DefaultBuildGridImpl extends BuildGrid {
12 | /**
13 | * Actual data.
14 | */
15 | private final Map> data = new HashMap>();
16 |
17 | /**
18 | * Dimension of the {@link #data}
19 | */
20 | private int rows, cols;
21 |
22 | /**
23 | * Mutable, but only for the code that instantiates {@link DefaultBuildGridImpl}.
24 | *
25 | * @param row
26 | * position of the form
27 | * @param col
28 | * position of the form
29 | * @param p
30 | * The build to add. null to remove the value.
31 | */
32 | public void set(int row, int col, BuildForm p) {
33 | Map c = data.get(row);
34 | if (c == null) {
35 | c = new HashMap();
36 | data.put(row, c);
37 | }
38 | c.put(col, p);
39 |
40 | rows = Math.max(rows, row + 1);
41 | cols = Math.max(cols, col + 1);
42 | }
43 |
44 | @Override
45 | public BuildForm get(int row, int col) {
46 | final Map cols = data.get(row);
47 | if (cols == null) {
48 | return null;
49 | }
50 | return cols.get(col);
51 | }
52 |
53 | @Override
54 | public int getColumns() {
55 | return cols;
56 | }
57 |
58 | @Override
59 | public int getRows() {
60 | return rows;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/resources/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildPipelineView/configure-entries.jelly:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/testsupport/LoginLogoutPage.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import java.net.URL;
8 | import java.net.URLEncoder;
9 |
10 | import static au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.TestUtils.waitForElement;
11 |
12 | public class LoginLogoutPage implements Page {
13 |
14 | private final URL baseUrl;
15 | private final WebDriver driver;
16 |
17 | public LoginLogoutPage(WebDriver driver, URL baseUrl) {
18 | this.driver = driver;
19 | this.baseUrl = baseUrl;
20 | }
21 |
22 | public void login(String username) {
23 | driver.get(baseUrl + "login");
24 |
25 | usernameField().sendKeys(username);
26 | passwordField().sendKeys(username);
27 | passwordField().submit();
28 | }
29 |
30 | private WebElement usernameField() {
31 | return waitForElement(By.name("j_username"), driver);
32 | }
33 |
34 | private WebElement passwordField() {
35 | return waitForElement(By.name("j_password"), driver);
36 | }
37 |
38 | public void logout() {
39 | driver.get(baseUrl + "logout");
40 | }
41 |
42 | public String getRelativeUrl() {
43 | return "login";
44 | }
45 |
46 | public String getUrl(T nextPage) {
47 | return baseUrl + "login?from=" + encodeSafely(nextPage.getRelativeUrl());
48 | }
49 |
50 | private String encodeSafely(String s) {
51 | try {
52 | return URLEncoder.encode(s, "utf-8");
53 | } catch (Exception e) {
54 | throw new RuntimeException(e);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/DefaultProjectGridImpl.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * {@link ProjectGrid} backed by map.
8 | *
9 | * @author Kohsuke Kawaguchi
10 | * @author Centrum Systems
11 | */
12 | public abstract class DefaultProjectGridImpl extends ProjectGrid {
13 | /**
14 | * Actual data store is a sparse map.
15 | */
16 | private final Map> data = new HashMap>();
17 |
18 | /**
19 | * Dimension of the {@link #data}
20 | */
21 | private int rows, cols;
22 |
23 | /**
24 | * Mutable, but only for {@link ProjectGridBuilder}
25 | *
26 | * @param row
27 | * position of the form
28 | * @param col
29 | * position of the form
30 | * @param p
31 | * The project to add. null to remove the value.
32 | */
33 | public void set(int row, int col, ProjectForm p) {
34 | Map c = data.get(row);
35 | if (c == null) {
36 | c = new HashMap();
37 | data.put(row, c);
38 | }
39 | c.put(col, p);
40 |
41 | rows = Math.max(rows, row + 1);
42 | cols = Math.max(cols, col + 1);
43 | }
44 |
45 | /**
46 | * Gets the project at the specified location.
47 | *
48 | * @param row
49 | * position of the form
50 | * @param col
51 | * position of the form
52 | * @return
53 | * possibly null.
54 | */
55 | @Override
56 | public ProjectForm get(int row, int col) {
57 | final Map cols = data.get(row);
58 | if (cols == null) {
59 | return null;
60 | }
61 | return cols.get(col);
62 | }
63 |
64 | @Override
65 | public int getColumns() {
66 | return cols;
67 | }
68 |
69 | @Override
70 | public int getRows() {
71 | return rows;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildPipelineForm.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import com.google.common.collect.Iterables;
4 |
5 | import java.util.Arrays;
6 | import java.util.List;
7 | import java.util.logging.Logger;
8 |
9 | /**
10 | * @author Centrum Systems
11 | *
12 | * Representation of the projects and their related builds making up the build pipeline view
13 | *
14 | */
15 | public class BuildPipelineForm {
16 | /**
17 | * logger
18 | */
19 | private static final Logger LOGGER = Logger.getLogger(BuildPipelineForm.class.getName());
20 |
21 | /**
22 | * projects laid out in a grid using maps to ease accessing (or maybe I made it way too complicated by not using a 2-dimensional array)
23 | * Outside map holds rows and inner map has ProjectForm at a particular position (defined with key)
24 | */
25 | private final ProjectGrid projectGrid;
26 | /**
27 | * a list of maps of map represents build pipelines laid out in grids, similar to projectGrid, but we have many of these grids
28 | */
29 | private final List buildGrids;
30 |
31 | /**
32 | *
33 | * @param grid
34 | * Project to be laid out in a grid
35 | * @param builds
36 | * builds to be laid out in a grid
37 | */
38 | public BuildPipelineForm(final ProjectGrid grid, final Iterable builds) {
39 | projectGrid = grid;
40 | buildGrids = Arrays.asList(Iterables.toArray(builds, BuildGrid.class));
41 | }
42 |
43 | public ProjectGrid getProjectGrid() {
44 | return projectGrid;
45 | }
46 |
47 | /**
48 | * grid width is the longest column map counting empties (keys represent position, so they are used to determine width)
49 | *
50 | * @return width
51 | */
52 | public Integer getGridWidth() {
53 | return projectGrid.getColumns();
54 | }
55 |
56 | public Integer getGridHeight() {
57 | return projectGrid.getRows();
58 | }
59 |
60 | public List getBuildGrids() {
61 | return buildGrids;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/testsupport/PipelinePage.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import java.net.URL;
8 |
9 | import static au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.TestUtils.elementIsPresent;
10 | import static au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.TestUtils.waitForElement;
11 |
12 | public class PipelinePage implements Page {
13 |
14 | private static final String TRIGGER_PIPELINE_BUTTON = "trigger-pipeline-button";
15 |
16 | private final String pipelineName;
17 | private final WebDriver webDriver;
18 | private final URL baseUrl;
19 |
20 | public PipelinePage(WebDriver webDriver, String pipelineName, URL baseUrl) {
21 | this.webDriver = webDriver;
22 | this.pipelineName = pipelineName;
23 | this.baseUrl = baseUrl;
24 | }
25 |
26 | public PipelinePage open() {
27 | webDriver.get(baseUrl + getRelativeUrl());
28 | return this;
29 | }
30 |
31 | public boolean runButtonIsPresent() {
32 | return triggerPipelineButton() != null;
33 | }
34 |
35 | public boolean runButtonIsAbsent() {
36 | waitForElement(By.xpath("//img[@alt='Pipeline History']"), webDriver);
37 | return !elementIsPresent(By.id(TRIGGER_PIPELINE_BUTTON), webDriver);
38 | }
39 |
40 | public PipelinePage clickRunButton() {
41 | triggerPipelineButton().click();
42 | return this;
43 | }
44 |
45 | public void reload() throws Exception {
46 | webDriver.navigate().refresh();
47 | }
48 |
49 | public BuildCardComponent buildCard(int pipelineGroup, int pipeline, int card) {
50 | return new BuildCardComponent(webDriver, pipelineGroup, pipeline, card).waitFor();
51 | }
52 |
53 | private WebElement triggerPipelineButton() {
54 | return waitForElement(By.id(TRIGGER_PIPELINE_BUTTON), webDriver);
55 | }
56 |
57 | @Override
58 | public String getRelativeUrl() {
59 | return "view/" + pipelineName;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/testsupport/PipelineWebDriverTestBase.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport;
2 |
3 | import au.com.centrumsystems.hudson.plugin.buildpipeline.BuildPipelineView;
4 | import au.com.centrumsystems.hudson.plugin.buildpipeline.DownstreamProjectGridBuilder;
5 | import hudson.model.FreeStyleProject;
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Rule;
9 | import org.jvnet.hudson.test.FailureBuilder;
10 | import org.jvnet.hudson.test.JenkinsRule;
11 | import org.openqa.selenium.WebDriver;
12 | import org.openqa.selenium.firefox.FirefoxDriver;
13 |
14 | public class PipelineWebDriverTestBase {
15 |
16 | protected static final String INITIAL_JOB = "initial-job";
17 | protected static final String SECOND_JOB = "second-job";
18 |
19 | @Rule
20 | public JenkinsRule jr = new JenkinsRule();
21 |
22 | protected FreeStyleProject initialJob;
23 |
24 | protected JenkinsRule.DummySecurityRealm realm;
25 | protected BuildPipelineView pipelineView;
26 | protected LoginLogoutPage loginLogoutPage;
27 | protected PipelinePage pipelinePage;
28 | protected WebDriver webDriver;
29 |
30 | @Before
31 | public void initSharedComponents() throws Exception {
32 | realm = jr.createDummySecurityRealm();
33 | jr.jenkins.setSecurityRealm(realm);
34 | pipelineView = new BuildPipelineView("pipeline", "Pipeline", new DownstreamProjectGridBuilder(INITIAL_JOB), "5", false, true, false, false, false, 1, null, null);
35 | jr.jenkins.addView(pipelineView);
36 |
37 | initialJob = jr.createFreeStyleProject(INITIAL_JOB);
38 |
39 | webDriver = new FirefoxDriver();
40 | loginLogoutPage = new LoginLogoutPage(webDriver, jr.getURL());
41 | pipelinePage = new PipelinePage(webDriver, pipelineView.getViewName(), jr.getURL());
42 | }
43 |
44 | @After
45 | public void cleanUpWebDriver() {
46 | webDriver.close();
47 | webDriver.quit();
48 | }
49 |
50 | protected FreeStyleProject createFailingJob(String name) throws Exception{
51 | FreeStyleProject failingJob = jr.createFreeStyleProject(name);
52 | failingJob.getBuildersList().add(new FailureBuilder());
53 | return failingJob;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/testsupport/TestUtils.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport;
2 |
3 | import com.google.common.base.Function;
4 | import org.openqa.selenium.By;
5 | import org.openqa.selenium.NoSuchElementException;
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.WebElement;
8 | import org.openqa.selenium.support.ui.FluentWait;
9 | import org.openqa.selenium.support.ui.WebDriverWait;
10 |
11 | import java.util.concurrent.TimeUnit;
12 |
13 | import static java.util.concurrent.TimeUnit.SECONDS;
14 |
15 | public class TestUtils {
16 |
17 | public static WebElement waitForElement(final By findBy, WebDriver driver) {
18 | return waitForElement(findBy, driver, 10, SECONDS);
19 | }
20 |
21 | public static WebElement waitForElement(final By findBy, WebDriver driver, long timeout, TimeUnit timeUnit) {
22 | return new WebDriverWait(driver, 10)
23 | .withTimeout(timeout, timeUnit)
24 | .until(new Function() {
25 | public WebElement apply(WebDriver driver) {
26 | return driver.findElement(findBy);
27 | }
28 | });
29 | }
30 |
31 | public static WebElement waitForElement(final By findBy, WebElement parentElement) {
32 | return waitForElement(findBy, parentElement, 10, SECONDS);
33 | }
34 |
35 | public static WebElement waitForElement(final By findBy, WebElement parentElement, long timeout, TimeUnit timeUnit) {
36 | return new FluentWait(parentElement)
37 | .withTimeout(timeout, timeUnit)
38 | .ignoring(NoSuchElementException.class)
39 | .until(new Function() {
40 | public WebElement apply(WebElement element) {
41 | return element.findElement(findBy);
42 | }
43 | });
44 | }
45 |
46 | public static void checkState(boolean condition, String message) {
47 | if (!condition) {
48 | throw new IllegalStateException(message);
49 | }
50 | }
51 |
52 | public static boolean elementIsPresent(By locator, WebDriver driver) {
53 | try {
54 | driver.findElement(locator);
55 | } catch (NoSuchElementException e) {
56 | return false;
57 | }
58 |
59 | return true;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/webapp/css/main_dashboard.css:
--------------------------------------------------------------------------------
1 | #side-panel {
2 | display: block;
3 | width: 280px;
4 | padding: 4px;
5 | }
6 |
7 | .build-detail {
8 | padding: 5px 10px;
9 | background-color:#fafafa;
10 | }
11 |
12 | .build-body, .build-actions {
13 | display: None;
14 | }
15 |
16 | .rounded {
17 | height: 100%;
18 | vertical-align: middle;
19 | }
20 |
21 | tbody.pipelineGroup {
22 | background-color: rgb(175, 225, 255);
23 | background-color: rgba(175, 225, 255, 0.5);
24 | }
25 |
26 | #build-pipeline-plugin-content {
27 | background: white;
28 | }
29 |
30 | #view-message {
31 | display: block;
32 | }
33 |
34 | .revision {
35 | /* generated from http://www.colorzilla.com/gradient-editor/*/
36 | display: table-cell;
37 | vertical-align: middle;
38 | height: 70px;
39 | margin: 2px;
40 | padding: 5px;
41 | text-align: center;
42 |
43 | background: rgb(255,116,0); /* Old browsers */
44 | /* IE9 SVG, needs conditional override of 'filter' to 'none' */
45 | background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmNzQwMCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZjc0MDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
46 | background: -moz-linear-gradient(top, rgba(255,116,0,1) 0%, rgba(255,116,0,1) 100%); /* FF3.6+ */
47 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,116,0,1)), color-stop(100%,rgba(255,116,0,1))); /* Chrome,Safari4+ */
48 | background: -webkit-linear-gradient(top, rgba(255,116,0,1) 0%,rgba(255,116,0,1) 100%); /* Chrome10+,Safari5.1+ */
49 | background: -o-linear-gradient(top, rgba(255,116,0,1) 0%,rgba(255,116,0,1) 100%); /* Opera 11.10+ */
50 | background: -ms-linear-gradient(top, rgba(255,116,0,1) 0%,rgba(255,116,0,1) 100%); /* IE10+ */
51 | background: linear-gradient(top, rgba(255,116,0,1) 0%,rgba(255,116,0,1) 100%); /* W3C */
52 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff7400', endColorstr='#ff7400',GradientType=0 ); /* IE6-8 */
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/dashboard/ReadOnlyBuildPipelineView.java:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // ADOBE SYSTEMS INCORPORATED
4 | // Copyright 2012 Adobe Systems Incorporated
5 | // All Rights Reserved.
6 | //
7 | // NOTICE: Adobe permits you to use, modify, and distribute this file
8 | // in accordance with the terms of the license agreement accompanying it.
9 | //
10 | ////////////////////////////////////////////////////////////////////////////////
11 | package au.com.centrumsystems.hudson.plugin.buildpipeline.dashboard;
12 |
13 | import au.com.centrumsystems.hudson.plugin.buildpipeline.BuildPipelineView;
14 | import au.com.centrumsystems.hudson.plugin.buildpipeline.ProjectGridBuilder;
15 | import hudson.security.Permission;
16 |
17 | /**
18 | * This class provides a read-only view for the existing build-pipeline view. All calls checking permissions return false. The other reason
19 | * for this class is that it's used in a different context and not as a child of the view tab.
20 | *
21 | * @author Ingo Richter (irichter@adobe.com)
22 | * @since 04/01/2012
23 | */
24 | public class ReadOnlyBuildPipelineView extends BuildPipelineView {
25 | /**
26 | *
27 | * @param displayName
28 | * display name of build pipeline view
29 | * @param description
30 | * description of build pipeline view
31 | * @param gridBuilder
32 | * controls the data to be displayed.
33 | * @param noOfDisplayedBuilds
34 | * number of displayed build of build pipeline view
35 | * @param triggerOnlyLatestJob
36 | * is trigger only latest job?
37 | * @param cssUrl
38 | * URL for the custom CSS file.
39 | */
40 | public ReadOnlyBuildPipelineView(final String displayName, final String description, final ProjectGridBuilder gridBuilder,
41 | final String noOfDisplayedBuilds, final boolean triggerOnlyLatestJob, final String cssUrl) {
42 | super(displayName, displayName, gridBuilder, noOfDisplayedBuilds, triggerOnlyLatestJob, cssUrl);
43 | // this is ugly, but there is no other way to set the description of the view
44 | super.description = description;
45 | }
46 |
47 | @Override
48 | public boolean hasBuildPermission() {
49 | // we are not a 'real view' in this case and we don't care in R/O mode
50 | return false;
51 | }
52 |
53 | @Override
54 | public boolean hasPermission(final Permission p) {
55 | return false;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/testsupport/BuildCardComponent.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import static au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.TestUtils.elementIsPresent;
10 | import static au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.TestUtils.waitForElement;
11 | import static java.util.concurrent.TimeUnit.SECONDS;
12 |
13 | public class BuildCardComponent {
14 |
15 | private static final String TRIGGER_SPAN_XPATH = "//span[@class='pointer trigger']";
16 | private static final String RETRY_IMG_XPATH = "//span[@class='pointer trigger']/img[@alt='retry']";
17 |
18 | private final WebDriver webDriver;
19 | private final int pipelineGroup;
20 | private final int pipeline;
21 | private final int card;
22 |
23 | private WebElement cardWebElement;
24 |
25 | public BuildCardComponent(WebDriver webDriver, int pipelineGroup, int pipeline, int card) {
26 | this.webDriver = webDriver;
27 | this.pipelineGroup = pipelineGroup;
28 | this.pipeline = pipeline;
29 | this.card = card;
30 | }
31 |
32 | public BuildCardComponent waitFor() {
33 | cardWebElement = waitForElement(By.xpath(cardXPath(pipelineGroup, pipeline, card)), webDriver);
34 | return this;
35 | }
36 |
37 | public BuildCardComponent waitForBuildToStart() throws Exception {
38 | waitForElement(By.xpath("//table[@class='progress-bar']"), webDriver);
39 | return this;
40 | }
41 |
42 | public BuildCardComponent waitForFailure() {
43 | return waitForStatus("FAILURE");
44 | }
45 |
46 | public BuildCardComponent waitForStatus(String status) {
47 | waitForElement(
48 | By.xpath("//table[contains(@class, '" + status + "')]"),
49 | cardWebElement,
50 | 20, SECONDS);
51 | return this;
52 | }
53 |
54 | public boolean hasManualTriggerButton() {
55 | return elementIsPresent(By.xpath(TRIGGER_SPAN_XPATH), webDriver);
56 | }
57 |
58 | public boolean hasRetryButton() {
59 | return elementIsPresent(By.xpath(RETRY_IMG_XPATH), webDriver);
60 | }
61 |
62 | public BuildCardComponent clickTriggerButton() throws Exception {
63 | triggerButtonHtmlElement().click();
64 | return this;
65 | }
66 |
67 | private WebElement triggerButtonHtmlElement() {
68 | return cardWebElement.findElement(By.xpath(TRIGGER_SPAN_XPATH));
69 | }
70 |
71 | private String cardXPath(int pipelineGroup, int pipeline, int card) {
72 | return String.format("//table[@id = 'pipelines']/tbody[%d]/tr[@class='build-pipeline'][%d]/td[starts-with(@id,'build-')][%d]",
73 | pipelineGroup, pipeline, card);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/groovy/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildJSONBuilder.groovy:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline
2 |
3 | import groovy.json.JsonBuilder
4 | import hudson.model.Cause
5 | import hudson.model.Item
6 | import hudson.model.ItemGroup
7 |
8 | class BuildJSONBuilder {
9 |
10 | static String asJSON(ItemGroup context, PipelineBuild pipelineBuild, Integer formId, Integer projectId, List buildDependencyIds, ArrayList params) {
11 | def builder = new JsonBuilder()
12 | def buildStatus = pipelineBuild.currentBuildResult
13 | def root = builder {
14 | id(formId)
15 | build {
16 | dependencyIds(buildDependencyIds)
17 | displayName(pipelineBuild.currentBuild?.displayName)
18 | duration(pipelineBuild.buildDuration)
19 | extId(pipelineBuild.currentBuild?.externalizableId)
20 | hasPermission(pipelineBuild.project?.hasPermission(Item.BUILD));
21 | hasUpstreamBuild(null != pipelineBuild.upstreamBuild)
22 | isBuilding(buildStatus == 'BUILDING')
23 | isComplete(buildStatus != 'BUILDING' && buildStatus != 'PENDING' && buildStatus != 'MANUAL')
24 | isPending(buildStatus == 'PENDING')
25 | isSuccess(buildStatus == 'SUCCESS')
26 | isReadyToBeManuallyBuilt(pipelineBuild.isReadyToBeManuallyBuilt())
27 | isManualTrigger(pipelineBuild.isManualTrigger())
28 | isRerunnable(pipelineBuild.isRerunnable())
29 | isLatestBuild(null != pipelineBuild.currentBuild?.number && pipelineBuild.currentBuild?.number == pipelineBuild.project.getLastBuild()?.number)
30 | isUpstreamBuildLatest(null != pipelineBuild.upstreamBuild?.number && pipelineBuild.upstreamBuild?.number == pipelineBuild.upstreamPipelineBuild?.project?.getLastBuild()?.number)
31 | isUpstreamBuildLatestSuccess(null != pipelineBuild.upstreamBuild?.number && pipelineBuild.upstreamBuild?.number == pipelineBuild.upstreamPipelineBuild?.project?.lastSuccessfulBuild?.number)
32 | number(pipelineBuild.currentBuild?.number)
33 | progress(pipelineBuild.buildProgress)
34 | progressLeft(100 - pipelineBuild.buildProgress)
35 | startDate(pipelineBuild.formattedStartDate)
36 | startTime(pipelineBuild.formattedStartTime)
37 | status(buildStatus)
38 | url(pipelineBuild.buildResultURL ? pipelineBuild.buildResultURL : pipelineBuild.projectURL)
39 | userId(pipelineBuild.currentBuild?.getCause(Cause.UserIdCause.class)?.getUserId())
40 | estimatedRemainingTime(pipelineBuild.currentBuild?.executor?.estimatedRemainingTime)
41 | }
42 | project {
43 | disabled(pipelineBuild.projectDisabled)
44 | name(pipelineBuild.project.getRelativeNameFromGroup(context))
45 | url(pipelineBuild.projectURL)
46 | health(pipelineBuild.projectHealth)
47 | id(projectId)
48 | parameters(params)
49 | }
50 | upstream {
51 | projectName(pipelineBuild.upstreamPipelineBuild?.project?.getRelativeNameFromGroup(context))
52 | buildNumber(pipelineBuild.upstreamBuild?.number)
53 | }
54 | }
55 | return builder.toString()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/ProjectGridBuilder.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import hudson.model.AbstractDescribableImpl;
4 | import hudson.model.Item;
5 | import org.kohsuke.stapler.AncestorInPath;
6 | import org.kohsuke.stapler.HttpResponse;
7 | import org.kohsuke.stapler.StaplerRequest;
8 |
9 | import java.io.IOException;
10 |
11 | /**
12 | * Encapsulates the definition of how to layout projects into a {@link ProjectGrid}.
13 | *
14 | * @author Kohsuke Kawaguchi
15 | */
16 | public abstract class ProjectGridBuilder extends AbstractDescribableImpl {
17 |
18 | /**
19 | * Builds the grid.
20 | *
21 | * @param owner
22 | * The view for which this builder is working. Never null.
23 | * If the {@link ProjectGridBuilder} takes user-supplied job name,
24 | * this parameter should be used as a context to resolve relative names.
25 | * See {@link jenkins.model.Jenkins#getItem(String, hudson.model.ItemGroup)} (where you obtain
26 | * {@link hudson.model.ItemGroup} by {@link BuildPipelineView#getOwnerItemGroup()}.
27 | * @return
28 | * Never null, although the obtained {@link ProjectGrid} can be empty.
29 | */
30 | public abstract ProjectGrid build(BuildPipelineView owner);
31 |
32 | /**
33 | * Called by {@link BuildPipelineView} when one of its members are renamed.
34 | *
35 | * @param owner
36 | * View that this builder is operating under.
37 | * @param oldName
38 | * Old short name of the job
39 | * @param newName
40 | * New short name of the job
41 | * @param item
42 | * Job being renamed.
43 | */
44 | public void onJobRenamed(BuildPipelineView owner, Item item, String oldName, String newName) throws IOException {
45 | // no-op
46 | }
47 |
48 | /**
49 | * If the grid produced by this builder supports the notion of "starting a new pipeline instance",
50 | * and if the current user has a permission to do so, then return true.
51 | *
52 | * @param owner
53 | * View that this builder is operating under.
54 | * @return
55 | * True if the user has a permission.
56 | */
57 | public abstract boolean hasBuildPermission(BuildPipelineView owner);
58 |
59 | /**
60 | * If the first job of the grid produced by this builder has parameters
61 | *
62 | * @param owner
63 | * View that this builder is operating under.
64 | * @return
65 | * True if the first job has parameters.
66 | */
67 | public abstract boolean startsWithParameters(BuildPipelineView owner);
68 |
69 | /**
70 | * Called to start a new pipeline instance
71 | * (normally by triggering some job.)
72 | *
73 | * @param req
74 | * Current HTTP request
75 | * @param owner
76 | * View that this builder is operating under.
77 | * @return
78 | * The HTTP response.
79 | */
80 | public abstract HttpResponse doBuild(StaplerRequest req, @AncestorInPath BuildPipelineView owner) throws IOException;
81 |
82 | /**
83 | * {@inheritDoc}
84 | */
85 | @Override
86 | public ProjectGridBuilderDescriptor getDescriptor() {
87 | return (ProjectGridBuilderDescriptor) super.getDescriptor();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/Grid.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | /**
4 | * Two-dimensional finite sparse placement of things (such as projects and builds) into a grid/matrix layout.
5 | *
6 | * @param
7 | * The type of the data that gets placed in a two dimensional table.
8 | * @author Kohsuke Kawaguchi
9 | */
10 | public abstract class Grid {
11 | /**
12 | * Height of the grid. Total number of rows.
13 | *
14 | * @return positive integer
15 | */
16 | public abstract int getRows();
17 |
18 | /**
19 | * Width of the grid. Total number of columns.
20 | *
21 | * @return positive integer
22 | */
23 | public abstract int getColumns();
24 |
25 | /**
26 | * Obtains the project placed at the specific position.
27 | *
28 | * @param row
29 | * {@code 0<=row<getRows()}
30 | * @param col
31 | * {@code 0<=col<getColumns()}
32 | * @return
33 | * null if there's nothing placed in that position.
34 | */
35 | public abstract T get(int row, int col);
36 |
37 | /**
38 | * Tests if the layout is empty.
39 | *
40 | * @return
41 | * true if this grid contains no {@link ProjectForm} at all.
42 | */
43 | public boolean isEmpty() {
44 | return getRows() == 0; // && getColumns()==0; -- testing one is enough
45 | }
46 |
47 | /**
48 | * Determines the next row of the grid that should be populated.
49 | *
50 | * Given (currentRow,currentColumn), find a row R>=currentRow such that
51 | * the row R contains no project to any column to the right of current column.
52 | * That is, find the row in which we can place a sibling of the project
53 | * placed in (currentRow,currentColumn).
54 | *
55 | * This method is useful for determining the position to insert a {@link ProjectForm}
56 | * when the layout is tree-like.
57 | *
58 | * @param currentRow
59 | * - The current row of the grid being used
60 | * @param currentColumn
61 | * - The current column of the grid being used
62 | * @return - The row number to be used
63 | */
64 | public int getNextAvailableRow(final int currentRow, final int currentColumn) {
65 | final int rows = getRows();
66 | for (int nextRow = currentRow; nextRow < rows; nextRow++) {
67 | if (hasDataToRight(nextRow, currentColumn)) {
68 | nextRow++;
69 | } else {
70 | return nextRow;
71 | }
72 | }
73 | return rows;
74 | }
75 |
76 | /**
77 | * Tests if the row of the grid already contains entries in the columns greater than the entered column.
78 | *
79 | * @param row
80 | * - The row of the grid
81 | * @param col
82 | * - The current column of the grid
83 | * @return - true: The row does contain data in the columns greater than col, false: The row does not contain data in the columns
84 | * greater than col
85 | */
86 | private boolean hasDataToRight(final int row, final int col) {
87 | final int cols = getColumns();
88 | for (int i = col; i < cols; i++) {
89 | if (get(row, i) != null) {
90 | return true;
91 | }
92 | }
93 | return false;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/ProjectFormTest.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
5 | import static org.junit.Assert.assertThat;
6 | import hudson.model.FreeStyleBuild;
7 | import hudson.model.FreeStyleProject;
8 | import hudson.tasks.BuildTrigger;
9 |
10 | import java.io.IOException;
11 |
12 | import org.junit.Before;
13 | import org.junit.Test;
14 | import org.jvnet.hudson.test.HudsonTestCase;
15 |
16 | public class ProjectFormTest extends HudsonTestCase {
17 | @Override
18 | @Before
19 | public void setUp() throws Exception {
20 | super.setUp();
21 | }
22 |
23 | @Test
24 | public void testConstructor() throws Exception {
25 | final String proj1 = "Project1";
26 | final String proj2 = "Project2";
27 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
28 | final FreeStyleProject project2 = createFreeStyleProject(proj2);
29 | project1.getPublishersList().add(new BuildTrigger(proj2, false));
30 | hudson.rebuildDependencyGraph();
31 | final FreeStyleBuild build1 = buildAndAssertSuccess(project1);
32 | waitUntilNoActivity();
33 |
34 | final PipelineBuild pb = new PipelineBuild(build1, project1, null);
35 | final ProjectForm pf = new ProjectForm(project1);
36 | assertEquals(project1.getName(), pf.getName());
37 | assertEquals(pb.getCurrentBuildResult(), pf.getResult());
38 | assertEquals(pb.getProjectURL(), pf.getUrl());
39 | assertEquals(pb.getProject().getBuildHealth().getIconUrl().replaceAll("\\.gif", "\\.png"), pf.getHealth());
40 | assertThat(pf.getDependencies().get(0).getName(), is(project2.getName()));
41 | }
42 |
43 | @Test
44 | public void testEquals() throws IOException {
45 | final String proj1 = "Project1";
46 | final String proj2 = "Project2";
47 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
48 | final FreeStyleProject project2 = createFreeStyleProject(proj2);
49 | project1.getPublishersList().add(new BuildTrigger(proj2, false));
50 | hudson.rebuildDependencyGraph();
51 |
52 | final ProjectForm pf = new ProjectForm(project1);
53 | final ProjectForm pf1 = new ProjectForm(project1);
54 | final ProjectForm pf2 = new ProjectForm(project2);
55 | final String proj3 = null;
56 | final ProjectForm pf3 = new ProjectForm(proj3);
57 |
58 | assertTrue(pf.equals(pf1));
59 | assertFalse(pf.equals(pf2));
60 | assertNotNull(pf);
61 | assertFalse(pf.equals(pf3));
62 |
63 | }
64 |
65 | @Test
66 | public void testNoInfiniteRecursion() throws IOException {
67 | final String proj1 = "Project1";
68 | final String proj2 = "Project2";
69 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
70 | final FreeStyleProject project2 = createFreeStyleProject(proj2);
71 | project1.getPublishersList().add(new BuildTrigger(proj2, false));
72 | project2.getPublishersList().add(new BuildTrigger(proj1, false));
73 | hudson.rebuildDependencyGraph();
74 |
75 | final ProjectForm form1 = new ProjectForm(project1);
76 | assertThat(form1.getDependencies(), hasSize(1));
77 | assertThat(form1.getDependencies().get(0).getDependencies(), hasSize(0));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/au/com/centrumsystems/hudson/plugin/buildpipeline/trigger/DownstreamDependency.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2011, Centrum Systems Pty Ltd
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 | package au.com.centrumsystems.hudson.plugin.buildpipeline.trigger;
26 |
27 | import hudson.model.Action;
28 | import hudson.model.DependencyGraph.Dependency;
29 | import hudson.model.Result;
30 | import hudson.model.TaskListener;
31 | import hudson.model.AbstractBuild;
32 | import hudson.model.AbstractProject;
33 |
34 | import java.util.List;
35 | import java.util.logging.Logger;
36 |
37 | import au.com.centrumsystems.hudson.plugin.util.ProjectUtil;
38 |
39 | /**
40 | * Defines downstream dependency for the build pipeline trigger
41 | *
42 | * @author Centrum Systems
43 | */
44 | public class DownstreamDependency extends Dependency {
45 | /**
46 | * logger
47 | */
48 | private static final Logger LOGGER = Logger.getLogger(DownstreamDependency.class.getName());
49 |
50 | /**
51 | * Downstream Dependency
52 | *
53 | * @param upstream
54 | * the upstream job
55 | * @param downstream
56 | * the downstream job
57 | */
58 | public DownstreamDependency(final AbstractProject, ?> upstream, final AbstractProject, ?> downstream) {
59 | super(upstream, downstream);
60 | }
61 |
62 | /**
63 | * {@inheritDoc}
64 | */
65 | @SuppressWarnings("rawtypes")
66 | @Override
67 | public boolean shouldTriggerBuild(final AbstractBuild build, final TaskListener listener, final List actions) {
68 | LOGGER.fine("Checking if build should be triggered.");
69 |
70 | // If the upstream project has an automatic trigger to the downstream project
71 | // and the current build result was SUCCESS then return true.
72 | if (ProjectUtil.isManualTrigger(build.getProject(), getDownstreamProject())) {
73 | LOGGER.fine("(shouldn't trigger: manual)");
74 | return false;
75 | }
76 |
77 | final Result result = build.getResult();
78 | if (result == null) {
79 | throw new IllegalStateException("Build with a null result in DownstreamDependency#shouldTriggerBuilder");
80 | }
81 | if (result.isWorseThan(Result.SUCCESS)) {
82 | LOGGER.fine("(shouldn't trigger: unsuccessful build)");
83 | return false;
84 | }
85 | LOGGER.fine("(should trigger)");
86 | return true;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildFormTest.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
5 | import static org.junit.Assert.assertThat;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import hudson.model.FreeStyleBuild;
11 | import hudson.model.FreeStyleProject;
12 | import hudson.model.ParameterDefinition;
13 | import hudson.model.ParameterValue;
14 | import hudson.model.ParametersDefinitionProperty;
15 | import hudson.model.StringParameterDefinition;
16 | import hudson.tasks.BuildTrigger;
17 | import net.sf.json.JSONObject;
18 |
19 | import org.junit.Before;
20 | import org.junit.Test;
21 | import org.jvnet.hudson.test.HudsonTestCase;
22 | import org.kohsuke.stapler.StaplerRequest;
23 |
24 | public class BuildFormTest extends HudsonTestCase {
25 | @Override
26 | @Before
27 | public void setUp() throws Exception {
28 | super.setUp();
29 | }
30 |
31 | @Test
32 | public void testConstructor() throws Exception {
33 | final String proj1 = "Project1";
34 | final String proj2 = "Project2";
35 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
36 | project1.getPublishersList().add(new BuildTrigger(proj2, false));
37 | hudson.rebuildDependencyGraph();
38 | final FreeStyleBuild build1 = buildAndAssertSuccess(project1);
39 | waitUntilNoActivity();
40 |
41 | final PipelineBuild pb = new PipelineBuild(build1, project1, null);
42 | final BuildForm bf = new BuildForm(jenkins, pb);
43 |
44 | assertThat(bf.getStatus(), is(pb.getCurrentBuildResult()));
45 | }
46 |
47 | @Test
48 | public void testGetParameterList() throws Exception {
49 | final String proj1 = "Project1";
50 | final String proj2 = "Project2";
51 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
52 | project1.getPublishersList().add(new BuildTrigger(proj2, false));
53 |
54 | final List pds = new ArrayList();
55 | pds.add(new StringParameterDefinition("tag",""));
56 | pds.add(new StringParameterDefinition("branch",""));
57 |
58 | project1.addProperty(new ParametersDefinitionProperty(pds));
59 | hudson.rebuildDependencyGraph();
60 | final FreeStyleBuild build1 = buildAndAssertSuccess(project1);
61 | waitUntilNoActivity();
62 | final ArrayList paramList = new ArrayList();
63 | paramList.add("tag");
64 | paramList.add("branch");
65 |
66 | final PipelineBuild pb = new PipelineBuild(build1, project1, null);
67 | final BuildForm bf = new BuildForm(jenkins, pb);
68 |
69 | assertEquals(paramList, bf.getParameterList());
70 | }
71 |
72 | @Test
73 | public void testNoInfiniteRecursion() throws Exception {
74 | final String proj1 = "Project1";
75 | final String proj2 = "Project2";
76 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
77 | final FreeStyleProject project2 = createFreeStyleProject(proj2);
78 | project1.getPublishersList().add(new BuildTrigger(proj2, false));
79 | project2.getPublishersList().add(new BuildTrigger(proj1, false));
80 | hudson.rebuildDependencyGraph();
81 |
82 | final BuildForm form1 = new BuildForm(jenkins, new PipelineBuild(null, project1, null));
83 | assertThat(form1.getDependencies(), hasSize(1));
84 | assertThat(form1.getDependencies().get(0).getDependencies(), hasSize(0));
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/trigger/DownstreamDependencyTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2011, Centrumsystems Pty Ltd
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */package au.com.centrumsystems.hudson.plugin.buildpipeline.trigger;
25 |
26 | import hudson.model.FreeStyleProject;
27 | import hudson.model.Hudson;
28 | import hudson.tasks.BuildTrigger;
29 |
30 | import java.io.IOException;
31 |
32 | import org.junit.Before;
33 | import org.junit.Test;
34 | import org.jvnet.hudson.test.HudsonTestCase;
35 |
36 | /**
37 | * @author Centrum Systems
38 | *
39 | */
40 | public class DownstreamDependencyTest extends HudsonTestCase {
41 |
42 | @Override
43 | @Before
44 | public void setUp() throws Exception {
45 | super.setUp();
46 | }
47 |
48 | @Test
49 | public void testDownstreamDependency() throws IOException {
50 | final String proj1 = "Proj1";
51 | final String proj2 = "Proj2";
52 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
53 | final FreeStyleProject project2 = createFreeStyleProject(proj2);
54 |
55 | final DownstreamDependency myDD = new DownstreamDependency(project1, project2);
56 | assertEquals("Upstream project should be " + proj1, project1, myDD.getUpstreamProject());
57 | assertEquals("Downstream project should be " + proj2, project2, myDD.getDownstreamProject());
58 | }
59 |
60 | @Test
61 | public void testShouldTriggerBuild() throws Exception {
62 | final String proj1 = "Proj1";
63 | final String proj2 = "Proj2";
64 | final String proj3 = "Proj3";
65 | final FreeStyleProject project1 = createFreeStyleProject(proj1);
66 | final FreeStyleProject project2 = createFreeStyleProject(proj2);
67 | final FreeStyleProject project3 = createFreeStyleProject(proj3);
68 |
69 | // Add TEST_PROJECT2 as a Manually executed pipeline project
70 | // Add TEST_PROJECT3 as a Post-build action -> build other projects
71 | project1.getPublishersList().add(new BuildPipelineTrigger(proj2, null));
72 | project1.getPublishersList().add(new BuildTrigger(proj3, true));
73 |
74 | // Important; we must do this step to ensure that the dependency graphs are updated
75 | Hudson.getInstance().rebuildDependencyGraph();
76 |
77 | // Build project1 and wait until completion
78 | buildAndAssertSuccess(project1);
79 | waitUntilNoActivity();
80 |
81 | assertNull(project2.getLastBuild());
82 | assertNotNull(project3.getLastBuild());
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/BuildPipelineViewConstructorTest.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.junit.Assert.assertFalse;
5 | import static org.junit.Assert.assertThat;
6 | import static org.junit.Assert.assertTrue;
7 | import hudson.model.Hudson;
8 | import hudson.model.ItemGroup;
9 | import hudson.model.TopLevelItem;
10 |
11 | import java.io.IOException;
12 |
13 | import org.junit.Test;
14 |
15 | public class BuildPipelineViewConstructorTest {
16 |
17 | final String bpViewName = "MyTestView";
18 | final String bpViewTitle = "MyTestViewTitle";
19 | final String proj1 = "Proj1";
20 | final DownstreamProjectGridBuilder gridBuilder = new DownstreamProjectGridBuilder(proj1);
21 | final DownstreamProjectGridBuilder nullGridBuilder = new DownstreamProjectGridBuilder("");
22 | final String noOfBuilds = "5";
23 |
24 | @Test
25 | public void testAlwaysAllowManualTrigger() throws IOException {
26 | // True
27 | BuildPipelineView testView = new BuildPipelineView(bpViewName, bpViewTitle, gridBuilder, noOfBuilds, true, true, false, false, false, 2, null, null);
28 | assertTrue(testView.isAlwaysAllowManualTrigger());
29 |
30 | // False
31 | testView = new BuildPipelineView(bpViewName, bpViewTitle, nullGridBuilder, noOfBuilds, true, false, false, false, false, 2, null, null);
32 | assertFalse(testView.isAlwaysAllowManualTrigger());
33 | }
34 |
35 | @Test
36 | public void testShowPipelineParameters() throws IOException {
37 |
38 | // True
39 | BuildPipelineView testView = new BuildPipelineView(bpViewName, bpViewTitle, gridBuilder, noOfBuilds, true, false, true, false, false, 2, null, null);
40 | assertTrue(testView.isShowPipelineParameters());
41 |
42 | // False
43 | testView = new BuildPipelineView(bpViewName, bpViewTitle, nullGridBuilder, noOfBuilds, true, false, false, false, false, 2, null, null);
44 | assertFalse(testView.isShowPipelineParameters());
45 | }
46 |
47 | @Test
48 | public void testShowPipelineParametersInHeaders() throws IOException {
49 |
50 | // True
51 | BuildPipelineView testView = new BuildPipelineView(bpViewName, bpViewTitle, gridBuilder, noOfBuilds, true, false, true, true, false, 2, null, null);
52 | assertTrue(testView.isShowPipelineParametersInHeaders());
53 |
54 | // False
55 | testView = new BuildPipelineView(bpViewName, bpViewTitle, nullGridBuilder, noOfBuilds, true, false, false, false, false, 2, null, null);
56 | assertFalse(testView.isShowPipelineParametersInHeaders());
57 | }
58 |
59 | @Test
60 | public void testRefreshFrequency() throws IOException {
61 |
62 | // False
63 | final BuildPipelineView testView = new BuildPipelineView(bpViewName, bpViewTitle, nullGridBuilder, noOfBuilds, true, false, false, false, false, 2, null, null);
64 | assertThat(testView.getRefreshFrequency(), is(2));
65 | assertThat(testView.getRefreshFrequencyInMillis(), is(2000));
66 | }
67 |
68 | /**
69 | * This is a factory to create an instance of the class under test. This helps to avoid a NPE in View.java when calling
70 | * getOwnerItemGroup and it's not set. This doesn't solve the root cause and it't only intended to make our tests succeed.
71 | */
72 | static class BuildPipelineViewFactory {
73 | public static BuildPipelineView getBuildPipelineView(final String bpViewName, final String bpViewTitle, final ProjectGridBuilder gridBuilder,
74 | final String noOfBuilds, final boolean triggerOnlyLatestJob) {
75 | return new BuildPipelineView(bpViewName, bpViewTitle, gridBuilder, noOfBuilds, triggerOnlyLatestJob, null) {
76 |
77 | @Override
78 | public ItemGroup extends TopLevelItem> getOwnerItemGroup() {
79 | return Hudson.getInstance();
80 | }
81 | };
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/functionaltest/ParameterPassingTest.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.functionaltest;
2 |
3 | import au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.PipelineWebDriverTestBase;
4 | import au.com.centrumsystems.hudson.plugin.buildpipeline.trigger.BuildPipelineTrigger;
5 | import com.google.common.base.Optional;
6 | import com.google.common.base.Predicate;
7 | import hudson.model.FreeStyleBuild;
8 | import hudson.model.FreeStyleProject;
9 | import hudson.model.ParametersAction;
10 | import hudson.model.StringParameterValue;
11 | import hudson.plugins.parameterizedtrigger.AbstractBuildParameters;
12 | import hudson.plugins.parameterizedtrigger.PredefinedBuildParameters;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 | import org.openqa.selenium.support.ui.FluentWait;
16 |
17 | import java.util.Arrays;
18 |
19 | import static hudson.model.Result.FAILURE;
20 | import static java.util.concurrent.TimeUnit.SECONDS;
21 | import static org.hamcrest.core.Is.is;
22 | import static org.junit.Assert.assertThat;
23 |
24 | public class ParameterPassingTest extends PipelineWebDriverTestBase {
25 |
26 | FreeStyleProject secondJob;
27 |
28 | @Before
29 | public void init() throws Exception {
30 | secondJob = createFailingJob(SECOND_JOB);
31 | initialJob.getPublishersList().add(
32 | new BuildPipelineTrigger(secondJob.getName(),
33 | Arrays.asList(new PredefinedBuildParameters("myProp=some-value"))));
34 | jr.jenkins.rebuildDependencyGraph();
35 | }
36 |
37 | @Test
38 | public void shouldPassParametersFromFirstJobToSecond() throws Exception {
39 | jr.buildAndAssertSuccess(initialJob);
40 | pipelinePage.open()
41 | .buildCard(1, 1, 2)
42 | .clickTriggerButton()
43 | .waitForFailure();
44 |
45 | assertParameterValueIsPresentInBuild(secondJob.getBuilds().getFirstBuild());
46 | }
47 |
48 | @Test
49 | public void secondJobShouldRetainParameterWhenRetried() throws Exception {
50 | jr.buildAndAssertSuccess(initialJob);
51 | pipelinePage.open()
52 | .buildCard(1, 1, 2)
53 | .clickTriggerButton()
54 | .waitForFailure()
55 | .clickTriggerButton();
56 |
57 | waitForBuild2ToFail();
58 |
59 | assertParameterValueIsPresentInBuild(secondJob.getBuilds().getLastBuild());
60 | }
61 |
62 | private void waitForBuild2ToFail() {
63 | new FluentWait(secondJob)
64 | .ignoring(IllegalStateException.class)
65 | .withTimeout(10, SECONDS)
66 | .until(new Predicate() {
67 | public boolean apply(FreeStyleProject input) {
68 | return buildNumbered(2, input).getResult() == FAILURE;
69 | }
70 | });
71 | }
72 |
73 | private void assertParameterValueIsPresentInBuild(FreeStyleBuild build) {
74 | assertThat(getMyPropParameterFrom(build).or(absentParameter()).value, is("some-value"));
75 | }
76 |
77 | private Optional getMyPropParameterFrom(FreeStyleBuild build) {
78 | ParametersAction parametersAction = build.getAction(ParametersAction.class);
79 | if (parametersAction != null) {
80 | return Optional.fromNullable((StringParameterValue) parametersAction.getParameter("myProp"));
81 | }
82 |
83 | return Optional.absent();
84 | }
85 |
86 | private FreeStyleBuild buildNumbered(int number, FreeStyleProject job) {
87 | for (FreeStyleBuild build: job.getBuilds()) {
88 | if (build.getNumber() == number) {
89 | return build;
90 | }
91 | }
92 |
93 | throw new IllegalStateException("No build numbered " + number + " in " + job);
94 | }
95 |
96 | private StringParameterValue absentParameter() {
97 | return new StringParameterValue("myProp", "[absent]");
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/au/com/centrumsystems/hudson/plugin/buildpipeline/functionaltest/BuildSecurityTest.java:
--------------------------------------------------------------------------------
1 | package au.com.centrumsystems.hudson.plugin.buildpipeline.functionaltest;
2 |
3 | import au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.BuildCardComponent;
4 | import au.com.centrumsystems.hudson.plugin.buildpipeline.testsupport.PipelineWebDriverTestBase;
5 | import au.com.centrumsystems.hudson.plugin.buildpipeline.trigger.BuildPipelineTrigger;
6 | import hudson.model.FreeStyleProject;
7 | import hudson.model.Item;
8 | import hudson.plugins.parameterizedtrigger.AbstractBuildParameters;
9 | import hudson.security.GlobalMatrixAuthorizationStrategy;
10 | import hudson.security.Permission;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 |
14 | import java.util.Collections;
15 |
16 | import static org.junit.Assert.assertFalse;
17 | import static org.junit.Assert.assertTrue;
18 |
19 | public class BuildSecurityTest extends PipelineWebDriverTestBase {
20 |
21 | static final String UNPRIVILEGED_USER = "unprivilegeduser";
22 | static final String PRIVILEGED_USER = "privilegeduser";
23 |
24 | FreeStyleProject secondJob;
25 |
26 | @Before
27 | public void init() throws Exception {
28 | GlobalMatrixAuthorizationStrategy authorizationStrategy = new GlobalMatrixAuthorizationStrategy();
29 | jr.jenkins.setAuthorizationStrategy(authorizationStrategy);
30 | authorizationStrategy.add(Permission.READ, UNPRIVILEGED_USER);
31 | authorizationStrategy.add(Permission.READ, PRIVILEGED_USER);
32 | authorizationStrategy.add(Item.BUILD, PRIVILEGED_USER);
33 | authorizationStrategy.add(Item.CONFIGURE, PRIVILEGED_USER);
34 |
35 | secondJob = createFailingJob(SECOND_JOB);
36 | initialJob.getPublishersList().add(new BuildPipelineTrigger(secondJob.getName(), Collections.emptyList()));
37 | jr.jenkins.rebuildDependencyGraph();
38 | }
39 |
40 | @Test
41 | public void pipelineShouldNotShowRunButtonIfUserNotPermittedToTriggerBuild() throws Exception {
42 | loginLogoutPage.login(UNPRIVILEGED_USER);
43 | pipelinePage.open();
44 |
45 | assertTrue("The Run button should not be present",
46 | pipelinePage.runButtonIsAbsent());
47 | }
48 |
49 | @Test
50 | public void pipelineShouldShowRunButtonIfUserPermittedToTriggerBuild() throws Exception {
51 | loginLogoutPage.login(PRIVILEGED_USER);
52 | pipelinePage.open();
53 |
54 | assertTrue("The Run button should be present",
55 | pipelinePage.runButtonIsPresent());
56 | }
57 |
58 | @Test
59 | public void manualBuildTriggerShouldNotBeShownIfNotPeritted() throws Exception {
60 | jr.buildAndAssertSuccess(initialJob);
61 |
62 | loginLogoutPage.login(UNPRIVILEGED_USER);
63 | pipelinePage.open();
64 |
65 | assertFalse("Second card in pipeline should not have a trigger button",
66 | pipelinePage.buildCard(1, 1, 2).hasManualTriggerButton());
67 | }
68 |
69 | @Test
70 | public void manualBuildTriggerShouldBeShownIfPermitted() throws Exception {
71 | jr.buildAndAssertSuccess(initialJob);
72 |
73 | loginLogoutPage.login(PRIVILEGED_USER);
74 | pipelinePage.open();
75 |
76 | assertTrue("Second card in pipeline should have a trigger button",
77 | pipelinePage.buildCard(1, 1, 2).hasManualTriggerButton());
78 | }
79 |
80 | @Test
81 | public void retryButtonShouldNotBeShownIfNotPermitted() throws Exception {
82 | jr.buildAndAssertSuccess(initialJob);
83 | loginLogoutPage.login(PRIVILEGED_USER);
84 | pipelinePage.open();
85 | BuildCardComponent secondBuildCard = pipelinePage.buildCard(1, 1, 2);
86 | secondBuildCard.clickTriggerButton();
87 | secondBuildCard.waitForFailure();
88 |
89 | loginLogoutPage.logout();
90 | loginLogoutPage.login(UNPRIVILEGED_USER);
91 | pipelinePage.open();
92 |
93 | assertFalse("Second card in pipeline should not have a retry button",
94 | pipelinePage.buildCard(1, 1, 2).hasRetryButton());
95 | }
96 |
97 | @Test
98 | public void retryButtonShouldBeShownIfPermitted() throws Exception {
99 | jr.buildAndAssertSuccess(initialJob);
100 |
101 | loginLogoutPage.login(PRIVILEGED_USER);
102 | pipelinePage.open();
103 |
104 | BuildCardComponent secondBuildCard = pipelinePage.buildCard(1, 1, 2);
105 | secondBuildCard.clickTriggerButton();
106 | secondBuildCard.waitForFailure();
107 |
108 | assertTrue("Second card in pipeline should have a retry button",
109 | pipelinePage.buildCard(1, 1, 2).hasRetryButton());
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/webapp/js/jquery.tooltip.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Tooltip plugin 1.3
3 | *
4 | * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
5 | * http://docs.jquery.com/Plugins/Tooltip
6 | *
7 | * Copyright (c) 2006 - 2008 Jörn Zaefferer
8 | *
9 | * $Id: jquery.tooltip.js 5741 2008-06-21 15:22:16Z joern.zaefferer $
10 | *
11 | * Dual licensed under the MIT and GPL licenses:
12 | * http://www.opensource.org/licenses/mit-license.php
13 | * http://www.gnu.org/licenses/gpl.html
14 | */;(function($){var helper={},current,title,tID,IE=$.browser.msie&&/MSIE\s(5\.5|6\.)/.test(navigator.userAgent),track=false;$.tooltip={blocked:false,defaults:{delay:200,fade:false,showURL:true,extraClass:"",top:15,left:15,id:"tooltip"},block:function(){$.tooltip.blocked=!$.tooltip.blocked;}};$.fn.extend({tooltip:function(settings){settings=$.extend({},$.tooltip.defaults,settings);createHelper(settings);return this.each(function(){$.data(this,"tooltip",settings);this.tOpacity=helper.parent.css("opacity");this.tooltipText=this.title;$(this).removeAttr("title");this.alt="";}).mouseover(save).mouseout(hide).click(hide);},fixPNG:IE?function(){return this.each(function(){var image=$(this).css('backgroundImage');if(image.match(/^url\(["']?(.*\.png)["']?\)$/i)){image=RegExp.$1;$(this).css({'backgroundImage':'none','filter':"progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='"+image+"')"}).each(function(){var position=$(this).css('position');if(position!='absolute'&&position!='relative')$(this).css('position','relative');});}});}:function(){return this;},unfixPNG:IE?function(){return this.each(function(){$(this).css({'filter':'',backgroundImage:''});});}:function(){return this;},hideWhenEmpty:function(){return this.each(function(){$(this)[$(this).html()?"show":"hide"]();});},url:function(){return this.attr('href')||this.attr('src');}});function createHelper(settings){if(helper.parent)return;helper.parent=$('