[FIX JENKINS-39625] Page preloading with pipeline state - Runs (#613)

* Introduce PageStatePreloader extension point

* Added BlueoceanUrl to blueocean-commons

* Updates to BlueoceanUrl

* Added PipelineStatePreloader to dashboard

* PageStatePreloader impl for js-extensions data

* RESTFetchPreloader

* CapabilityAugmenter changes to handle an Array of _class names

* Client side mods to handle prefetchdata

* Updated deps for core-js

* Add a test for PipelineStatePreloader

* Rename BlueoceanUrl to BlueOceanUrl

* Rename BlueOceanUrl to BlueUrlTokenizer

* Added a comment to BlueUrlTokenizer

* Fix Javadoc errors

* core-js version 0.0.35

* Updating to @jenkins-cd/blueocean-core-js@0.0.35 on dependants
This commit is contained in:
Tom Fennelly 2016-12-15 15:50:42 +00:00 committed by GitHub
parent dc4e9006dc
commit e25ae38a26
26 changed files with 1089 additions and 126 deletions

View File

@ -0,0 +1,294 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.commons;
import hudson.model.Run;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* General purpose Blue Ocean UI URL parser.
* <p>
* This class performs a "best effort" attempt to parse a URL as a Blue Ocean
* URL, extracting what it thinks are the relevant "parts" and making available via the
* {@link #getPart(UrlPart)} and {@link #hasPart(UrlPart)} functions.
* <p>
* See TBD comment on {@link UrlPart}.
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
@Restricted(NoExternalUse.class) // Internal use only for now because there's a fair chance we'll change how it works. See TBD comment on UrlPart.
public class BlueUrlTokenizer {
private static final Set<String> PIPELINE_TABS =
new LinkedHashSet<>(Arrays.asList("activity", "branches", "pr"));
private static final Set<String> PIPELINE_RUN_DETAIL_TABS =
new LinkedHashSet<>(Arrays.asList("pipeline", "changes", "tests", "artifacts"));
private Map<UrlPart, String> urlParts = new LinkedHashMap<>();
private UrlPart lastPart;
/**
* Enum of URL "parts".
* <p>
* Use {@link #getPart(UrlPart)} to get a specific URL "part",
* or call {@link #hasPart(UrlPart)} to check for it's existence.
* <p>
* *** TBD: decide whether to stick with this model, or to switch to more of a straight getters/setters style on the {@link BlueUrlTokenizer} instance.
* Reason for trying this approach ("parts" enum) is that I (TF) think the straight properties style with getters/setters
* would get messy as we add support for parsing more URL paths/parts i.e. a getters/setters API explosion.
* That said ... not sure I love this approach either, hence marked BlueoceanUrl as @Restricted(NoExternalUse.class). Let's suck it
* and see for a bit and change if it sucks :)
*/
public enum UrlPart {
/**
* Main blue ocean pipelines dashboard.
* i.e. /blue/pipelines/
*/
DASHBOARD_PIPELINES,
/**
* A URL pointing at a page associated with an "organization" resource.
* e.g. /blue/organizations/jenkins/...
* <p>
* Call Use {@link #getPart(UrlPart)} to get the organization name.
*/
ORGANIZATION,
/**
* A URL pointing at a pipeline.
* e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/...
* <p>
* Call Use {@link #getPart(UrlPart)} to get the pipeline name. Note that the URL
* may have additional parts (e.g. {@link UrlPart#PIPELINE_TAB} or {@link UrlPart#PIPELINE_RUN_DETAIL}).
*/
PIPELINE,
/**
* A URL pointing at a pipeline tab.
* e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/activity
* <p>
* Call Use {@link #getPart(UrlPart)} to get the tab name.
*/
PIPELINE_TAB,
/**
* A URL pointing at a pipeline Run Details.
* e.g. // e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/...
* <p>
* See {@link #BRANCH} for sub-component of this URL.
*/
PIPELINE_RUN_DETAIL,
/**
* A URL pointing at a pipeline Run Details for a specific branch.
* e.g. // e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/...
* <p>
* See {@link #PIPELINE_RUN_DETAIL_ID} for sub-component of this URL.
* <p>
* Call Use {@link #getPart(UrlPart)} to get the branch name.
*/
BRANCH,
/**
* A URL pointing at a pipeline Run Details for a specific run of a specific branch.
* e.g. // e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/55/...
* <p>
* See {@link #PIPELINE_RUN_DETAIL_ID} for sub-component of this URL.
* <p>
* Call Use {@link #getPart(UrlPart)} to get the {@link Run} ID.
*/
PIPELINE_RUN_DETAIL_ID,
/**
* A URL pointing at one of the tabs on a pipeline Run Details for a specific run of a specific branch.
* e.g. // e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/55/artifacts
* <p>
* Call Use {@link #getPart(UrlPart)} to get the tab name.
*/
PIPELINE_RUN_DETAIL_TAB,
}
private BlueUrlTokenizer() {
}
/**
* Parse the {@link Stapler#getCurrentRequest() current Stapler request} and return a {@link BlueUrlTokenizer} instance
* iff the URL is a Blue Ocean UI URL.
*
* @return A {@link BlueUrlTokenizer} instance iff the URL is a Blue Ocean UI URL, otherwise {@code null}.
* @throws IllegalStateException Called outside the scope of an active {@link StaplerRequest}.
*/
public static @CheckForNull
BlueUrlTokenizer parseCurrentRequest() throws IllegalStateException {
StaplerRequest currentRequest = Stapler.getCurrentRequest();
if (currentRequest == null) {
throw new IllegalStateException("Illegal call to BlueoceanUrl.parseCurrentRequest outside the scope of an active StaplerRequest.");
}
String path = currentRequest.getOriginalRequestURI();
String contextPath = currentRequest.getContextPath();
path = path.substring(contextPath.length());
return parse(path);
}
/**
* Parse the supplied URL string and return a {@link BlueUrlTokenizer} instance
* iff the URL is a Blue Ocean UI URL.
*
* @param url The URL to be parsed. The URL must not be decoded in any way, so as to ensure
* that no URL component data is lost.
* @return A {@link BlueUrlTokenizer} instance iff the URL is a Blue Ocean UI URL, otherwise {@code null}.
*/
public static @CheckForNull
BlueUrlTokenizer parse(@Nonnull String url) {
Iterator<String> urlTokens = extractTokens(url);
//
// Yes, the following code is quite ugly, but it's easy enough to understand atm.
// Unless this gets a lot more detailed, please don't get super clever ideas about using
// some fancy-pants abstractions/patterns/3rd-party-libs for parsing the URL that, while
// might make the code look neater structurally, also makes the code logic a lot harder
// to follow (without using a debugger).
//
if (urlTokens.hasNext()) {
if (urlTokens.next().equalsIgnoreCase("blue")) {
BlueUrlTokenizer blueUrlTokenizer = new BlueUrlTokenizer();
if (urlTokens.hasNext()) {
String next = urlTokens.next();
if (next.equalsIgnoreCase("pipelines")) {
// i.e. /blue/pipelines/
blueUrlTokenizer.addPart(UrlPart.DASHBOARD_PIPELINES, next);
} else if (next.equalsIgnoreCase("organizations")) {
// i.e. /blue/organizations/...
if (urlTokens.hasNext()) {
// e.g. /blue/organizations/jenkins/...
blueUrlTokenizer.addPart(UrlPart.ORGANIZATION, urlTokens.next());
if (urlTokens.hasNext()) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/...
blueUrlTokenizer.addPart(UrlPart.PIPELINE, urlDecode(urlTokens.next()));
if (urlTokens.hasNext()) {
next = urlTokens.next();
if (next.equalsIgnoreCase("detail")) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/...
blueUrlTokenizer.addPart(UrlPart.PIPELINE_RUN_DETAIL, next);
if (urlTokens.hasNext()) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/...
blueUrlTokenizer.addPart(UrlPart.BRANCH, urlDecode(urlTokens.next()));
if (urlTokens.hasNext()) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/55/...
blueUrlTokenizer.addPart(UrlPart.PIPELINE_RUN_DETAIL_ID, urlDecode(urlTokens.next()));
if (urlTokens.hasNext()) {
next = urlTokens.next();
if (PIPELINE_RUN_DETAIL_TABS.contains(next.toLowerCase())) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/55/pipeline
blueUrlTokenizer.addPart(UrlPart.PIPELINE_RUN_DETAIL_TAB, next.toLowerCase());
}
}
}
}
} else if (PIPELINE_TABS.contains(next.toLowerCase())) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/activity/
blueUrlTokenizer.addPart(UrlPart.PIPELINE_TAB, next.toLowerCase());
}
}
}
}
}
}
return blueUrlTokenizer;
}
}
return null;
}
private void addPart(@Nonnull UrlPart urlPart, @Nonnull String value) {
urlParts.put(urlPart, value);
this.lastPart = urlPart;
}
public boolean hasPart(@Nonnull UrlPart urlPart) {
return urlParts.containsKey(urlPart);
}
public @CheckForNull String getPart(@Nonnull UrlPart urlPart) {
return urlParts.get(urlPart);
}
/**
* Get the last {@link UrlPart} for the URL.
* @return The last {@link UrlPart} for the URL.
*/
public @CheckForNull UrlPart getLastPart() {
return this.lastPart;
}
public boolean lastPartIs(@Nonnull UrlPart urlPart) {
return this.lastPart == urlPart;
}
public boolean lastPartIs(@Nonnull UrlPart urlPart, @Nonnull String value) {
if (this.lastPart == urlPart) {
return getPart(this.lastPart).equals(value);
}
return false;
}
private static String urlDecode(String string) {
try {
return URLDecoder.decode(string, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Unexpected UnsupportedEncodingException for UTF-8.");
}
}
private static Iterator<String> extractTokens(String url) {
String[] uncleanedTokens = url.split("/");
List<String> cleanedTokens = new ArrayList<>();
for (String uncleanedToken : uncleanedTokens) {
if (uncleanedToken.length() != 0) {
cleanedTokens.add(uncleanedToken);
}
}
return cleanedTokens.iterator();
}
}

View File

@ -0,0 +1,61 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.commons;
import hudson.ExtensionList;
import org.apache.tools.ant.ExtensionPoint;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Page state "preloader" extension point.
* <p>
* Allows the loading page's JavaScript blueocean global scope to
* be pre-populated with data that we know the page is going to need, thereby
* providing a mechanism for eliminating the request overhead for that data.
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
public abstract class PageStatePreloader extends ExtensionPoint {
/**
* Get the JavaScript object graph path at shiwh the state is to be stored.
* @return The JavaScript object graph path at shiwh the state is to be stored.
*/
@Nonnull
public abstract String getStatePropertyPath();
/**
* Get the state JSON to be set in the page's JavaScript blueocean global scope.
* @return The state JSON to be set in the page's JavaScript blueocean global
* scope, or {@code null} if no data is to be set of this page.
*/
@CheckForNull
public abstract String getStateJson();
public static ExtensionList<PageStatePreloader> all() {
return ExtensionList.lookup(PageStatePreloader.class);
}
}

View File

@ -0,0 +1,98 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.commons;
import net.sf.json.JSONObject;
import javax.annotation.Nonnull;
/**
* REST prefetch data preloader.
* <p>
* Pre-populates the page with REST data, allowing the client side {@code Fetch}
* module (see {@code Fetch} module in the {@code @jenkins-cd/blueocean-core-js NPM packages})
* to avoid the REST API call overhead.
* <p>
* Create implementations of this class (and annotate with {@code @Extension}) for data that
* we know is going to be needed by the page.
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
public abstract class RESTFetchPreloader extends PageStatePreloader {
/**
* {@inheritDoc}
*/
@Override
public final String getStatePropertyPath() {
return "prefetchdata." + getClass().getSimpleName();
}
/**
* {@inheritDoc}
*/
@Override
public final String getStateJson() {
BlueUrlTokenizer blueUrl = BlueUrlTokenizer.parseCurrentRequest();
if (blueUrl == null) {
// Not a Blue Ocean page, so nothing to be added.
return null;
}
FetchData fetchData = getFetchData(blueUrl);
if (fetchData != null) {
return fetchData.toJSON();
}
return null;
}
protected abstract FetchData getFetchData(@Nonnull BlueUrlTokenizer blueUrl);
public static final class FetchData {
private String restUrl;
private String data;
public FetchData(@Nonnull String restUrl, @Nonnull String data) {
this.restUrl = restUrl;
this.data = data;
}
public String getRestUrl() {
return restUrl;
}
public String getData() {
return data;
}
public String toJSON() {
JSONObject json = new JSONObject();
json.put("restUrl", restUrl);
json.put("data", data);
return json.toString();
}
}
}

View File

@ -0,0 +1,71 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.commons;
import org.junit.Assert;
import org.junit.Test;
/**
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
public class BlueUrlTokenizerTest {
@Test
public void test_MalformedURLException() {
Assert.assertNull(BlueUrlTokenizer.parse("/a"));
}
@Test
public void test() {
BlueUrlTokenizer blueUrl;
blueUrl = BlueUrlTokenizer.parse("/blue/pipelines/");
Assert.assertEquals("pipelines", blueUrl.getPart(BlueUrlTokenizer.UrlPart.DASHBOARD_PIPELINES));
Assert.assertEquals(BlueUrlTokenizer.UrlPart.DASHBOARD_PIPELINES, blueUrl.getLastPart());
Assert.assertTrue(blueUrl.lastPartIs(BlueUrlTokenizer.UrlPart.DASHBOARD_PIPELINES));
blueUrl = BlueUrlTokenizer.parse("/blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/activity/");
Assert.assertFalse(blueUrl.hasPart(BlueUrlTokenizer.UrlPart.DASHBOARD_PIPELINES));
Assert.assertTrue(blueUrl.hasPart(BlueUrlTokenizer.UrlPart.ORGANIZATION));
Assert.assertEquals("jenkins", blueUrl.getPart(BlueUrlTokenizer.UrlPart.ORGANIZATION));
Assert.assertEquals("f1/f3 with spaces/f3 pipeline", blueUrl.getPart(BlueUrlTokenizer.UrlPart.PIPELINE));
Assert.assertEquals("activity", blueUrl.getPart(BlueUrlTokenizer.UrlPart.PIPELINE_TAB));
Assert.assertEquals(BlueUrlTokenizer.UrlPart.PIPELINE_TAB, blueUrl.getLastPart());
Assert.assertTrue(blueUrl.lastPartIs(BlueUrlTokenizer.UrlPart.PIPELINE_TAB, "activity"));
blueUrl = BlueUrlTokenizer.parse("/blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/detail/magic-branch-X/55/pipeline");
Assert.assertFalse(blueUrl.hasPart(BlueUrlTokenizer.UrlPart.DASHBOARD_PIPELINES));
Assert.assertTrue(blueUrl.hasPart(BlueUrlTokenizer.UrlPart.ORGANIZATION));
Assert.assertEquals("jenkins", blueUrl.getPart(BlueUrlTokenizer.UrlPart.ORGANIZATION));
Assert.assertEquals("f1/f3 with spaces/f3 pipeline", blueUrl.getPart(BlueUrlTokenizer.UrlPart.PIPELINE));
Assert.assertFalse(blueUrl.hasPart(BlueUrlTokenizer.UrlPart.PIPELINE_TAB));
Assert.assertTrue(blueUrl.hasPart(BlueUrlTokenizer.UrlPart.PIPELINE_RUN_DETAIL));
Assert.assertEquals("magic-branch-X", blueUrl.getPart(BlueUrlTokenizer.UrlPart.BRANCH));
Assert.assertEquals("55", blueUrl.getPart(BlueUrlTokenizer.UrlPart.PIPELINE_RUN_DETAIL_ID));
Assert.assertEquals("pipeline", blueUrl.getPart(BlueUrlTokenizer.UrlPart.PIPELINE_RUN_DETAIL_TAB));
Assert.assertEquals(BlueUrlTokenizer.UrlPart.PIPELINE_RUN_DETAIL_TAB, blueUrl.getLastPart());
Assert.assertTrue(blueUrl.lastPartIs(BlueUrlTokenizer.UrlPart.PIPELINE_RUN_DETAIL_TAB, "pipeline"));
}
}

View File

@ -1,92 +0,0 @@
package io.jenkins.blueocean.config;
import hudson.Extension;
import hudson.model.User;
import hudson.security.AuthorizationStrategy;
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
import hudson.security.SecurityRealm;
import io.jenkins.blueocean.BluePageDecorator;
import io.jenkins.blueocean.commons.BlueOceanConfigProperties;
import io.jenkins.blueocean.commons.stapler.ModelObjectSerializer;
import io.jenkins.blueocean.service.embedded.rest.UserImpl;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONBuilder;
import java.io.IOException;
import java.io.StringWriter;
/**
* @author Vivek Pandey
*/
@Extension(ordinal = 10)
public class BlueOceanConfig extends BluePageDecorator {
public boolean isRollBarEnabled(){
return BlueOceanConfigProperties.ROLLBAR_ENABLED;
}
public String getBlueOceanUser() throws IOException {
try (StringWriter writer = new StringWriter()) {
User currentUser = User.current();
JSONObject currentUserJson;
if (currentUser != null) {
currentUserJson = JSONObject.fromObject(ModelObjectSerializer.toJson(new UserImpl(currentUser)));
} else {
currentUserJson = new JSONObject();
currentUserJson.put("id", "anonymous");
}
new JSONBuilder(writer)
.object()
.key("user").value(currentUserJson)
.endObject();
return writer.toString();
}
}
public String getBlueOceanConfig() throws IOException {
try (StringWriter writer = new StringWriter()) {
Jenkins jenkins = Jenkins.getInstance();
String version = Jenkins.getVersion() != null ? Jenkins.getVersion().toString() : Jenkins.VERSION;
AuthorizationStrategy authorizationStrategy = jenkins.getAuthorizationStrategy();
boolean allowAnonymousRead = true;
if(authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy){
allowAnonymousRead = ((FullControlOnceLoggedInAuthorizationStrategy) authorizationStrategy).isAllowAnonymousRead();
}
new JSONBuilder(writer)
.object()
.key("version").value(getBlueOceanPluginVersion())
.key("jenkinsConfig")
.object()
.key("version").value(version)
.key("security")
.object()
.key("enabled").value(jenkins.isUseSecurity())
.key("loginUrl").value(jenkins.getSecurityRealm() == SecurityRealm.NO_AUTHENTICATION ? null : jenkins.getSecurityRealm().getLoginUrl())
.key("authorizationStrategy").object()
.key("allowAnonymousRead").value(allowAnonymousRead)
.endObject()
.key("enableJWT").value(BlueOceanConfigProperties.BLUEOCEAN_FEATURE_JWT_AUTHENTICATION)
.endObject()
.endObject()
.endObject();
return writer.toString();
}
}
public String getJsExtensions() {
return JenkinsJSExtensions.getExtensionsData().toString();
}
/** gives Blueocean plugin version. blueocean-web being core module is looked at to determine the version */
private String getBlueOceanPluginVersion(){
return Jenkins.getInstance().getPlugin("blueocean-web").getWrapper().getVersion();
}
}

View File

@ -0,0 +1,71 @@
package io.jenkins.blueocean.config;
import hudson.Extension;
import hudson.security.AuthorizationStrategy;
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
import hudson.security.SecurityRealm;
import io.jenkins.blueocean.commons.BlueOceanConfigProperties;
import io.jenkins.blueocean.commons.PageStatePreloader;
import jenkins.model.Jenkins;
import net.sf.json.util.JSONBuilder;
import java.io.StringWriter;
import java.util.logging.Logger;
/**
* @author Vivek Pandey
*/
@Extension
public class BlueOceanConfigStatePreloader extends PageStatePreloader {
private static final Logger LOGGER = Logger.getLogger(BlueOceanConfigStatePreloader.class.getName());
/**
* {@inheritDoc}
*/
@Override
public String getStatePropertyPath() {
return "config";
}
/**
* {@inheritDoc}
*/
@Override
public String getStateJson() {
StringWriter writer = new StringWriter();
Jenkins jenkins = Jenkins.getInstance();
String version = Jenkins.getVersion() != null ? Jenkins.getVersion().toString() : Jenkins.VERSION;
AuthorizationStrategy authorizationStrategy = jenkins.getAuthorizationStrategy();
boolean allowAnonymousRead = true;
if(authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy){
allowAnonymousRead = ((FullControlOnceLoggedInAuthorizationStrategy) authorizationStrategy).isAllowAnonymousRead();
}
new JSONBuilder(writer)
.object()
.key("version").value(getBlueOceanPluginVersion())
.key("jenkinsConfig")
.object()
.key("version").value(version)
.key("security")
.object()
.key("enabled").value(jenkins.isUseSecurity())
.key("loginUrl").value(jenkins.getSecurityRealm() == SecurityRealm.NO_AUTHENTICATION ? null : jenkins.getSecurityRealm().getLoginUrl())
.key("authorizationStrategy").object()
.key("allowAnonymousRead").value(allowAnonymousRead)
.endObject()
.key("enableJWT").value(BlueOceanConfigProperties.BLUEOCEAN_FEATURE_JWT_AUTHENTICATION)
.endObject()
.endObject()
.endObject();
return writer.toString();
}
/** gives Blueocean plugin version. blueocean-web being core module is looked at to determine the version */
private String getBlueOceanPluginVersion(){
return Jenkins.getInstance().getPlugin("blueocean-web").getWrapper().getVersion();
}
}

View File

@ -0,0 +1,27 @@
package io.jenkins.blueocean.config;
import hudson.Extension;
import io.jenkins.blueocean.commons.PageStatePreloader;
/**
* {@link PageStatePreloader} for js-extensions data.
*/
@Extension
public class JenkinsJSExtensionsStatePreloader extends PageStatePreloader {
/**
* {@inheritDoc}
*/
@Override
public String getStatePropertyPath() {
return "jsExtensions";
}
/**
* {@inheritDoc}
*/
@Override
public String getStateJson() {
return JenkinsJSExtensions.getExtensionsData().toString();
}
}

View File

@ -0,0 +1,16 @@
package io.jenkins.blueocean.config;
import hudson.Extension;
import io.jenkins.blueocean.BluePageDecorator;
import io.jenkins.blueocean.commons.BlueOceanConfigProperties;
/**
* @author Vivek Pandey
*/
@Extension(ordinal = 10)
public class RollbarDecorator extends BluePageDecorator {
public boolean isRollBarEnabled(){
return BlueOceanConfigProperties.ROLLBAR_ENABLED;
}
}

View File

@ -1,13 +1,4 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<script>
(function () {
window.$$blueocean = {};
window.$$blueocean.user = ${it.blueOceanUser}.user;
window.$$blueocean.config = ${it.blueOceanConfig};
window.$$blueocean.jsExtensions = ${it.jsExtensions};
})();
</script>
<j:if test="${it.rollBarEnabled}">
<!--
Running the plugin build (or just "gulp" from the command line) will

View File

@ -1,6 +1,6 @@
{
"name": "@jenkins-cd/blueocean-core-js",
"version": "0.0.35-unpublished",
"version": "0.0.35",
"description": "Shared JavaScript libraries for use with Jenkins Blue Ocean",
"main": "dist/js/index.js",
"scripts": {

View File

@ -3,7 +3,20 @@
*/
const addClass = (clazz, classMap) => {
const className = clazz._class;
let className;
if (Array.isArray(clazz._class)) {
// If it's an array of class names, just take the first.
// TODO: Hmmm ... not sure if this is the right thing to do when we have an array of class names.
// Not sure what the array is about tbh. What are the relationships?
if (clazz._class.length > 0) {
className = clazz._class[0];
} else {
return;
}
} else {
className = clazz._class;
}
if (!classMap[className]) {
// eslint-disable-next-line no-param-reassign

View File

@ -5,6 +5,9 @@ import utils from './utils';
import config from './config';
import dedupe from './utils/dedupe-calls';
import urlconfig from './urlconfig';
import { prefetchdata } from './scopes';
const Promise = es6Promise.Promise;
import { capabilityAugmenter } from './capability/index';
let refreshToken = null;
@ -124,10 +127,13 @@ export const FetchFunctions = {
*/
rawFetchJSON(url, { onSuccess, onError, fetchOptions, disableDedupe } = {}) {
const request = () => {
const future = isoFetch(url, FetchFunctions.sameOriginFetchOption(fetchOptions))
.then(FetchFunctions.checkRefreshHeader)
.then(FetchFunctions.checkStatus)
.then(FetchFunctions.parseJSON);
let future = getPrefetchedDataFuture(url); // eslint-disable-line no-use-before-define
if (!future) {
future = isoFetch(url, FetchFunctions.sameOriginFetchOption(fetchOptions))
.then(FetchFunctions.checkRefreshHeader)
.then(FetchFunctions.checkStatus)
.then(FetchFunctions.parseJSON);
}
if (onSuccess) {
return future.then(onSuccess).catch(FetchFunctions.onError(onError));
}
@ -155,10 +161,12 @@ export const FetchFunctions = {
*/
rawFetch(url, { onSuccess, onError, fetchOptions, disableDedupe } = {}) {
const request = () => {
const future = isoFetch(url, FetchFunctions.sameOriginFetchOption(fetchOptions))
.then(FetchFunctions.checkRefreshHeader)
.then(FetchFunctions.checkStatus);
let future = getPrefetchedDataFuture(url); // eslint-disable-line no-use-before-define
if (!future) {
future = isoFetch(url, FetchFunctions.sameOriginFetchOption(fetchOptions))
.then(FetchFunctions.checkRefreshHeader)
.then(FetchFunctions.checkStatus);
}
if (onSuccess) {
return future.then(onSuccess).catch(FetchFunctions.onError(onError));
}
@ -224,15 +232,15 @@ export const Fetch = {
*/
fetch(url, { onSuccess, onError, fetchOptions } = {}) {
let fixedUrl = url;
if (urlconfig.getJenkinsRootURL() !== '' && !url.startsWith(urlconfig.getJenkinsRootURL())) {
fixedUrl = `${urlconfig.getJenkinsRootURL()}${url}`;
}
if (!config.isJWTEnabled()) {
return FetchFunctions.rawFetch(fixedUrl, { onSuccess, onError, fetchOptions });
}
return jwt.getToken()
.then(token => FetchFunctions.rawFetch(fixedUrl, {
onSuccess,
@ -242,3 +250,42 @@ export const Fetch = {
},
};
function trimRestUrl(url) {
const REST_PREFIX = 'blue/rest/organizations';
const prefixOffset = url.indexOf(REST_PREFIX);
if (prefixOffset !== -1) {
return url.substring(prefixOffset);
}
return url;
}
function getPrefetchedDataFuture(url) {
const trimmedUrl = trimRestUrl(url);
for (const prop in prefetchdata) {
if (prefetchdata.hasOwnProperty(prop)) {
const preFetchEntry = prefetchdata[prop];
if (preFetchEntry.restUrl && preFetchEntry.data) {
// If the trimmed/normalized rest URL matches the url arg supplied
// to the function, construct a pre-resolved future object containing
// the prefetched data as the value.
if (trimRestUrl(preFetchEntry.restUrl) === trimmedUrl) {
try {
return Promise.resolve(JSON.parse(preFetchEntry.data));
} finally {
// Delete the preFetchEntry i.e. we only use these entries once. So, this
// works only for the first request for the data at that URL. Subsequent
// calls on that REST endpoint will result in a proper fetch. A local
// store needs to be used (redux/mobx etc) if you want to avoid multiple calls
// for the same data. This is not a caching layer/mechanism !!!
delete prefetchdata[prop];
}
}
}
}
}
return undefined;
}

View File

@ -30,3 +30,5 @@ export const root = (typeof self === 'object' && self.self === self && self) ||
// and blueocean-config/src/main/resources/io/jenkins/blueocean/config/BlueOceanConfig/header.jelly
//
export const blueocean = (root.$blueocean || {});
export const prefetchdata = (blueocean.prefetchdata || {});

View File

@ -3,9 +3,9 @@
"version": "0.0.1",
"dependencies": {
"@jenkins-cd/blueocean-core-js": {
"version": "0.0.34",
"from": "@jenkins-cd/blueocean-core-js@0.0.34",
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.34.tgz",
"version": "0.0.35",
"from": "@jenkins-cd/blueocean-core-js@0.0.35",
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.35.tgz",
"dependencies": {
"@jenkins-cd/sse-gateway": {
"version": "0.0.10",

View File

@ -37,7 +37,7 @@
"skin-deep": "0.16.0"
},
"dependencies": {
"@jenkins-cd/blueocean-core-js": "0.0.34",
"@jenkins-cd/blueocean-core-js": "0.0.35",
"@jenkins-cd/design-language": "0.0.91",
"@jenkins-cd/js-extensions": "0.0.32",
"@jenkins-cd/js-modules": "0.0.8",

View File

@ -18,6 +18,30 @@
<groupId>${project.groupId}</groupId>
<artifactId>blueocean-events</artifactId>
</dependency>
<!-- Test deps -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>blueocean-rest-impl</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<!--
jsoup HTML parser library @ http://jsoup.org/
Using this to get the raw/unprocessed HTML from Jenkins
-->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,116 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.preload;
import hudson.Extension;
import hudson.model.Item;
import io.jenkins.blueocean.commons.BlueUrlTokenizer;
import io.jenkins.blueocean.commons.RESTFetchPreloader;
import io.jenkins.blueocean.commons.stapler.ModelObjectSerializer;
import io.jenkins.blueocean.rest.model.BluePipeline;
import io.jenkins.blueocean.rest.model.BlueRun;
import io.jenkins.blueocean.rest.model.BlueRunContainer;
import io.jenkins.blueocean.service.embedded.rest.BluePipelineFactory;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Preload pipeline runs onto the page if the requested page is a pipeline runs page.
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
@Extension
public class PipelineStatePreloader extends RESTFetchPreloader {
private static final Logger LOGGER = Logger.getLogger(PipelineStatePreloader.class.getName());
private static final int DEFAULT_LIMIT = 26;
@Override
protected FetchData getFetchData(@Nonnull BlueUrlTokenizer blueUrl) {
BluePipeline pipeline = getPipeline(blueUrl);
if (pipeline != null) {
// It's a pipeline page. Let's prefetch the pipeline runs and add them to the page,
// saving the frontend the overhead of requesting them.
BlueRunContainer runsContainer = pipeline.getRuns();
Iterator<BlueRun> runsIterator = runsContainer.iterator(0, DEFAULT_LIMIT);
JSONArray runs = new JSONArray();
while(runsIterator.hasNext()) {
BlueRun blueRun = runsIterator.next();
try {
runs.add(JSONObject.fromObject(ModelObjectSerializer.toJson(blueRun)));
} catch (IOException e) {
LOGGER.log(Level.FINE, String.format("Unable to preload runs for Job '%s'. Run serialization error.", pipeline.getFullName()), e);
return null;
}
}
return new FetchData(
pipeline.getActivities().getLink().getHref() + "?start=0&limit=" + DEFAULT_LIMIT,
runs.toString());
}
// Don't preload any data on the page.
return null;
}
private BluePipeline getPipeline(BlueUrlTokenizer blueUrl) {
if (addPipelineRuns(blueUrl)) {
Jenkins jenkins = Jenkins.getInstance();
String pipelineFullName = blueUrl.getPart(BlueUrlTokenizer.UrlPart.PIPELINE);
try {
Item pipelineJob = jenkins.getItemByFullName(pipelineFullName);
return (BluePipeline) BluePipelineFactory.resolve(pipelineJob);
} catch (Exception e) {
LOGGER.log(Level.FINE, String.format("Unable to find Job named '%s'.", pipelineFullName), e);
return null;
}
}
return null;
}
private boolean addPipelineRuns(@Nonnull BlueUrlTokenizer blueUrl) {
if (blueUrl.lastPartIs(BlueUrlTokenizer.UrlPart.PIPELINE)) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/
return true;
} else if (blueUrl.lastPartIs(BlueUrlTokenizer.UrlPart.PIPELINE_TAB, "activity")) {
// e.g. /blue/organizations/jenkins/f1%2Ff3%20with%20spaces%2Ff3%20pipeline/activity/
return true;
}
return false;
}
}

View File

@ -0,0 +1,80 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.preload;
import hudson.Extension;
import hudson.model.User;
import io.jenkins.blueocean.commons.PageStatePreloader;
import io.jenkins.blueocean.commons.stapler.ModelObjectSerializer;
import io.jenkins.blueocean.service.embedded.rest.UserImpl;
import net.sf.json.JSONObject;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Preload the user object for the active user.
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
@Extension
public class UserStatePreloader extends PageStatePreloader {
private static final Logger LOGGER = Logger.getLogger(UserStatePreloader.class.getName());
private static final String ANONYMOUS;
/**
* {@inheritDoc}
*/
@Override
public String getStatePropertyPath() {
return "user";
}
/**
* {@inheritDoc}
*/
@Override
public String getStateJson() {
try {
User currentUser = User.current();
if (currentUser != null) {
return ModelObjectSerializer.toJson(new UserImpl(currentUser));
} else {
return ANONYMOUS;
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Unexpected error serializing active User object and adding to page preload state.");
return ANONYMOUS;
}
}
static {
JSONObject anonUserJson = new JSONObject();
anonUserJson.put("id", "anonymous");
ANONYMOUS = anonUserJson.toString();
}
}

View File

@ -0,0 +1,60 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean.preload;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import io.jenkins.blueocean.BlueOceanWebURLBuilder;
import io.jenkins.blueocean.service.embedded.BaseTest;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Assert;
import org.junit.Test;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
/**
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
public class PipelineStatePreloaderTest extends BaseTest {
@Test
public void test() throws IOException, ExecutionException, InterruptedException, SAXException {
// Create a project and run a build on it.
FreeStyleProject freestyleProject = j.createProject(FreeStyleProject.class, "freestyle");
FreeStyleBuild run = freestyleProject.scheduleBuild2(0).get();
j.waitForCompletion(run);
// Lets request the activity page for that project. The page should
// contain some prefetched javascript for the runs on the page
String projectBlueUrl = BlueOceanWebURLBuilder.toBlueOceanURL(freestyleProject);
Document doc = Jsoup.connect(projectBlueUrl + "/activity/").get();
String script = doc.select("head script").toString();
Assert.assertTrue(script.contains(String.format("setState('prefetchdata.%s',", PipelineStatePreloader.class.getSimpleName())));
Assert.assertTrue(script.contains("\"restUrl\":\"/blue/rest/organizations/jenkins/pipelines/freestyle/activities/?start=0&limit=26\""));
}
}

View File

@ -3,9 +3,9 @@
"version": "0.0.1",
"dependencies": {
"@jenkins-cd/blueocean-core-js": {
"version": "0.0.34",
"from": "@jenkins-cd/blueocean-core-js@0.0.34",
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.34.tgz",
"version": "0.0.35",
"from": "@jenkins-cd/blueocean-core-js@0.0.35",
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.35.tgz",
"dependencies": {
"react-router": {
"version": "3.0.0",

View File

@ -35,7 +35,7 @@
"react-addons-test-utils": "15.3.2"
},
"dependencies": {
"@jenkins-cd/blueocean-core-js": "0.0.34",
"@jenkins-cd/blueocean-core-js": "0.0.35",
"@jenkins-cd/design-language": "0.0.91",
"@jenkins-cd/js-extensions": "0.0.32",
"@jenkins-cd/js-modules": "0.0.8",

View File

@ -3,9 +3,9 @@
"version": "0.0.1",
"dependencies": {
"@jenkins-cd/blueocean-core-js": {
"version": "0.0.34",
"from": "@jenkins-cd/blueocean-core-js@0.0.34",
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.34.tgz",
"version": "0.0.35",
"from": "@jenkins-cd/blueocean-core-js@0.0.35",
"resolved": "https://registry.npmjs.org/@jenkins-cd/blueocean-core-js/-/blueocean-core-js-0.0.35.tgz",
"dependencies": {
"history": {
"version": "3.2.1",

View File

@ -29,7 +29,7 @@
"zombie": "4.2.1"
},
"dependencies": {
"@jenkins-cd/blueocean-core-js": "0.0.34",
"@jenkins-cd/blueocean-core-js": "0.0.35",
"@jenkins-cd/design-language": "0.0.91",
"@jenkins-cd/js-extensions": "0.0.32",
"@jenkins-cd/js-modules": "0.0.8",

View File

@ -16,6 +16,10 @@
<url>https://wiki.jenkins-ci.org/display/JENKINS/Blue+Ocean+Plugin</url>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>blueocean-commons</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>metrics</artifactId>

View File

@ -0,0 +1,41 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.blueocean;
import hudson.Extension;
import io.jenkins.blueocean.commons.PageStatePreloader;
import java.util.List;
/**
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
@Extension
public class PageStatePreloadDecorator extends BluePageDecorator {
public List<PageStatePreloader> getPageStatePreloaders(){
return PageStatePreloader.all();
}
}

View File

@ -0,0 +1,39 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<script>
(function () {
// construct the state object parent path inside window.$$blueocean.
function setState(statePropertyPath, state) {
var pathTokens = ('$blueocean.' + statePropertyPath).split('.');
var contextObj = window;
// Basically an Array shift
function nextToken() {
var nextToken = pathTokens[0];
pathTokens = pathTokens.slice(1);
return nextToken;
}
var pathToken = nextToken();
// Construct up to, but not including, the last point in the graph.
while (pathTokens.length !== 0) {
if (!contextObj[pathToken]) {
contextObj[pathToken] = {};
}
contextObj = contextObj[pathToken];
pathToken = nextToken();
}
// And set the state on the last object on the graph.
contextObj[pathToken] = state;
}
<j:forEach var="preloader" items="${it.pageStatePreloaders}">
// State Preloader: ${preloader.class.name}
<j:set var="stateJson" value="${preloader.stateJson}"/>
<j:if test="${stateJson != null}">
setState('${preloader.statePropertyPath}', ${stateJson});
</j:if>
</j:forEach>
})();
</script>
</j:jelly>