Atlassian galaxy of plugins : which plugins to AVOID at all cost
https://confluence.atlassian.com/jirakb/known-major-problems-with-3rd-party-apps-in-jira-946618564.html
Atlassian galaxy of plugins : which plugins to AVOID at all cost
https://confluence.atlassian.com/jirakb/known-major-problems-with-3rd-party-apps-in-jira-946618564.html
Atlassian documentation
https://confluence.atlassian.com/alldoc/atlassian-documentation-32243719.html
Atlassian Webinars
https://www.atlassian.com/webinars/all?tab=all
https://community.atlassian.com/
https://community.developer.atlassian.com/
Atlassian Developer documentation
https://developer.atlassian.com/
Atlassian Marketplace
https://marketplace.atlassian.com/
Atlassian open source add-ons
https://ecosystem.atlassian.net/
Limited access :
https://partners.atlassian.com
Atlassian support
* https://support.atlassian.com/ (you'll need a valid SEN...)
* for app developpers : https://ecosystem.atlassian.net/servicedesk/customer/portal/9
Install the Thready app to make sure that your threads have a meaningful name.
Then use the following scripts :
https://bitbucket.org/atlassianlabs/atlassian-support/src/master/
https://confluence.atlassian.com/doc/configuring-a-datasource-connection-937166084.html
pg_ctl -D /usr/ local /var/postgres start && brew services start postgresql psql postgres CREATE ROLE otrs WITH LOGIN PASSWORD 'otrs' ; ALTER ROLE otrs CREATEDB; \q # create user and db psql postgres -U otrs CREATE DATABASE otrs; GRANT ALL PRIVILEGES ON DATABASE otrs TO otrs; #show databases \list #import dump psql -h localhost -d otrs -U otrs -f /Users/tokamak/Desktop/ IN /MISSIONS/TOTAL/OTRS/Files/psql_otrs_dump.sql #show all db sizes SELECT pg_database.datname, pg_size_pretty(pg_database_size(pg_database.datname)) AS size FROM pg_database; # Create jiradb and user CREATE USER jiradbuser WITH PASSWORD 'jiraSQL' ; CREATE DATABASE jiradb WITH ENCODING 'UNICODE' LC_COLLATE 'C' LC_CTYPE 'C' TEMPLATE template0; GRANT ALL PRIVILEGES ON DATABASE jiradb TO jiradbuser; |
opsgenie is a tool allowing filtering and routing of monitoring-triggered alerts (nagios, AWS SNS, datadog, ...) to specific channels (SMS, phone-call, Slack, Jira, ...).
Main features on top of this :
Opsgenie Learning Center : https://docs.opsgenie.com/
[video] Opsgenie : "What do we do?" https://www.youtube.com/watch?v=yphtZ9z2TtA&feature=youtu.be
[video] Opsgenie: "First Look" https://www.youtube.com/watch?v=pyM2dROKn6g
Opsgenie Pricing : https://www.atlassian.com/software/opsgenie/pricing
Implement nagios to opsgenie Heartbeats :
If you're using the AWS SNS opsgenie integration and want to publish to JIRA, you can for example use the following code to present the data in a slightly better way :
I this use-case I'm using the SNS channel to publish outputs from a system manager (AWS SSM) command that also publishes it's outputs to an S3, so we're using this extraction to provide the direct links to the s3 logs and the SSM run command history.
And in the end, we copy the message we received from the SNS channel "raw"..
In Opsgenie, in the specific Amazon SNS integration (Incoming Amazon SNS), in the Alert Fields, you can for example modify the "Description" so that it transforms the Message received like this :
h3. Details
|| AWS region | {{ TopicArn.extract(/arn:aws:sns:([^:]*):.*/) }} |
|| Status | {{ Message.extract(/.*"status":"([^"]*)".*/) }} |
|| Instance ID | {{ Message.extract(/.*"instanceId":"([^"]*)".*/) }} [(aws link)|https://{{ TopicArn.extract(/arn:aws:sns:([^:]*):.*/) }}.console.aws.amazon.com/ec2/v2/home?region={{ TopicArn.extract(/arn:aws:sns:([^:]*):.*/) }}#InstanceDetails:instanceId={{ Message.extract(/.*"instanceId":"([^"]*)".*/) }}]|
|| Command ID | {{ Message.extract(/.*"commandId":"([^"]*)".*/) }} [(aws cmd)|https://console.aws.amazon.com/systems-manager/run-command/{{ Message.extract(/.*"commandId":"([^"]*)".*/) }}] [(s3 logs)|https://console.aws.amazon.com/s3/buckets/ssm-output/ssm-log/{{ Message.extract(/.*"commandId":"([^"]*)".*/) }}/{{ Message.extract(/.*"instanceId":"([^"]*)".*/) }}/?region={{ TopicArn.extract(/arn:aws:sns:([^:]*):.*/) }}&showversions=false ]
|
|| documentName | {{ Message.extract(/.*"documentName":"([^"]*)".*/) }} |
|| requestedDateTime | {{ Message.extract(/.*"requestedDateTime":"([^"]*)".*/) }} |
|| eventTime | {{ Message.extract(/.*"eventTime":"([^"]*)".*/) }} |
h3. Opsgenie info
|| EventType | {{eventType}} |
|| Timestamp (opsgenie) | {{Timestamp}}|
|| Tags | {{tags}} |
|| TopicArn | {{TopicArn}} |
|| Actions | {{actions}} |
h3. Original Message (raw):
{code}
{{Message}}
{code}
Nb: this might only be available in certain OpsGenie subscriptions unfortunately :-(
an old post found back on disqr about rsync (original date : circa 2016)
You might want to use the rsync exclude options directly in your script either by specifying a file with all the exclusions to perform, or by specifying them in the command line directly :
--exclude-from <file-name with 1 pattern by line>
--exclude <file or="" dir="">
For example :
One important thing to keep in mind when excluding a directory is that rsync will always consider the path to be relative to the source directory.
This can be used for example when you want to push your production from a Production JIRA instance to a Staging JIRA instance, but your dbconfig.xml is different (different DB auth parameters for example), and hence want to avoid some files.
Know where/if a plugin is used. Both have to be adapted to match a specific plugin.
First version gives more data but requires ScriptRunner.
Second version only uses SQL, but is less extensive.
You need to know what you're looking for : com.innovalog.jmwe.jira-misc-workflow-extensions
Adapted from : https://answers.atlassian.com/questions/205094/how-to-find-all-workflows-which-are-using-postfunctions-of-a-plugin
import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.workflow.JiraWorkflow import com.atlassian.jira.workflow.WorkflowManager import java.util.regex.Matcher String searchText = 'com.innovalog.jmwe.jira-misc-workflow-extensions' WorkflowManager workflowManager = ComponentAccessor.getWorkflowManager() Collection<JiraWorkflow> workflows = workflowManager.getWorkflows() String result = "Workflow;Function;Type;Action;Step\r\n<br/>" workflows.each{workflow -> def workflowXml = workflow.descriptor.asXML() Matcher m = workflowXml =~ /${searchText}/ if (m.find()) { String workflow_name = "${workflow.name}${if(!workflow.isActive()){" ;(inactive) "} else ';active'}${if(workflow.isDraftWorkflow()){" (draft) "} else ''}" def wf = new XmlParser().parseText(workflowXml) List<Node> wf_flat = wf.depthFirst() wf_flat.each{ if (it.text() ==~ /.*${searchText}.*/){ result += "$workflow_name;${getNodeInfo(it,searchText)}\n\n<br/>" } } } } result String getNodeInfo(Node n, String search) { String nodetext = '' nodetext += "${n.text() - search}" def p = n.parent() while (p) { switch (p.name()) { case '"post-functions"' : nodetext += ";post-function " ; break case 'validators' : nodetext += ";validator " ; break case 'conditions' : nodetext += ";condition " ; break case 'action' : nodetext += ";${p.attribute('name')} (${p.attribute('id')})" ; break case 'step' : nodetext += ";${p.attribute('name')} (${p.attribute('id')})" ; break case 'global-actions' : nodetext += ";GLOBAL " ; break case 'initial-actions' : nodetext += ";INITIAL " ; break } p = p.parent() } return nodetext } |
SELECT workflowscheme. name , workflowschemeentity.scheme, issuetype.pname, workflowschemeentity.issuetype FROM issuetype, workflowschemeentity, workflowscheme WHERE issuetype.id = workflowschemeentity.issuetype and workflowscheme.id = workflowschemeentity.scheme and workflowschemeentity.workflow in ( SELECT jiraworkflows.workflowname FROM jiraworkflows WHERE jiraworkflows.descriptor like '%com.company.plugin%' ); |
snippet of code to use the output logs in ScriptRunner Adaptavist groovy scripts
tool | ScriptRunner for JIRA |
---|---|
version | 8.1.1 |
use case | log.debug SR script |
/***********/
import
org.apache.log4j.Logger
import
org.apache.log4j.Level
def log = Logger.getLogger(
"com.scriptname"
)
log.setLevel(Level.DEBUG)
/***********/
//and then, for example to print the variable trem
log.debug
"trem=${trem}"
|
example :
Tips for the plugin Xporter
tool | Xporter |
---|---|
version | 5.0.0 |
use case | Print the current date |
Document generated at %{( new Date()).getDate() + "/" + (( new Date()).getMonth()+ 1 ) + "/" + ( new Date()).getFullYear()} |
tool | Xporter, excel |
---|---|
version | 5.0.0 |
use case | Count number of issues in a JQL |
JQL | <filter> |
---|---|
nfiches1 | ${jqlcount: |
nfiches2 | } |
=CONCATENATE(nfiches1;<JQL>;nfiches2) |
${jqlcount:project = PJAB AND issuetype = Pénal AND created >= -90d AND status not in ("Constitution du dossier", "Saisir Avocat") AND Entité = "AXA Banque"} |
tool | Xporter, excel |
---|---|
version | 5.0.0 |
use case | Sum a CF for all issues on a JQL |
JQL | <filter> |
---|---|
CF | <cf name> |
nsomme1 | ${set(count,0)} #{for n=JQLIssuesCount|clause= |
nsomme2 | } #{if (%{'${JQLIssues[n]. |
nsomme3 | }' .length > 0})} ${set(count,%{${count} + ${JQLIssues[n]. |
nsomme4 | }})} #{end} #{end} %{Number(${count}).toFixed(2)} |
in a excel cell :
=CONCATENATE(nsomme1;<JQL>;nsomme2;<CFname>;nsomme3;<CFname>;nsomme4) |
${set(count, 0 )} #{ for n=JQLIssuesCount|clause=project = PJAB AND issuetype = Pénal AND created >= -90d AND status not in ( "Constitution du dossier" , "Saisir Avocat" ) AND Entité = "AXA Banque" } #{ if (%{ '${JQLIssues[n].Créance comptable}' .length > 0 })} ${set(count,%{${count} + ${JQLIssues[n].Créance comptable}})} #{end} #{end} %{Number(${count}).toFixed( 2 )} |
tool | Xporter, excel |
---|---|
version | 5.0.1 |
use case | Number of links matching a criteria (including creation date) |
Filter on a date :
This
requires to create different Date objects, either initiated with the
date returned from the issue, or the calculated date. In the end we are
indeed comparing milliseconds since 1-1-1970, so the > is enough.
/* test if the creation date is within the last 90 days */ ( new Date( '${dateformat("yyyy-MM-dd HH:mm:ss"):Links[n].Created}' ) > ( new Date( new Date().setDate( new Date().getDate()- 90 )))) |
/* Set the variable countD4 to the number of links to the current issue "A" where : * link with the current is : "A" -(est modifié par)-> other issue * linked issue was creted less than 90 days ago * linked issue type is "Créa-Modif Document". */ ${set(countD4, 0 )} #{ for n=LinksCount|filter=%{( '${Links[n].LinkType}' .equals( 'est modifié par' )) && ( new Date( '${dateformat("yyyy-MM-dd HH:mm:ss"):Links[n].Created}' ) > ( new Date( new Date().setDate( new Date().getDate()- 90 )))) && '${Links[n].IssueTypeName}' .equals( 'Créa-Modif Document' )}} ${set(countD4,%{${countD4}+ 1 })} * ${Links[n].Key} ${Links[n].Summary} ${Links[n].IssueTypeName} ${Links[n].Status} #{end} ${countD4} |
tool | Xporter, excel |
---|---|
version | 5.0.1 |
use case | Count the number of issues, and print it in excel JQL format |
%{Number(${jqlcount:project = PJAB AND issuetype = Pénal AND created >= -92d AND status not in ( "Constitution du dossier" , "Saisir Avocat" ) AND Entité = "AXA Banque" })} |
tool | Xporter |
---|---|
version | ? |
use case | Format a number with specific locale's decimal separator |
use the ${numberformat("fr","#,##0.00#") function
${numberformat( "fr" , "#,##0.00#" ):JQLIssues[j].Montant de l'opération (en €)} |
tool | Excel, Xporter |
---|---|
version | 5.1.1 |
use case | Sum values that Xporter printed in the wrong locale. |
Sometime Xporter has some trouble printing numbers that are actually recognized as numbers by excel. An example is by
let's say you need to sum values in a locale where the decimal separator is ",", but xporter outputs a decimal separator as "."; hence the =SUM() function will not work.
=SUM(VALUE(CLEAN(SUBSTITUTE(C11; "." ; "," )));VALUE(CLEAN(SUBSTITUTE(C12; "." ; "," )));VALUE(CLEAN(SUBSTITUTE(C13; "." ; "," )))) |
tool | Excel, Xporter, ScriptRunner |
---|---|
version | 5.1.1 |
use case | Bulk export, export the issues & insert their subtasks |
One way of doing so is to use the ScriptRunner JQL functions "subtasksOf(<key>)" and iterate on it.
example
#{ for j=JQLIssuesCount|clause=project = PRF and issueFunction in subtasksOf( "key = ${Key}" ) and issuetype NOT IN ( "Débit" , "Crédit" ) order by updated } |
(note : this works on JIRA Server / Datacenter at the time of the writing of this, Feb 2017)
Sometimes, you want to set some permissions directly depending on the JIRA Issue Workflow status (i.e. not depending on the whole Permissions scheme applied at the whole project level).
This hard to find trick consist in using Workflow properties on the WF steps.
ref :
so
that only JIRA administrators can edit an issuejira.permission.edit.group=jira-administrators |
jira.permission.edit.group. 1 =jira-administrators jira.permission.edit.group. 2 =jira-fr jira.permission.edit.group. 3 =jira-pmo |
This of course needs to be applied for each step/status of the workflow impacted, making sure that this WF is not shared with other projects or other IT.
Please note that the expected format is not <property> = denied This format actually grants access to the user named denied.
The proper format is is:
<property>.denied = whatever
The complete format is:
<permission key> = jira.permission[.subtasks].<system project permission>.<grant type>[.<suffix>] <system project permission> = assign | assignable | attach | attachdeleteall | attachdeleteown | browse | close | comment | commentdeleteall | commentdeleteown | commenteditall | commenteditown | create | delete | edit | link | managewatcherlist | modifyreporter | move | project | resolve | scheduleissue | setsecurity | transition | viewversioncontrol | viewvotersandwatchers | viewworkflowreadonly | work | worklogdeleteall | worklogdeleteown | worklogeditall | worklogeditown <grant type> = denied | groupCF | assignee | assigneeassignable | reporter | reportercreate | userCF | applicationRole | group | lead | projectrole | user <suffix> = any text that makes the permission key unique among all keys of permissions in the same workflow step. |
(note : this works with most version of JIRA available as of now, Janvier 2017)
Instead of staying in the current view, you can use the following tricks to make your JIRA SERVER / DATACENTER redirect directly to the recently created issue.
The trick here is to disable the "popup" with the create screen, hence going to the "full create screen" which opens the page afterward.
<script type= "text/javascript" > AJS.$( "#create_link" ).removeClass( "create-issue" ); $( "#announcement-banner" ).hide()</script> |
cf. : https://confluence.atlassian.com/jirakb/how-to-disable-create-issue-popup-300813780.html
The following plugin aims at the same :
Excerpt from the source :
AJS.$(document).on( 'DOMNodeInserted' , function (event) { if (event.target.id == 'aui-flag-container' ) { console.log( 'issue-quick-start: Got post-it note!' ); AJS.$(event.target).on( 'DOMNodeInserted' , function (event) { console.log( 'issue-quick-start: Post-it HTML: ' + event.target.innerHTML); var postItLink = AJS.$(event.target.innerHTML).find( 'a' ); var postItPath = postItLink.attr( 'href' ); if (postItPath && postItLink.attr( 'data-issue-key' )) { console.log( 'issue-quick-start: Going to new issue path ' + postItPath); window.location = postItPath; } }) } }); |
cf. https://scriptrunner.adaptavist.com/4.3.7/jira/fragments/WebItem.html#_redirects
import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.label.LabelManager import com.atlassian.sal.api.ApplicationProperties import com.onresolve.scriptrunner.runner.ScriptRunnerImpl import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.transform.BaseScript import javax.ws.rs.core.MultivaluedMap import javax.ws.rs.core.Response @BaseScript CustomEndpointDelegate delegate def labelManager = ComponentAccessor.getComponent(LabelManager) def applicationProperties = ScriptRunnerImpl.getOsgiService(ApplicationProperties) def issueManager = ComponentAccessor.getIssueManager() labelIssue(httpMethod: "GET" ) { MultivaluedMap queryParams -> def issueId = queryParams.getFirst( "issueId" ) as Long def issue = issueManager.getIssueObject(issueId) /** def label = labelManager.getLabels(issueId) if (! label) { labelManager.addLabel(null, issueId, "approved", false) } **/ Response.temporaryRedirect(URI.create( "${applicationProperties.baseUrl}/browse/${issue.key}" )).build() } |