Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,360,057
Community Members
 
Community Events
168
Community Groups

Urgent Help: After Upgrading Jira 7.0.1 to 9.1.1 groovy script stops working and breaks with "Null"

Urgent Help: After Upgrading Jira 7.0.1 to 9.1.1 groovy script stops working and breaks with "Null" values

 

I have this groovy in a post fuction and it stopped working after migrating jira server 7.0.1 to Jira Server 9.1.1:

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
//import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.issue.comments.ComponentAccessor
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.user.util.UserManager
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.search.SearchProvider;
import com.atlassian.jira.jql.parser.JqlQueryParser;
import com.atlassian.jira.web.bean.PagerFilter;

String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";

String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo

// Nueva version
def int Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def int Remito_to_value = (issue.get("Remito To")); // Cota Superior
def int Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def int Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def int Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));


// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info("Valores de Entrada:")
log.info("Get Remito_from_value:" +Remito_from_value);
log.info("Get Remito_usado_value:" +Remito_usado_value);
log.info("Get Remito_to_value:" +Remito_to_value);
log.info("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info("");
log.info("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info("Error:No hay remitos disponibles,cota superior superada");
}


if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info("Remito Asignado A:" +Remito_id_value);
log.info("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);


Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);

 

Error:

There was an error during the execution of your script against issue CRM-336

Message:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'null' with class 'null' to class 'int'. Try 'java.lang.Integer' instead
Stack:
org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToNumber(DefaultTypeTransformation.java:175)
org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.intUnbox(DefaultTypeTransformation.java:81)
script_f4e3ffc3a1093c31ab9473ca1f131c62.run(script_f4e3ffc3a1093c31ab9473ca1
f131c62.groovy:31)

I think I have an error in the "import".

3 answers

Hi @Diego Cañete , is it script for Script Runner console? I can't believe that your script worked in earlier version because for example method

issue.get("XXX)

is not valid method for https://docs.atlassian.com/software/jira/docs/api/7.1.2/com/atlassian/jira/issue/Issue.html

When you upgraded from v7 to v9 you can have problem with version of groovy and version of Java installed on your environment. Can you provide more information, please?

Hi Martin, 

Thank you for reply me.

 

  • Java version prior and after the upgrade:
  • Prior:1.8.0_51 After:11.0.13
  • JMWE version prior to upgrade: 4.0.2 I dont remeber well, but was old versión. (jia-misc-workflow-extension-4.0.2.jar)
  • Workflow XML export
  • What is your full use case?

This script is used to take the number of initial and final remittances and take the number of remittances used.

The parent ticket in this case is what we call Checkbook and the related subtasks are the Remittances, the remittances can be created as many as the number of remittances_to. This script controls that no other remittances are created in each Checkbook.

I am going to add an image of a Remittance and a Checkbook.

 

 

  • What are the custom field types of the following:
    • customfield_19010 : Text Field(single line)
    • customfield_13418 : Text Field(single line)
    • customfield_19011 :  Multi Select Searcher
    • Remito From: Number Field
    • Remito To: Number Field
    • Remito Numero:Number Field
    • Remito_Utilizado_R:Number Field
    • customfield_19018: Number Field

This before the update worked perfectly, you can see in the images that in the Talonarion recent remittances created appear.

Last try and somes updates in script:

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.opensymphony.workflow.InvalidInputException;
import java.sql.ResultSet;
import java.util.Map;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.WorkflowException;
//import groovy.sql.GroovyRowResult
//import groovy.sql.*
//import groovy.sql.Sql
//import java.sql.*;
//import java.sql.Connection;
//import java.sql.ResultSet;
//import java.sql.SQLException;
//import java.sql.Statement;

import org.apache.log4j.Logger;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";

String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo

// Nueva version
def Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def Remito_to_value = (issue.get("Remito To")); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));

// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info ("Valores de Entrada:")
log.info ("Get Remito_from_value:" +Remito_from_value);
log.info ("Get Remito_usado_value:" +Remito_usado_value);
log.info ("Get Remito_to_value:" +Remito_to_value);
log.info ("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info ("");
log.info ("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info ("Error:No hay remitos disponibles,cota superior superada");
}

if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info ("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info ("Remito Asignado A:" +Remito_id_value);
log.info ("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);

Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info ("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);

 

Result:

There was an error during the execution of your script against issue CRM-147

Message:
java.lang.NullPointerException: Cannot invoke method mi

Full result log:

There was an error during the execution of your script against issue CRM-147

Message:
java.lang.NullPointerException: Cannot invoke method minus() on null object
Stack:
org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:91)
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:43)
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:34)
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:127)
script_7468e350f9891d1acdd5321511dc63d8.run(script_7468e350f9891d1acdd5321511dc63d8.groovy:93)
Logs:
INFO : Valores de Entrada:
INFO : Get Remito_from_value:null
INFO : Get Remito_usado_value:null
INFO : Get Remito_to_value:null
INFO : Get Remito_disponibles:0
INFO : 
INFO : Valores de Salida:
INFO : Usted alcanzo el final
INFO : Remito Asignado A:null
INFO : Estado Activo SI/NO: NO

It should say "Talonario" where it says the text that goes up as "CheckBox".
"Talonarios" are a set of "Remitos" that are delivered for the transportation of merchandise here in Argentina..

Defined how:

Parent: Talonario.

SubTask: Remito

 

The number of subtasks (Remitos) that can be created in a "Talonario" is defined by the "Remito From" and "Remito To" fields.

Hi @Diego Cañete I'm trying to read it and I understand it better (not perfectly :)). Can you adjust your script (only some parts)?

// Nueva version
def Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def Remito_to_value = (issue.get("Remito To")); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));

TO

// Nueva version
def Remito_from_value = (issue.get("Remito From", 0)); // Cota Inferior
def Remito_to_value = (issue.get("Remito To", 0)); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero", 0)); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R", 0)); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));

 

AND

//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);


Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);

TO

//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);

log.info("Current issue is:" + issue.getKey());
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);

 

 

It should be enough to share your logs after you check it...

Hi Martin, post update the script with the updates sent by you, the log change , it does not pass values but it does not give so many errors.

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.opensymphony.workflow.InvalidInputException;
import java.sql.ResultSet;
import java.util.Map;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.WorkflowException;
//import groovy.sql.GroovyRowResult
//import groovy.sql.*
//import groovy.sql.Sql
//import java.sql.*;
//import java.sql.Connection;
//import java.sql.ResultSet;
//import java.sql.SQLException;
//import java.sql.Statement;

import org.apache.log4j.Logger;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";

String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo

// Nueva version
def Remito_from_value = (issue.get("Remito From", 0)); // Cota Inferior
def Remito_to_value = (issue.get("Remito To", 0)); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero", 0)); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R", 0)); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));

// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info ("Valores de Entrada:")
log.info ("Get Remito_from_value:" +Remito_from_value);
log.info ("Get Remito_usado_value:" +Remito_usado_value);
log.info ("Get Remito_to_value:" +Remito_to_value);
log.info ("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info ("");
log.info ("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info ("Error:No hay remitos disponibles,cota superior superada");
}

if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info ("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info ("Remito Asignado A:" +Remito_id_value);
log.info ("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);

log.info("Current issue is:" + issue.getKey());
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);

Your script ran successfully against issue CRM-336

Result value:
null
Logs:
INFO : Valores de Entrada:
INFO : Get Remito_from_value:0
INFO : Get Remito_usado_value:0
INFO : Get Remito_to_value:0
INFO : Get Remito_disponibles:84.0
INFO : 
INFO : Valores de Salida:
INFO : Usted alcanzo el final
INFO : Remito Asignado A:0
INFO : Estado Activo SI/NO: NO
INFO : Current issue is:CRM-336
INFO : Available Quantity:1

Ok, so it looks like the required values for "remito to", "remito from" and "remito usado" is not available on CRM-336. I can't see the values on your screenshots...

Hi Martin, 

You have right, 

The last code works but I add "int".

End Code:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.opensymphony.workflow.InvalidInputException;
import java.sql.ResultSet;
import java.util.Map;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.WorkflowException;
//import groovy.sql.GroovyRowResult
//import groovy.sql.*
//import groovy.sql.Sql
//import java.sql.*;
//import java.sql.Connection;
//import java.sql.ResultSet;
//import java.sql.SQLException;
//import java.sql.Statement;

import org.apache.log4j.Logger;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";

String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo SI/NO

// Nueva version
def int Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def int Remito_to_value = (issue.get("Remito To")); // Cota Superior
def int Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def int Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def int Remitos_disponibles_value = (issue.get("customfield_19018")); // (issue.get("Available Quantity"));

// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info ("Valores de Entrada:")
log.info ("Get Remito_from_value:" +Remito_from_value);
log.info ("Get Remito_usado_value:" +Remito_usado_value);
log.info ("Get Remito_to_value:" +Remito_to_value);
log.info ("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info ("");
log.info ("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info ("Error:No hay remitos disponibles,cota superior superada");
}

if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info ("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info ("Remito Asignado A:" +Remito_id_value);
log.info ("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);

log.info("Current issue is:" + issue.getKey());
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);

 

For check, if I put values pre assigned the log result is:

Your script ran successfully against issue CRM-336

Result value:
null
Logs:
INFO : Valores de Entrada:
INFO : Get Remito_from_value:501
INFO : Get Remito_usado_value:519
INFO : Get Remito_to_value:600
INFO : Get Remito_disponibles:81
INFO : 
INFO : Valores de Salida:
INFO : Remito Asignado A:520
INFO : Estado Activo SI/NO: SI
INFO : Current issue is:CRM-336
INFO : Available Quantity:81

Hi Martin,

Thank you very much for your assistance with them I managed to make it work and a lot of attitude too.
Thank you so much...

Martin,

You just have to be very careful in the location of the grooby in the psot function, it must be after create issue and not before, otherwise it doesn't work.

Hi @Diego Cañete happy to help :). It would be great if you accept my answer :) thank you

Suggest an answer

Log in or Sign up to answer
DEPLOYMENT TYPE
SERVER
TAGS
Community showcase
Published in Apps & Integrations

Apps for Confluence you won't want to miss: RSVP for September's Appy Hours

Calling all collaborators and Confluence users! Our Appy Hours event on September 29th features 4 presenters demoing functionality to superpower Confluence. Don't miss learning about these apps i...

117 views 0 9
Read article

Atlassian Community Events