diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/iconselect/.gitignore b/iconselect/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/iconselect/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/iconselect/.idea/.gitignore b/iconselect/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/iconselect/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/iconselect/.idea/encodings.xml b/iconselect/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/iconselect/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/iconselect/.idea/misc.xml b/iconselect/.idea/misc.xml new file mode 100644 index 0000000..82dbec8 --- /dev/null +++ b/iconselect/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/iconselect/pom.xml b/iconselect/pom.xml new file mode 100644 index 0000000..4e6128d --- /dev/null +++ b/iconselect/pom.xml @@ -0,0 +1,250 @@ + + + + + com.codebarrel + base-pom + 1.0.3 + + 4.0.0 + com.codebarrel.jira + iconselectlist + 2.0.2 + + Code Barrel Pty Ltd + http://codebarrel.io + + Icons for JIRA + This is the Code Barrel Icons for JIRA Add-on. + atlassian-plugin + + scm:git:ssh://git@bitbucket.org/codebarrel/barrel.git + scm:git:ssh://git@bitbucket.org/codebarrel/barrel.git + https://bitbucket.org/codebarrel/barrel + HEAD + + + + com.atlassian.jira + jira-api + ${jira.version} + provided + + + com.atlassian.jira + jira-core + ${jira.version} + provided + + + com.atlassian.plugin + atlassian-spring-scanner-annotation + ${atlassian.spring.scanner.version} + compile + + + com.atlassian.plugin + atlassian-spring-scanner-runtime + ${atlassian.spring.scanner.version} + runtime + + + javax.inject + javax.inject + 1 + provided + + + org.slf4j + slf4j-api + 1.6.6 + provided + + + org.mockito + mockito-all + 1.8.5 + test + + + org.apache.httpcomponents + httpclient + 4.1.1 + test + + + javax.servlet + servlet-api + 2.4 + provided + + + javax.xml.bind + jaxb-api + 2.1 + provided + + + com.atlassian.plugins.rest + atlassian-rest-common + 3.2.2 + provided + + + com.atlassian.jira + jira-rest-api + ${jira.version} + provided + + + com.atlassian.sal + sal-api + 2.6.0 + provided + + + org.apache.wink + wink-client + 1.1.3-incubating + test + + + org.apache.felix + org.apache.felix.framework + 4.0.0 + provided + + + + + + + + + + + + + com.atlassian.activeobjects + activeobjects-plugin + ${ao.version} + provided + + + com.atlassian.plugins + atlassian-plugins-osgi-javaconfig + 0.2.0 + + + + + + com.atlassian.maven.plugins + jira-maven-plugin + ${amps.version} + true + + ${jira.version} + ${jira.version} + + + jira-software + ${jira.version} + + + + + true + false + false + true + false + false + true + + + + ${atlassian.plugin.key} + + com.codebarrel.jira.api, + + org.springframework.osgi.*;resolution:="optional", + org.eclipse.gemini.blueprint.*;resolution:="optional", + com.atlassian.jira.*;resolution:="optional", *;resolution:=optional + + + * + + -Xmx1g -XX:MaxPermSize=512m -Djira.dev.mode=true -Datlassian.mail.senddisabled=false + + + + com.atlassian.labs.plugins + quickreload + 1.30.1 + + + + + + com.atlassian.plugin + atlassian-spring-scanner-maven-plugin + ${atlassian.spring.scanner.version} + + + + atlassian-spring-scanner + + process-classes + + + + + + com.atlassian.plugin + atlassian-spring-scanner-external-jar + + + false + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + + + + + + + + 9.12.0 + 8.2.0 + 3.0.0 + 0.2.0 + 2.0.2 + 1.2.13 + + ${project.groupId}.${project.artifactId} + 6.3.11 + 6.1.13 + 1.18.12 + + + diff --git a/iconselect/src/main/java/com/codebarrel/iconselect/api/IconOptionBean.java b/iconselect/src/main/java/com/codebarrel/iconselect/api/IconOptionBean.java new file mode 100644 index 0000000..26cfecc --- /dev/null +++ b/iconselect/src/main/java/com/codebarrel/iconselect/api/IconOptionBean.java @@ -0,0 +1,107 @@ +package com.codebarrel.iconselect.api; + +import com.atlassian.jira.issue.customfields.option.Option; +import com.atlassian.jira.issue.fields.rest.json.beans.JiraBaseUrls; +import com.atlassian.jira.util.JiraUrlCodec; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "option") +@XmlAccessorType(XmlAccessType.FIELD) +public class IconOptionBean { + @XmlElement(name = "id") + private Long id; + + @XmlElement(name = "self") + private String self; + + @XmlElement(name = "label") + private String label; + + @XmlElement(name = "iconUrl") + private String iconUrl; + + @XmlElement(name = "avatarId") + private Long avatarId; + + @XmlElement(name = "sequence") + private Long sequence; + + @XmlElement(name = "disabled") + private boolean disabled; + + public IconOptionBean() {} + + public IconOptionBean(Long id, String label, Long avatarId, Long sequence, boolean disabled) { + this(id, label, avatarId, sequence, disabled, ""); + } + + public IconOptionBean(Long id, String label, Long avatarId, Long sequence, boolean disabled, String baseUrl) { + this.id = id; + this.label = label; + this.avatarId = avatarId; + this.sequence = sequence; + this.disabled = disabled; + if (baseUrl == null) + baseUrl = ""; + this.self = baseUrl + "/rest/iconselectoptions/1.0/option/" + JiraUrlCodec.encode(id.toString()); + this.iconUrl = baseUrl + "/secure/viewavatar?size=xsmall&avatarId=" + avatarId + "&avatarType=iconselectlist"; + } + + public String getSelf() { + return this.self; + } + + public void setSelf(String self) { + this.self = self; + } + + public String getLabel() { + return this.label; + } + + public void setLabel(String label) { + this.label = label; + } + + public Long getAvatarId() { + return this.avatarId; + } + + public void setAvatarId(Long avatarId) { + this.avatarId = avatarId; + } + + public Long getSequence() { + return this.sequence; + } + + public void setSequence(Long sequence) { + this.sequence = sequence; + } + + public boolean isDisabled() { + return this.disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public static IconOptionBean fromOption(Option option, Long avatarId, JiraBaseUrls urls) { + if (option == null) + return null; + IconOptionBean bean = new IconOptionBean(option.getOptionId(), option.getValue(), avatarId, option.getSequence(), option.getDisabled().booleanValue(), urls.baseUrl()); + return bean; + } +} diff --git a/iconselect/src/main/java/com/codebarrel/iconselect/api/IconOptionsService.java b/iconselect/src/main/java/com/codebarrel/iconselect/api/IconOptionsService.java new file mode 100644 index 0000000..a0bc4f6 --- /dev/null +++ b/iconselect/src/main/java/com/codebarrel/iconselect/api/IconOptionsService.java @@ -0,0 +1,31 @@ +package com.codebarrel.iconselect.api; + +import com.atlassian.jira.bc.ServiceOutcome; +import com.atlassian.jira.bc.ServiceResult; +import com.atlassian.jira.issue.customfields.option.Option; +import com.atlassian.jira.user.ApplicationUser; +import java.util.List; + +public interface IconOptionsService { + ServiceOutcome getIconOptionById(ApplicationUser paramApplicationUser, Long paramLong); + + ServiceOutcome getIconOptionByFieldConfigAndId(ApplicationUser paramApplicationUser, Long paramLong1, Long paramLong2); + + ServiceOutcome> getAllIconOptionForFieldConfig(ApplicationUser paramApplicationUser, Long paramLong); + + ServiceOutcome createIconOption(ApplicationUser paramApplicationUser, Long paramLong, IconOptionBean paramIconOptionBean); + + ServiceOutcome updateIconOption(ApplicationUser paramApplicationUser, Long paramLong, IconOptionBean paramIconOptionBean); + + ServiceResult deleteIconOption(ApplicationUser paramApplicationUser, Long paramLong1, Long paramLong2); + + ServiceResult disableIconOption(ApplicationUser paramApplicationUser, Long paramLong1, Long paramLong2); + + ServiceResult enableIconOption(ApplicationUser paramApplicationUser, Long paramLong1, Long paramLong2); + + ServiceOutcome moveToPosition(ApplicationUser paramApplicationUser, Long paramLong1, Long paramLong2, int paramInt); + + ServiceOutcome moveToAfter(ApplicationUser paramApplicationUser, Long paramLong1, Long paramLong2, Long paramLong3); + + Long getAvatarIdForOption(Option paramOption); +} diff --git a/iconselect/src/main/java/com/codebarrel/iconselect/api/PositionBean.java b/iconselect/src/main/java/com/codebarrel/iconselect/api/PositionBean.java new file mode 100644 index 0000000..71bf751 --- /dev/null +++ b/iconselect/src/main/java/com/codebarrel/iconselect/api/PositionBean.java @@ -0,0 +1,40 @@ +package com.codebarrel.iconselect.api; + + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "position") +@XmlAccessorType(XmlAccessType.FIELD) +public class PositionBean { + @XmlElement(name = "after") + private String after = null; + + @XmlElement(name = "position") + private String position = null; + + public PositionBean() {} + + public PositionBean(String after, String position) { + this.after = after; + this.position = position; + } + + public String getAfter() { + return this.after; + } + + public void setAfter(String after) { + this.after = after; + } + + public String getPosition() { + return this.position; + } + + public void setPosition(String position) { + this.position = position; + } +} diff --git a/iconselect/src/main/java/com/codebarrel/iconselect/config/IconSelectPluginBeanConfig.java b/iconselect/src/main/java/com/codebarrel/iconselect/config/IconSelectPluginBeanConfig.java new file mode 100644 index 0000000..427f2ff --- /dev/null +++ b/iconselect/src/main/java/com/codebarrel/iconselect/config/IconSelectPluginBeanConfig.java @@ -0,0 +1,155 @@ +package com.codebarrel.iconselect.config; + +import com.atlassian.activeobjects.external.ActiveObjects; +import com.atlassian.jira.avatar.AvatarManager; +import com.atlassian.jira.bc.issue.search.SearchService; +import com.atlassian.jira.config.ConstantsManager; +import com.atlassian.jira.config.FeatureManager; +import com.atlassian.jira.issue.CustomFieldManager; +import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; +import com.atlassian.jira.issue.customfields.manager.OptionsManager; +import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; +import com.atlassian.jira.issue.fields.FieldManager; +import com.atlassian.jira.issue.fields.config.manager.FieldConfigManager; +import com.atlassian.jira.issue.fields.config.manager.FieldConfigSchemeManager; +import com.atlassian.jira.issue.fields.config.manager.IssueTypeSchemeManager; +import com.atlassian.jira.issue.fields.rest.json.beans.JiraBaseUrls; +import com.atlassian.jira.project.ProjectFactory; +import com.atlassian.jira.propertyset.JiraPropertySetFactory; +import com.atlassian.jira.security.GlobalPermissionManager; +import com.atlassian.jira.security.JiraAuthenticationContext; +import com.atlassian.jira.security.PermissionManager; +import com.atlassian.jira.util.I18nHelper; +import com.atlassian.jira.web.FieldVisibilityManager; +import com.atlassian.plugins.osgi.javaconfig.configs.beans.ModuleFactoryBean; +import com.atlassian.plugins.osgi.javaconfig.configs.beans.PluginAccessorBean; +import com.atlassian.soy.renderer.SoyTemplateRenderer; +import com.atlassian.webresource.api.assembler.PageBuilderService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import static com.atlassian.plugins.osgi.javaconfig.OsgiServices.importOsgiService; + +@Configuration +@Import({ + ModuleFactoryBean.class, + PluginAccessorBean.class +}) +public class IconSelectPluginBeanConfig { + @Bean + public ActiveObjects activeObjects() { + return importOsgiService(ActiveObjects.class); + } + + @Bean + public OptionsManager optionsManager() { + return importOsgiService(OptionsManager.class); + } + + @Bean + public PageBuilderService pageBuilderService() { + return importOsgiService(PageBuilderService.class); + } + + @Bean + public JiraBaseUrls jiraBaseUrls() { + return importOsgiService(JiraBaseUrls.class); + } + + @Bean + public SearchService searchService() { + return importOsgiService(SearchService.class); + } + + @Bean + public ProjectFactory projectFactory() { + return importOsgiService(ProjectFactory.class); + } + + @Bean + public SoyTemplateRenderer soyTemplateRenderer() { + return importOsgiService(SoyTemplateRenderer.class); + } + + @Bean + public AvatarManager avatarManager() { + return importOsgiService(AvatarManager.class); + } + + @Bean + public JiraPropertySetFactory jiraPropertySetFactory() { + return importOsgiService(JiraPropertySetFactory.class); + } + + @Bean + public I18nHelper.BeanFactory beanFactory() { + return importOsgiService(I18nHelper.BeanFactory.class); + } + + @Bean + public ConstantsManager constantsManager() { + return importOsgiService(ConstantsManager.class); + } + + @Bean + public CustomFieldManager customFieldManager() { + return importOsgiService(CustomFieldManager.class); + } + + @Bean + public CustomFieldValuePersister customFieldValuePersister() { + return importOsgiService(CustomFieldValuePersister.class); + } + + @Bean + public FieldConfigSchemeManager fieldConfigSchemeManager() { + return importOsgiService(FieldConfigSchemeManager.class); + } + + @Bean + public FieldConfigManager fieldConfigManager() { + return importOsgiService(FieldConfigManager.class); + } + + @Bean + public FieldManager fieldManager() { + return importOsgiService(FieldManager.class); + } + + @Bean + public FieldVisibilityManager fieldVisibilityManager() { + return importOsgiService(FieldVisibilityManager.class); + } + + @Bean + public FeatureManager featureManager() { + return importOsgiService(FeatureManager.class); + } + + @Bean + public GenericConfigManager genericConfigManager() { + return importOsgiService(GenericConfigManager.class); + } + + @Bean + public GlobalPermissionManager globalPermissionManager() { + return importOsgiService(GlobalPermissionManager.class); + } + + @Bean + public IssueTypeSchemeManager issueTypeSchemeManager() { + return importOsgiService(IssueTypeSchemeManager.class); + } + + @Bean + public JiraAuthenticationContext jiraAuthenticationContext() { + return importOsgiService(JiraAuthenticationContext.class); + } + + @Bean + public PermissionManager permissionManager() { + return importOsgiService(PermissionManager.class); + } +} + diff --git a/iconselect/src/main/java/com/codebarrel/iconselect/customfield/IconMultiSelectCF.java b/iconselect/src/main/java/com/codebarrel/iconselect/customfield/IconMultiSelectCF.java new file mode 100644 index 0000000..a655149 --- /dev/null +++ b/iconselect/src/main/java/com/codebarrel/iconselect/customfield/IconMultiSelectCF.java @@ -0,0 +1,113 @@ +package com.codebarrel.iconselect.customfield; + +import com.atlassian.jira.bc.issue.search.SearchService; +import com.atlassian.jira.config.FeatureManager; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.context.IssueContext; +import com.atlassian.jira.issue.customfields.config.item.DefaultValueConfigItem; +import com.atlassian.jira.issue.customfields.impl.MultiSelectCFType; +import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; +import com.atlassian.jira.issue.customfields.manager.OptionsManager; +import com.atlassian.jira.issue.customfields.option.Option; +import com.atlassian.jira.issue.customfields.option.Options; +import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; +import com.atlassian.jira.issue.fields.CustomField; +import com.atlassian.jira.issue.fields.config.FieldConfig; +import com.atlassian.jira.issue.fields.config.FieldConfigItemType; +import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem; +import com.atlassian.jira.issue.fields.rest.FieldJsonRepresentation; +import com.atlassian.jira.issue.fields.rest.FieldTypeInfo; +import com.atlassian.jira.issue.fields.rest.FieldTypeInfoContext; +import com.atlassian.jira.issue.fields.rest.json.JsonData; +import com.atlassian.jira.issue.fields.rest.json.beans.JiraBaseUrls; +import com.atlassian.jira.security.JiraAuthenticationContext; +import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; +import com.atlassian.soy.renderer.SoyTemplateRenderer; +import com.atlassian.webresource.api.assembler.PageBuilderService; +import com.codebarrel.iconselect.api.IconOptionBean; +import com.codebarrel.iconselect.api.IconOptionsService; +import com.codebarrel.iconselect.service.IconOptionUtil; +import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Named; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Scanned +@Named +public class IconMultiSelectCF extends MultiSelectCFType { + private static final Logger log = LoggerFactory.getLogger(IconMultiSelectCF.class); + + private final OptionsManager optionsManager; + private final SoyTemplateRenderer soyTemplateRenderer; + private final PageBuilderService pageBuilderService; + private final IconOptionsService iconOptionsService; + private final JiraBaseUrls jiraBaseUrls; + + @Inject + public IconMultiSelectCF(OptionsManager optionsManager, + CustomFieldValuePersister valuePersister, + GenericConfigManager genericConfigManager, + JiraBaseUrls jiraBaseUrls, + SearchService searchService, + FeatureManager featureManager, + SoyTemplateRenderer soyTemplateRenderer, + PageBuilderService pageBuilderService, + JiraAuthenticationContext jiraAuthenticationContext, + IconOptionsService iconOptionsService) { + super(optionsManager, valuePersister, genericConfigManager, jiraBaseUrls, searchService, featureManager, jiraAuthenticationContext); + this.optionsManager = optionsManager; + this.soyTemplateRenderer = soyTemplateRenderer; + this.pageBuilderService = pageBuilderService; + this.iconOptionsService = iconOptionsService; + this.jiraBaseUrls = jiraBaseUrls; + } + + @Nonnull + public List getConfigurationItemTypes() { + return Lists.newArrayList(new DefaultValueConfigItem(), new IconOptionsConfigItem(this, this.optionsManager, this.soyTemplateRenderer, this.iconOptionsService, this.jiraBaseUrls)); + } + + @Nonnull + public Map getVelocityParameters(Issue issue, CustomField field, FieldLayoutItem fieldLayoutItem) { + Map velocityParams = super.getVelocityParameters(issue, field, fieldLayoutItem); + velocityParams.put("iconOptionUtil", new IconOptionUtil(this.iconOptionsService)); + this.pageBuilderService.assembler().resources().requireWebResource("com.codebarrel.jira.iconselectlist:iconselectlist-resources"); + return velocityParams; + } + + public FieldTypeInfo getFieldTypeInfo(FieldTypeInfoContext fieldTypeInfoContext) { + FieldConfig config = ((CustomField) fieldTypeInfoContext.getOderableField()).getRelevantConfig(fieldTypeInfoContext.getIssueContext()); + Options options = this.optionsManager.getOptions(config); + List optionBeans = options.stream() + .map(option -> IconOptionBean + .fromOption(option, this.iconOptionsService.getAvatarIdForOption(option), this.jiraBaseUrls)) + .collect(Collectors.toList()); + return new FieldTypeInfo(optionBeans, this.jiraBaseUrls.baseUrl() + "/rest/iconselectoptions/1.0/option/context/" + config.getId()); + } + + public FieldJsonRepresentation getJsonFromIssue(CustomField field, Issue issue, boolean renderedVersionRequested, @Nullable FieldLayoutItem fieldLayoutItem) { + Collection