diff --git a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/icon.groovy b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/icon.groovy new file mode 100644 index 000000000..899e5fbd7 --- /dev/null +++ b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/icon.groovy @@ -0,0 +1,3 @@ +// use the stock icon +// see https://github.com/jenkinsci/custom-folder-icon-plugin for custom icons +folder('stock') diff --git a/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/AbstractFolder.groovy b/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/AbstractFolder.groovy index ae2bfe88e..a788bfe2a 100644 --- a/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/AbstractFolder.groovy +++ b/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/AbstractFolder.groovy @@ -1,6 +1,7 @@ package javaposse.jobdsl.dsl import javaposse.jobdsl.dsl.helpers.AuthorizationContext +import javaposse.jobdsl.dsl.helpers.icon.FolderIconContext import javaposse.jobdsl.dsl.helpers.properties.FolderPropertiesContext abstract class AbstractFolder extends Item { @@ -54,4 +55,22 @@ abstract class AbstractFolder extends Item { } } } + + /** + * Sets the icon of the folder. + * + * @since 1.83 + */ + void icon(@DslContext(FolderIconContext) Closure closure) { + FolderIconContext context = new FolderIconContext(jobManagement, this) + ContextHelper.executeInContext(closure, context) + + configure { Node folder -> + Node icon = folder / icon + if (icon) { + folder.remove(icon) + } + folder << context.icon + } + } } diff --git a/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/icon/FolderIconContext.groovy b/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/icon/FolderIconContext.groovy new file mode 100644 index 000000000..2321243a0 --- /dev/null +++ b/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/icon/FolderIconContext.groovy @@ -0,0 +1,21 @@ +package javaposse.jobdsl.dsl.helpers.icon + +import javaposse.jobdsl.dsl.AbstractExtensibleContext +import javaposse.jobdsl.dsl.ContextHelper +import javaposse.jobdsl.dsl.ContextType +import javaposse.jobdsl.dsl.Item +import javaposse.jobdsl.dsl.JobManagement + +@ContextType('com.cloudbees.hudson.plugins.folder.FolderIcon') +class FolderIconContext extends AbstractExtensibleContext { + Node icon + + FolderIconContext(JobManagement jobManagement, Item item) { + super(jobManagement, item) + } + + @Override + protected void addExtensionNode(Node node) { + icon = ContextHelper.toNamedNode('icon', node) + } +} diff --git a/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy b/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy index 1065340db..907cda85d 100644 --- a/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy +++ b/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy @@ -127,6 +127,23 @@ class FolderSpec extends Specification { folder.node.properties[0].children()[0].name() == 'hack' } + def 'call icon'() { + when: + folder.icon { + icon = new Node(null, + 'icon', ['class': 'jenkins.plugins.foldericon.CustomFolderIcon', 'plugin': 'custom-folder-icon'], + new Node(null, + 'customFolderIcon', 'test.png')) + } + + then: + folder.node.icon[0].name() == 'icon' + folder.node.icon[0].attribute('class') == 'jenkins.plugins.foldericon.CustomFolderIcon' + folder.node.icon[0].attribute('plugin') == 'custom-folder-icon' + folder.node.icon[0].children()[0].name() == 'customFolderIcon' + folder.node.icon[0].children()[0].value() == 'test.png' + } + def 'configure'() { when: folder.configure { diff --git a/job-dsl-plugin/pom.xml b/job-dsl-plugin/pom.xml index 4de7c6563..503e94458 100644 --- a/job-dsl-plugin/pom.xml +++ b/job-dsl-plugin/pom.xml @@ -54,7 +54,7 @@ io.jenkins.tools.bom bom-2.387.x - 1887.vda_d0ddb_c15c4 + 1935.v530f4395930f pom import @@ -139,6 +139,11 @@ test-harness test + + io.jenkins.plugins + custom-folder-icon + test + org.jenkins-ci.plugins credentials diff --git a/job-dsl-plugin/src/test/groovy/javaposse/jobdsl/plugin/ExecuteDslScriptsSpec.groovy b/job-dsl-plugin/src/test/groovy/javaposse/jobdsl/plugin/ExecuteDslScriptsSpec.groovy index 11ff191ee..024953582 100644 --- a/job-dsl-plugin/src/test/groovy/javaposse/jobdsl/plugin/ExecuteDslScriptsSpec.groovy +++ b/job-dsl-plugin/src/test/groovy/javaposse/jobdsl/plugin/ExecuteDslScriptsSpec.groovy @@ -1,9 +1,12 @@ package javaposse.jobdsl.plugin import com.cloudbees.hudson.plugins.folder.Folder +import com.cloudbees.hudson.plugins.folder.FolderIcon +import com.cloudbees.hudson.plugins.folder.icons.StockFolderIcon import hudson.FilePath import hudson.model.AbstractItem import hudson.model.AbstractProject +import hudson.model.BallColor import hudson.model.Computer import hudson.model.FreeStyleBuild import hudson.model.FreeStyleProject @@ -25,13 +28,19 @@ import javaposse.jobdsl.plugin.actions.GeneratedViewsAction import javaposse.jobdsl.plugin.actions.GeneratedViewsBuildAction import javaposse.jobdsl.plugin.actions.SeedJobAction import javaposse.jobdsl.plugin.fixtures.ExampleJobDslExtension +import jenkins.branch.MetadataActionFolderIcon import jenkins.model.Jenkins +import jenkins.plugins.foldericon.BuildStatusFolderIcon +import jenkins.plugins.foldericon.CustomFolderIcon +import jenkins.plugins.foldericon.EmojiFolderIcon +import jenkins.plugins.foldericon.IoniconFolderIcon import jenkins.security.QueueItemAuthenticator import jenkins.security.QueueItemAuthenticatorConfiguration import org.acegisecurity.Authentication import org.jenkinsci.plugins.configfiles.GlobalConfigFiles import org.jenkinsci.plugins.configfiles.custom.CustomConfig import org.jenkinsci.plugins.managedscripts.PowerShellConfig +import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage import org.junit.ClassRule import org.junit.Rule @@ -43,7 +52,6 @@ import org.jvnet.hudson.test.WithoutJenkins import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll -import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval import static hudson.model.Result.FAILURE import static hudson.model.Result.SUCCESS @@ -104,6 +112,48 @@ folder('folder-a/folder-b') { description('lorem ipsum') }""" + private static final String FOLDER_WITH_STOCK_ICON_SCRIPT = """folder('stock') { + icon { + stockFolderIcon() + } +}""" + + private static final String FOLDER_WITH_METADATA_ICON_SCRIPT = """folder('metadata') { + icon { + metadataActionFolderIcon() + } +}""" + + private static final String FOLDER_WITH_CUSTOM_ICON_SCRIPT = """folder('custom') { + icon { + customFolderIcon { + foldericon('custom.png') + } + } +}""" + + private static final String FOLDER_WITH_IONICON_ICON_SCRIPT = """folder('ionicon') { + icon { + ioniconFolderIcon { + ionicon('jenkins') + } + } +}""" + + private static final String FOLDER_WITH_BUILD_STATUS_ICON_SCRIPT = """folder('build-status') { + icon { + buildStatusFolderIcon() + } +}""" + + private static final String FOLDER_WITH_EMOJI_ICON_SCRIPT = """folder('emoji') { + icon { + emojiFolderIcon { + emoji('sloth') + } + } +}""" + @Shared @ClassRule @SuppressWarnings('JUnitPublicField') @@ -877,6 +927,106 @@ folder('folder-a/folder-b') { SeedJobAction).isConfigChanged() } + def createFolderWithStockIcon() { + setup: + FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed') + job.buildersList.add(new ExecuteDslScripts(FOLDER_WITH_STOCK_ICON_SCRIPT)) + + when: + FreeStyleBuild freeStyleBuild = job.scheduleBuild2(0).get() + + then: + freeStyleBuild.result == SUCCESS + Item item = jenkinsRule.instance.getItemByFullName('stock') + item instanceof Folder + FolderIcon icon = ((Folder) item).getIcon() + icon instanceof StockFolderIcon + } + + def createFolderWithMetadataIcon() { + setup: + FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed') + job.buildersList.add(new ExecuteDslScripts(FOLDER_WITH_METADATA_ICON_SCRIPT)) + + when: + FreeStyleBuild freeStyleBuild = job.scheduleBuild2(0).get() + + then: + freeStyleBuild.result == SUCCESS + Item item = jenkinsRule.instance.getItemByFullName('metadata') + item instanceof Folder + FolderIcon icon = ((Folder) item).getIcon() + icon instanceof MetadataActionFolderIcon + } + + def createFolderWithCustomIcon() { + setup: + FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed') + job.buildersList.add(new ExecuteDslScripts(FOLDER_WITH_CUSTOM_ICON_SCRIPT)) + + when: + FreeStyleBuild freeStyleBuild = job.scheduleBuild2(0).get() + + then: + freeStyleBuild.result == SUCCESS + Item item = jenkinsRule.instance.getItemByFullName('custom') + item instanceof Folder + FolderIcon icon = ((Folder) item).getIcon() + icon instanceof CustomFolderIcon + ((CustomFolderIcon) icon).getFoldericon() == 'custom.png' + } + + def createFolderWithIoniconIcon() { + setup: + FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed') + job.buildersList.add(new ExecuteDslScripts(FOLDER_WITH_IONICON_ICON_SCRIPT)) + + when: + FreeStyleBuild freeStyleBuild = job.scheduleBuild2(0).get() + + then: + freeStyleBuild.result == SUCCESS + Item item = jenkinsRule.instance.getItemByFullName('ionicon') + item instanceof Folder + FolderIcon icon = ((Folder) item).getIcon() + icon instanceof IoniconFolderIcon + ((IoniconFolderIcon) icon).getIonicon() == 'jenkins' + } + + def createFolderWithBuildStatusIcon() { + setup: + FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed') + job.buildersList.add(new ExecuteDslScripts(FOLDER_WITH_BUILD_STATUS_ICON_SCRIPT)) + + when: + FreeStyleBuild freeStyleBuild = job.scheduleBuild2(0).get() + + then: + freeStyleBuild.result == SUCCESS + Item item = jenkinsRule.instance.getItemByFullName('build-status') + item instanceof Folder + FolderIcon icon = ((Folder) item).getIcon() + icon instanceof BuildStatusFolderIcon + ((BuildStatusFolderIcon) icon).getIconClassName() == BallColor.NOTBUILT.getIconClassName() + } + + def createFolderWithEmojiIcon() { + setup: + FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed') + job.buildersList.add(new ExecuteDslScripts(FOLDER_WITH_EMOJI_ICON_SCRIPT)) + + when: + FreeStyleBuild freeStyleBuild = job.scheduleBuild2(0).get() + + then: + freeStyleBuild.result == SUCCESS + Item item = jenkinsRule.instance.getItemByFullName('emoji') + item instanceof Folder + FolderIcon icon = ((Folder) item).getIcon() + icon instanceof EmojiFolderIcon + ((EmojiFolderIcon) icon).getEmoji() == 'sloth' + } + def createJobInFolder() { setup: FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')