Commits

James Winters committed a03c1d2

Initial commit

  • Participants

Comments (0)

Files changed (12)

+Copyright (c) 2011, Atlassian Pty Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of Atlassian nor the names of its contributors may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+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 HOLDER 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.
+You have successfully created a plugin using the JIRA plugin archetype!
+
+Here are the SDK commands you'll use immediately:
+
+* atlas-run   -- installs this plugin into JIRA and starts it on http://localhost:2990/jira
+* atlas-debug -- same as atlas-run, but allows a debugger to attach at port 5005
+* atlas-cli   -- after atlas-run or atlas-debug, opens a Maven command line window:
+                 - 'pi' reinstalls the plugin into the running JIRA instance
+* atlas-help  -- prints description for all commands in the SDK
+
+Full documentation is always available at:
+
+http://confluence.atlassian.com/display/DEVNET/Developing+your+Plugin+using+the+Atlassian+Plugin+SDK
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.example.plugins.tutorial</groupId>
+  <artifactId>add-workflow-extensions</artifactId>
+  <packaging>atlassian-plugin</packaging>
+  <name>add-workflow-extensions</name>
+  <version>1.0</version>
+  <description>This is the com.example.plugins.tutorial:add-workflow-extensions plugin for Atlassian JIRA.</description>
+  <organization>
+    <name>Example Company</name>
+    <url>http://www.example.com/</url>
+  </organization>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>com.atlassian.maven.plugins</groupId>
+        <artifactId>maven-jira-plugin</artifactId>
+        <version>3.7</version>
+        <extensions>true</extensions>
+        <configuration>
+          <productVersion>${jira.version}</productVersion>
+          <productDataVersion>${jira.version}</productDataVersion>
+          <productDataPath>${basedir}/src/test/resources/generated-test-resources.zip</productDataPath>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>com.atlassian.jira</groupId>
+      <artifactId>jira-api</artifactId>
+      <version>${jira.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.atlassian.jira</groupId>
+      <artifactId>jira-tests</artifactId>
+      <version>${jira.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.atlassian.jira</groupId>
+      <artifactId>jira-func-tests</artifactId>
+      <version>${jira.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.atlassian.jira</groupId>
+      <artifactId>jira-core</artifactId>
+      <version>${jira.version}</version>
+      <scope>test</scope>
+  </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>1.8.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <properties>
+    <amps.version>3.7</amps.version>
+    <jira.version>5.0-beta3</jira.version>
+  </properties>
+</project>
+

File src/main/java/com/example/plugins/tutorial/jira/workflow/ParentIssueBlockingCondition.java

+package com.example.plugins.tutorial.jira.workflow;
+
+import com.atlassian.jira.component.ComponentAccessor;
+import com.atlassian.jira.issue.Issue;
+import com.atlassian.jira.workflow.WorkflowFunctionUtils;
+import com.atlassian.jira.workflow.condition.AbstractJiraCondition;
+import com.opensymphony.module.propertyset.PropertySet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.StringTokenizer;
+
+public class ParentIssueBlockingCondition extends AbstractJiraCondition
+{
+    private static final Logger log = LoggerFactory.getLogger(ParentIssueBlockingCondition.class);
+
+    public boolean passesCondition(Map transientVars, Map args, PropertySet ps)
+    {
+        Issue subTask = (Issue) transientVars.get(WorkflowFunctionUtils.ORIGINAL_ISSUE_KEY);
+
+        // Retrieve the parent issue
+        Issue parentIssue = ComponentAccessor.getIssueManager().getIssueObject(subTask.getParentId());
+
+        if (parentIssue == null)
+        {
+            return false;
+        }
+
+        // Comma separated list of status ids
+        String statuses = (String) args.get("statuses");
+        StringTokenizer st = new StringTokenizer(statuses, ",");
+
+        // Check if the parent issue is associated with one the specified statuses.
+        while(st.hasMoreTokens())
+        {
+            String statusId = st.nextToken();
+
+            if (parentIssue.getStatusObject().getId().equals(statusId))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}

File src/main/java/com/example/plugins/tutorial/jira/workflow/ParentIssueBlockingConditionFactory.java

+package com.example.plugins.tutorial.jira.workflow;
+
+import com.atlassian.jira.config.ConstantsManager;
+import com.atlassian.jira.issue.comparator.ConstantsComparator;
+import com.atlassian.jira.issue.status.Status;
+import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory;
+import com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory;
+import com.atlassian.jira.util.collect.MapBuilder;
+import com.opensymphony.workflow.loader.AbstractDescriptor;
+import com.opensymphony.workflow.loader.ConditionDescriptor;
+
+import java.util.*;
+
+/*
+This is the factory class responsible for dealing with the UI for the post-function.
+This is typically where you put default values into the velocity context and where you store user input.
+ */
+
+public class ParentIssueBlockingConditionFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginConditionFactory
+{
+    private final ConstantsManager constantsManager;
+
+    public ParentIssueBlockingConditionFactory(ConstantsManager constantsManager)
+    {
+        this.constantsManager = constantsManager;
+    }
+
+    protected void getVelocityParamsForInput(Map velocityParams)
+    {
+        //all available statuses
+        Collection<Status> statuses = constantsManager.getStatusObjects();
+        velocityParams.put("statuses", Collections.unmodifiableCollection(statuses));
+    }
+
+    protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor)
+    {
+        getVelocityParamsForInput(velocityParams);
+        velocityParams.put("selectedStatuses", getSelectedStatusIds(descriptor));
+    }
+
+    protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor)
+    {
+        Collection selectedStatusIds = getSelectedStatusIds(descriptor);
+        List selectedStatuses = new LinkedList();
+        for (Iterator iterator = selectedStatusIds.iterator(); iterator.hasNext();)
+        {
+            String statusId = (String) iterator.next();
+            Status selectedStatus = constantsManager.getStatusObject(statusId);
+            if (selectedStatus != null)
+            {
+                selectedStatuses.add(selectedStatus);
+            }
+        }
+        // Sort the list of statuses so as they are displayed consistently
+        Collections.sort(selectedStatuses, new ConstantsComparator());
+
+        velocityParams.put("statuses", Collections.unmodifiableCollection(selectedStatuses));
+    }
+
+    public Map getDescriptorParams(Map conditionParams)
+    {
+        //  process the map which will contain the request parameters
+        //  for now simply concatenate into a comma separated string
+        // production code would do something more robust, for starters it would remove the params
+        // you are not  interested in, like atl_token and workflowMode
+        Collection statusIds = conditionParams.keySet();
+        StringBuffer statIds = new StringBuffer();
+
+        for (Iterator iterator = statusIds.iterator(); iterator.hasNext();)
+        {
+            statIds.append((String) iterator.next() + ",");
+        }
+
+        return MapBuilder.build("statuses", statIds.substring(0, statIds.length() - 1));
+    }
+
+    private Collection getSelectedStatusIds(AbstractDescriptor descriptor)
+    {
+        Collection selectedStatusIds = new LinkedList();
+        if (!(descriptor instanceof ConditionDescriptor))
+        {
+            throw new IllegalArgumentException("Descriptor must be a ConditionDescriptor.");
+        }
+
+        ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor;
+
+        String statuses = (String) conditionDescriptor.getArgs().get("statuses");
+        StringTokenizer st = new StringTokenizer(statuses, ",");
+
+        while (st.hasMoreTokens())
+        {
+            selectedStatusIds.add(st.nextToken());
+        }
+
+        return selectedStatusIds;
+    }
+
+}

File src/main/resources/atlassian-plugin.properties

+#
+#Wed Nov 09 18:48:31 EST 2011
+parent-issue-blocking-condition.description=The Parent Issue Blocking Condition Plugin
+parent-issue-blocking-condition.name=Parent Issue Blocking Condition

File src/main/resources/atlassian-plugin.xml

+<?xml version="1.0" encoding="UTF-8"?>
+
+<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
+  <plugin-info>
+    <description>${project.description}</description>
+    <version>${project.version}</version>
+    <vendor name="${project.organization.name}" url="${project.organization.url}"/>
+  </plugin-info>
+  <workflow-condition key="parent-issue-blocking-condition" name="Parent Issue Blocking Condition" i18n-name-key="parent-issue-blocking-condition.name" class="com.example.plugins.tutorial.jira.workflow.ParentIssueBlockingConditionFactory">
+    <description key="parent-issue-blocking-condition.description">Sub tasks cannnot be reopened if the parent issue is closed.</description>
+    <condition-class>com.example.plugins.tutorial.jira.workflow.ParentIssueBlockingCondition</condition-class>
+    <resource type="velocity" name="view" location="templates/conditions/parent-issue-blocking-condition.vm"/>
+    <resource type="velocity" name="input-parameters" location="templates/conditions/parent-issue-blocking-condition-input.vm"/>
+    <resource type="velocity" name="edit-parameters" location="templates/conditions/parent-issue-blocking-condition-input.vm"/>
+  </workflow-condition>
+  <resource type="i18n" name="i18n" location="atlassian-plugin"/>
+</atlassian-plugin>

File src/main/resources/templates/conditions/parent-issue-blocking-condition-input.vm

+<tr bgcolor="ffffff">
+    <td align="right" valign="top" bgcolor="fffff0">
+        <span class="label">Statuses:</span>
+    </td>
+    <td bgcolor="ffffff" nowrap>
+        <table cellpadding="2" cellspacing="2">
+        #foreach ($status in $statuses)
+            <tr>
+                <td><input type="checkbox" name="$status.getId()"
+                #if (${selectedStatuses})
+                    #if (${selectedStatuses.contains($status.getId())})
+                    CHECKED
+                    #end
+                #end
+                ></td>
+                <td>#displayConstantIcon ($status)&nbsp;$status.getName()</td>
+            </tr>
+        #end
+        </table>
+        <br><font size="1">The parent issue statuses required to allow sub-task issue transitions.</font>
+    </td>
+</tr>

File src/main/resources/templates/conditions/parent-issue-blocking-condition.vm

+The parent issue must have one of the following statuses to allow sub-task transitions:
+#foreach ($status in $statuses)<b>$status.getName()</b>#if($velocityCount != $statuses.size())#if($velocityCount == ($statuses.size() - 1))&nbsp;or&nbsp;#else,&nbsp;#end#else.#end#end

File src/test/java/com/example/plugins/tutorial/jira/workflow/AbstractWorkflowTest.java

+package com.example.plugins.tutorial.jira.workflow;
+
+import com.atlassian.jira.issue.IssueManager;
+import com.atlassian.jira.issue.MutableIssue;
+import com.atlassian.jira.issue.status.Status;
+import org.junit.Before;
+
+import java.util.Map;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ *  This acts as the base test for all our workflow tests and it simply is used to create some common mocks
+ *
+ * @since v5.0
+ */
+public abstract class AbstractWorkflowTest {
+    protected MutableIssue mockParentIssue, mockSubTaskIssue;
+    protected IssueManager mockIssueManager;
+    protected Map transientVars, args;
+    protected Status mockStatus;
+
+    @Before
+    public void setupMocks()
+    {
+        createMocks();
+        stubMockMethods();
+    }
+
+    private void createMocks() {
+        mockIssueManager = mock(IssueManager.class);
+        mockSubTaskIssue = mock(MutableIssue.class);
+        mockParentIssue = mock(MutableIssue.class);
+        mockStatus = mock(Status.class);
+    }
+
+    private void stubMockMethods() {
+        when(mockSubTaskIssue.getParentId()).thenReturn(1L);
+        when(mockParentIssue.getId()).thenReturn(1L);
+        when(mockParentIssue.getStatusObject()).thenReturn(mockStatus);
+        when(mockIssueManager.getIssueObject(1L)).thenReturn(mockParentIssue);
+    }
+}

File src/test/java/com/example/plugins/tutorial/jira/workflow/ParentIssueBlockingConditionTest.java

+package com.example.plugins.tutorial.jira.workflow;
+
+import com.atlassian.jira.component.ComponentAccessor;
+import com.atlassian.jira.issue.IssueManager;
+import com.atlassian.jira.mock.component.MockComponentWorker;
+import com.atlassian.jira.util.collect.MapBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+public class ParentIssueBlockingConditionTest  extends AbstractWorkflowTest
+{
+    private ParentIssueBlockingCondition condition;
+
+
+    @Before
+    public void setup()
+    {
+        condition = new ParentIssueBlockingCondition();
+        transientVars = MapBuilder.build("originalissueobject", mockSubTaskIssue);
+        args = MapBuilder.build("statuses", "1,2,3");
+        ComponentAccessor.initialiseWorker(new MockComponentWorker().addMock(IssueManager.class, mockIssueManager));
+    }
+
+    @Test
+    public void testPassesCondition() throws Exception
+    {
+
+        when(mockStatus.getId()).thenReturn("3");
+        assertTrue("condition should pass",condition.passesCondition(transientVars, args, null));
+    }
+
+    @Test
+    public void testFailsCondition() throws Exception
+    {
+        when(mockStatus.getId()).thenReturn("4");
+        assertFalse("condition should fail",condition.passesCondition(transientVars, args, null));
+    }
+
+
+}

File src/test/resources/generated-test-resources.zip

Binary file added.