├── .gitignore ├── src ├── main │ ├── java │ │ ├── velocity_implicit.vm │ │ └── com │ │ │ └── googlecode │ │ │ └── jsu │ │ │ ├── helpers │ │ │ ├── checkers │ │ │ │ ├── ValueConverter.java │ │ │ │ ├── ComparingSnipet.java │ │ │ │ ├── SnipetG.java │ │ │ │ ├── SnipetL.java │ │ │ │ ├── SnipetE.java │ │ │ │ ├── SnipetGE.java │ │ │ │ ├── SnipetLE.java │ │ │ │ ├── SnipetNE.java │ │ │ │ ├── ConverterDateWithoutTime.java │ │ │ │ ├── ConverterDate.java │ │ │ │ ├── ConverterNumber.java │ │ │ │ ├── CheckerCompositeFactory.java │ │ │ │ ├── CheckerComposite.java │ │ │ │ └── ConverterString.java │ │ │ ├── ConditionChecker.java │ │ │ ├── NameComparatorEx.java │ │ │ ├── YesNoType.java │ │ │ ├── ComparisonType.java │ │ │ ├── ConditionType.java │ │ │ ├── FormattableDuration.java │ │ │ └── ConditionCheckerFactory.java │ │ │ ├── annotation │ │ │ ├── AbstractVisitor.java │ │ │ ├── Argument.java │ │ │ ├── TransientVariable.java │ │ │ ├── AnnotationProcessor.java │ │ │ └── MapFieldProcessor.java │ │ │ ├── customfields │ │ │ ├── LocationSelectSearcher.java │ │ │ ├── LocationSelectCFType.java │ │ │ ├── DirectionsCFType.java │ │ │ ├── LocationTextCFType.java │ │ │ └── TimeInSourceStatusCFType.java │ │ │ ├── transitionssummary │ │ │ ├── issuetabpanel │ │ │ │ ├── TransitionSummaryComparator.java │ │ │ │ ├── TransitionSummaryAction.java │ │ │ │ └── TransitionsSummaryTabPanel.java │ │ │ ├── StatusDelegate.java │ │ │ ├── Transition.java │ │ │ └── TransitionSummary.java │ │ │ ├── workflow │ │ │ ├── FieldContainer.java │ │ │ ├── function │ │ │ │ ├── ClearFieldValuePostFunction.java │ │ │ │ ├── AbstractPreserveChangesPostFunction.java │ │ │ │ └── CopyValueFromOtherFieldPostFunction.java │ │ │ ├── condition │ │ │ │ ├── UserIsInAnyRolesCondition.java │ │ │ │ ├── UserIsInAnyGroupsCondition.java │ │ │ │ └── UserIsInCustomFieldCondition.java │ │ │ ├── WorkflowClearFieldValueFunctionPluginFactory.java │ │ │ ├── validator │ │ │ │ ├── RegexpFieldValidator.java │ │ │ │ ├── DateCompareValidator.java │ │ │ │ └── AbstractDateCompareValidator.java │ │ │ ├── WorkflowRegexpFieldValidatorPluginFactory.java │ │ │ ├── WorkflowUserIsInAnyGroupsConditionPluginFactory.java │ │ │ ├── WorkflowUserIsInAnyRolesConditionPluginFactory.java │ │ │ ├── WorkflowWindowsDateValidatorPluginFactory.java │ │ │ ├── WorkflowFieldsRequiredValidatorPluginFactory.java │ │ │ ├── WorkflowUpdateIssueCustomFieldFunctionPluginFactory.java │ │ │ ├── WorkflowUserIsInCustomFieldConditionPluginFactory.java │ │ │ └── WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory.java │ │ │ └── util │ │ │ └── ValidatorErrorsBuilder.java │ └── resources │ │ ├── templates │ │ └── jira │ │ │ ├── fields │ │ │ └── view │ │ │ │ ├── view-timeinsourcestatus.vm │ │ │ │ ├── view-directions.vm │ │ │ │ └── view-location.vm │ │ │ ├── workflow │ │ │ ├── function │ │ │ │ ├── clearfieldvalue-function-view.vm │ │ │ │ ├── copyvaluefromfield-function-view.vm │ │ │ │ ├── updateissuefield-function-view.vm │ │ │ │ ├── clearfieldvalue-function-edit.vm │ │ │ │ ├── copyvaluefromfield-function-input.vm │ │ │ │ ├── copyvaluefromfield-function-edit.vm │ │ │ │ └── updateissuefield-function-input.vm │ │ │ ├── validator │ │ │ │ ├── regexpfield-validator-view.vm │ │ │ │ ├── fieldsrequired-validator-view.vm │ │ │ │ ├── windowsdate-validator-view.vm │ │ │ │ ├── datecompare-validator-view.vm │ │ │ │ ├── dateexpressioncompare-validator-view.vm │ │ │ │ ├── regexpfield-validator-edit.vm │ │ │ │ ├── windowsdate-validator-input.vm │ │ │ │ ├── windowsdate-validator-edit.vm │ │ │ │ ├── datecompare-validator-input.vm │ │ │ │ ├── dateexpressioncompare-validator-input.vm │ │ │ │ ├── dateexpressioncompare-validator-edit.vm │ │ │ │ ├── datecompare-validator-edit.vm │ │ │ │ └── fieldsrequired-validator-edit.vm │ │ │ └── condition │ │ │ │ ├── userIsInAnyRoles-condition-view.vm │ │ │ │ ├── userIsInAnyGroups-condition-view.vm │ │ │ │ ├── UserIsInCustomField-Condition-view.vm │ │ │ │ ├── fieldvalue-condition-view.vm │ │ │ │ ├── UserIsInCustomField-Condition-edit.vm │ │ │ │ ├── fieldvalue-condition-edit.vm │ │ │ │ ├── userIsInAnyRoles-condition-edit.vm │ │ │ │ └── userIsInAnyGroups-condition-edit.vm │ │ │ └── issuetabpanel │ │ │ └── transitionssummary │ │ │ └── transitions-summary-view.vm │ │ └── com │ │ └── googlecode │ │ └── jsu │ │ ├── images │ │ ├── pluginIcon.png │ │ └── pluginLogo.png │ │ ├── maps │ │ └── LocationField.css │ │ ├── i18n_es.properties │ │ ├── i18n_fr.properties │ │ └── i18n_ru.properties └── test │ ├── resources │ ├── generated-test-resources.zip │ └── localtest.properties │ └── java │ └── it │ └── com │ └── googlecode │ └── jsu │ └── workflow │ └── function │ ├── OtherTest.java │ └── AbstractTestBase.java ├── README.md ├── .hgignore ├── licence.txt └── .hgtags /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | jira-suite-utilities.iml 4 | SOURCEME 5 | -------------------------------------------------------------------------------- /src/main/java/velocity_implicit.vm: -------------------------------------------------------------------------------- 1 | #* @implicitly included *# 2 | #* @vtlvariable name="textutils" type="com.opensymphony.util.TextUtils" *# 3 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/fields/view/view-timeinsourcestatus.vm: -------------------------------------------------------------------------------- 1 | #if ($value) 2 | #disable_html_escaping() 3 | $value.getFormatted() 4 | #end -------------------------------------------------------------------------------- /src/test/resources/generated-test-resources.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlassian/jira-suite-utilities/HEAD/src/test/resources/generated-test-resources.zip -------------------------------------------------------------------------------- /src/main/resources/com/googlecode/jsu/images/pluginIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlassian/jira-suite-utilities/HEAD/src/main/resources/com/googlecode/jsu/images/pluginIcon.png -------------------------------------------------------------------------------- /src/main/resources/com/googlecode/jsu/images/pluginLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlassian/jira-suite-utilities/HEAD/src/main/resources/com/googlecode/jsu/images/pluginLogo.png -------------------------------------------------------------------------------- /src/test/resources/localtest.properties: -------------------------------------------------------------------------------- 1 | jira.protocol = http 2 | jira.host = localhost 3 | jira.port = 2990 4 | jira.context = /jira 5 | jira.edition = all 6 | 7 | jira.xml.data.location = src/test/xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JIRA Suite Utilities 2 | ==================== 3 | 4 | This is the source code for the JIRA Suite Utilities plugin. 5 | 6 | * **Home Page**: https://plugins.atlassian.com/plugin/details/5048 7 | * **Issue Tracking**: https://studio.plugins.atlassian.com/browse/JSUTIL 8 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | 2 | # Automatically generated by `hgimportsvn` 3 | syntax:glob 4 | .svn 5 | .hgsvn 6 | 7 | # These lines are suggested according to the svn:ignore property 8 | # Feel free to enable them by uncommenting them 9 | syntax:glob 10 | 11 | syntax: regexp 12 | ^target$ 13 | syntax: glob 14 | .* -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/clearfieldvalue-function-view.vm: -------------------------------------------------------------------------------- 1 | #if(${selectedField}) 2 | $i18n.getText("clearfieldvalue-function-view.function_text","$textutils.htmlEncode(${selectedField.name})") 3 | #else 4 | $i18n.getText("clearfieldvalue-function-view.wrong_selection") 5 | #end -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/regexpfield-validator-view.vm: -------------------------------------------------------------------------------- 1 | #if (${val-fieldSelected} && ${val-expressionSelected}) 2 | $i18n.getText("regexpfield-validator-view.validation_text", 3 | "${val-fieldSelected.name}", 4 | "${val-expressionSelected}") 5 | #else 6 | $i18n.getText("regexpfield-validator-view.wrong_selection") 7 | #end -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/ValueConverter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | interface ValueConverter { 7 | /** 8 | * Get comparable value from object. 9 | */ 10 | Comparable getComparable(Object object); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/ComparingSnipet.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | interface ComparingSnipet { 7 | /** 8 | * Execute comparing action for objects. 9 | */ 10 | boolean compareObjects(Comparable> comp1, Comparable comp2); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/ConditionChecker.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | /** 4 | * Interface for checking conditions. Used as strategy pattern. 5 | * 6 | * @author Alexey Abashev 7 | */ 8 | public interface ConditionChecker { 9 | /** 10 | * Check two values and return true if condition success or false is not. 11 | */ 12 | boolean checkValues(Object value1, Object value2); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/annotation/AbstractVisitor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | 6 | /** 7 | * @author Alexey Abashev 8 | */ 9 | public abstract class AbstractVisitor { 10 | public abstract Class getAnnotation(); 11 | 12 | public void visitField(Object source, Field field, Annotation annotation) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/userIsInAnyRoles-condition-view.vm: -------------------------------------------------------------------------------- 1 | #if(${val-rolesListSelected}) 2 | #if (${val-rolesListSelected.isEmpty()}) 3 | $i18n.getText("userisinanyroles-condition-view.empty.text") 4 | #else 5 | $i18n.getText("userisinanyroles-condition-view.condition.text") 6 | #foreach ($singleValue in ${val-rolesListSelected}) 7 | $textutils.htmlEncode($!singleValue.getName())#if ($velocityCount != ${val-rolesListSelected.size()}), #end 8 | #end 9 | #end 10 | #end 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/userIsInAnyGroups-condition-view.vm: -------------------------------------------------------------------------------- 1 | #if(${val-groupsListSelected}) 2 | #if (${val-groupsListSelected.isEmpty()}) 3 | $i18n.getText("userisinanygroups-condition-view.empty.text") 4 | #else 5 | $i18n.getText("userisinanygroups-condition-view.condition.text") 6 | #foreach ($singleValue in ${val-groupsListSelected}) 7 | $textutils.htmlEncode($!singleValue.getName())#if ($velocityCount != ${val-groupsListSelected.size()}), #end 8 | #end 9 | #end 10 | #end 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/fieldsrequired-validator-view.vm: -------------------------------------------------------------------------------- 1 | #if (${val-contextHandling} && ${val-contextHandling} == "ignore") 2 | $i18n.getText("fieldsrequired-validator-edit.required_fields_ignoring_context"): 3 | #else 4 | $i18n.getText("fieldsrequired-validator-edit.required_fields"): 5 | #end 6 | 7 | #if (${val-fieldsListSelected}) 8 | #foreach ($singleValue in ${val-fieldsListSelected}) 9 | $!singleValue.getName()#if ($velocityCount != ${val-fieldsListSelected.size()}), #end 10 | #end 11 | #end 12 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/windowsdate-validator-view.vm: -------------------------------------------------------------------------------- 1 | #if (${val-date1Selected} && ${val-windowsDays} && ${val-date2Selected}) 2 | #if (!${val-windowsDays.equals("")}) 3 | #set($days = "${val-windowsDays}") 4 | #else 5 | #set($days = "0") 6 | #end 7 | $i18n.getText("windowsdate-validator-view.validation_text","$textutils.htmlEncode(${val-date1Selected.name})","$days","$textutils.htmlEncode(${val-date2Selected.name})") 8 | #else 9 | $i18n.getText("windowsdate-validator-view.wrong_selection") 10 | #end -------------------------------------------------------------------------------- /src/main/resources/com/googlecode/jsu/maps/LocationField.css: -------------------------------------------------------------------------------- 1 | .jsuLocation .jsuMap, .jsuLocation .jsuHideMap, .jsuLocation .jsuMapNotFound { 2 | display: none 3 | } 4 | 5 | .jsuLocation .jsuMap { 6 | width: 100%; 7 | height: 300px 8 | } 9 | 10 | .jsuLocation.notFound .jsuMapNotFound { 11 | text-align: center; 12 | } 13 | 14 | .jsuLocation .jsuHideMap { 15 | float: right 16 | } 17 | 18 | .jsuLocation.open .jsuHideMap, .jsuLocation.open .jsuMap, .jsuLocation.notFound .jsuMapNotFound { 19 | display: block 20 | } 21 | 22 | .type-locationtextfield, .type-locationselect , .type-directionsfield { 23 | display: block !important; 24 | } -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/datecompare-validator-view.vm: -------------------------------------------------------------------------------- 1 | #if (${val-date1Selected} && ${val-conditionSelected} && ${val-date2Selected}) 2 | $i18n.getText("datecompare-validator-view.validation_text", 3 | "${val-date1Selected.name}", 4 | "$i18n.getText(${val-conditionSelected.getDisplayTextKey()})", 5 | "${val-date2Selected.name}") 6 | #else 7 | $i18n.getText("datecompare-validator-view.wrong_selection") 8 | #end 9 | #if (${val-includeTimeSelected.id}==1) 10 | $i18n.getText("datecompare-validator-view.including_time") 11 | #else 12 | $i18n.getText("datecompare-validator-view.only_date") 13 | #end -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/copyvaluefromfield-function-view.vm: -------------------------------------------------------------------------------- 1 | #if(${val-destinationFieldSelected} && ${val-sourceFieldSelected}) 2 | $i18n.getText("copyvaluefromfield-function-view.function_text","$textutils.htmlEncode(${val-destinationFieldSelected.name})","$textutils.htmlEncode(${val-sourceFieldSelected.name})") 3 | #if(${val-copyType} && ${val-copyType} == 'parent') 4 | $i18n.getText("copyvaluefromfield-function-view.function_from_parent") 5 | #else 6 | $i18n.getText("copyvaluefromfield-function-view.function_within_same") 7 | #end 8 | #else 9 | $i18n.getText("copyvaluefromfield-function-view.wrong_selection") 10 | #end -------------------------------------------------------------------------------- /src/main/resources/com/googlecode/jsu/i18n_es.properties: -------------------------------------------------------------------------------- 1 | datecompare-validator-view.is_required={0} es obligatorio. 2 | datecompare-validator-view.is_not={0} no es {1} {2} {3} 3 | datecompare-validator-view.not_a_date={0} no es un valor de fecha ({1}) 4 | 5 | conditiontype.greater_than=mayor que 6 | conditiontype.greater_than_or_equal_to=mayor que o igual a 7 | conditiontype.equal_to=igual a 8 | conditiontype.less_than_or_equal_to=menos o igual a 9 | contitiontype.less_than=menos que 10 | conditiontype.not_equal_to=desigual a 11 | comparisontype.string=Texto 12 | comparisontype.number=N\u00famero 13 | comparisontype.date_with_time=Fecha con el tiempo 14 | comparisontype.date_without_time=Fecha sin tiempo -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/dateexpressioncompare-validator-view.vm: -------------------------------------------------------------------------------- 1 | #if (${val-dateFieldSelected} && ${val-conditionSelected} && ${val-expressionSelected}) 2 | $i18n.getText("datecompare-validator-view.validation_text", 3 | "${val-dateFieldSelected.name}", 4 | "$i18n.getText(${val-conditionSelected.getDisplayTextKey()})", 5 | "${val-expressionSelected}") 6 | #else 7 | $i18n.getText("dateexpressioncompare-validator-view.wrong_selection") 8 | #end 9 | #if (${val-includeTimeSelected.id}==1) 10 | $i18n.getText("datecompare-validator-view.including_time") 11 | #else 12 | $i18n.getText("datecompare-validator-view.only_date") 13 | #end -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/UserIsInCustomField-Condition-view.vm: -------------------------------------------------------------------------------- 1 | #if(${val-errorMessage}) 2 |
${val-errorMessage}
3 | #else 4 | #if(${val-fieldSelected}) 5 | #if (!${allowUserInField-selected}) 6 | $i18n.getText("userisincustomfield-condition-view.condition_text_must_not","$textutils.htmlEncode(${val-fieldSelected.name})") 7 | #else 8 | $i18n.getText("userisincustomfield-condition-view.condition_text_must","$textutils.htmlEncode(${val-fieldSelected.name})") 9 | #end 10 | #else 11 | $i18n.getText("userisincustomfield-condition-view.wrong_selection") 12 | #end 13 | #end -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/annotation/Argument.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.annotation; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * This annotation is used for marking fields as container for arguments in validators, 12 | * conditions and post-function. 13 | * @author Alexey Abashev 14 | */ 15 | @Documented 16 | @Retention(RUNTIME) 17 | @Target(FIELD) 18 | public @interface Argument { 19 | String value() default ""; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/SnipetG.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class SnipetG implements ComparingSnipet { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ComparingSnipet#compareObjects(java.lang.Comparable, java.lang.Comparable) 9 | */ 10 | public boolean compareObjects(Comparable> comp1, Comparable comp2) { 11 | if (comp1 == null) { 12 | return false; 13 | } 14 | 15 | if (comp2 == null) { 16 | return false; 17 | } 18 | 19 | return (comp1.compareTo(comp2) > 0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/SnipetL.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class SnipetL implements ComparingSnipet { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ComparingSnipet#compareObjects(java.lang.Comparable, java.lang.Comparable) 9 | */ 10 | public boolean compareObjects(Comparable> comp1, Comparable comp2) { 11 | if (comp1 == null) { 12 | return false; 13 | } 14 | 15 | if (comp2 == null) { 16 | return false; 17 | } 18 | 19 | return (comp1.compareTo(comp2) < 0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/updateissuefield-function-view.vm: -------------------------------------------------------------------------------- 1 | #if (${fieldValue} && $fieldValue.length() != 0 && ${fieldValue} != 'null') 2 | #if (${appendValue} && 'true' == ${appendValue}) 3 | $i18n.getText("updateissuefield-function-view.append", "$textutils.htmlEncode(${fieldValue})", "$textutils.htmlEncode(${descriptor.getText($fieldId)})") 4 | #else 5 | $i18n.getText("admin.workflow.function.update-issue-field.view.1", "", "$textutils.htmlEncode(${descriptor.getText($fieldId)})", "", "$textutils.htmlEncode(${fieldValue})") 6 | #end 7 | #else 8 | $i18n.getText("admin.workflow.function.update-issue-field.view.2", "", "$textutils.htmlEncode(${descriptor.getText($fieldId)})", "", "") 9 | #end -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/SnipetE.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class SnipetE implements ComparingSnipet { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ComparingSnipet#compareObjects(java.lang.Comparable, java.lang.Comparable) 9 | */ 10 | public boolean compareObjects(Comparable> comp1, Comparable comp2) { 11 | if (comp1 == null) { 12 | return (comp2 == null); 13 | } 14 | 15 | if (comp2 == null) { 16 | return false; 17 | } 18 | 19 | return (comp1.compareTo(comp2) == 0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/SnipetGE.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class SnipetGE implements ComparingSnipet { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ComparingSnipet#compareObjects(java.lang.Comparable, java.lang.Comparable) 9 | */ 10 | public boolean compareObjects(Comparable> comp1, Comparable comp2) { 11 | if (comp1 == null) { 12 | return (comp2 == null); 13 | } 14 | 15 | if (comp2 == null) { 16 | return false; 17 | } 18 | 19 | return (comp1.compareTo(comp2) >= 0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/SnipetLE.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class SnipetLE implements ComparingSnipet { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ComparingSnipet#compareObjects(java.lang.Comparable, java.lang.Comparable) 9 | */ 10 | public boolean compareObjects(Comparable> comp1, Comparable comp2) { 11 | if (comp1 == null) { 12 | return (comp2 == null); 13 | } 14 | 15 | if (comp2 == null) { 16 | return false; 17 | } 18 | 19 | return (comp1.compareTo(comp2) <= 0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/SnipetNE.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class SnipetNE implements ComparingSnipet { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ComparingSnipet#compareObjects(java.lang.Comparable, java.lang.Comparable) 9 | */ 10 | public boolean compareObjects(Comparable> comp1, Comparable comp2) { 11 | if (comp1 == null) { 12 | return (comp2 != null); 13 | } 14 | 15 | if (comp2 == null) { 16 | return true; 17 | } 18 | 19 | return (comp1.compareTo(comp2) != 0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/fields/view/view-directions.vm: -------------------------------------------------------------------------------- 1 | #if ($value) 2 | #disable_html_escaping() 3 | 4 | #if ($textOnly || $excelView) 5 | $textutils.br($textutils.htmlEncode($!value.toString(), false)) 6 | #else 7 | 8 |
9 | $!value.toString() 10 | $i18n.getText("view-location.hide_map") 11 |
12 |
$i18n.getText("view-directions.directions_not_found", $!value.toString())
13 |
14 | 15 | #end 16 | #end -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/annotation/TransientVariable.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.annotation; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * This annotation is used for marking fields as container for transient field in validators, 12 | * conditions and post-function. 13 | * 14 | * @author Alexey Abashev 15 | */ 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target(FIELD) 19 | public @interface TransientVariable { 20 | String value() default ""; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/clearfieldvalue-function-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("clearfieldvalue-function-edit.field.label"): 4 | 5 | 6 | 15 |
$i18n.getText("clearfieldvalue-function-edit.field.description") 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/customfields/LocationSelectSearcher.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.customfields; 2 | 3 | import com.atlassian.jira.issue.customfields.searchers.SelectSearcher; 4 | import com.atlassian.jira.util.JiraComponentFactory; 5 | import com.atlassian.jira.util.JiraComponentLocator; 6 | 7 | /** 8 | * Simple wrapper around {@link SelectSearcher} with instances of component factory and locator 9 | * for its constructor. 10 | * 11 | * @author Stephan Bielmann 12 | */ 13 | public class LocationSelectSearcher extends SelectSearcher { 14 | /** 15 | * Simply invokes {@link SelectSearcher#SelectSearcher(com.atlassian.jira.util.ComponentFactory, com.atlassian.jira.util.ComponentLocator)} 16 | * with appropriate arguments. 17 | */ 18 | public LocationSelectSearcher() { 19 | super(JiraComponentFactory.getInstance(), new JiraComponentLocator()); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/fieldvalue-condition-view.vm: -------------------------------------------------------------------------------- 1 | #if (${val-errorMessage}) 2 |
${val-errorMessage}
3 | #else 4 | #if (${val-fieldSelected} && ${val-conditionSelected} && ${val-comparisonTypeSelected}) 5 | #if (!${val-fieldValue.equals("")}) 6 | #set($val = "$textutils.htmlEncode(${val-fieldValue.toString()})") 7 | #else 8 | #set($val = $i18n.getText("fieldvalue-condition-view.null.text")) 9 | #end 10 | $i18n.getText("fieldvalue-condition-view.condition.text","$textutils.htmlEncode(${val-fieldSelected.name})","$i18n.getText(${val-conditionSelected.getDisplayTextKey()})","$val","$i18n.getText(${val-comparisonTypeSelected.valueKey})") 11 | #else 12 | $i18n.getText("fieldvalue-condition-view.wrong_selection") 13 | #end 14 | #end -------------------------------------------------------------------------------- /src/main/resources/templates/jira/fields/view/view-location.vm: -------------------------------------------------------------------------------- 1 | #if ($value) 2 | #disable_html_escaping() 3 | 4 | #if ($textOnly || $excelView) 5 | $textutils.br($textutils.htmlEncode($!value.toString(), false)) 6 | #else 7 | #if($customField.customFieldType.name == "Location Select List") 8 | #set ($value = $textutils.htmlEncode($value.toString())) 9 | #end 10 |
11 | $!value.toString() 12 | $i18n.getText("view-location.hide_map") 13 |
14 |
$i18n.getText("view-location.location_not_found", $!value.toString())
15 |
16 | 17 | #end 18 | #end -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/ConverterDateWithoutTime.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | import java.util.Calendar; 4 | 5 | /** 6 | * @author Alexey Abashev 7 | */ 8 | class ConverterDateWithoutTime extends ConverterDate { 9 | /* (non-Javadoc) 10 | * @see com.googlecode.jsu.helpers.checkers.ConverterDate#getComparable(java.lang.Object) 11 | */ 12 | @Override 13 | public Comparable getComparable(Object object) { 14 | if (object == null || "".equals(object)) { 15 | return null; 16 | } 17 | 18 | Calendar cal = (Calendar) super.getComparable(object); 19 | 20 | cal.set(Calendar.HOUR_OF_DAY, 0); 21 | cal.set(Calendar.MINUTE, 0); 22 | cal.set(Calendar.SECOND, 0); 23 | cal.set(Calendar.MILLISECOND, 0); 24 | 25 | return cal; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/it/com/googlecode/jsu/workflow/function/OtherTest.java: -------------------------------------------------------------------------------- 1 | package it.com.googlecode.jsu.workflow.function; 2 | 3 | import com.atlassian.jira.functest.framework.suite.Category; 4 | import com.atlassian.jira.functest.framework.suite.WebTest; 5 | import com.atlassian.jira.testkit.client.restclient.Issue; 6 | 7 | @WebTest({ Category.FUNC_TEST, Category.REST }) 8 | public class OtherTest extends AbstractTestBase { 9 | private static final String ISSUE_O1_PASS = "TJP-32"; 10 | 11 | //Verifies that text "Removed Status" is on the "Transition Summary", for Status Test that has been removed 12 | public void testO1Pass() throws Exception { 13 | Issue issue = issueClient.get(ISSUE_O1_PASS); 14 | assertNotNull(issue); 15 | 16 | navigation.gotoPage("/browse/TJP-32?page=com.googlecode.jira-suite-utilities:transitions-summary-tabpanel"); 17 | text.assertTextPresent("Removed Status"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/ConverterDate.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | import java.util.Calendar; 4 | 5 | /** 6 | * @author Alexey Abashev 7 | */ 8 | class ConverterDate implements ValueConverter { 9 | /* (non-Javadoc) 10 | * @see com.googlecode.jsu.helpers.checkers.ValueConverter#getComparable(java.lang.Object) 11 | */ 12 | public Comparable getComparable(Object object) { 13 | if (object == null || "".equals(object)) { 14 | return null; 15 | } 16 | 17 | if (object instanceof Calendar) { 18 | Calendar cal = (Calendar) object; 19 | 20 | cal.clear(Calendar.SECOND); 21 | cal.clear(Calendar.MILLISECOND); 22 | 23 | return cal; 24 | } 25 | 26 | throw new UnsupportedOperationException("Unsupported value type " + object); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/ConverterNumber.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | /** 4 | * @author Alexey Abashev 5 | */ 6 | class ConverterNumber implements ValueConverter { 7 | /* (non-Javadoc) 8 | * @see com.googlecode.jsu.helpers.checkers.ValueConverter#getComparable(java.lang.Object) 9 | */ 10 | public Comparable getComparable(Object object) { 11 | if (object == null) { 12 | return null; 13 | } 14 | 15 | Double numberValue; 16 | 17 | if (object instanceof String) { 18 | final String s = (String) object; 19 | 20 | if (s.trim().length() == 0) { 21 | return null; 22 | } 23 | 24 | numberValue = Double.valueOf(s); 25 | } else if (object instanceof Number) { 26 | numberValue = ((Number) object).doubleValue(); 27 | } else { 28 | throw new NumberFormatException(); 29 | } 30 | 31 | return numberValue; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/annotation/AnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Alexey Abashev 10 | */ 11 | public class AnnotationProcessor { 12 | private List visitors = new ArrayList(); 13 | 14 | public void addVisitor(AbstractVisitor visitor) { 15 | this.visitors.add(visitor); 16 | } 17 | 18 | public void processAnnotations(Object object) { 19 | Class clazz = object.getClass(); 20 | 21 | for (Field field : clazz.getDeclaredFields()) { 22 | for (AbstractVisitor visitor : visitors) { 23 | Annotation a = field.getAnnotation(visitor.getAnnotation()); 24 | 25 | if (a != null) { 26 | visitor.visitField(object, field, a); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/NameComparatorEx.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | import java.util.Comparator; 4 | 5 | import com.atlassian.jira.issue.fields.Field; 6 | import com.atlassian.jira.util.I18nHelper; 7 | 8 | /** 9 | * @author Gustavo Martin 10 | * 11 | * This Comparator is used to compare two fields by its internationalized name. 12 | * 13 | */ 14 | public class NameComparatorEx implements Comparator { 15 | private final I18nHelper i18nHelper; 16 | 17 | public NameComparatorEx(I18nHelper i18nHelper) { 18 | this.i18nHelper = i18nHelper; 19 | } 20 | 21 | public int compare(Field o1, Field o2) { 22 | if (o1 == null) 23 | throw new IllegalArgumentException("The first parameter is null"); 24 | if (o2 == null) 25 | throw new IllegalArgumentException("The second parameter is null"); 26 | 27 | String name1 = i18nHelper.getText(o1.getName()); 28 | String name2 = i18nHelper.getText(o2.getName()); 29 | 30 | return name1.compareTo(name2); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/customfields/LocationSelectCFType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.customfields; 2 | 3 | import com.atlassian.jira.issue.customfields.impl.SelectCFType; 4 | import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; 5 | import com.atlassian.jira.issue.customfields.manager.OptionsManager; 6 | import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; 7 | import com.atlassian.jira.issue.fields.rest.json.beans.JiraBaseUrls; 8 | 9 | /** 10 | * Wrapper on Jira SelectCFType for using inside plugins v2. 11 | * 12 | * @author Alexey Abashev 13 | */ 14 | //TODO Can we remove dependencies to jira-core (instead only jira-api) by using interfaces (instead of classes) of referenced custom field types? 15 | public class LocationSelectCFType extends SelectCFType { 16 | public LocationSelectCFType( 17 | CustomFieldValuePersister customFieldValuePersister, 18 | OptionsManager optionsManager, 19 | GenericConfigManager genericConfigManager, 20 | JiraBaseUrls jiraBaseUrls 21 | ) { 22 | super(customFieldValuePersister, optionsManager, genericConfigManager, jiraBaseUrls); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/transitionssummary/issuetabpanel/TransitionSummaryComparator.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.transitionssummary.issuetabpanel; 2 | 3 | import java.sql.Timestamp; 4 | import java.util.Comparator; 5 | 6 | import com.googlecode.jsu.transitionssummary.TransitionSummary; 7 | 8 | /** 9 | * Comparator for comparing trasition summary by last update timestamp. 10 | * @author Alexey Abashev 11 | */ 12 | final class TransitionSummaryComparator implements Comparator { 13 | /* (non-Javadoc) 14 | * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 15 | */ 16 | public int compare(TransitionSummary summary1, TransitionSummary summary2) { 17 | if (summary1 == summary2) { 18 | return 0; 19 | } 20 | 21 | int result = (-1); 22 | 23 | if ((summary1 != null) && (summary1.getLastUpdate() != null)) { 24 | Timestamp ts = summary1.getLastUpdate(); 25 | 26 | if (summary2 != null) { 27 | return ts.compareTo(summary2.getLastUpdate()); 28 | } else { 29 | return ts.compareTo(null); 30 | } 31 | } 32 | 33 | return result; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/transitionssummary/StatusDelegate.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.transitionssummary; 2 | 3 | import com.atlassian.jira.issue.status.Status; 4 | import com.atlassian.jira.util.I18nHelper; 5 | 6 | /** 7 | * Simple delegate to encapsulate the data we need from a status, to be 8 | * independent of API changes, mostly from JIRA 6.0 to 6.1 9 | * 10 | * @author Stephan Bielmann 11 | */ 12 | public class StatusDelegate { 13 | private final Status status; 14 | private final I18nHelper i18nHelper; 15 | 16 | public StatusDelegate(Status status, I18nHelper i18nHelper) { 17 | this.status = status; 18 | this.i18nHelper = i18nHelper; 19 | } 20 | 21 | public String getDescTranslation() { 22 | return status!=null? status.getDescription():i18nHelper.getText("transitions-summary-view.removed-status.description"); 23 | } 24 | 25 | public String getNameTranslation() { 26 | return status!=null? status.getName():i18nHelper.getText("transitions-summary-view.removed-status.name"); 27 | } 28 | 29 | public String getIconUrl() { 30 | return status!=null?status.getIconUrl():null; 31 | } 32 | 33 | public String getId() { 34 | return status!=null?status.getId():"-1"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /licence.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Gustavo Martin 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | Neither the name of the Quadratica SRL nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/UserIsInCustomField-Condition-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("userisincustomfield-condition-edit.user.label"): 4 | 5 | 6 | 14 |
$i18n.getText("userisincustomfield-condition-edit.user.description") 15 | 16 | 17 | 18 | 19 | $i18n.getText("userisincustomfield-condition-edit.field.label"): 20 | 21 | 22 | 29 |
$i18n.getText("userisincustomfield-condition-edit.field.description") 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/regexpfield-validator-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("regexpfield-validator-edit.validate_field.label"): 4 | 5 | 6 | 15 |
$i18n.getText("regexpfield-validator-edit.validate_field.description") 16 | 17 | 18 | 19 | 20 | $i18n.getText("regexpfield-validator-edit.regexp.label"): 21 | 22 | 23 | 24 |
$i18n.getText("regexpfield-validator-edit.regexp.description") 25 | 26 | 27 | 28 | 29 |
$i18n.getText("regexpfield-validator.infobox.text")
30 | 31 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/YesNoType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | /** 4 | * @author Gustavo Martin 5 | * 6 | * This class represents a Yes/No Type. Its values could be YES or NO. 7 | * It will be used in Workflow Condition, Validator, or Function. 8 | * 9 | */ 10 | public class YesNoType { 11 | public static final YesNoType YES = new YesNoType(1, "yes"); 12 | public static final YesNoType NO = new YesNoType(2, "no"); 13 | 14 | private final int id; 15 | private final String value; 16 | 17 | private YesNoType(int id, String value) { 18 | this.id = id; 19 | this.value = value; 20 | } 21 | 22 | public Integer getId() { 23 | return id; 24 | } 25 | 26 | public String getValue() { 27 | return value; 28 | } 29 | 30 | /* (non-Javadoc) 31 | * @see java.lang.Object#hashCode() 32 | */ 33 | @Override 34 | public int hashCode() { 35 | final int prime = 31; 36 | int result = 1; 37 | result = prime * result + id; 38 | return result; 39 | } 40 | 41 | /* (non-Javadoc) 42 | * @see java.lang.Object#equals(java.lang.Object) 43 | */ 44 | @Override 45 | public boolean equals(Object obj) { 46 | if (this == obj) 47 | return true; 48 | if (obj == null) 49 | return false; 50 | if (!(obj instanceof YesNoType)) 51 | return false; 52 | YesNoType other = (YesNoType) obj; 53 | return id == other.id; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/CheckerCompositeFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * @author Alexey Abashev 8 | */ 9 | public class CheckerCompositeFactory { 10 | private static final Logger log = LoggerFactory.getLogger(CheckerCompositeFactory.class); 11 | 12 | /** 13 | * Create composite for checking values. 14 | */ 15 | public CheckerComposite getComposite(String converterClass, String snipetClass) { 16 | ComparingSnipet snipet = getInstance(snipetClass); 17 | 18 | if (snipet == null) { 19 | return null; 20 | } 21 | 22 | ValueConverter converter = getInstance(converterClass); 23 | 24 | if (converter == null) { 25 | return null; 26 | } 27 | 28 | return (new CheckerComposite(converter, snipet)); 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | private T getInstance(String className) { 33 | T instance = null; 34 | 35 | try { 36 | instance = (T) Class.forName(className).newInstance(); 37 | } catch (InstantiationException e) { 38 | log.error("Unable to initialize class [" + className + "]", e); 39 | } catch (IllegalAccessException e) { 40 | log.error("Unable to initialize class [" + className + "]", e); 41 | } catch (ClassNotFoundException e) { 42 | log.error("Unable to initialize class [" + className + "]", e); 43 | } 44 | 45 | return instance; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/customfields/DirectionsCFType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.customfields; 2 | 3 | import com.atlassian.jira.component.ComponentAccessor; 4 | import com.atlassian.jira.issue.customfields.impl.FieldValidationException; 5 | import com.atlassian.jira.issue.customfields.impl.RenderableTextCFType; 6 | import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; 7 | import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; 8 | import com.atlassian.jira.util.I18nHelper; 9 | 10 | /** 11 | * Google Directions custom field, uses => as separator between origin 12 | * and destination location. 13 | */ 14 | public class DirectionsCFType extends RenderableTextCFType { 15 | protected final I18nHelper.BeanFactory beanFactory; 16 | 17 | public DirectionsCFType(CustomFieldValuePersister customFieldValuePersister, 18 | GenericConfigManager genericConfigManager, 19 | final I18nHelper.BeanFactory beanFactory) { 20 | super(customFieldValuePersister, genericConfigManager); 21 | this.beanFactory = beanFactory; 22 | } 23 | 24 | public String getSingularObjectFromString(final String string) throws FieldValidationException { 25 | if(string!=null && !string.contains("=>")) { 26 | I18nHelper i18nh = this.beanFactory.getInstance( 27 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()); 28 | String message = i18nh.getText("edit-directions.invalid_directions"); 29 | throw new FieldValidationException(message); 30 | } 31 | return string; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/FieldContainer.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import com.atlassian.jira.issue.fields.CustomField; 4 | import com.atlassian.jira.issue.fields.Field; 5 | 6 | /** 7 | * Simple wrapper around {@link Field} class which provides a type property 8 | * and replaces the original name to "name + ( TYPE )". 9 | */ 10 | public class FieldContainer implements Field { 11 | 12 | private final String name; 13 | private final String type; 14 | protected final Field field; 15 | 16 | public FieldContainer(final Field field) { 17 | this.field = field; 18 | if(field instanceof CustomField) { 19 | CustomField cf = (CustomField)field; 20 | this.type = cf.getCustomFieldType().getName(); 21 | this.name = field.getName() + " (" + this.type + ")"; 22 | } else { 23 | this.type = field.getId(); 24 | this.name = field.getName(); 25 | } 26 | } 27 | 28 | public String getId() { 29 | return field.getId(); 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public String getNameKey() { 37 | return field.getNameKey(); 38 | } 39 | 40 | public String toString() { 41 | return field.toString(); 42 | } 43 | 44 | public int hashCode() { 45 | return field.hashCode(); 46 | } 47 | 48 | public boolean equals(Object o) { 49 | return field.equals(o); 50 | } 51 | 52 | public int compareTo(Object o) { 53 | return field.compareTo(o); 54 | } 55 | 56 | public String getType() { 57 | return type; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/ComparisonType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | /** 4 | * @author Gustavo Martin 5 | * 6 | * This class represents a Comparison Type. This will be used in Workflow Condition, Validator, or Function. 7 | * 8 | */ 9 | public class ComparisonType { 10 | private final int id; 11 | private final String valueKey; 12 | private final String mnemonic; 13 | 14 | public ComparisonType(int id, String valueKey, String mnemonic) { 15 | this.id = id; 16 | this.valueKey = valueKey; 17 | this.mnemonic = mnemonic; 18 | } 19 | 20 | /** 21 | * Get comparision id. 22 | */ 23 | public Integer getId() { 24 | return id; 25 | } 26 | 27 | /** 28 | * Get name key of comparision. 29 | */ 30 | public String getValueKey() { 31 | return valueKey; 32 | } 33 | 34 | /** 35 | * @return the mnemonic 36 | */ 37 | public String getMnemonic() { 38 | return mnemonic; 39 | } 40 | 41 | /* (non-Javadoc) 42 | * @see java.lang.Object#hashCode() 43 | */ 44 | @Override 45 | public int hashCode() { 46 | final int prime = 31; 47 | int result = 1; 48 | result = prime * result + id; 49 | return result; 50 | } 51 | 52 | /* (non-Javadoc) 53 | * @see java.lang.Object#equals(java.lang.Object) 54 | */ 55 | @Override 56 | public boolean equals(Object obj) { 57 | if (this == obj) 58 | return true; 59 | if (obj == null) 60 | return false; 61 | if (!(obj instanceof ComparisonType)) 62 | return false; 63 | ComparisonType other = (ComparisonType) obj; 64 | return id == other.id; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/customfields/LocationTextCFType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.customfields; 2 | 3 | import com.atlassian.jira.component.ComponentAccessor; 4 | import com.atlassian.jira.issue.customfields.impl.FieldValidationException; 5 | import com.atlassian.jira.issue.customfields.impl.RenderableTextCFType; 6 | import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; 7 | import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; 8 | import com.atlassian.jira.util.I18nHelper; 9 | 10 | /** 11 | * Wrapper on Jira RenderableTextCFType for using inside plugins v2. 12 | * 13 | * @author Alexey Abashev 14 | */ 15 | //TODO Can we remove dependencies to jira-core (instead only jira-api) by using interfaces (instead of classes) of referenced custom field types? 16 | public class LocationTextCFType extends RenderableTextCFType { 17 | protected final I18nHelper.BeanFactory beanFactory; 18 | 19 | public LocationTextCFType( 20 | CustomFieldValuePersister customFieldValuePersister, 21 | GenericConfigManager genericConfigManager, 22 | final I18nHelper.BeanFactory beanFactory 23 | ) { 24 | super(customFieldValuePersister, genericConfigManager); 25 | this.beanFactory = beanFactory; 26 | } 27 | 28 | public String getSingularObjectFromString(final String string) throws FieldValidationException { 29 | if(string!=null && string.contains("=>")) { 30 | I18nHelper i18nh = this.beanFactory.getInstance( 31 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()); 32 | String message = i18nh.getText("edit-location.invalid_location"); 33 | throw new FieldValidationException(message); 34 | } 35 | return string; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/windowsdate-validator-input.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("windowsdate-validator-edit.date_to_validate"): 4 | 5 | 6 | 11 |
$i18n.getText("windowsdate-validator-edit.choose_date_field") 12 | 13 | 14 | 15 | 16 | $i18n.getText("windowsdate-validator-edit.day_frame.label"): 17 | 18 | 19 | 20 |
$i18n.getText("windowsdate-validator-edit.day_frame.description") 21 | 22 | 23 | 24 | 25 | $i18n.getText("windowsdate-validator-edit.in_relation_to.label"): 26 | 27 | 28 | 33 |
$i18n.getText("windowsdate-validator-edit.in_relation_to.description") 34 | 35 | 36 | 37 | 38 |
39 | $i18n.getText("windowsdate-validator-edit.infobox.text") 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/ConditionType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | /** 4 | * @author Gustavo Martin 5 | * 6 | * This class represents a Condition Type. This will be used in Workflow Condition, Validator, or Function. 7 | * 8 | */ 9 | public class ConditionType { 10 | private final int id; 11 | private final String shortText; 12 | private final String displayTextKey; 13 | private final String mnemonic; 14 | 15 | public ConditionType( 16 | int id, String shortText, String displayTextKey, String mnemonic 17 | ) { 18 | this.id = id; 19 | this.shortText = shortText; 20 | this.displayTextKey = displayTextKey; 21 | this.mnemonic = mnemonic; 22 | } 23 | 24 | public Integer getId() { 25 | return id; 26 | } 27 | 28 | public String getValue() { 29 | return shortText; 30 | } 31 | 32 | /** 33 | * @return the mnemonic 34 | */ 35 | public String getMnemonic() { 36 | return mnemonic; 37 | } 38 | 39 | /** 40 | * Get display text key for condition. 41 | */ 42 | public String getDisplayTextKey() { 43 | return displayTextKey; 44 | } 45 | 46 | /* (non-Javadoc) 47 | * @see java.lang.Object#hashCode() 48 | */ 49 | @Override 50 | public int hashCode() { 51 | final int prime = 31; 52 | int result = 1; 53 | result = prime * result + id; 54 | return result; 55 | } 56 | 57 | /* (non-Javadoc) 58 | * @see java.lang.Object#equals(java.lang.Object) 59 | */ 60 | @Override 61 | public boolean equals(Object obj) { 62 | if (this == obj) 63 | return true; 64 | if (obj == null) 65 | return false; 66 | if (!(obj instanceof ConditionType)) 67 | return false; 68 | ConditionType other = (ConditionType) obj; 69 | return id == other.id; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/resources/com/googlecode/jsu/i18n_fr.properties: -------------------------------------------------------------------------------- 1 | # atlassian-plugin.xml texts 2 | issue-tabpanel.transitions-summary-tabpanel.description=Pr\u00e9sente le temps pass\u00e9 entre chaque Transtion effectu\u00e9e, ainsi que le nombre de leur ocurrence. 3 | 4 | # velocity templates and java classes texts 5 | transitions-summary-view.name=Transitions 6 | transitions-summary-view.transition=Transition 7 | transitions-summary-view.timespent=Temps \u00e9coul\u00e9 8 | transitions-summary-view.occurence_nb=Nb. 9 | transitions-summary-view.last_executer=Dernier Intervenant 10 | transitions-summary-view.last_occurence_date=Derni\u00e8re ex\u00e9cution 11 | transitions-summary-view.not_yet_executed=Aucune transition au sein du workflow n''a eu lieu. 12 | 13 | datecompare-validator-view.is_required={0} est requis. 14 | # 0: datefield1, 1: comparison, 2: datefield2, 3: error message 15 | datecompare-validator-view.is_not={0} n''est pas {1} {2} {3} 16 | datecompare-validator-view.not_a_date={0} n''est pas une valeur de date correcte ({1}) 17 | 18 | conditiontype.greater_than=sup\u00e9rieur que 19 | conditiontype.greater_than_or_equal_to=sup\u00e9rieur ou \u00e9gal \u00e0 20 | conditiontype.equal_to=\u00e9gal \u00e0 21 | conditiontype.less_than_or_equal_to=inf\u00e9rieur ou \u00e9gal \u00e0 22 | contitiontype.less_than=moins que 23 | conditiontype.not_equal_to=in\u00e9gal \u00e0 24 | comparisontype.string=Texte 25 | comparisontype.number=Nombre 26 | comparisontype.date_with_time=Date avec l''heure 27 | comparisontype.date_without_time=Date sans l''heure 28 | 29 | # 0: field 30 | fieldsrequired-validator-view.is_required={0} est requis. 31 | # 0: field 32 | fieldsrequired-validator-view.is_required_not_present={0} est requis, mais n''est pas visible 33 | 34 | # 0: date2, 1: date frame 35 | windowsdate-validator-view.between_and=( Entre {0} et {1} ) 36 | # 0: datefield1, 2: datefield2, 2: days, 3: between dates message 37 | windowsdate-validator-view.not_within={0} est plus de {2} jours apres {1}. {3} 38 | # 0: field 39 | windowsdate-validator-view.is_required={0} est requis. -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/copyvaluefromfield-function-input.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("copyvaluefromfield-function.source.label"): 4 | 5 | 6 | 11 |
$i18n.getText("copyvaluefromfield-function.source.description") 12 | 13 | 14 | 15 | 16 | $i18n.getText("copyvaluefromfield-function.destination.label"): 17 | 18 | 19 | 24 |
$i18n.getText("copyvaluefromfield-function.destination.description") 25 | 26 | 27 | 28 | $i18n.getText("copyvaluefromfield-function.option.label"): 29 | 30 | $i18n.getText("copyvaluefromfield-function.optionsame.label")
31 | $i18n.getText("copyvaluefromfield-function.optionparent.label")
32 | 33 | 34 | 35 | 36 |
$i18n.getText("copyvaluefromfield-function.option.description")
37 | 38 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/util/ValidatorErrorsBuilder.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.atlassian.jira.issue.fields.Field; 7 | import com.opensymphony.workflow.InvalidInputException; 8 | 9 | /** 10 | * Container for storing and processing validator errors. 11 | * 12 | * @author Alexey Abashev 13 | */ 14 | public class ValidatorErrorsBuilder { 15 | private List fields = new ArrayList(); 16 | private List messages = new ArrayList(); 17 | private boolean forScreen; 18 | 19 | /** 20 | * @param forScreen is we throwing exception for screen 21 | */ 22 | public ValidatorErrorsBuilder(boolean forScreen) { 23 | this.forScreen = forScreen; 24 | } 25 | 26 | public void addError(Field field, String message) { 27 | this.fields.add(field); 28 | this.messages.add(message); 29 | } 30 | 31 | public void addError(String message) { 32 | this.addError(null, message); 33 | } 34 | 35 | public void process() throws InvalidInputException { 36 | if (this.fields.size() == 0) { 37 | return; 38 | } 39 | 40 | if(fields.size()==1) { 41 | Field f = this.fields.get(0); 42 | String m = this.messages.get(0); 43 | 44 | if(f == null || ! forScreen) { 45 | throw new InvalidInputException(m); 46 | } else { 47 | throw new InvalidInputException(f.getId(),m); 48 | } 49 | } else { 50 | InvalidInputException e = new InvalidInputException(); 51 | 52 | for (int i = 0; i < fields.size(); i++) { 53 | Field f = this.fields.get(i); 54 | String m = this.messages.get(i); 55 | 56 | if (f == null || ! forScreen) { 57 | e.addError(m); 58 | } else { 59 | e.addError(f.getId(), m); 60 | } 61 | } 62 | 63 | throw e; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/CheckerComposite.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.googlecode.jsu.helpers.ConditionChecker; 7 | 8 | /** 9 | * @author Alexey Abashev 10 | */ 11 | class CheckerComposite implements ConditionChecker { 12 | private final Logger log = LoggerFactory.getLogger(CheckerComposite.class); 13 | 14 | private final ValueConverter valueConverter; 15 | private final ComparingSnipet comparingSnipet; 16 | 17 | public CheckerComposite( 18 | ValueConverter valueConverter, ComparingSnipet comparingSnipet 19 | ) { 20 | this.valueConverter = valueConverter; 21 | this.comparingSnipet = comparingSnipet; 22 | } 23 | 24 | /* (non-Javadoc) 25 | * @see com.googlecode.jsu.helpers.ConditionChecker#checkValues(java.lang.Object, java.lang.Object) 26 | */ 27 | @SuppressWarnings("unchecked") 28 | public final boolean checkValues(Object value1, Object value2) { 29 | final Comparable> comp1; 30 | final Comparable comp2; 31 | 32 | try { 33 | comp1 = (Comparable>) valueConverter.getComparable(value1); 34 | } catch (NumberFormatException e) { 35 | log.warn("Wrong number format at [" + value1 + "]"); 36 | 37 | return false; 38 | } catch (Exception e) { 39 | log.warn("Unable to get comparable from [" + value1 + "]", e); 40 | 41 | return false; 42 | } 43 | 44 | try { 45 | comp2 = valueConverter.getComparable(value2); 46 | } catch (NumberFormatException e) { 47 | log.warn("Wrong number format at [" + value2 + "]"); 48 | 49 | return false; 50 | } catch (Exception e) { 51 | log.warn("Unable to get comparable from [" + value2 + "]", e); 52 | 53 | return false; 54 | } 55 | 56 | boolean result = comparingSnipet.compareObjects(comp1, comp2); 57 | 58 | if (log.isDebugEnabled()) { 59 | log.debug("Compare values [" + comp1 + "] and [" + comp2 + "] with result [" + result + "]"); 60 | } 61 | 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 0a7cd9fe49faf3c8100a16563450568f40b29b5d version_0_7_2 2 | 26a4a4fb5a429466ddfc55dcf41a59f270023961 version_0_7_3 3 | 79dcb45a74d981d95b891cea2b739dc3ebc14707 version_0_7_4 4 | fc1f726673301d0949ad55b2dbdf831844484f3b version_0_7_5 5 | 0df20fa0665e2a3a854dcbacc6a90d3ef2767d94 version_0_7_6 6 | 3f4a1aec12f13a9e3da590676bd18ca0b9f8c3fe version_0_7_7 7 | 286ae7f63fd19afb89223b598c4410684e5de7e1 version_0_7_8 8 | 996e8ef08680708d2a6d79cabb10b29108680c56 version_0_7_9 9 | 3b576afe255675f9689faeadbb3545d07c536aab version_0_7_10 10 | f137f4a87ea2592cc719d4d38c5609501d071946 version_0_7_11 11 | 89bec19bfcac25d5371c0605994728c3d08dbdd4 version_0_7_12 12 | b9c4d488be7087ed7c843de87bdec22bb00b5fab version_0_7_13 13 | 36046f97abe93ae73ec60ecd6bf6d059208a245d version_0_7_14 14 | f024d1f9a03dc8498d35db672de3c5b4ccedd939 version_0_7_15 15 | f024d1f9a03dc8498d35db672de3c5b4ccedd939 version_0_7_15 16 | 0000000000000000000000000000000000000000 version_0_7_15 17 | 0000000000000000000000000000000000000000 version_0_7_15 18 | 2645a928fdd15484b1a62a4667ce2346749326d7 version_0_7_15 19 | 2645a928fdd15484b1a62a4667ce2346749326d7 version_0_7_15 20 | 0000000000000000000000000000000000000000 version_0_7_15 21 | 742af27d414ffbf25ff2c172b6a70937ffa32b43 version_0_7_15_1 22 | 8a8a5b1cfa5757c3b8834d96b3a9cfdc12aae0dc version_0_7_16 23 | 8a8a5b1cfa5757c3b8834d96b3a9cfdc12aae0dc version_0_7_16 24 | 0000000000000000000000000000000000000000 version_0_7_16 25 | 9037034dcbf90c3ed132e2a5cc17bf148a9cafe4 version_0_7_16_1 26 | 9037034dcbf90c3ed132e2a5cc17bf148a9cafe4 version_0_7_16_1 27 | 0000000000000000000000000000000000000000 version_0_7_16_1 28 | 0000000000000000000000000000000000000000 version_0_7_16 29 | 43c6bac3fa0f52d971dd284094c4b1df86e7afbe version_0_7_16 30 | 571672286d251c518c7a746fafa1ad287ce4080c version_0_7_16_3 31 | f627393dec88cecebb3fee36ac116e864ea88eb8 0_7_16 32 | 43c6bac3fa0f52d971dd284094c4b1df86e7afbe version_0_7_16 33 | 0000000000000000000000000000000000000000 version_0_7_16 34 | f627393dec88cecebb3fee36ac116e864ea88eb8 0_7_16 35 | 0000000000000000000000000000000000000000 0_7_16 36 | 571672286d251c518c7a746fafa1ad287ce4080c version_0_7_16_3 37 | 0000000000000000000000000000000000000000 version_0_7_16_3 38 | 0000000000000000000000000000000000000000 version_0_7_16 39 | b30ec5c479d5692da4c9d04ef646ac5a8df8b828 version_0_7_16 40 | 0310d81f765a1dbeb386d681adfa59c6d69d3629 version_0_7_16_5 41 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/windowsdate-validator-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("windowsdate-validator-edit.date_to_validate"): 4 | 5 | 6 | 15 |
$i18n.getText("windowsdate-validator-edit.choose_date_field") 16 | 17 | 18 | 19 | 20 | $i18n.getText("windowsdate-validator-edit.day_frame.label"): 21 | 22 | 23 | #if (${val-windowsDays}) 24 | 25 | #else 26 | 27 | #end 28 |
$i18n.getText("windowsdate-validator-edit.day_frame.description") 29 | 30 | 31 | 32 | 33 | $i18n.getText("windowsdate-validator-edit.in_relation_to.label"): 34 | 35 | 36 | 45 |
$i18n.getText("windowsdate-validator-edit.in_relation_to.description") 46 | 47 | 48 | 49 | 50 |
51 | $i18n.getText("windowsdate-validator-edit.infobox.text") 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/datecompare-validator-input.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("datecompare-validator.date.label"): 4 | 5 | 6 | 11 |
$i18n.getText("datecompare-validator.date.description") 12 | 13 | 14 | 15 | 16 | $i18n.getText("datecompare-validator.condition.label"): 17 | 18 | 19 | 24 |
$i18n.getText("datecompare-validator.condition.description") 25 | 26 | 27 | 28 | 29 | $i18n.getText("datecompare-validator.compare.label"): 30 | 31 | 32 | 37 |
$i18n.getText("datecompare-validator.compare.description") 38 | 39 | 40 | 41 | 42 | $i18n.getText("datecompare-validator.timepart.label"): 43 | 44 | 45 | 50 |
$i18n.getText("datecompare-validator.timepart.description") 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/dateexpressioncompare-validator-input.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("dateexpressioncompare-validator.date.label"): 4 | 5 | 6 | 11 |
$i18n.getText("dateexpressioncompare-validator.date.description") 12 | 13 | 14 | 15 | 16 | $i18n.getText("datecompare-validator.condition.label"): 17 | 18 | 19 | 24 |
$i18n.getText("datecompare-validator.condition.description") 25 | 26 | 27 | 28 | 29 | $i18n.getText("datecompare-validator.compare.label"): 30 | 31 | 32 | 33 |
$i18n.getText("dateexpressioncompare-validator.compare.description") 34 | 35 | 36 | 37 | 38 | $i18n.getText("datecompare-validator.timepart.label"): 39 | 40 | 41 | 46 |
$i18n.getText("datecompare-validator.timepart.description") 47 | 48 | 49 | 50 | 51 |
$i18n.getText("dateexpressioncompare-validator.infobox.text")
52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/transitionssummary/Transition.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.transitionssummary; 2 | 3 | import java.sql.Timestamp; 4 | 5 | import com.atlassian.jira.component.ComponentAccessor; 6 | import com.atlassian.jira.util.I18nHelper; 7 | 8 | /** 9 | * @author Gustavo Martin 10 | * 11 | * This class represent a Workflow Transition. And it will keep, temporarily, all its values. 12 | * It obtains the values, reading the Change History. 13 | * 14 | */ 15 | public class Transition { 16 | private final I18nHelper i18nHelper; 17 | 18 | private String changedBy; 19 | private Timestamp changedAt; 20 | private StatusDelegate fromStatus; 21 | private StatusDelegate toStatus; 22 | private Timestamp startAt; 23 | private Long duration; 24 | 25 | public Transition (final I18nHelper i18nHelper) { 26 | this.startAt = null; 27 | this.i18nHelper = i18nHelper; 28 | } 29 | 30 | public Long getDurationInMillis(){ 31 | return this.duration; 32 | } 33 | 34 | private void setDuration(){ 35 | Long retVal = new Long("-1"); 36 | 37 | // It calculates the duration since the transition began until the next one is executed. 38 | if (this.startAt != null) { 39 | retVal = this.changedAt.getTime() - this.startAt.getTime(); 40 | } 41 | 42 | this.duration = retVal; 43 | } 44 | 45 | public Timestamp getChangedAt() { 46 | return changedAt; 47 | } 48 | public void setChangedAt(Timestamp changedAt) { 49 | this.changedAt = changedAt; 50 | } 51 | public String getChangedBy() { 52 | return changedBy; 53 | } 54 | public void setChangedBy(String changedBy) { 55 | this.changedBy = changedBy; 56 | } 57 | public StatusDelegate getFromStatus() { 58 | return fromStatus; 59 | } 60 | public void setFromStatus(Long fromStatus) { 61 | this.fromStatus = new StatusDelegate(ComponentAccessor.getConstantsManager().getStatusObject(String.valueOf(fromStatus)),i18nHelper); 62 | } 63 | public StatusDelegate getToStatus() { 64 | return toStatus; 65 | } 66 | public void setToStatus(Long toStatus) { 67 | this.toStatus = new StatusDelegate(ComponentAccessor.getConstantsManager().getStatusObject(String.valueOf(toStatus)),i18nHelper); 68 | } 69 | public Timestamp getStartAt() { 70 | return startAt; 71 | } 72 | public void setStartAt(Timestamp startAt) { 73 | this.startAt = startAt; 74 | setDuration(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/copyvaluefromfield-function-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("copyvaluefromfield-function.source.label"): 4 | 5 | 6 | 15 |
$i18n.getText("copyvaluefromfield-function.source.description") 16 | 17 | 18 | 19 | 20 | $i18n.getText("copyvaluefromfield-function.destination.label"): 21 | 22 | 23 | 32 |
$i18n.getText("copyvaluefromfield-function.destination.description") 33 | 34 | 35 | 36 | $i18n.getText("copyvaluefromfield-function.option.label"): 37 | 38 | $i18n.getText("copyvaluefromfield-function.optionsame.label")
43 | $i18n.getText("copyvaluefromfield-function.optionparent.label")
48 | 49 | 50 | 51 | 52 |
$i18n.getText("copyvaluefromfield-function.option.description")
53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/issuetabpanel/transitionssummary/transitions-summary-view.vm: -------------------------------------------------------------------------------- 1 | #disable_html_escaping() 2 | #macro(statusWithIcon $status) 3 | #set ($iconurl = $status.getIconUrl()) 4 | 5 | 6 | #if ($iconurl.startsWith('http://') || $iconurl.startsWith('https://')) 7 | $textutils.htmlEncode($status.getNameTranslation()) 8 | #else 9 | $textutils.htmlEncode($status.getNameTranslation()) 10 | #end 11 | 12 | 13 | $textutils.htmlEncode(${status.nameTranslation}) 14 | 15 | #end 16 | 17 | #macro(headerCell $caption $width) 18 | $i18n.getText($caption) 19 | #end 20 | 21 | 22 | 23 | #headerCell("transitions-summary-view.transition" "34%") 24 | #headerCell("transitions-summary-view.timespent" "15%") 25 | #headerCell("transitions-summary-view.occurence_nb" "15%") 26 | #headerCell("transitions-summary-view.last_executer" "18%") 27 | #headerCell("transitions-summary-view.last_occurence_date" "18%") 28 | 29 | 30 | #foreach ($tran in ${action.getTransitions()}) 31 | 32 | 43 | 46 | 49 | 56 | 59 | 60 | #end 61 |
33 | 34 | 35 | #statusWithIcon ($tran.fromStatus) 36 | 39 | #statusWithIcon ($tran.toStatus) 40 | 41 |
37 | 38 |
42 |
44 | $textutils.htmlEncode(${tran.durationAsString}) 45 | 47 | ${tran.timesToTransition} 48 | 50 | #if (${action.isUserExists(${tran.lastUpdater})}) 51 | #authorlinkname(${tran.lastUpdater}) 52 | #else 53 | $textutils.htmlEncode(${tran.lastUpdater}) 54 | #end 55 | 57 | $textutils.htmlEncode(${tran.lastUpdateAsString}) 58 |
62 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/FormattableDuration.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | /** 4 | * Simple wrapper around a duration in milliseconds. Used for {@link com.googlecode.jsu.customfields.TimeInSourceStatusCFType} 5 | * and {@link com.googlecode.jsu.transitionssummary.TransitionSummary}. 6 | */ 7 | public class FormattableDuration { 8 | private final long millis; 9 | 10 | public FormattableDuration(final long millis) { 11 | this.millis = millis; 12 | } 13 | 14 | public FormattableDuration(final String text) { 15 | if(text!=null) { 16 | millis = Long.parseLong(text); 17 | } else { 18 | millis = 0; 19 | } 20 | } 21 | 22 | public long getMillis() { 23 | return millis; 24 | } 25 | 26 | public String getFormatted() { 27 | return getFormatted(millis); 28 | } 29 | 30 | /** 31 | * @return a nice String format of the duration. 32 | */ 33 | public static String getFormatted(long duration) { 34 | String retVal; 35 | 36 | if(duration!=0){ 37 | Long days = duration / 86400000; 38 | Long restDay = duration % 86400000; 39 | 40 | Long hours = restDay / 3600000; 41 | Long resthours = restDay % 3600000; 42 | 43 | Long minutes = resthours / 60000; 44 | Long restMinutes = resthours % 60000; 45 | 46 | Long seconds = restMinutes / 1000; 47 | 48 | // If it has been days, it does not have sense to show the seconds. 49 | retVal = days.equals(new Long("0"))?"":String.valueOf(days) + "d "; 50 | retVal = retVal + (hours.equals(new Long("0"))?"":String.valueOf(hours) + "h "); 51 | retVal = retVal + (minutes.equals(new Long("0"))?"":String.valueOf(minutes) + "m "); 52 | if((days.equals(new Long("0"))) && (hours.equals(new Long("0")))){ 53 | retVal = retVal + (seconds.equals(new Long("0"))?"":String.valueOf(seconds) + "s"); 54 | } 55 | 56 | }else{ 57 | retVal = "0s"; 58 | } 59 | 60 | return retVal; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return Long.toString(millis); 66 | } 67 | 68 | @Override 69 | public boolean equals(Object o) { 70 | if (this == o) return true; 71 | if (o == null || getClass() != o.getClass()) return false; 72 | 73 | FormattableDuration that = (FormattableDuration) o; 74 | 75 | if (millis != that.millis) return false; 76 | 77 | return true; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return (int) (millis ^ (millis >>> 32)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/com/googlecode/jsu/i18n_ru.properties: -------------------------------------------------------------------------------- 1 | # atlassian-plugin.xml texts 2 | customfield-type.locationtextfield.name=\u0410\u0434\u0440\u0435\u0441 3 | customfield-type.locationtextfield.description=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u043E\u0435 \u043F\u043E\u043B\u0435 \u0434\u043B\u044F \u0432\u0432\u043E\u0434\u0430 \u0430\u0434\u0440\u0435\u0441\u0430 \u0438 \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u0435\u0433\u043E \u043D\u0430 \u043A\u0430\u0440\u0442\u0435 Google. 4 | 5 | customfield-type.locationselect.name=\u0421\u043F\u0438\u0441\u043E\u043A \u0430\u0434\u0440\u0435\u0441\u043E\u0432 6 | customfield-type.locationselect.description=\u041F\u043E\u043B\u0435 \u0434\u043B\u044F \u0432\u0432\u043E\u0434\u0430 \u0441\u043F\u0438\u0441\u043A\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u0432 \u0438 \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u0438\u0445 \u043D\u0430 \u043A\u0430\u0440\u0442\u0435 Google. 7 | 8 | issue-tabpanel.transitions-summary-tabpanel.description=\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u0438 \u0431\u044B\u043B\u043E \u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E \u043D\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u0432 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0435. 9 | 10 | # velocity templates and java classes texts 11 | view-location.hide_map=\u0421\u043A\u0440\u044B\u0442\u044C \u043A\u0430\u0440\u0442\u0443 12 | view-location.location_not_found=\u0410\u0434\u0440\u0435\u0441 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D 13 | 14 | transitions-summary-view.last_executer=\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C 15 | transitions-summary-view.last_occurence_date=\u0414\u0430\u0442\u0430 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 16 | transitions-summary-view.name=\u041F\u0435\u0440\u0435\u0445\u043E\u0434\u044B 17 | transitions-summary-view.occurence_nb=\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u043E\u0432 18 | transitions-summary-view.timespent=\u041F\u0440\u043E\u0431\u044B\u043B \u0432 \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u043C \u0441\u0442\u0430\u0442\u0443\u0441\u0435 19 | transitions-summary-view.transition=\u041F\u0435\u0440\u0435\u0445\u043E\u0434 20 | transitions-summary-view.not_yet_executed=\u041F\u0435\u0440\u0435\u0445\u043E\u0434\u043E\u0432 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E. -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/annotation/MapFieldProcessor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.Method; 7 | import java.util.Map; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * @author Alexey Abashev 14 | */ 15 | public class MapFieldProcessor extends AbstractVisitor { 16 | private final Logger log = LoggerFactory.getLogger(MapFieldProcessor.class); 17 | 18 | private final Class annotation; 19 | private final Map values; 20 | 21 | public MapFieldProcessor(Class annotation, Map values) { 22 | super(); 23 | 24 | this.annotation = annotation; 25 | this.values = values; 26 | } 27 | 28 | public Class getAnnotation() { 29 | return annotation; 30 | } 31 | 32 | /* (non-Javadoc) 33 | * @see com.googlecode.jsu.annotation.AbstractVisitor#visitField(java.lang.reflect.Field) 34 | */ 35 | public void visitField(Object source, Field field, Annotation sourceAnnon) { 36 | String fieldName = getAnnotationValue(sourceAnnon); 37 | 38 | if ((fieldName == null) || ("".equals(fieldName))) { 39 | fieldName = field.getName(); 40 | } 41 | 42 | try { 43 | boolean access = field.isAccessible(); 44 | 45 | field.setAccessible(true); 46 | field.set(source, values.get(fieldName)); 47 | field.setAccessible(access); 48 | } catch (IllegalArgumentException e) { 49 | log.error("Unable to set class field - " + fieldName, e); 50 | } catch (IllegalAccessException e) { 51 | log.error("Unable to set class field - " + fieldName, e); 52 | } 53 | } 54 | 55 | protected String getAnnotationValue(Annotation annotation) { 56 | String result = null; 57 | 58 | try { 59 | Method valueMethod = annotation.getClass().getDeclaredMethod("value", new Class[] {}); 60 | 61 | result = (String) valueMethod.invoke(annotation, new Object[] {}); 62 | } catch (SecurityException e) { 63 | // Everything ok 64 | } catch (NoSuchMethodException e) { 65 | // Everything ok 66 | } catch (IllegalArgumentException e) { 67 | // Everything ok 68 | } catch (IllegalAccessException e) { 69 | // Everything ok 70 | } catch (InvocationTargetException e) { 71 | // Everything ok 72 | } 73 | 74 | return result; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/transitionssummary/issuetabpanel/TransitionSummaryAction.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.transitionssummary.issuetabpanel; 2 | 3 | import com.atlassian.jira.plugin.issuetabpanel.AbstractIssueAction; 4 | import com.atlassian.jira.plugin.issuetabpanel.IssueTabPanelModuleDescriptor; 5 | import com.atlassian.jira.user.util.UserManager; 6 | import com.atlassian.jira.web.action.JiraWebActionSupport; 7 | import com.googlecode.jsu.transitionssummary.TransitionSummary; 8 | import org.ofbiz.core.util.UtilMisc; 9 | 10 | import java.sql.Timestamp; 11 | import java.util.Calendar; 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author Gustavo Martin 18 | * 19 | * This is a valid Action, that it allows to visualize the Transition Summaries. 20 | */ 21 | public class TransitionSummaryAction extends AbstractIssueAction { 22 | protected final IssueTabPanelModuleDescriptor descriptor; 23 | protected List tranSummaries; 24 | protected Timestamp timePerformed; 25 | protected final UserManager userManager; 26 | 27 | /** 28 | * @param tranSummaries List containing TransitionSummary objects. 29 | */ 30 | public TransitionSummaryAction(List tranSummaries, IssueTabPanelModuleDescriptor descriptor, UserManager userManager){ 31 | super(descriptor); 32 | 33 | this.tranSummaries = tranSummaries; 34 | this.descriptor = descriptor; 35 | this.timePerformed = new Timestamp(Calendar.getInstance().getTimeInMillis()); 36 | this.userManager = userManager; 37 | } 38 | 39 | /** 40 | * @return a List 41 | * 42 | * It allows Velocity to obtain the List of Transition Summaries. 43 | */ 44 | public List getTransitions() { 45 | return tranSummaries; 46 | } 47 | 48 | /* (non-Javadoc) 49 | * @see com.atlassian.jira.issue.action.IssueAction#getTimePerformed() 50 | */ 51 | public Date getTimePerformed() { 52 | return this.timePerformed; 53 | } 54 | 55 | /* (non-Javadoc) 56 | * @see com.atlassian.jira.issue.action.IssueAction#getHtml(com.atlassian.jira.web.action.JiraWebActionSupport) 57 | */ 58 | public String getHtml(JiraWebActionSupport webAction) { 59 | @SuppressWarnings("unchecked") 60 | Map params = UtilMisc.toMap("webAction", webAction, "action", this); 61 | 62 | return descriptor.getHtml("view", params); 63 | } 64 | 65 | protected void populateVelocityParams(Map params) { 66 | params.put("action", this); 67 | } 68 | 69 | 70 | public boolean isUserExists(String username) { 71 | try { 72 | return userManager.getUserByName(username) != null; 73 | } catch (Throwable t) { 74 | return false; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/function/ClearFieldValuePostFunction.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.function; 2 | 3 | import java.util.Map; 4 | 5 | import com.atlassian.jira.component.ComponentAccessor; 6 | import com.atlassian.jira.issue.MutableIssue; 7 | import com.atlassian.jira.issue.fields.Field; 8 | import com.atlassian.jira.issue.util.IssueChangeHolder; 9 | import com.atlassian.jira.user.ApplicationUser; 10 | import com.atlassian.jira.util.I18nHelper; 11 | import com.googlecode.jsu.util.WorkflowUtils; 12 | import com.googlecode.jsu.workflow.WorkflowClearFieldValueFunctionPluginFactory; 13 | import com.opensymphony.module.propertyset.PropertySet; 14 | import com.opensymphony.workflow.WorkflowException; 15 | 16 | /** 17 | * This function clears field value. 18 | * 19 | * @author Alexey Abashev 20 | */ 21 | public class ClearFieldValuePostFunction extends AbstractPreserveChangesPostFunction { 22 | private final WorkflowUtils workflowUtils; 23 | private final I18nHelper.BeanFactory beanFactory; 24 | 25 | public ClearFieldValuePostFunction(WorkflowUtils workflowUtils, I18nHelper.BeanFactory beanFactory) { 26 | this.workflowUtils = workflowUtils; 27 | this.beanFactory = beanFactory; 28 | } 29 | 30 | /* (non-Javadoc) 31 | * @see com.googlecode.jsu.workflow.function.AbstractPreserveChangesPostFunction#executeFunction(java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet, com.atlassian.jira.issue.util.IssueChangeHolder) 32 | */ 33 | @Override 34 | protected void executeFunction( 35 | Map transientVars, Map args, 36 | PropertySet ps, IssueChangeHolder holder 37 | ) throws WorkflowException { 38 | String fieldKey = args.get(WorkflowClearFieldValueFunctionPluginFactory.FIELD); 39 | Field field = workflowUtils.getFieldFromKey(fieldKey); 40 | 41 | final String fieldName = (field != null) ? field.getName() : "null"; 42 | 43 | // It set the value to field. 44 | try { 45 | ApplicationUser currentUser = getCallerUser(transientVars, args); 46 | MutableIssue issue = getIssue(transientVars); 47 | 48 | if (log.isDebugEnabled()) { 49 | log.debug(String.format( 50 | "Clean field '%s - %s' in the issue [%s]", 51 | fieldKey, fieldName, issue.getKey() 52 | )); 53 | } 54 | 55 | workflowUtils.setFieldValue(currentUser, issue, fieldKey, null, holder); 56 | } catch (Exception e) { 57 | I18nHelper i18nh = this.beanFactory.getInstance( 58 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()); 59 | String message = i18nh.getText("clearfieldvalue-function-view.unable_to_purge",fieldKey,fieldName); 60 | 61 | log.error(message, e); 62 | 63 | throw new WorkflowException(message); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/dateexpressioncompare-validator-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("dateexpressioncompare-validator.date.label"): 4 | 5 | 6 | 15 |
$i18n.getText("dateexpressioncompare-validator.date.description") 16 | 17 | 18 | 19 | 20 | $i18n.getText("datecompare-validator.condition.label"): 21 | 22 | 23 | 32 |
$i18n.getText("datecompare-validator.condition.description") 33 | 34 | 35 | 36 | 37 | $i18n.getText("datecompare-validator.compare.label"): 38 | 39 | 40 | 41 |
$i18n.getText("dateexpressioncompare-validator.compare.description") 42 | 43 | 44 | 45 | 46 | $i18n.getText("datecompare-validator.timepart.label"): 47 | 48 | 49 | 58 |
$i18n.getText("datecompare-validator.timepart.description") 59 | 60 | 61 | 62 | 63 |
$i18n.getText("dateexpressioncompare-validator.infobox.text")
64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/datecompare-validator-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("datecompare-validator.date.label"): 4 | 5 | 6 | 15 |
$i18n.getText("datecompare-validator.date.description") 16 | 17 | 18 | 19 | 20 | $i18n.getText("datecompare-validator.condition.label"): 21 | 22 | 23 | 32 |
$i18n.getText("datecompare-validator.condition.description") 33 | 34 | 35 | 36 | 37 | $i18n.getText("datecompare-validator.compare.label"): 38 | 39 | 40 | 49 |
$i18n.getText("datecompare-validator.compare.description") 50 | 51 | 52 | 53 | 54 | $i18n.getText("datecompare-validator.timepart.label"): 55 | 56 | 57 | 66 |
$i18n.getText("datecompare-validator.timepart.description") 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/fieldvalue-condition-edit.vm: -------------------------------------------------------------------------------- 1 | #* @vtlvariable name="val-comparisonList" type="java.util.List" *# 2 | #* @vtlvariable name="val-fieldValue" type="java.lang.String" *# 3 | #* @vtlvariable name="val-conditionSelected" type="com.googlecode.jsu.helpers.ConditionType" *# 4 | #* @vtlvariable name="val-conditionList" type="java.util.List" *# 5 | #* @vtlvariable name="val-fieldSelected" type="com.atlassian.jira.issue.fields.Field" *# 6 | #* @vtlvariable name="val-fieldsList" type="java.util.List" *# 7 | 8 | 9 | $i18n.getText("fieldvalue-condition-edit.field.label"): 10 | 11 | 12 | 19 |
$i18n.getText("fieldvalue-condition-edit.field.description") 20 | 21 | 22 | 23 | 24 | 25 | $i18n.getText("fieldvalue-condition-edit.condition.label"): 26 | 27 | 28 | 35 |
$i18n.getText("fieldvalue-condition-edit.condition.description") 36 | 37 | 38 | 39 | 40 | 41 | $i18n.getText("fieldvalue-condition-edit.value.label"): 42 | 43 | 44 | 45 |
$i18n.getText("fieldvalue-condition-edit.value.description") 46 | 47 | 48 | 49 | 50 | 51 | $i18n.getText("fieldvalue-condition-edit.comparison_type.label"): 52 | 53 | 54 | 61 |
$i18n.getText("fieldvalue-condition-edit.comparison.description") 62 | 63 | 64 | 65 | 66 | 67 |
68 | $i18n.getText("fieldvalue-condition-edit.infobox.text","=","!=","!=") 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/condition/UserIsInAnyRolesCondition.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.condition; 2 | 3 | import com.atlassian.crowd.embedded.api.User; 4 | import com.atlassian.jira.issue.Issue; 5 | import com.atlassian.jira.security.roles.ProjectRole; 6 | import com.atlassian.jira.security.roles.ProjectRoleManager; 7 | import com.atlassian.jira.user.ApplicationUser; 8 | import com.atlassian.jira.user.util.UserManager; 9 | import com.atlassian.jira.workflow.condition.AbstractJiraCondition; 10 | import com.googlecode.jsu.util.WorkflowUtils; 11 | import com.opensymphony.module.propertyset.PropertySet; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.Collection; 16 | import java.util.Map; 17 | 18 | /** 19 | * This Condition validates if the current user is in any of the selected roles. 20 | */ 21 | public class UserIsInAnyRolesCondition extends AbstractJiraCondition { 22 | private static final Logger LOG = LoggerFactory.getLogger(UserIsInAnyRolesCondition.class); 23 | 24 | private final WorkflowUtils workflowUtils; 25 | private final UserManager userManager; 26 | private final ProjectRoleManager projectRoleManager; 27 | 28 | public UserIsInAnyRolesCondition(WorkflowUtils workflowUtils, UserManager userManager, ProjectRoleManager projectRoleManager) { 29 | this.workflowUtils = workflowUtils; 30 | this.userManager = userManager; 31 | this.projectRoleManager = projectRoleManager; 32 | } 33 | 34 | /* (non-Javadoc) 35 | * @see com.opensymphony.workflow.Condition#passesCondition(java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet) 36 | */ 37 | public boolean passesCondition(Map transientVars, Map args, PropertySet ps) { 38 | // Obtains the current user. 39 | Issue issue=getIssue(transientVars); 40 | String caller = getCallerKey(transientVars,args); 41 | 42 | if (caller != null) { // null -> User not logged in 43 | ApplicationUser userLogged = workflowUtils.getApplicationUser(caller); 44 | 45 | // If there aren't roles selected, hidRolesList is equal to "". 46 | // And rolesSelected will be an empty collection. 47 | String strRolesSelected = (String) args.get("hidRolesList"); 48 | Collection rolesSelected = workflowUtils.getRoles(strRolesSelected, WorkflowUtils.SPLITTER); 49 | 50 | for (ProjectRole role : rolesSelected) { 51 | try { 52 | if(projectRoleManager.isUserInProjectRole(userLogged, role, issue.getProjectObject())) { 53 | return true; 54 | } 55 | } catch (Exception e) { 56 | //see JSUTIL-68 57 | } 58 | } 59 | } 60 | 61 | return false; 62 | } 63 | /** 64 | * This ist deprecated because Atlassian API is not working with ApplicationUser 65 | * As soon as this is working this method can be deleted 66 | */ 67 | @Deprecated 68 | private User convertApplicationUserToCrowdEmbeddedUser(ApplicationUser applicationUser){ 69 | return userManager.getUserObject(applicationUser.getUsername()); 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/condition/UserIsInAnyGroupsCondition.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.condition; 2 | 3 | import com.atlassian.crowd.embedded.api.CrowdService; 4 | import com.atlassian.crowd.embedded.api.Group; 5 | import com.atlassian.crowd.embedded.api.User; 6 | import com.atlassian.jira.user.ApplicationUser; 7 | import com.atlassian.jira.user.util.UserManager; 8 | import com.atlassian.jira.workflow.condition.AbstractJiraCondition; 9 | import com.googlecode.jsu.util.WorkflowUtils; 10 | import com.opensymphony.module.propertyset.PropertySet; 11 | import com.opensymphony.workflow.WorkflowContext; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.Collection; 16 | import java.util.Map; 17 | 18 | /** 19 | * @author Gustavo Martin 20 | * 21 | * This Condition validates if the current user is in any of the selected groups. 22 | * 23 | */ 24 | public class UserIsInAnyGroupsCondition extends AbstractJiraCondition { 25 | private static final Logger LOG = LoggerFactory.getLogger(UserIsInAnyGroupsCondition.class); 26 | 27 | private final WorkflowUtils workflowUtils; 28 | private final UserManager userManager; 29 | private final CrowdService crowdService; 30 | 31 | public UserIsInAnyGroupsCondition(WorkflowUtils workflowUtils, UserManager userManager, CrowdService crowdService) { 32 | this.workflowUtils = workflowUtils; 33 | this.userManager = userManager; 34 | this.crowdService = crowdService; 35 | } 36 | 37 | /* (non-Javadoc) 38 | * @see com.opensymphony.workflow.Condition#passesCondition(java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet) 39 | */ 40 | public boolean passesCondition(Map transientVars, Map args, PropertySet ps) { 41 | // Obtains the current user. 42 | WorkflowContext context = (WorkflowContext) transientVars.get("context"); 43 | String caller = context.getCaller(); 44 | 45 | if (caller != null) { // null -> User not logged in 46 | ApplicationUser userLogged = workflowUtils.getApplicationUser(caller); 47 | 48 | // If there aren't groups selected, hidGroupsList is equal to "". 49 | // And groupsSelected will be an empty collection. 50 | String strGroupsSelected = (String) args.get("hidGroupsList"); 51 | Collection groupsSelected = workflowUtils.getGroups(strGroupsSelected, WorkflowUtils.SPLITTER); 52 | 53 | for (Group group : groupsSelected) { 54 | try { 55 | if (crowdService.isUserMemberOfGroup(convertApplicationUserToCrowdEmbeddedUser(userLogged), group)) { 56 | return true; 57 | } 58 | } catch (Exception e) { 59 | //see JSUTIL-68 60 | } 61 | } 62 | } 63 | 64 | return false; 65 | } 66 | /** 67 | * This ist deprecated because Atlassian API is not working with ApplicationUser 68 | * As soon as this is working this method can be deleted 69 | */ 70 | @Deprecated 71 | private User convertApplicationUserToCrowdEmbeddedUser(ApplicationUser applicationUser){ 72 | return userManager.getUserObject(applicationUser.getUsername()); 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/checkers/ConverterString.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers.checkers; 2 | 3 | import com.atlassian.crowd.embedded.api.User; 4 | import com.atlassian.jira.issue.label.Label; 5 | import com.atlassian.jira.user.ApplicationUser; 6 | import com.atlassian.jira.issue.customfields.option.Option; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.ofbiz.core.entity.GenericEntity; 9 | 10 | import com.atlassian.jira.issue.IssueConstant; 11 | import com.atlassian.jira.project.Project; 12 | 13 | 14 | import java.lang.reflect.Method; 15 | import java.util.Collection; 16 | 17 | /** 18 | * @author Alexey Abashev 19 | */ 20 | public class ConverterString implements ValueConverter { 21 | /* (non-Javadoc) 22 | * @see com.googlecode.jsu.helpers.checkers.ValueConverter#getComparable(java.lang.Object) 23 | */ 24 | public Comparable getComparable(Object object) { 25 | if (object == null) { 26 | return null; 27 | } 28 | 29 | String result = convert(object); 30 | 31 | if (StringUtils.isBlank(result)) { 32 | return null; 33 | } 34 | 35 | return result; 36 | } 37 | 38 | 39 | public String convert(Object value) { 40 | if (value == null || value instanceof String) { 41 | return (String) value; 42 | } else if (value instanceof IssueConstant) { 43 | return ((IssueConstant) value).getName(); 44 | } else if (value instanceof Project) { 45 | return ((Project)value).getKey(); 46 | } else if (value instanceof Collection && ((Collection) value).size() == 1) { 47 | return convert(((Collection) value).iterator().next()); 48 | } else if (value instanceof Option) { 49 | return ((Option) value).getValue(); 50 | } else if (value instanceof com.atlassian.jira.issue.fields.option.Option) { 51 | return ((com.atlassian.jira.issue.fields.option.Option) value).getName(); 52 | } else if (value instanceof ApplicationUser) { 53 | return ((ApplicationUser) value).getName(); 54 | //https://developer.atlassian.com/display/JIRADEV/Renamable+Users+in+JIRA+6.0 55 | } else if (value instanceof User) { 56 | return ((User) value).getName(); 57 | } else if (value instanceof Label) { 58 | return ((Label) value).getLabel(); 59 | } else if (value instanceof GenericEntity) { 60 | String s = ((GenericEntity) value).getString("name"); 61 | if (StringUtils.isEmpty(s)) { 62 | s = ((GenericEntity) value).getString("id"); 63 | if (StringUtils.isEmpty(s)) { 64 | s = value.toString(); 65 | } 66 | } 67 | return s; 68 | } else { 69 | try { 70 | Method getName = value.getClass().getMethod("getName"); 71 | return getName.invoke(value).toString(); 72 | } catch (Exception e) { /* try getId() ... */ } 73 | try { 74 | Method getId = value.getClass().getMethod("getId"); 75 | return getId.invoke(value).toString(); 76 | } catch (Exception e) { /* use toString() ... */ } 77 | return value.toString(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/function/updateissuefield-function-input.vm: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | $i18n.getText("updateissuefield-function-input.field.label"): 35 | 36 | 37 | 46 |
$i18n.getText("updateissuefield-function-input.field.description") 47 | 48 | 49 | 50 | 51 | 52 | $i18n.getText("updateissuefield-function-input.value.label"): 53 | 54 | #foreach ($field in ${fields}) 55 | 56 | 57 |
$i18n.getText("updateissuefield-function-input.value.description") 58 | 59 | #end 60 | 61 | 62 | 63 | $i18n.getText("updateissuefield-function-input.append.label"): 64 | 65 | 70 |
$i18n.getText("updateissuefield-function-input.append.description") 71 | 72 | 73 | 74 | 75 | 81 | 82 | 83 |
$i18n.getText("updateissuefield-function-input.infobox.text")
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/transitionssummary/issuetabpanel/TransitionsSummaryTabPanel.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.transitionssummary.issuetabpanel; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.atlassian.crowd.embedded.api.User; 8 | import com.atlassian.jira.issue.Issue; 9 | import com.atlassian.jira.issue.tabpanels.GenericMessageAction; 10 | import com.atlassian.jira.plugin.issuetabpanel.IssueAction; 11 | import com.atlassian.jira.plugin.issuetabpanel.IssueTabPanel; 12 | import com.atlassian.jira.plugin.issuetabpanel.IssueTabPanelModuleDescriptor; 13 | import com.atlassian.jira.user.util.UserManager; 14 | import com.atlassian.jira.util.I18nHelper; 15 | import com.googlecode.jsu.transitionssummary.TransitionSummary; 16 | import com.googlecode.jsu.transitionssummary.TransitionsManager; 17 | 18 | /** 19 | * @author Gustavo Martin 20 | * 21 | * It will be add a new Issue Tab Panel. 22 | * 23 | */ 24 | public class TransitionsSummaryTabPanel implements IssueTabPanel { 25 | 26 | protected IssueTabPanelModuleDescriptor descriptor; 27 | 28 | private final TransitionsManager transitionsManager; 29 | private final UserManager userManager; 30 | private final I18nHelper.BeanFactory beanFactory; 31 | 32 | public TransitionsSummaryTabPanel(TransitionsManager transitionsManager, 33 | UserManager userManager, 34 | I18nHelper.BeanFactory beanFactory) { 35 | this.transitionsManager = transitionsManager; 36 | this.userManager = userManager; 37 | this.beanFactory = beanFactory; 38 | } 39 | 40 | /* (non-Javadoc) 41 | * @see com.googlecode.jsu.issuetabpanel.IssueTabPanel#init(com.googlecode.jsu.issuetabpanel.IssueTabPanelModuleDescriptor) 42 | */ 43 | public void init(IssueTabPanelModuleDescriptor descriptor) { 44 | this.descriptor = descriptor; 45 | 46 | } 47 | 48 | /* (non-Javadoc) 49 | * @see com.googlecode.jsu.issuetabpanel.IssueTabPanel#getActions(org.ofbiz.core.entity.GenericValue, com.opensymphony.user.User) 50 | */ 51 | public List getActions(Issue issue, User remoteUser) { 52 | List retList = new ArrayList(); 53 | List transitions = transitionsManager.getTransitionSummary(issue); 54 | 55 | // Allway adds only one record to the tab. This is thus, because if there are transition sumeries, 56 | // it exposes the List with all Transition Summaries. Then, velocity will be in charge of render it properly. 57 | if (transitions == null || transitions.isEmpty()) { 58 | 59 | GenericMessageAction action = new GenericMessageAction( 60 | this.beanFactory.getInstance(remoteUser).getText("transitions-summary-view.not_yet_executed")); 61 | retList.add(action); 62 | } else { 63 | Collections.sort(transitions, new TransitionSummaryComparator()); 64 | 65 | retList.add(new TransitionSummaryAction(transitions, descriptor, userManager)); 66 | } 67 | 68 | return retList; 69 | } 70 | 71 | /* (non-Javadoc) 72 | * @see com.googlecode.jsu.issuetabpanel.IssueTabPanel#showPanel(org.ofbiz.core.entity.GenericValue) 73 | */ 74 | public boolean showPanel(Issue issue, User remoteUser) { 75 | return true; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/function/AbstractPreserveChangesPostFunction.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.function; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.atlassian.jira.issue.history.ChangeItemBean; 11 | import com.atlassian.jira.issue.util.DefaultIssueChangeHolder; 12 | import com.atlassian.jira.issue.util.IssueChangeHolder; 13 | import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider; 14 | import com.opensymphony.module.propertyset.PropertySet; 15 | import com.opensymphony.workflow.WorkflowException; 16 | 17 | /** 18 | * Abstract post-function with transparent change tracking. 19 | * 20 | * @author Alexey Abashev 21 | */ 22 | abstract class AbstractPreserveChangesPostFunction extends AbstractJiraFunctionProvider { 23 | private static final String CHANGE_ITEMS = "changeItems"; 24 | 25 | protected final Logger log = LoggerFactory.getLogger(this.getClass()); 26 | 27 | /** 28 | * Mirror for execute method but with holder for changes 29 | * 30 | * @throws WorkflowException 31 | */ 32 | protected abstract void executeFunction( 33 | Map transientVars, Map args, 34 | PropertySet ps, IssueChangeHolder holder 35 | ) throws WorkflowException; 36 | 37 | @SuppressWarnings("unchecked") 38 | public final void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException { 39 | IssueChangeHolder holder = createChangeHolder(transientVars); 40 | 41 | if (log.isDebugEnabled()) { 42 | log.debug( 43 | "Executing function with [transientVars=" + 44 | transientVars + 45 | ";args=" + 46 | args + 47 | ";ps=" + 48 | ps + 49 | "]" 50 | ); 51 | } 52 | 53 | try { 54 | executeFunction(transientVars, args, ps, holder); 55 | } finally { 56 | releaseChangeHolder(holder, transientVars); 57 | } 58 | } 59 | 60 | /** 61 | * Create new holder with changes from transient vars 62 | */ 63 | @SuppressWarnings("unchecked") 64 | private IssueChangeHolder createChangeHolder(Map transientVars) { 65 | List changeItems = (List) transientVars.get(CHANGE_ITEMS); 66 | 67 | if (changeItems == null) { 68 | changeItems = new LinkedList(); 69 | } 70 | 71 | if (log.isDebugEnabled()) { 72 | log.debug("Create new holder with items - " + changeItems.toString()); 73 | } 74 | 75 | IssueChangeHolder holder = new DefaultIssueChangeHolder(); 76 | 77 | holder.setChangeItems(changeItems); 78 | 79 | return holder; 80 | } 81 | 82 | /** 83 | * Release holder for changes. 84 | */ 85 | @SuppressWarnings("unchecked") 86 | private void releaseChangeHolder(IssueChangeHolder holder, Map transientVars) { 87 | List items = holder.getChangeItems(); 88 | 89 | if (log.isDebugEnabled()) { 90 | log.debug("Release holder with items - " + items.toString()); 91 | } 92 | 93 | transientVars.put(CHANGE_ITEMS, items); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowClearFieldValueFunctionPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import com.atlassian.jira.issue.fields.Field; 9 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 10 | import com.atlassian.jira.plugin.workflow.WorkflowPluginFunctionFactory; 11 | import com.googlecode.jsu.util.FieldCollectionsUtils; 12 | import com.googlecode.jsu.util.WorkflowUtils; 13 | import com.opensymphony.workflow.loader.AbstractDescriptor; 14 | 15 | /** 16 | * @author Alexey Abashev 17 | */ 18 | public class WorkflowClearFieldValueFunctionPluginFactory 19 | extends AbstractWorkflowPluginFactory 20 | implements WorkflowPluginFunctionFactory { 21 | 22 | public static final String FIELD = "field"; 23 | public static final String SELECTED_FIELD = "selectedField"; 24 | public static final String FIELD_LIST = "fieldList"; 25 | 26 | private final FieldCollectionsUtils fieldCollectionsUtils; 27 | private final WorkflowUtils workflowUtils; 28 | 29 | public WorkflowClearFieldValueFunctionPluginFactory(FieldCollectionsUtils fieldCollectionsUtils, 30 | WorkflowUtils workflowUtils) { 31 | this.fieldCollectionsUtils = fieldCollectionsUtils; 32 | this.workflowUtils = workflowUtils; 33 | } 34 | 35 | /* (non-Javadoc) 36 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 37 | */ 38 | @SuppressWarnings("unchecked") 39 | protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) { 40 | this.getVelocityParamsForInput(velocityParams); 41 | 42 | velocityParams.put(SELECTED_FIELD, workflowUtils.getFieldFromDescriptor(descriptor, FIELD)); 43 | } 44 | 45 | /* (non-Javadoc) 46 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 47 | */ 48 | @SuppressWarnings("unchecked") 49 | protected void getVelocityParamsForInput(Map velocityParams) { 50 | List fields = fieldCollectionsUtils.getAllClearableFields(); 51 | 52 | velocityParams.put(FIELD_LIST, Collections.unmodifiableList(fields)); 53 | } 54 | 55 | /* (non-Javadoc) 56 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 57 | */ 58 | @SuppressWarnings("unchecked") 59 | protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) { 60 | velocityParams.put(SELECTED_FIELD, workflowUtils.getFieldFromDescriptor(descriptor, FIELD)); 61 | } 62 | 63 | /* (non-Javadoc) 64 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 65 | */ 66 | @SuppressWarnings("unchecked") 67 | public Map getDescriptorParams(Map conditionParams) { 68 | Map params = new HashMap(); 69 | 70 | try{ 71 | String sourceField = extractSingleParam(conditionParams, FIELD); 72 | 73 | params.put(FIELD, sourceField); 74 | } catch(IllegalArgumentException e) { 75 | // Aggregate so that Transitions can be added. 76 | } 77 | 78 | return params; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/userIsInAnyRoles-condition-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("userisinanyroles-condition-edit.title"): 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 32 | 33 | 34 | 35 | 38 | 41 | 42 |
11 | $i18n.getText("userisinanyroles-condition-edit.available_roles.label"):
12 | 17 | 18 |
19 |
22 | $i18n.getText("userisinanyroles-condition-edit.allowed_roles.label"):
23 | 30 | 31 |
36 | 37 | 39 | 40 |
43 | 44 | 45 | 46 | 47 |
48 | $i18n.getText("userisinanyroles-condition-edit.infobox.text") 49 |
50 | 51 | 52 | 53 | 86 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/condition/UserIsInCustomFieldCondition.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.condition; 2 | 3 | import static com.googlecode.jsu.workflow.WorkflowUserIsInCustomFieldConditionPluginFactory.getAllowUserInField; 4 | 5 | import java.util.Collection; 6 | import java.util.Map; 7 | 8 | import com.atlassian.jira.user.ApplicationUser; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import com.atlassian.jira.issue.Issue; 13 | import com.atlassian.jira.issue.fields.Field; 14 | import com.atlassian.jira.user.util.UserManager; 15 | import com.atlassian.jira.workflow.condition.AbstractJiraCondition; 16 | import com.googlecode.jsu.util.WorkflowUtils; 17 | import com.opensymphony.module.propertyset.PropertySet; 18 | import com.opensymphony.workflow.WorkflowContext; 19 | 20 | /** 21 | * This Condition validates if the current user is in any of the selected groups. 22 | * @author Anton Afanassiev 23 | */ 24 | public class UserIsInCustomFieldCondition extends AbstractJiraCondition { 25 | private final Logger log = LoggerFactory.getLogger(UserIsInCustomFieldCondition.class); 26 | 27 | private final UserManager userManager; 28 | private final WorkflowUtils workflowUtils; 29 | 30 | public UserIsInCustomFieldCondition(UserManager userManager, WorkflowUtils workflowUtils) { 31 | this.userManager = userManager; 32 | this.workflowUtils = workflowUtils; 33 | } 34 | 35 | /* (non-Javadoc) 36 | * @see com.opensymphony.workflow.Condition#passesCondition(java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet) 37 | */ 38 | public boolean passesCondition(Map transientVars, Map args, PropertySet ps) { 39 | boolean allowUser = false; 40 | 41 | // Obtains the current user. 42 | WorkflowContext context = (WorkflowContext) transientVars.get("context"); 43 | ApplicationUser userLogged = workflowUtils.getApplicationUser(context.getCaller()); 44 | 45 | if (userLogged == null) { 46 | log.warn("Unable to check condition"); 47 | 48 | return false; 49 | } 50 | 51 | // If there aren't groups selected, hidGroupsList is equal to "". 52 | // And groupsSelected will be an empty collection. 53 | String fieldKey = (String) args.get("fieldsList"); 54 | 55 | boolean allowUserInField = getAllowUserInField(args); 56 | 57 | Field field = workflowUtils.getFieldFromKey(fieldKey); 58 | Issue issue = getIssue(transientVars); 59 | 60 | Object fieldValue = workflowUtils.getFieldValueFromIssue(issue, field); 61 | 62 | if (fieldValue != null) { 63 | if (fieldValue instanceof Collection) { 64 | // support for MultiUser lists. user must be member of that list to pass condition 65 | for (Object value : (Collection) fieldValue) { 66 | allowUser = compareValues(value, userLogged, allowUserInField); 67 | 68 | if (allowUser == allowUserInField) { 69 | break; 70 | } 71 | } 72 | } else { 73 | allowUser = compareValues(fieldValue, userLogged, allowUserInField); 74 | } 75 | } else { 76 | allowUser = !allowUserInField; 77 | } 78 | 79 | return allowUser; 80 | } 81 | 82 | private boolean compareValues(Object fieldValue, ApplicationUser user, boolean allowUserInField) { 83 | boolean result = !allowUserInField; 84 | 85 | if (fieldValue instanceof String) { 86 | if (fieldValue.equals(user.getUsername())) { 87 | result = allowUserInField; 88 | } 89 | } else { 90 | if (fieldValue.equals(user)) { 91 | result = allowUserInField; 92 | } 93 | } 94 | 95 | return result; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/condition/userIsInAnyGroups-condition-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("userisinanygroups-condition-edit.title"): 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 32 | 33 | 34 | 35 | 38 | 41 | 42 |
11 | $i18n.getText("userisinanygroups-condition-edit.available_groups.label"):
12 | 17 | 18 |
19 |
22 | $i18n.getText("userisinanygroups-condition-edit.allowed_groups.label"):
23 | 30 | 31 |
36 | 37 | 39 | 40 |
43 | 44 | 45 | 46 | 47 |
48 | $i18n.getText("userisinanygroups-condition-edit.infobox.text") 49 |
50 | 51 | 52 | 53 | 91 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/transitionssummary/TransitionSummary.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.transitionssummary; 2 | 3 | import java.sql.Timestamp; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import com.atlassian.jira.datetime.DateTimeFormatter; 8 | import com.googlecode.jsu.helpers.FormattableDuration; 9 | 10 | /** 11 | * @author Gustavo Martin 12 | * 13 | * This class represents the summary of a set of Transitions. 14 | * 15 | * Allowing to obtain the total duration, how many times it happened through her, 16 | * and who/when it was the last update. 17 | * 18 | */ 19 | public class TransitionSummary { 20 | private String id; 21 | private StatusDelegate fromStatus; 22 | private StatusDelegate toStatus; 23 | private Long duration; 24 | private String lastUpdater; 25 | private Timestamp lastUpdate; 26 | private List transitions = new ArrayList(); 27 | private DateTimeFormatter userFormatter; 28 | 29 | /** 30 | * @param id an external ID generate. 31 | */ 32 | public TransitionSummary( 33 | String id, 34 | StatusDelegate fromStatus, 35 | StatusDelegate toStatus, 36 | DateTimeFormatter userFormatter 37 | ) { 38 | setId(id); 39 | setFromStatus(fromStatus); 40 | setToStatus(toStatus); 41 | setDuration(new Long("0")); 42 | this.userFormatter = userFormatter; 43 | } 44 | 45 | /** 46 | * @param tran a simple Transition. 47 | * 48 | * Allows to add a transition and recalculate the summary values. 49 | */ 50 | public void addTransition(Transition tran){ 51 | transitions.add(tran); 52 | 53 | setLastUpdater(tran.getChangedBy()); 54 | setLastupdate(tran.getChangedAt()); 55 | 56 | addTime(tran.getDurationInMillis()); 57 | } 58 | 59 | /** 60 | * @return a nice String format of the duration. 61 | */ 62 | public String getDurationAsString(){ 63 | return FormattableDuration.getFormatted(this.getDurationInMillis()); 64 | } 65 | 66 | public int getTimesToTransition(){ 67 | return transitions.size(); 68 | } 69 | 70 | private void addTime(Long timeInMillis){ 71 | setDuration(getDurationInMillis() + timeInMillis); 72 | } 73 | 74 | public String getId() { 75 | return id; 76 | } 77 | 78 | public StatusDelegate getFromStatus() { 79 | return fromStatus; 80 | } 81 | 82 | public StatusDelegate getToStatus() { 83 | return toStatus; 84 | } 85 | 86 | /** 87 | * @return a nice formatted date as String. 88 | */ 89 | public String getLastUpdateAsString(){ 90 | return this.userFormatter.format(lastUpdate); 91 | } 92 | 93 | /** 94 | * @return the lastUpdate 95 | */ 96 | public Timestamp getLastUpdate() { 97 | return lastUpdate; 98 | } 99 | 100 | public String getLastUpdater() { 101 | return lastUpdater; 102 | } 103 | 104 | private void setId(String id) { 105 | this.id = id; 106 | } 107 | 108 | public Long getDurationInMillis() { 109 | return duration; 110 | } 111 | 112 | private void setFromStatus(StatusDelegate fromStatus) { 113 | this.fromStatus = fromStatus; 114 | } 115 | 116 | private void setToStatus(StatusDelegate toStatus) { 117 | this.toStatus = toStatus; 118 | } 119 | 120 | private void setLastupdate(Timestamp lastupdate) { 121 | this.lastUpdate = lastupdate; 122 | } 123 | 124 | private void setLastUpdater(String lastUpdater) { 125 | this.lastUpdater = lastUpdater; 126 | } 127 | 128 | private void setDuration(Long duration) { 129 | this.duration = duration; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/it/com/googlecode/jsu/workflow/function/AbstractTestBase.java: -------------------------------------------------------------------------------- 1 | package it.com.googlecode.jsu.workflow.function; 2 | 3 | import com.atlassian.jira.functest.framework.FuncTestCase; 4 | import com.atlassian.jira.functest.framework.suite.Category; 5 | import com.atlassian.jira.functest.framework.suite.WebTest; 6 | import com.atlassian.jira.testkit.client.restclient.IssueClient; 7 | import com.atlassian.jira.testkit.client.restclient.IssueTransitionsMeta; 8 | import com.atlassian.jira.testkit.client.restclient.TransitionsClient; 9 | import com.atlassian.jira.testkit.client.restclient.WatchersClient; 10 | import com.atlassian.jira.testkit.client.util.TimeBombLicence; 11 | 12 | import java.text.SimpleDateFormat; 13 | 14 | @WebTest({ Category.FUNC_TEST, Category.REST }) 15 | public abstract class AbstractTestBase extends FuncTestCase { 16 | protected IssueClient issueClient; 17 | protected TransitionsClient transitionsClient; 18 | protected WatchersClient watchersClient; 19 | 20 | protected static final String STATUS_IN_PROGRESS = "In Progress"; 21 | protected static final String STATUS_RESOLVED = "Resolved"; 22 | 23 | protected static final long FIELD_DATE_TIME_ID = 10109L; 24 | protected static final long FIELD_DATE_PICKER_ID = 10100L; 25 | 26 | protected static final String FIELD_LOCATION_TEXT = "customfield_10001"; 27 | protected static final String FIELD_GROUP_PICKER = "customfield_10110"; 28 | protected static final String FIELD_USER_PICKER = "customfield_10002"; 29 | protected static final String FIELD_MULTI_USER = "customfield_10113"; 30 | protected static final String FIELD_SELECT_LIST = "customfield_10106"; 31 | protected static final String FIELD_FREE_TEXT = "customfield_10101"; 32 | protected static final String FIELD_LOCATION_SELECT = "customfield_10000"; 33 | protected static final String FIELD_MULTI_USER1 = "customfield_10200"; 34 | protected static final String FIELD_DATE_TIME = "customfield_" + FIELD_DATE_TIME_ID; 35 | protected static final String FIELD_MULTI_SELECT = "customfield_10103"; 36 | protected static final String FIELD_MULTI_GROUP = "customfield_10112"; 37 | protected static final String FIELD_READONLY_TEXT = "customfield_10115"; 38 | protected static final String FIELD_CASCADING_SELECT = "customfield_10108"; 39 | protected static final String FIELD_RADIO_BUTTONS = "customfield_10105"; 40 | protected static final String FIELD_DATE_PICKER = "customfield_" + FIELD_DATE_PICKER_ID; 41 | protected static final String FIELD_TEXT_FIELD = "customfield_10107"; 42 | protected static final String FIELD_LABELS = "customfield_10111"; 43 | protected static final String FIELD_MULTI_CHECKBOXES = "customfield_10102"; 44 | protected static final String FIELD_VERSION_PICKER = "customfield_10118"; 45 | 46 | protected static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); 47 | protected static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); 48 | 49 | @Override 50 | protected void setUpTest() { 51 | super.setUpTest(); 52 | administration.restoreDataWithLicense("test1.xml", TimeBombLicence.LICENCE_FOR_TESTING); 53 | issueClient = new IssueClient(getEnvironmentData()); 54 | transitionsClient = new TransitionsClient(getEnvironmentData()); 55 | watchersClient = new WatchersClient(getEnvironmentData()); 56 | } 57 | 58 | protected boolean hasTransition(String issueKey, String transitionId, String user) { 59 | transitionsClient.loginAs(user); 60 | return hasTransition(issueKey, transitionId); 61 | } 62 | 63 | protected boolean hasTransition(String issueKey, String transitionId) { 64 | IssueTransitionsMeta meta = transitionsClient.get(issueKey); 65 | for(IssueTransitionsMeta.Transition transition:meta.transitions) { 66 | if(transition.id==Integer.parseInt(transitionId)) { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/validator/RegexpFieldValidator.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.validator; 2 | 3 | import com.atlassian.jira.component.ComponentAccessor; 4 | import com.atlassian.jira.issue.fields.Field; 5 | import com.atlassian.jira.util.I18nHelper; 6 | import com.googlecode.jsu.annotation.Argument; 7 | import com.googlecode.jsu.util.FieldCollectionsUtils; 8 | import com.googlecode.jsu.util.WorkflowUtils; 9 | import com.opensymphony.workflow.InvalidInputException; 10 | import com.opensymphony.workflow.WorkflowException; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | 18 | /** 19 | * This validator verifies that a given field its contents are not empty and match agains 20 | * a regular expression. 21 | */ 22 | public class RegexpFieldValidator extends GenericValidator { 23 | private static final Logger log = LoggerFactory.getLogger(RegexpFieldValidator.class); 24 | 25 | @Argument("fieldSelected") 26 | private String validateField; 27 | 28 | @Argument("expressionSelected") 29 | private String expression; 30 | 31 | private final I18nHelper.BeanFactory beanFactory; 32 | 33 | public RegexpFieldValidator(FieldCollectionsUtils fieldCollectionsUtils, 34 | WorkflowUtils workflowUtils, 35 | I18nHelper.BeanFactory beanFactory 36 | ) { 37 | super(fieldCollectionsUtils, workflowUtils); 38 | 39 | this.beanFactory = beanFactory; 40 | } 41 | 42 | /* (non-Javadoc) 43 | * @see com.opensymphony.workflow.Validator#validate(java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet) 44 | */ 45 | protected void validate() throws InvalidInputException, WorkflowException { 46 | Field field = workflowUtils.getFieldFromKey(validateField); 47 | 48 | // check that field contents match against regular expression, split into single ones if list type 49 | if((field != null) && (expression != null)) { 50 | Object objValue = workflowUtils.getFieldValueFromIssue(getIssue(), field); 51 | 52 | String completeValue = ""; 53 | ArrayList list = new ArrayList(); 54 | if(objValue!=null) { 55 | completeValue = objValue.toString(); 56 | if(objValue instanceof List) { 57 | List l = (List)objValue; 58 | for(Object o:l) { 59 | list.add(o.toString()); 60 | } 61 | } else { 62 | list.add(objValue.toString()); 63 | } 64 | } else { 65 | list.add(""); 66 | } 67 | 68 | String lastValue = ""; 69 | boolean result = true; 70 | Iterator it = list.iterator(); 71 | while(result && it.hasNext()) { 72 | lastValue = it.next(); 73 | result = lastValue.matches(expression); 74 | } 75 | 76 | if (log.isDebugEnabled()) { 77 | log.debug( 78 | "Validate field \"" + field.getName() + 79 | "\" and expression \"" + expression + 80 | "\" with value [" + completeValue + "] with result " + result 81 | ); 82 | } 83 | 84 | if(!result) { 85 | I18nHelper i18nh = this.beanFactory.getInstance( 86 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()); 87 | String msg = i18nh.getText("regexpfield-validator-view.not_matching",field.getName(),lastValue,expression); 88 | this.setExceptionMessage( 89 | field, 90 | msg, 91 | msg 92 | ); 93 | } 94 | } else { 95 | log.error("Unable to find field with id [" + validateField + "]"); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowRegexpFieldValidatorPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import com.atlassian.jira.issue.fields.Field; 4 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 5 | import com.atlassian.jira.plugin.workflow.WorkflowPluginValidatorFactory; 6 | import com.googlecode.jsu.util.FieldCollectionsUtils; 7 | import com.googlecode.jsu.util.WorkflowUtils; 8 | import com.opensymphony.workflow.loader.AbstractDescriptor; 9 | import com.opensymphony.workflow.loader.ValidatorDescriptor; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * This class defines the parameters available for Regexp Field Validator. 17 | */ 18 | public class WorkflowRegexpFieldValidatorPluginFactory 19 | extends AbstractWorkflowPluginFactory 20 | implements WorkflowPluginValidatorFactory { 21 | 22 | public static final String PARAM_VALIDATE_FIELD = "fieldSelected"; 23 | public static final String PARAM_EXPRESSION = "expressionSelected"; 24 | 25 | private static final String VAL_VALIDATE_FIELD = "val-" + PARAM_VALIDATE_FIELD; 26 | private static final String VAL_EXPRESSION = "val-" + PARAM_EXPRESSION; 27 | 28 | private final FieldCollectionsUtils fieldCollectionsUtils; 29 | private final WorkflowUtils workflowUtils; 30 | 31 | public WorkflowRegexpFieldValidatorPluginFactory(FieldCollectionsUtils fieldCollectionsUtils,WorkflowUtils workflowUtils) { 32 | this.fieldCollectionsUtils = fieldCollectionsUtils; 33 | this.workflowUtils = workflowUtils; 34 | } 35 | 36 | /* (non-Javadoc) 37 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 38 | */ 39 | protected void getVelocityParamsForInput(Map velocityParams) { 40 | List allTextFields = fieldCollectionsUtils.getAllRegexpFields(); 41 | 42 | velocityParams.put("val-fieldList", allTextFields); 43 | } 44 | 45 | /* (non-Javadoc) 46 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 47 | */ 48 | protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) { 49 | getVelocityParamsForInput(velocityParams); 50 | getVelocityParamsForView(velocityParams,descriptor); 51 | } 52 | 53 | /* (non-Javadoc) 54 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 55 | */ 56 | protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) { 57 | ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor; 58 | @SuppressWarnings("unchecked") 59 | Map args = validatorDescriptor.getArgs(); 60 | 61 | String validateField = (String) args.get(PARAM_VALIDATE_FIELD); 62 | String expression = (String) args.get(PARAM_EXPRESSION); 63 | 64 | velocityParams.put(VAL_VALIDATE_FIELD, workflowUtils.getFieldFromKey(validateField)); 65 | velocityParams.put(VAL_EXPRESSION, expression); 66 | } 67 | 68 | /* (non-Javadoc) 69 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 70 | */ 71 | public Map getDescriptorParams(Map validatorParams) { 72 | Map params = new HashMap(); 73 | 74 | try{ 75 | String validateField = extractSingleParam(validatorParams, "fieldList"); 76 | String expression = extractSingleParam(validatorParams, "expression"); 77 | 78 | params.put(PARAM_VALIDATE_FIELD, validateField); 79 | params.put(PARAM_EXPRESSION, expression); 80 | 81 | } catch(IllegalArgumentException iae) { 82 | // Aggregate so that Transitions can be added. 83 | } 84 | 85 | return params; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/customfields/TimeInSourceStatusCFType.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.customfields; 2 | 3 | import com.atlassian.jira.issue.Issue; 4 | import com.atlassian.jira.issue.customfields.impl.CalculatedCFType; 5 | import com.atlassian.jira.issue.customfields.impl.FieldValidationException; 6 | import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; 7 | import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; 8 | import com.atlassian.jira.issue.fields.CustomField; 9 | import com.atlassian.jira.issue.fields.config.FieldConfig; 10 | import com.googlecode.jsu.helpers.FormattableDuration; 11 | import com.googlecode.jsu.transitionssummary.TransitionSummary; 12 | import com.googlecode.jsu.transitionssummary.TransitionsManager; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Time in source status custom field type, a calculated ready only field showing the amount 18 | * of time that has been spent in the current status of the issue until now. That amount is 19 | * the accumulated duration in milliseconds the issue is, or has been, even multiple times, 20 | * in that status. 21 | * 22 | * In the usual issue view, the time will be shown formatted in seconds, minutes and so on 23 | * depending its length in milliseconds, for issue navigator its list view or exports, milliseconds 24 | * will be taken instead, to be able to make computations with the field its value. 25 | * 26 | * NOTE: This does not properly work yet, this is why it is not enabled in atlassian-plugin.xml 27 | * The {@link com.googlecode.jsu.helpers.FormattableDuration}, used to be able to have either the 28 | * duration in millis and on the other hand properly formatted human readable, can not be handled 29 | * by the restore functionality of JIRA. This means backup/restore fails and integration tests too. 30 | * 31 | * NOTE 2: Not yet clear and to be tested, performance. Does this really work well in large 32 | * installations with workflows having lots of steps, and where transitions are used often? 33 | */ 34 | public class TimeInSourceStatusCFType extends CalculatedCFType { 35 | 36 | protected final CustomFieldValuePersister customFieldValuePersister; 37 | protected final GenericConfigManager genericConfigManager; 38 | private final TransitionsManager transitionsManager; 39 | 40 | public TimeInSourceStatusCFType(CustomFieldValuePersister customFieldValuePersister, GenericConfigManager genericConfigManager, TransitionsManager transitionsManager) { 41 | this.customFieldValuePersister = customFieldValuePersister; 42 | this.genericConfigManager = genericConfigManager; 43 | this.transitionsManager = transitionsManager; 44 | } 45 | 46 | @Override 47 | public String getStringFromSingularObject(FormattableDuration value) { 48 | return value!=null?value.toString():"0"; 49 | } 50 | 51 | @Override 52 | public FormattableDuration getSingularObjectFromString(String value) throws FieldValidationException { 53 | return value!=null?new FormattableDuration(value):new FormattableDuration(0); 54 | } 55 | 56 | @Override 57 | public FormattableDuration getValueFromIssue(CustomField customField, Issue issue) { 58 | //TODO field not yet sortable nor searchable ?? 59 | List summaries = transitionsManager.getTransitionSummary(issue); 60 | 61 | long duration = 0; 62 | TransitionSummary lastSummary = null; 63 | for(TransitionSummary summary:summaries) { 64 | if(summary.getFromStatus().getId().equals(issue.getStatusObject().getId())) { 65 | duration += summary.getDurationInMillis(); 66 | } 67 | lastSummary = summary; 68 | } 69 | 70 | //add time since very last switch, which lead to the current status, or since created if no transition yet 71 | if(lastSummary!=null) { 72 | duration += System.currentTimeMillis() - lastSummary.getLastUpdate().getTime(); 73 | } else { 74 | duration += System.currentTimeMillis() - issue.getCreated().getTime(); 75 | } 76 | 77 | // duration is in millis, return as seconds 78 | return new FormattableDuration(duration); 79 | } 80 | 81 | public FormattableDuration getDefaultValue(FieldConfig fieldConfig) { 82 | return new FormattableDuration(0); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/templates/jira/workflow/validator/fieldsrequired-validator-edit.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $i18n.getText("fieldsrequired-validator-edit.required_fields.title"): 4 | 5 | 6 | 7 | 8 | 18 | 19 | 30 | 31 | 32 | 33 | 36 | 40 | 41 |
9 | $i18n.getText("fieldsrequired-validator-edit.available_fields"):
10 | 15 | 16 |
17 |
20 | $i18n.getText("fieldsrequired-validator-edit.required_fields"):
21 | 28 | 29 |
34 | 35 | 37 | 38 | 39 |
42 | 43 | 44 | $i18n.getText("fieldsrequired-validator-edit.ignore_context"): 45 | 46 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | $i18n.getText("fieldsrequired-validator-edit.infobox.text") 58 |
59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowUserIsInAnyGroupsConditionPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import com.atlassian.crowd.embedded.api.Group; 9 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 10 | import com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory; 11 | import com.atlassian.jira.security.groups.GroupManager; 12 | import com.googlecode.jsu.util.WorkflowUtils; 13 | import com.opensymphony.workflow.loader.AbstractDescriptor; 14 | import com.opensymphony.workflow.loader.ConditionDescriptor; 15 | 16 | /** 17 | * @author Gustavo Martin. 18 | * 19 | * This class defines the parameters available for User Is In Any Group Condition. 20 | * 21 | */ 22 | public class WorkflowUserIsInAnyGroupsConditionPluginFactory extends 23 | AbstractWorkflowPluginFactory implements WorkflowPluginConditionFactory { 24 | 25 | private final WorkflowUtils workflowUtils; 26 | private final GroupManager groupManager; 27 | 28 | public WorkflowUserIsInAnyGroupsConditionPluginFactory(WorkflowUtils workflowUtils, GroupManager groupManager) { 29 | this.workflowUtils = workflowUtils; 30 | this.groupManager = groupManager; 31 | } 32 | 33 | /* (non-Javadoc) 34 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 35 | */ 36 | protected void getVelocityParamsForInput(Map velocityParams) { 37 | velocityParams.put("val-groupsList", groupManager.getAllGroups()); 38 | velocityParams.put("val-splitter", WorkflowUtils.SPLITTER); 39 | } 40 | 41 | /* (non-Javadoc) 42 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 43 | */ 44 | protected void getVelocityParamsForEdit( 45 | Map velocityParams, 46 | AbstractDescriptor descriptor 47 | ) { 48 | 49 | getVelocityParamsForInput(velocityParams); 50 | 51 | ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; 52 | Map args = conditionDescriptor.getArgs(); 53 | 54 | velocityParams.remove("val-groupsList"); 55 | 56 | String strGroupsSelected = (String)args.get("hidGroupsList"); 57 | Collection groupsSelected = workflowUtils.getGroups(strGroupsSelected, WorkflowUtils.SPLITTER); 58 | 59 | Collection groups = groupManager.getAllGroups(); 60 | 61 | Collection availableGroups = new ArrayList(); 62 | //do not use remove or removeAll, does not work in OD for whatever reason 63 | for(Group g:groups) { 64 | if(!groupsSelected.contains(g)) { 65 | availableGroups.add(g); 66 | } 67 | } 68 | velocityParams.put("val-groupsListSelected", groupsSelected); 69 | velocityParams.put("val-hidGroupsList", workflowUtils.getStringGroup(groupsSelected, WorkflowUtils.SPLITTER)); 70 | velocityParams.put("val-groupsList", availableGroups); 71 | } 72 | 73 | /* (non-Javadoc) 74 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 75 | */ 76 | protected void getVelocityParamsForView( 77 | Map velocityParams, 78 | AbstractDescriptor descriptor 79 | ) { 80 | ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; 81 | Map args = conditionDescriptor.getArgs(); 82 | 83 | String strGroupsSelected = (String)args.get("hidGroupsList"); 84 | Collection groupsSelected = workflowUtils.getGroups(strGroupsSelected, WorkflowUtils.SPLITTER); 85 | 86 | velocityParams.put("val-groupsListSelected", groupsSelected); 87 | } 88 | 89 | /* (non-Javadoc) 90 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 91 | */ 92 | public Map getDescriptorParams(Map conditionParams) { 93 | Map params = new HashMap(); 94 | 95 | try { 96 | String strGroupsSelected = extractSingleParam(conditionParams, "hidGroupsList"); 97 | 98 | params.put("hidGroupsList", strGroupsSelected); 99 | } catch(IllegalArgumentException iae) { 100 | // Aggregate so that Transitions can be added. 101 | } 102 | 103 | return params; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowUserIsInAnyRolesConditionPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 4 | import com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory; 5 | import com.atlassian.jira.security.roles.ProjectRole; 6 | import com.atlassian.jira.security.roles.ProjectRoleManager; 7 | import com.googlecode.jsu.util.WorkflowUtils; 8 | import com.opensymphony.workflow.loader.AbstractDescriptor; 9 | import com.opensymphony.workflow.loader.ConditionDescriptor; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * This class defines the parameters available for User Is In Any Roles Condition. 18 | */ 19 | public class WorkflowUserIsInAnyRolesConditionPluginFactory extends 20 | AbstractWorkflowPluginFactory implements WorkflowPluginConditionFactory { 21 | 22 | private final WorkflowUtils workflowUtils; 23 | private final ProjectRoleManager projectRoleManager; 24 | 25 | public WorkflowUserIsInAnyRolesConditionPluginFactory(WorkflowUtils workflowUtils, ProjectRoleManager projectRoleManager) { 26 | this.workflowUtils = workflowUtils; 27 | this.projectRoleManager = projectRoleManager; 28 | } 29 | 30 | /* (non-Javadoc) 31 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 32 | */ 33 | protected void getVelocityParamsForInput(Map velocityParams) { 34 | velocityParams.put("val-rolesList", projectRoleManager.getProjectRoles()); 35 | velocityParams.put("val-splitter", WorkflowUtils.SPLITTER); 36 | } 37 | 38 | /* (non-Javadoc) 39 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 40 | */ 41 | protected void getVelocityParamsForEdit( 42 | Map velocityParams, 43 | AbstractDescriptor descriptor 44 | ) { 45 | 46 | getVelocityParamsForInput(velocityParams); 47 | 48 | ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; 49 | Map args = conditionDescriptor.getArgs(); 50 | 51 | velocityParams.remove("val-rolesList"); 52 | 53 | String strRolesSelected = (String)args.get("hidRolesList"); 54 | Collection rolesSelected = workflowUtils.getRoles(strRolesSelected, WorkflowUtils.SPLITTER); 55 | 56 | Collection roles = projectRoleManager.getProjectRoles(); 57 | 58 | Collection availableRoles = new ArrayList(); 59 | //do not use remove or removeAll, does not work in OD for whatever reason 60 | for(ProjectRole r:roles) { 61 | if(!rolesSelected.contains(r)) { 62 | availableRoles.add(r); 63 | } 64 | } 65 | velocityParams.put("val-rolesListSelected", rolesSelected); 66 | velocityParams.put("val-hidRolesList", workflowUtils.getStringRole(rolesSelected, WorkflowUtils.SPLITTER)); 67 | velocityParams.put("val-rolesList", availableRoles); 68 | } 69 | 70 | /* (non-Javadoc) 71 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 72 | */ 73 | protected void getVelocityParamsForView( 74 | Map velocityParams, 75 | AbstractDescriptor descriptor 76 | ) { 77 | ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; 78 | Map args = conditionDescriptor.getArgs(); 79 | 80 | String strRolesSelected = (String)args.get("hidRolesList"); 81 | Collection rolesSelected = workflowUtils.getRoles(strRolesSelected, WorkflowUtils.SPLITTER); 82 | 83 | velocityParams.put("val-rolesListSelected", rolesSelected); 84 | } 85 | 86 | /* (non-Javadoc) 87 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 88 | */ 89 | public Map getDescriptorParams(Map conditionParams) { 90 | Map params = new HashMap(); 91 | 92 | try { 93 | String strRolesSelected = extractSingleParam(conditionParams, "hidRolesList"); 94 | 95 | params.put("hidRolesList", strRolesSelected); 96 | } catch(IllegalArgumentException iae) { 97 | // Aggregate so that Transitions can be added. 98 | } 99 | 100 | return params; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/function/CopyValueFromOtherFieldPostFunction.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.function; 2 | 3 | 4 | import static com.googlecode.jsu.workflow.WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory.PARAM_SOURCE_FIELD; 5 | import static com.googlecode.jsu.workflow.WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory.PARAM_DEST_FIELD; 6 | import static com.googlecode.jsu.workflow.WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory.PARAM_COPY_TYPE; 7 | 8 | import java.util.Map; 9 | 10 | import com.atlassian.jira.component.ComponentAccessor; 11 | import com.atlassian.jira.issue.MutableIssue; 12 | import com.atlassian.jira.issue.fields.Field; 13 | import com.atlassian.jira.issue.util.IssueChangeHolder; 14 | import com.atlassian.jira.user.ApplicationUser; 15 | import com.atlassian.jira.util.I18nHelper; 16 | import com.googlecode.jsu.util.WorkflowUtils; 17 | import com.opensymphony.module.propertyset.PropertySet; 18 | import com.opensymphony.workflow.WorkflowException; 19 | 20 | /** 21 | * @author Gustavo Martin 22 | * 23 | * This function copies the value from a field to another one. 24 | */ 25 | public class CopyValueFromOtherFieldPostFunction extends AbstractPreserveChangesPostFunction { 26 | private final WorkflowUtils workflowUtils; 27 | private final I18nHelper.BeanFactory beanFactory; 28 | 29 | public CopyValueFromOtherFieldPostFunction(WorkflowUtils workflowUtils, I18nHelper.BeanFactory beanFactory) { 30 | this.workflowUtils = workflowUtils; 31 | this.beanFactory = beanFactory; 32 | } 33 | 34 | /* (non-Javadoc) 35 | * @see com.googlecode.jsu.workflow.function.AbstractPreserveChangesPostFunction#executeFunction(java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet, com.atlassian.jira.issue.util.IssueChangeHolder) 36 | */ 37 | @Override 38 | protected void executeFunction( 39 | Map transientVars, Map args, 40 | PropertySet ps, IssueChangeHolder holder 41 | ) throws WorkflowException { 42 | String fieldFromKey = args.get(PARAM_SOURCE_FIELD); 43 | String fieldToKey = args.get(PARAM_DEST_FIELD); 44 | String copyType = args.get(PARAM_COPY_TYPE); 45 | 46 | Field fieldFrom = workflowUtils.getFieldFromKey(fieldFromKey); 47 | Field fieldTo = workflowUtils.getFieldFromKey(fieldToKey); 48 | 49 | String fieldFromName = (fieldFrom != null) ? fieldFrom.getName() : fieldFromKey; 50 | String fieldToName = (fieldTo != null) ? fieldTo.getName() : fieldToKey; 51 | 52 | try { 53 | ApplicationUser currentUser = getCallerUser(transientVars, args); 54 | MutableIssue issue = getIssue(transientVars); 55 | MutableIssue sourceIssue; 56 | MutableIssue destIssue; 57 | if("parent".equals(copyType)) { 58 | sourceIssue = (MutableIssue) issue.getParentObject(); 59 | destIssue = issue; 60 | if(sourceIssue==null) { 61 | log.debug("Issue (" + destIssue.getKey() + ") has no parent, wont do anything."); 62 | return; 63 | } 64 | } else { 65 | //either same, not set, past versions of this did not have this feature, they will be unset 66 | sourceIssue = issue; 67 | destIssue = issue; 68 | } 69 | 70 | // It gives the value from the source field. 71 | Object sourceValue = workflowUtils.getFieldValueFromIssue(sourceIssue, fieldFrom, true); 72 | 73 | if (log.isDebugEnabled()) { 74 | log.debug( 75 | String.format( 76 | "Copying value [%s] from issue %s field '%s' to issue %s field '%s'", 77 | sourceValue, 78 | sourceIssue.getKey(), 79 | fieldFromName, 80 | destIssue.getKey(), 81 | fieldToName 82 | ) 83 | ); 84 | } 85 | 86 | // It set the value to field. 87 | workflowUtils.setFieldValue(currentUser, destIssue, fieldToKey, sourceValue, holder); 88 | 89 | if (log.isDebugEnabled()) { 90 | log.debug("Value was successfully copied"); 91 | } 92 | } catch (Exception e) { 93 | I18nHelper i18nh = this.beanFactory.getInstance( 94 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()); 95 | String message = i18nh.getText("copyvaluefromfield-function-view.unable_to_copy",fieldFromName,fieldToName); 96 | log.error(message, e); 97 | throw new WorkflowException(message); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowWindowsDateValidatorPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.atlassian.jira.issue.fields.Field; 8 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 9 | import com.atlassian.jira.plugin.workflow.WorkflowPluginValidatorFactory; 10 | import com.googlecode.jsu.util.FieldCollectionsUtils; 11 | import com.googlecode.jsu.util.WorkflowUtils; 12 | import com.opensymphony.workflow.loader.AbstractDescriptor; 13 | import com.opensymphony.workflow.loader.ValidatorDescriptor; 14 | import org.apache.commons.lang.StringUtils; 15 | 16 | /** 17 | * @author Gustavo Martin. 18 | * 19 | * This class defines the parameters available for Windows Date Validator. 20 | * 21 | */ 22 | public class WorkflowWindowsDateValidatorPluginFactory extends 23 | AbstractWorkflowPluginFactory implements WorkflowPluginValidatorFactory { 24 | 25 | private final FieldCollectionsUtils fieldCollectionsUtils; 26 | private final WorkflowUtils workflowUtils; 27 | 28 | public WorkflowWindowsDateValidatorPluginFactory( 29 | FieldCollectionsUtils fieldCollectionsUtils, 30 | WorkflowUtils workflowUtils 31 | ) { 32 | this.fieldCollectionsUtils = fieldCollectionsUtils; 33 | this.workflowUtils = workflowUtils; 34 | } 35 | 36 | /* (non-Javadoc) 37 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 38 | */ 39 | protected void getVelocityParamsForInput(Map velocityParams) { 40 | List allDateFields = fieldCollectionsUtils.getAllDateFields(); 41 | 42 | velocityParams.put("val-date1FieldsList", allDateFields); 43 | velocityParams.put("val-date2FieldsList", allDateFields); 44 | } 45 | 46 | /* (non-Javadoc) 47 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 48 | */ 49 | protected void getVelocityParamsForEdit( 50 | Map velocityParams, 51 | AbstractDescriptor descriptor 52 | ) { 53 | getVelocityParamsForInput(velocityParams); 54 | 55 | ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor; 56 | Map args = validatorDescriptor.getArgs(); 57 | 58 | String date1 = (String) args.get("date1Selected"); 59 | String date2 = (String) args.get("date2Selected"); 60 | String windowsDays = (String) args.get("windowsDays"); 61 | 62 | velocityParams.put("val-date1Selected", workflowUtils.getFieldFromKey(date1)); 63 | velocityParams.put("val-date2Selected", workflowUtils.getFieldFromKey(date2)); 64 | velocityParams.put("val-windowsDays", windowsDays); 65 | 66 | } 67 | 68 | /* (non-Javadoc) 69 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 70 | */ 71 | protected void getVelocityParamsForView( 72 | Map velocityParams, 73 | AbstractDescriptor descriptor 74 | ) { 75 | ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor; 76 | Map args = validatorDescriptor.getArgs(); 77 | 78 | String date1 = (String) args.get("date1Selected"); 79 | String date2 = (String) args.get("date2Selected"); 80 | String windowsDays = (String) args.get("windowsDays"); 81 | 82 | velocityParams.put("val-date1Selected", workflowUtils.getFieldFromKey(date1)); 83 | velocityParams.put("val-date2Selected", workflowUtils.getFieldFromKey(date2)); 84 | velocityParams.put("val-windowsDays", windowsDays); 85 | 86 | } 87 | 88 | /* (non-Javadoc) 89 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 90 | */ 91 | public Map getDescriptorParams(Map validatorParams) { 92 | Map params = new HashMap(); 93 | 94 | try{ 95 | String date1 = extractSingleParam(validatorParams, "date1FieldsList"); 96 | String date2 = extractSingleParam(validatorParams, "date2FieldsList"); 97 | String windowsDays = extractSingleParam(validatorParams, "windowsDays"); 98 | if (StringUtils.isEmpty(windowsDays)) { 99 | windowsDays = "0"; 100 | } 101 | 102 | params.put("date1Selected", date1); 103 | params.put("date2Selected", date2); 104 | params.put("windowsDays", windowsDays); 105 | 106 | }catch(IllegalArgumentException iae){ 107 | // Aggregate so that Transitions can be added. 108 | } 109 | 110 | return params; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/helpers/ConditionCheckerFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.helpers; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedHashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.googlecode.jsu.helpers.checkers.CheckerCompositeFactory; 12 | 13 | /** 14 | * Return object for checking conditions. 15 | * 16 | * @author Alexey Abashev 17 | */ 18 | public class ConditionCheckerFactory { 19 | public static final ConditionType GREATER = new ConditionType(1, ">", "conditiontype.greater_than", "G"); 20 | public static final ConditionType GREATER_EQUAL = new ConditionType(2, ">=", "conditiontype.greater_than_or_equal_to", "GE"); 21 | public static final ConditionType EQUAL = new ConditionType(3, "=", "conditiontype.equal_to", "E"); 22 | public static final ConditionType LESS_EQUAL = new ConditionType(4, "<=", "conditiontype.less_than_or_equal_to", "LE"); 23 | public static final ConditionType LESS = new ConditionType(5, "<", "contitiontype.less_than", "L"); 24 | public static final ConditionType NOT_EQUAL = new ConditionType(6, "!=", "conditiontype.not_equal_to", "NE"); 25 | 26 | public static final ComparisonType STRING = new ComparisonType(1, "comparisontype.string", "String"); 27 | public static final ComparisonType NUMBER = new ComparisonType(2, "comparisontype.number", "Number"); 28 | public static final ComparisonType DATE = new ComparisonType(3, "comparisontype.date_with_time", "Date"); 29 | public static final ComparisonType DATE_WITHOUT_TIME = new ComparisonType(4, "comparisontype.date_without_time", "DateWithoutTime"); 30 | public static final ComparisonType OPTIONID = new ComparisonType(5, "comparisontype.optionid", "OptionID"); 31 | 32 | /** Template for checker class. */ 33 | private static final String PACKAGE = ConditionCheckerFactory.class.getPackage().getName(); 34 | private static final String CONDITION_CLASS_TEMPLATE = PACKAGE + ".checkers.Snipet"; 35 | private static final String COMPARISON_CLASS_TEMPLATE = PACKAGE + ".checkers.Converter"; 36 | 37 | /** Cache for searching through conditions */ 38 | @SuppressWarnings("serial") 39 | private static final Map CONDITIONS_CACHE = 40 | new LinkedHashMap(6) {{ 41 | put(GREATER.getId(), GREATER); 42 | put(GREATER_EQUAL.getId(), GREATER_EQUAL); 43 | put(EQUAL.getId(), EQUAL); 44 | put(LESS_EQUAL.getId(), LESS_EQUAL); 45 | put(LESS.getId(), LESS); 46 | put(NOT_EQUAL.getId(), NOT_EQUAL); 47 | }}; 48 | 49 | /** Cache for searching through types */ 50 | @SuppressWarnings("serial") 51 | private static final Map COMPARISONS_CACHE = 52 | new LinkedHashMap(4) {{ 53 | put(STRING.getId(), STRING); 54 | put(NUMBER.getId(), NUMBER); 55 | put(DATE.getId(), DATE); 56 | put(DATE_WITHOUT_TIME.getId(), DATE_WITHOUT_TIME); 57 | put(OPTIONID.getId(),OPTIONID); 58 | }}; 59 | 60 | private final Logger log = LoggerFactory.getLogger(ConditionCheckerFactory.class); 61 | private final CheckerCompositeFactory checkerCompositeFactory = new CheckerCompositeFactory(); 62 | 63 | public ConditionChecker getChecker(ComparisonType type, ConditionType condition) { 64 | String conditionClassName = CONDITION_CLASS_TEMPLATE + condition.getMnemonic(); 65 | String comparisonClassName = COMPARISON_CLASS_TEMPLATE + type.getMnemonic(); 66 | 67 | if (log.isDebugEnabled()) { 68 | log.debug( 69 | "Using class [" + conditionClassName + 70 | "] for condition [" + condition.getValue() + 71 | "]; class [" + comparisonClassName + 72 | "] for type [" + type.getValueKey() + 73 | "]" 74 | ); 75 | } 76 | 77 | return checkerCompositeFactory.getComposite(comparisonClassName, conditionClassName); 78 | } 79 | 80 | /** 81 | * Get all possible condition types. 82 | */ 83 | public List getConditionTypes() { 84 | return new ArrayList(CONDITIONS_CACHE.values()); 85 | } 86 | 87 | /** 88 | * Get all possible comparison types. 89 | */ 90 | public List getComparisonTypes() { 91 | 92 | List comparisonTypes = new ArrayList(COMPARISONS_CACHE.values()); 93 | return comparisonTypes; 94 | } 95 | 96 | /** 97 | * Find condition by id. 98 | */ 99 | public ConditionType findConditionById(String id) { 100 | return CONDITIONS_CACHE.get(Integer.valueOf(id)); 101 | } 102 | 103 | /** 104 | * Find comparison by id. 105 | */ 106 | public ComparisonType findComparisonById(String id) { 107 | return COMPARISONS_CACHE.get(Integer.valueOf(id)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowFieldsRequiredValidatorPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import com.atlassian.jira.issue.fields.Field; 9 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 10 | import com.atlassian.jira.plugin.workflow.WorkflowPluginValidatorFactory; 11 | import com.googlecode.jsu.util.FieldCollectionsUtils; 12 | import com.googlecode.jsu.util.WorkflowUtils; 13 | import com.opensymphony.workflow.loader.AbstractDescriptor; 14 | import com.opensymphony.workflow.loader.ValidatorDescriptor; 15 | import org.apache.commons.lang3.StringUtils; 16 | 17 | /** 18 | * This class defines the parameters available for Fields Required Validator. 19 | * 20 | * @author Gustavo Martin. 21 | */ 22 | public class WorkflowFieldsRequiredValidatorPluginFactory 23 | extends AbstractWorkflowPluginFactory 24 | implements WorkflowPluginValidatorFactory { 25 | 26 | public static final String SELECTED_FIELDS = "hidFieldsList"; 27 | public static final String CONTEXT_HANDLING = "contextHandling"; 28 | 29 | private final FieldCollectionsUtils fieldCollectionsUtils; 30 | private final WorkflowUtils workflowUtils; 31 | 32 | public WorkflowFieldsRequiredValidatorPluginFactory( 33 | FieldCollectionsUtils fieldCollectionsUtils, 34 | WorkflowUtils workflowUtils 35 | ) { 36 | this.fieldCollectionsUtils = fieldCollectionsUtils; 37 | this.workflowUtils = workflowUtils; 38 | } 39 | 40 | /* (non-Javadoc) 41 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 42 | */ 43 | protected void getVelocityParamsForInput(Map velocityParams) { 44 | List allFields = fieldCollectionsUtils.getRequirableFields(); 45 | 46 | velocityParams.put("val-fieldsList", allFields); 47 | velocityParams.put("val-splitter", WorkflowUtils.SPLITTER); 48 | } 49 | 50 | /* (non-Javadoc) 51 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 52 | */ 53 | protected void getVelocityParamsForEdit( 54 | Map velocityParams, AbstractDescriptor descriptor 55 | ) { 56 | getVelocityParamsForInput(velocityParams); 57 | 58 | ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor; 59 | Map args = validatorDescriptor.getArgs(); 60 | 61 | velocityParams.remove("val-fieldsList"); 62 | 63 | Collection fieldsSelected = getSelectedFields(args); 64 | List allFields = fieldCollectionsUtils.getRequirableFields(); 65 | 66 | String contextHandling = (String) args.get(CONTEXT_HANDLING); 67 | 68 | allFields.removeAll(fieldsSelected); 69 | 70 | velocityParams.put("val-fieldsListSelected", fieldsSelected); 71 | velocityParams.put("val-hidFieldsList", workflowUtils.getStringField(fieldsSelected, WorkflowUtils.SPLITTER)); 72 | velocityParams.put("val-fieldsList", allFields); 73 | velocityParams.put("val-contextHandling", contextHandling); 74 | } 75 | 76 | /* (non-Javadoc) 77 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 78 | */ 79 | protected void getVelocityParamsForView( 80 | Map velocityParams, AbstractDescriptor descriptor 81 | ) { 82 | ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor; 83 | Map args = validatorDescriptor.getArgs(); 84 | 85 | velocityParams.put("val-fieldsListSelected", getSelectedFields(args)); 86 | velocityParams.put("val-contextHandling", args.get(CONTEXT_HANDLING)); 87 | } 88 | 89 | /* (non-Javadoc) 90 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 91 | */ 92 | public Map getDescriptorParams(Map validatorParams) { 93 | Map params = new HashMap(); 94 | String strFieldsSelected = extractSingleParam(validatorParams, SELECTED_FIELDS); 95 | String contextHandling = null; 96 | if(validatorParams.containsKey(CONTEXT_HANDLING)) { 97 | contextHandling = extractSingleParam(validatorParams, CONTEXT_HANDLING); 98 | } 99 | 100 | params.put(SELECTED_FIELDS, strFieldsSelected); 101 | params.put(CONTEXT_HANDLING, contextHandling); 102 | 103 | return params; 104 | } 105 | 106 | /** 107 | * Get fields were selected in UI. 108 | */ 109 | public Collection getSelectedFields(Map args) { 110 | String strFieldsSelected = (String) args.get(SELECTED_FIELDS); 111 | 112 | return workflowUtils.getFields(strFieldsSelected, WorkflowUtils.SPLITTER); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowUpdateIssueCustomFieldFunctionPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.atlassian.jira.issue.CustomFieldManager; 8 | import com.atlassian.jira.issue.fields.CustomField; 9 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 10 | import com.atlassian.jira.plugin.workflow.WorkflowPluginFunctionFactory; 11 | import com.opensymphony.workflow.loader.AbstractDescriptor; 12 | import com.opensymphony.workflow.loader.FunctionDescriptor; 13 | import org.apache.commons.lang3.StringUtils; 14 | 15 | /** 16 | * Class responsible for setting up all that is necessary for the execution of 17 | * the plugin. 18 | * 19 | * @author Cristiane Fontana 20 | * @version 1.0 Plugin creation. 21 | */ 22 | public class WorkflowUpdateIssueCustomFieldFunctionPluginFactory extends 23 | AbstractWorkflowPluginFactory implements WorkflowPluginFunctionFactory { 24 | 25 | public static final String PARAM_FIELD_ID = "fieldId"; 26 | public static final String PARAM_FIELD_VALUE = "fieldValue"; 27 | public static final String TARGET_FIELD_NAME = "field.name"; 28 | public static final String TARGET_FIELD_VALUE = "field.value"; 29 | public static final String PARAM_APPEND_VALUE = "appendValue"; 30 | public static final String TARGET_APPEND_VALUE = "append.value"; 31 | 32 | private final CustomFieldManager customFieldManager; 33 | 34 | public WorkflowUpdateIssueCustomFieldFunctionPluginFactory(CustomFieldManager customFieldManager) { 35 | this.customFieldManager = customFieldManager; 36 | } 37 | 38 | public Map getDescriptorParams(Map conditionParams) { 39 | Map params = new HashMap(); 40 | 41 | String fieldId = extractSingleParam(conditionParams, PARAM_FIELD_ID); 42 | params.put(TARGET_FIELD_NAME, fieldId); 43 | 44 | String fieldValue = extractSingleParam(conditionParams, PARAM_FIELD_VALUE); 45 | params.put(TARGET_FIELD_VALUE, fieldValue.trim()); 46 | 47 | if(conditionParams.containsKey(PARAM_APPEND_VALUE)) { 48 | String appendValue = extractSingleParam(conditionParams, PARAM_APPEND_VALUE); 49 | if(!StringUtils.isBlank(appendValue)) { 50 | params.put(TARGET_APPEND_VALUE, appendValue.trim()); 51 | } 52 | } 53 | 54 | return params; 55 | } 56 | 57 | protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) { 58 | getVelocityParamsForInput(velocityParams); 59 | 60 | if (!(descriptor instanceof FunctionDescriptor)) { 61 | throw new IllegalArgumentException("Descriptor must be a FunctionDescriptor."); 62 | } 63 | 64 | FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; 65 | 66 | velocityParams.put(PARAM_FIELD_ID, functionDescriptor.getArgs().get(TARGET_FIELD_NAME)); 67 | 68 | String value = (String) functionDescriptor.getArgs().get(TARGET_FIELD_VALUE); 69 | 70 | if (value == null || value.equals("null")) { 71 | velocityParams.put(PARAM_FIELD_VALUE, null); 72 | } else { 73 | velocityParams.put(PARAM_FIELD_VALUE, value.trim()); 74 | } 75 | 76 | String appendValue = (String) functionDescriptor.getArgs().get(TARGET_APPEND_VALUE); 77 | 78 | if (appendValue == null) { 79 | velocityParams.put(PARAM_APPEND_VALUE, null); 80 | } else { 81 | velocityParams.put(PARAM_APPEND_VALUE, appendValue.trim()); 82 | } 83 | } 84 | 85 | protected void getVelocityParamsForInput(Map velocityParams) { 86 | List customFields = customFieldManager.getCustomFieldObjects(); 87 | 88 | velocityParams.put("fields", customFields); 89 | } 90 | 91 | protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) { 92 | if (!(descriptor instanceof FunctionDescriptor)) { 93 | throw new IllegalArgumentException("Descriptor must be a FunctionDescriptor."); 94 | } else { 95 | FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; 96 | 97 | String fieldName = (String) functionDescriptor.getArgs().get(TARGET_FIELD_NAME); 98 | 99 | velocityParams.put( 100 | PARAM_FIELD_ID, 101 | customFieldManager.getCustomFieldObject(fieldName).getNameKey() 102 | ); 103 | velocityParams.put( 104 | PARAM_FIELD_VALUE, 105 | functionDescriptor.getArgs().get(TARGET_FIELD_VALUE) 106 | ); 107 | 108 | String appendValue = (String) functionDescriptor.getArgs().get(TARGET_APPEND_VALUE); 109 | if(!StringUtils.isBlank(appendValue)) { 110 | velocityParams.put(PARAM_APPEND_VALUE, appendValue.trim()); 111 | } 112 | 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowUserIsInCustomFieldConditionPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.atlassian.jira.issue.CustomFieldManager; 7 | import com.atlassian.jira.issue.fields.Field; 8 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 9 | import com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory; 10 | import com.googlecode.jsu.util.WorkflowUtils; 11 | import com.opensymphony.workflow.loader.AbstractDescriptor; 12 | import com.opensymphony.workflow.loader.ConditionDescriptor; 13 | 14 | /** 15 | * @author Anton Afanassiev 16 | */ 17 | public class WorkflowUserIsInCustomFieldConditionPluginFactory 18 | extends AbstractWorkflowPluginFactory 19 | implements WorkflowPluginConditionFactory { 20 | 21 | private static final String ALLOW_USER_IN_FIELD = "allowUserInField"; 22 | 23 | private final CustomFieldManager customFieldManager; 24 | private final WorkflowUtils workflowUtils; 25 | 26 | public WorkflowUserIsInCustomFieldConditionPluginFactory( 27 | CustomFieldManager customFieldManager, 28 | WorkflowUtils workflowUtils 29 | ) { 30 | this.customFieldManager = customFieldManager; 31 | this.workflowUtils = workflowUtils; 32 | } 33 | 34 | /* (non-Javadoc) 35 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 36 | */ 37 | protected void getVelocityParamsForInput(Map velocityParams) { 38 | velocityParams.put("val-fieldsList", customFieldManager.getCustomFieldObjects()); 39 | } 40 | 41 | /* (non-Javadoc) 42 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 43 | */ 44 | protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) { 45 | getVelocityParamsForInput(velocityParams); 46 | 47 | ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; 48 | Map args = conditionDescriptor.getArgs(); 49 | 50 | String sField = (String) args.get("fieldsList"); 51 | 52 | Field field = null; 53 | 54 | try { 55 | field = workflowUtils.getFieldFromKey(sField); 56 | } catch (Exception e) { 57 | } 58 | 59 | if (field != null) { 60 | velocityParams.put("val-fieldSelected", field); 61 | } 62 | 63 | boolean allowUserInField = getAllowUserInField(args); 64 | 65 | velocityParams.put("allowUserInField-selected", allowUserInField); 66 | } 67 | 68 | /* (non-Javadoc) 69 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 70 | */ 71 | protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) { 72 | ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; 73 | Map args = conditionDescriptor.getArgs(); 74 | 75 | String sField = (String) args.get("fieldsList"); 76 | 77 | Field field = null; 78 | 79 | try { 80 | field = workflowUtils.getFieldFromKey(sField); 81 | } catch (Exception e) { 82 | } 83 | 84 | if (field != null) { 85 | velocityParams.put("val-fieldSelected", field); 86 | } else { 87 | velocityParams.put("val-errorMessage", "Unable to find field '" + sField + "'"); 88 | } 89 | 90 | boolean allowUserInField = getAllowUserInField(args); 91 | 92 | velocityParams.put("allowUserInField-selected", allowUserInField); 93 | } 94 | 95 | /* (non-Javadoc) 96 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 97 | */ 98 | public Map getDescriptorParams(Map conditionParams) { 99 | Map params = new HashMap(); 100 | 101 | try { 102 | String field = extractSingleParam(conditionParams, "fieldsList"); 103 | String allowUser = extractSingleParam(conditionParams, ALLOW_USER_IN_FIELD); 104 | 105 | params.put("fieldsList", field); 106 | params.put(ALLOW_USER_IN_FIELD, allowUser); 107 | 108 | } catch(IllegalArgumentException iae) { 109 | // Aggregate so that Transitions can be added. 110 | } 111 | 112 | return params; 113 | } 114 | 115 | /** 116 | * Get allowUserInField parameter. Not use default valueOf to store previous behavior. 117 | */ 118 | public static boolean getAllowUserInField(Map args) { 119 | String param = (String) args.get(ALLOW_USER_IN_FIELD); 120 | 121 | if (param == null) { 122 | return true; 123 | } else { 124 | return Boolean.valueOf(param); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/validator/DateCompareValidator.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.validator; 2 | 3 | import com.atlassian.jira.config.properties.ApplicationProperties; 4 | import com.atlassian.jira.issue.fields.Field; 5 | import com.atlassian.jira.util.I18nHelper; 6 | import com.googlecode.jsu.annotation.Argument; 7 | import com.googlecode.jsu.helpers.ComparisonType; 8 | import com.googlecode.jsu.helpers.ConditionChecker; 9 | import com.googlecode.jsu.helpers.ConditionCheckerFactory; 10 | import com.googlecode.jsu.helpers.ConditionType; 11 | import com.googlecode.jsu.util.FieldCollectionsUtils; 12 | import com.googlecode.jsu.util.WorkflowUtils; 13 | import com.opensymphony.workflow.InvalidInputException; 14 | import com.opensymphony.workflow.WorkflowException; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.Calendar; 19 | import java.util.Date; 20 | 21 | import static com.googlecode.jsu.helpers.ConditionCheckerFactory.DATE; 22 | import static com.googlecode.jsu.helpers.ConditionCheckerFactory.DATE_WITHOUT_TIME; 23 | 24 | /** 25 | * This validator compare two datetime fields, using the given comparison type. 26 | * And returning an exception if it doesn't fulfill the condition. 27 | */ 28 | public class DateCompareValidator extends AbstractDateCompareValidator { 29 | @Argument("date1Selected") 30 | private String date1; 31 | 32 | @Argument("date2Selected") 33 | private String date2; 34 | 35 | @Argument("conditionSelected") 36 | private String conditionId; 37 | 38 | @Argument("includeTimeSelected") 39 | private String includeTimeValue; 40 | 41 | private final Logger log = LoggerFactory.getLogger(DateCompareValidator.class); 42 | 43 | private final ApplicationProperties applicationProperties; 44 | private final ConditionCheckerFactory conditionCheckerFactory; 45 | private final I18nHelper.BeanFactory beanFactory; 46 | 47 | public DateCompareValidator( 48 | ApplicationProperties applicationProperties, 49 | ConditionCheckerFactory conditionCheckerFactory, 50 | FieldCollectionsUtils fieldCollectionsUtils, 51 | WorkflowUtils workflowUtils, 52 | I18nHelper.BeanFactory beanFactory 53 | ) { 54 | super(applicationProperties,fieldCollectionsUtils, workflowUtils,beanFactory); 55 | 56 | this.applicationProperties = applicationProperties; 57 | this.conditionCheckerFactory = conditionCheckerFactory; 58 | this.beanFactory = beanFactory; 59 | } 60 | 61 | /* (non-Javadoc) 62 | * @see com.googlecode.jsu.workflow.validator.GenericValidator#validate() 63 | */ 64 | protected void validate() throws InvalidInputException, WorkflowException { 65 | Field field1 = workflowUtils.getFieldFromKey(date1); 66 | Field field2 = workflowUtils.getFieldFromKey(date2); 67 | 68 | ConditionType condition = conditionCheckerFactory.findConditionById(conditionId); 69 | boolean includeTime = Integer.parseInt(includeTimeValue) == 1; 70 | 71 | // Compare Dates. 72 | if ((field1 != null) && (field2 != null)) { 73 | Object objValue1 = workflowUtils.getFieldValueFromIssue(getIssue(), field1); 74 | Object objValue2 = workflowUtils.getFieldValueFromIssue(getIssue(), field2); 75 | Date objDate1, objDate2; 76 | 77 | try { 78 | objDate1 = (Date) objValue1; 79 | } catch (ClassCastException e) { 80 | wrongDataErrorMessage(field1, objValue1); 81 | 82 | return; 83 | } 84 | 85 | try { 86 | objDate2 = (Date) objValue2; 87 | } catch (ClassCastException e) { 88 | wrongDataErrorMessage(field2, objValue2); 89 | 90 | return; 91 | } 92 | 93 | if ((objDate1 != null) && (objDate2 != null)) { 94 | ComparisonType comparison = (includeTime) ? DATE : DATE_WITHOUT_TIME; 95 | ConditionChecker checker = conditionCheckerFactory.getChecker(comparison, condition); 96 | 97 | Calendar calDate1 = Calendar.getInstance(applicationProperties.getDefaultLocale()); 98 | Calendar calDate2 = Calendar.getInstance(applicationProperties.getDefaultLocale()); 99 | 100 | calDate1.setTime( objDate1); 101 | calDate2.setTime( objDate2); 102 | 103 | boolean result = checker.checkValues(calDate1, calDate2); 104 | 105 | if (log.isDebugEnabled()) { 106 | log.debug( 107 | "Compare field \"" + field1.getName() + 108 | "\" and field \"" + field2.getName() + 109 | "\" with values [" + calDate1 + 110 | "] and [" + calDate2 + 111 | "] with result " + result 112 | ); 113 | } 114 | 115 | if (!result) { 116 | generateErrorMessage(field1, objDate1, field2.getName(), objDate2, condition, includeTime); 117 | } 118 | } else { 119 | // If any of fields are null, validates if the field is required. Otherwise, doesn't throws an Exception. 120 | if (objDate1 == null) { 121 | validateRequired(field1); 122 | } 123 | 124 | if (objDate2 == null) { 125 | validateRequired(field2); 126 | } 127 | } 128 | } else { 129 | log.error("Unable to find field with ids [" + date1 + "] and [" + date2 + "]"); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow; 2 | 3 | import java.util.*; 4 | 5 | import com.atlassian.jira.issue.fields.Field; 6 | import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory; 7 | import com.atlassian.jira.plugin.workflow.WorkflowPluginFunctionFactory; 8 | import com.googlecode.jsu.util.FieldCollectionsUtils; 9 | import com.googlecode.jsu.util.WorkflowUtils; 10 | import com.opensymphony.workflow.loader.AbstractDescriptor; 11 | import com.opensymphony.workflow.loader.FunctionDescriptor; 12 | 13 | /** 14 | * This class defines the parameters available for Copy Value From Other Field Post Function. 15 | * 16 | * @author Gustavo Martin. 17 | */ 18 | public class WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginFunctionFactory { 19 | 20 | public static final String PARAM_SOURCE_FIELD = "sourceField"; 21 | public static final String PARAM_DEST_FIELD = "destinationField"; 22 | public static final String PARAM_COPY_TYPE = "copyType"; 23 | private static final String VALUE_SOURCE_LIST = "val-sourceFieldsList"; 24 | private static final String VALUE_DEST_LIST = "val-destinationFieldsList"; 25 | private static final String VALUE_SOURCE_SELECTED = "val-sourceFieldSelected"; 26 | private static final String VALUE_DEST_SELECTED = "val-destinationFieldSelected"; 27 | private static final String VALUE_COPY_TYPE = "val-copyType"; 28 | 29 | 30 | private final FieldCollectionsUtils fieldCollectionsUtils; 31 | private final WorkflowUtils workflowUtils; 32 | 33 | public WorkflowCopyValueFromOtherFieldPostFunctionPluginFactory( 34 | FieldCollectionsUtils fieldCollectionsUtils, 35 | WorkflowUtils workflowUtils 36 | ) { 37 | this.fieldCollectionsUtils = fieldCollectionsUtils; 38 | this.workflowUtils = workflowUtils; 39 | } 40 | 41 | /* (non-Javadoc) 42 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForInput(java.util.Map) 43 | */ 44 | protected void getVelocityParamsForInput(Map velocityParams) { 45 | List sourceFields = fieldCollectionsUtils.getFieldContainers(fieldCollectionsUtils.getCopyFromFields()); 46 | List destinationFields = fieldCollectionsUtils.getFieldContainers(fieldCollectionsUtils.getCopyToFields()); 47 | 48 | velocityParams.put(VALUE_SOURCE_LIST, Collections.unmodifiableList(sourceFields)); 49 | velocityParams.put(VALUE_DEST_LIST, Collections.unmodifiableList(destinationFields)); 50 | } 51 | 52 | /* (non-Javadoc) 53 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForEdit(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 54 | */ 55 | protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) { 56 | getVelocityParamsForInput(velocityParams); 57 | 58 | Field sourceFieldId = workflowUtils.getFieldFromDescriptor(descriptor, PARAM_SOURCE_FIELD); 59 | Field destinationField = workflowUtils.getFieldFromDescriptor(descriptor, PARAM_DEST_FIELD); 60 | 61 | velocityParams.put(VALUE_SOURCE_SELECTED, sourceFieldId); 62 | velocityParams.put(VALUE_DEST_SELECTED, destinationField); 63 | 64 | FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; 65 | velocityParams.put(VALUE_COPY_TYPE,getCopyType(functionDescriptor)); 66 | } 67 | 68 | /* (non-Javadoc) 69 | * @see com.googlecode.jsu.workflow.AbstractWorkflowPluginFactory#getVelocityParamsForView(java.util.Map, com.opensymphony.workflow.loader.AbstractDescriptor) 70 | */ 71 | protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) { 72 | Field sourceFieldId = workflowUtils.getFieldFromDescriptor(descriptor, PARAM_SOURCE_FIELD); 73 | Field destinationField = workflowUtils.getFieldFromDescriptor(descriptor, PARAM_DEST_FIELD); 74 | 75 | velocityParams.put(VALUE_SOURCE_SELECTED, sourceFieldId); 76 | velocityParams.put(VALUE_DEST_SELECTED, destinationField); 77 | 78 | FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; 79 | velocityParams.put(VALUE_COPY_TYPE,getCopyType(functionDescriptor)); 80 | } 81 | 82 | /* (non-Javadoc) 83 | * @see com.googlecode.jsu.workflow.WorkflowPluginFactory#getDescriptorParams(java.util.Map) 84 | */ 85 | public Map getDescriptorParams(Map conditionParams) { 86 | Map params = new HashMap(); 87 | 88 | try{ 89 | String sourceField = extractSingleParam(conditionParams, "sourceFieldsList"); 90 | String destinationField = extractSingleParam(conditionParams, "destinationFieldsList"); 91 | String copyType = extractSingleParam(conditionParams, PARAM_COPY_TYPE); 92 | 93 | params.put(PARAM_SOURCE_FIELD, sourceField); 94 | params.put(PARAM_DEST_FIELD, destinationField); 95 | params.put(PARAM_COPY_TYPE, copyType); 96 | } catch (IllegalArgumentException iae) { 97 | // Aggregate so that Transitions can be added. 98 | } 99 | 100 | return params; 101 | } 102 | 103 | private String getCopyType(FunctionDescriptor functionDescriptor) { 104 | String value = (String) functionDescriptor.getArgs().get(PARAM_COPY_TYPE); 105 | 106 | if (value == null || value.equals("null")) { 107 | return "same"; 108 | } else { 109 | return value; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsu/workflow/validator/AbstractDateCompareValidator.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsu.workflow.validator; 2 | 3 | import com.atlassian.jira.component.ComponentAccessor; 4 | import com.atlassian.jira.config.properties.APKeys; 5 | import com.atlassian.jira.config.properties.ApplicationProperties; 6 | import com.atlassian.jira.issue.Issue; 7 | import com.atlassian.jira.issue.fields.Field; 8 | import com.atlassian.jira.util.I18nHelper; 9 | import com.googlecode.jsu.helpers.ConditionType; 10 | import com.googlecode.jsu.util.FieldCollectionsUtils; 11 | import com.googlecode.jsu.util.WorkflowUtils; 12 | import com.opensymphony.workflow.InvalidInputException; 13 | import com.opensymphony.workflow.WorkflowException; 14 | 15 | import java.text.SimpleDateFormat; 16 | 17 | /** 18 | * Abstract base class for date validators. 19 | */ 20 | public abstract class AbstractDateCompareValidator extends GenericValidator { 21 | 22 | private final ApplicationProperties applicationProperties; 23 | private final I18nHelper.BeanFactory beanFactory; 24 | 25 | public AbstractDateCompareValidator ( 26 | ApplicationProperties applicationProperties, 27 | FieldCollectionsUtils fieldCollectionsUtils, 28 | WorkflowUtils workflowUtils, 29 | I18nHelper.BeanFactory beanFactory 30 | ) { 31 | super(fieldCollectionsUtils, workflowUtils); 32 | 33 | this.applicationProperties = applicationProperties; 34 | this.beanFactory = beanFactory; 35 | } 36 | 37 | protected abstract void validate() throws InvalidInputException, WorkflowException; 38 | 39 | /** 40 | * @param fldDate 41 | * 42 | * Throws an Exception if the field is null, but it is required. 43 | */ 44 | protected void validateRequired(Field fldDate) { 45 | final Issue issue = getIssue(); 46 | 47 | if (fieldCollectionsUtils.isFieldRequired(issue, fldDate)) { 48 | String msg = this.beanFactory.getInstance( 49 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()) 50 | .getText("datecompare-validator-view.is_required", fldDate.getName()); 51 | this.setExceptionMessage( 52 | fldDate, 53 | msg, 54 | msg 55 | ); 56 | } 57 | } 58 | 59 | /** 60 | * @param dateField The date field to which the expression belongs to. 61 | * @param expression The expression value. 62 | * 63 | * Throws an Exception, because given expression is invalid (null or syntax) 64 | */ 65 | protected void invalidExpression(Field dateField, String expression) { 66 | String msg = this.beanFactory.getInstance( 67 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()) 68 | .getText("dateexpressioncompare-validator-view.is_invalid", expression); 69 | this.setExceptionMessage( 70 | dateField, 71 | msg, 72 | msg 73 | ); 74 | } 75 | 76 | protected void wrongDataErrorMessage( 77 | Field field, Object fieldValue 78 | ) { 79 | String msg = this.beanFactory.getInstance( 80 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()) 81 | .getText("datecompare-validator-view.not_a_date",field.getName(),fieldValue.toString()); 82 | this.setExceptionMessage( 83 | field, 84 | msg, 85 | msg 86 | ); 87 | } 88 | 89 | protected void generateErrorMessage( 90 | Field field1, Object fieldValue1, 91 | String nameOrValue, Object fieldValue2, 92 | ConditionType condition, boolean includeTime 93 | ) { 94 | // Formats date to current locale to display the Exception. 95 | SimpleDateFormat formatter; 96 | SimpleDateFormat defaultFormatter; 97 | 98 | if (includeTime) { 99 | defaultFormatter = new SimpleDateFormat( 100 | applicationProperties.getDefaultString(APKeys.JIRA_DATE_PICKER_JAVA_FORMAT) 101 | ); 102 | formatter = new SimpleDateFormat( 103 | applicationProperties.getDefaultString(APKeys.JIRA_DATE_PICKER_JAVA_FORMAT), 104 | applicationProperties.getDefaultLocale() 105 | ); 106 | }else{ 107 | defaultFormatter = new SimpleDateFormat( 108 | applicationProperties.getDefaultString(APKeys.JIRA_DATE_TIME_PICKER_JAVA_FORMAT) 109 | ); 110 | formatter = new SimpleDateFormat( 111 | applicationProperties.getDefaultString(APKeys.JIRA_DATE_TIME_PICKER_JAVA_FORMAT), 112 | applicationProperties.getDefaultLocale() 113 | ); 114 | } 115 | 116 | String errorMsg; 117 | 118 | try{ 119 | errorMsg = " ( " + formatter.format(fieldValue2) + " )"; 120 | } catch (IllegalArgumentException e) { 121 | try { 122 | errorMsg = " ( " + defaultFormatter.format(fieldValue2) + " )"; 123 | } catch(Exception e1) { 124 | errorMsg = " ( " + fieldValue2 + " )"; 125 | } 126 | } 127 | 128 | I18nHelper i18nh = this.beanFactory.getInstance( 129 | ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()); 130 | String msg = i18nh.getText("datecompare-validator-view.is_not", 131 | field1.getName(),i18nh.getText(condition.getDisplayTextKey()),nameOrValue==null?"":nameOrValue,errorMsg); 132 | 133 | this.setExceptionMessage( 134 | field1, 135 | msg, 136 | msg 137 | ); 138 | } 139 | } 140 | --------------------------------------------------------------------------------