[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"node-by-slug-\u002Fde\u002Fnode\u002F5456-de":3,"related-content-view":731},["Reactive",4],{"entity":5,"errors":-1},{"__typename":6,"status":7,"metatag":8,"id":206,"path":207,"title":13,"translations":208,"sharedHeaderRefMedia":212,"sharedTeaserTitle":13,"sharedTeaserText":241,"sharedTeaserRefMedia":243,"sharedParagraphs":252,"sharedTaxrefIndustries":716,"sharedTaxrefExpertises":720,"sharedIntroText":727,"sharedCtaLink":214,"sharedDate":729,"sharedAuthor":214},"NodeInsight",true,[9,14,18,21,25,30,34,38,42,46,50,54,58,62,66,69,73,76,79,81,84,87,91,95,99,103,107,111,115,119,123,127,131,135,139,142,145,148,151,154,157,160,163,166,170,174,177,181,185,189,193,196,200,204],{"tag":10,"attributes":11},"meta",{"name":12,"content":13},"title","How to Develop a Plugin for Atlassian Jira",{"tag":10,"attributes":15},{"name":16,"content":17},"description","Atlassian’s Jira is a very popular tool for planning and tracking projects. The data that Jira accumulates is a gold-mine for obtaining insights about productivity improvements. While some of this data is readily accessible for analysis with out-of-the box Jira features, you are likely to want to gain specific insights about your projects that Atlassian developers did not foresee.",{"tag":10,"attributes":19},{"name":20,"content":17},"abstract",{"tag":10,"attributes":22},{"name":23,"content":24},"icbm","51.525772, -0.082716",{"tag":26,"attributes":27},"link",{"rel":28,"href":29},"canonical","https:\u002F\u002Fcms.zuehlke.com\u002Fen\u002Finsights\u002Fhow-to-develop-a-plugin-for-atlassian-jira-0",{"tag":10,"attributes":31},{"name":32,"content":33},"robots","index, follow",{"tag":26,"attributes":35},{"rel":36,"href":37},"shortlink","https:\u002F\u002Fcms.zuehlke.com\u002Fapi\u002Fgraphql",{"tag":26,"attributes":39},{"rel":40,"href":41},"image_src","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Fmax_1300x1300\u002Fpublic\u002Fimages\u002Fabstract-network-and-data-speed.jpg?itok=HLN2WZdW,https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fzuhlke_innovation_social.jpg",{"tag":10,"attributes":43},{"name":44,"content":45},"referrer","unsafe-url",{"tag":10,"attributes":47},{"name":48,"content":49},"rights","Copyright ©2026 | Zühlke Engineering AG | All rights reserved.",{"tag":10,"attributes":51},{"property":52,"content":53},"og:site_name","Zühlke Engineering AG",{"tag":10,"attributes":55},{"property":56,"content":57},"og:type","website",{"tag":10,"attributes":59},{"property":60,"content":61},"og:url","https:\u002F\u002Fwww.zuehlke.com\u002Fen\u002Finsights\u002Fhow-to-develop-a-plugin-for-atlassian-jira-0",{"tag":10,"attributes":63},{"property":64,"content":65},"og:title","How to Develop a Plugin for Atlassian Jira – Zühlke",{"tag":10,"attributes":67},{"property":68,"content":17},"og:description",{"tag":10,"attributes":70},{"property":71,"content":72},"og:image","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Fmax_1300x1300\u002Fpublic\u002Fimages\u002Fabstract-network-and-data-speed.jpg?itok=HLN2WZdW",{"tag":10,"attributes":74},{"property":71,"content":75},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fzuhlke_innovation_social.jpg",{"tag":10,"attributes":77},{"property":78,"content":72},"og:image:url",{"tag":10,"attributes":80},{"property":78,"content":75},{"tag":10,"attributes":82},{"property":83,"content":72},"og:image:secure_url",{"tag":10,"attributes":85},{"property":83,"content":86},"https:\u002F\u002Fwww.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fzuhlke_innovation_social.jpg",{"tag":10,"attributes":88},{"property":89,"content":90},"og:image:alt","| Zühlke image",{"tag":10,"attributes":92},{"property":93,"content":94},"og:updated_time","2025-11-20",{"tag":10,"attributes":96},{"property":97,"content":98},"place:location:longitude","7.438014",{"tag":10,"attributes":100},{"property":101,"content":102},"place:location:latitude","46.947944",{"tag":10,"attributes":104},{"property":105,"content":106},"og:street_address","80 Great Eastern Street",{"tag":10,"attributes":108},{"property":109,"content":110},"og:locality","London",{"tag":10,"attributes":112},{"property":113,"content":114},"og:region","England",{"tag":10,"attributes":116},{"property":117,"content":118},"og:postal_code","EC2A 3JL",{"tag":10,"attributes":120},{"property":121,"content":122},"og:country_name","United Kingdom",{"tag":10,"attributes":124},{"property":125,"content":126},"og:email","london@zuhlke.com",{"tag":10,"attributes":128},{"property":129,"content":130},"og:phone_number","+44 20 7113 5000",{"tag":10,"attributes":132},{"property":133,"content":134},"og:locale","en_CH",{"tag":10,"attributes":136},{"property":137,"content":138},"og:locale:alternate","de_CH",{"tag":10,"attributes":140},{"property":137,"content":141},"de_DE",{"tag":10,"attributes":143},{"property":137,"content":144},"en_DE",{"tag":10,"attributes":146},{"property":137,"content":147},"en_AT",{"tag":10,"attributes":149},{"property":137,"content":150},"de_AT",{"tag":10,"attributes":152},{"property":137,"content":153},"en_HK",{"tag":10,"attributes":155},{"property":137,"content":156},"en_SR",{"tag":10,"attributes":158},{"property":137,"content":159},"en_BG",{"tag":10,"attributes":161},{"property":137,"content":162},"en_SG",{"tag":10,"attributes":164},{"property":137,"content":165},"en_GB",{"tag":10,"attributes":167},{"property":168,"content":169},"article:publisher","https:\u002F\u002Fwww.facebook.com\u002Fzuehlke.group",{"tag":10,"attributes":171},{"property":172,"content":173},"article:published_time","2021-11-25",{"tag":10,"attributes":175},{"property":176,"content":94},"article:modified_time",{"tag":10,"attributes":178},{"name":179,"content":180},"twitter:card","summary_large_image",{"tag":10,"attributes":182},{"name":183,"content":184},"twitter:site","@zuehlke_group",{"tag":10,"attributes":186},{"name":187,"content":188},"twitter:title","| Zühlke",{"tag":10,"attributes":190},{"name":191,"content":192},"twitter:site:id","247189965",{"tag":10,"attributes":194},{"name":195,"content":90},"twitter:image:alt",{"tag":10,"attributes":197},{"name":198,"content":199},"twitter:image","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Fmax_1300x1300\u002Fpublic\u002Fimages\u002Fabstract-network-and-data-speed.jpg?itok=HLN2WZdW,https:\u002F\u002Fwww.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fzuhlke_innovation_social.jpg",{"tag":10,"attributes":201},{"name":202,"content":203},"google-site-verification","VEzolc1Vxy1KyRIlIqUSmVS7fqP_3_ZeBqPRby4g2mE",{"tag":205},"script","5456","\u002Fen\u002Finsights\u002Fhow-to-develop-a-plugin-for-atlassian-jira-0",[209],{"path":207,"langcode":210},{"id":211},"en",{"id":213,"sharedMediaLegend":214,"mediaImage":215,"name":240},"3747",null,{"url":216,"width":217,"height":218,"alt":219,"title":214,"size":220,"mime":221,"variations":214,"headerMinimalVariations":222},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fsoftware-engineering_0.jpg",2000,1333,"Two software engineers at work",263931,"image\u002Fjpeg",[223,228,229,234,239],{"name":224,"url":225,"width":226,"height":227},"FOCAL_POINT32_SMALL","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_small\u002Fpublic\u002Fimages\u002Fsoftware-engineering_0.jpg.webp?itok=wMd61B7-",800,533,{"name":224,"url":225,"width":226,"height":227},{"name":230,"url":231,"width":232,"height":233},"FOCAL_POINT169_MEDIUM","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_16_9_medium\u002Fpublic\u002Fimages\u002Fsoftware-engineering_0.jpg.webp?itok=5hPh7vAN",1200,675,{"name":235,"url":236,"width":237,"height":238},"FOCAL_POINT169_LARGE","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_16_9_large\u002Fpublic\u002Fimages\u002Fsoftware-engineering_0.jpg.webp?itok=OdXiVeZ3",1600,900,{"name":235,"url":236,"width":237,"height":238},"software-engineering.jpg",{"processed":242},"\u003Cp>Atlassian’s Jira is a very popular tool for planning and tracking projects. The data that Jira accumulates is a gold-mine for obtaining insights about productivity improvements. While some of this data is readily accessible for analysis with out-of-the box Jira features, you are likely to want to gain specific insights about your projects that Atlassian developers did not foresee.&nbsp;\u003C\u002Fp>",{"id":244,"sharedMediaLegend":214,"mediaImage":245,"name":251},"168241",{"url":246,"width":247,"height":248,"alt":249,"title":214,"size":250,"mime":221,"variations":214},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fabstract-network-and-data-speed.jpg",1280,853,"Abstract data flows",89588,"abstract-network-and-data-speed.jpg",[253,260,264,269,276,281,313,338,343,349,354,359,365,392,415,420,425,430,435,440,446,452,458,464,469,474,480,485,490,496,502,507,512,517,522,527,532,538,543,548,553,558,563,568,573,578,584,589,594,599,604,609,614,619,624,629,634,639,644,650,655,677,683],{"__typename":254,"id":255,"ctaLink":214,"pgSharedText":256,"pgSharedTitle":214,"pgSharedCtaLink":214},"ParagraphPgText","23685",{"value":257,"processed":258,"format":259},"\u003Cul>\r\n\t\u003Cli>\r\n\t\u003Cp class=\"text-lead\">Read more about how to gain specific insights about your projects that&nbsp;Atlassian developers did not foresee.\u003C\u002Fp>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cp class=\"text-lead\">Learn about obstacles overcome while developing a plugin to extract custom project metrics from Atlassian Jira 7.12.3.\u003C\u002Fp>\r\n\t\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n","\u003Cul>\n\t\u003Cli>\n\t\u003Cp class=\"text-lead\">Read more about how to gain specific insights about your projects that&nbsp;Atlassian developers did not foresee.\u003C\u002Fp>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cp class=\"text-lead\">Learn about obstacles overcome while developing a plugin to extract custom project metrics from Atlassian Jira 7.12.3.\u003C\u002Fp>\n\t\u003C\u002Fli>\n\u003C\u002Ful>","full_html",{"__typename":254,"id":261,"ctaLink":214,"pgSharedText":262,"pgSharedTitle":214,"pgSharedCtaLink":214},"10590",{"value":263,"processed":263,"format":259},"\u003Cp class=\"text-normal\">Atlassian’s Jira is a very popular tool for planning and tracking projects. The data that Jira accumulates is a gold-mine for obtaining insights about productivity improvements. While some of this data is readily accessible for analysis with out-of-the box Jira features, you are likely to want to gain specific insights about your projects that Atlassian developers did not foresee.\u003C\u002Fp>\u003Cp class=\"text-normal\">This is where the Jira plug-in mechanism comes in handy as it enables you to crunch through Jira’s repository for your own purposes.\u003C\u002Fp>",{"__typename":254,"id":265,"ctaLink":214,"pgSharedText":266,"pgSharedTitle":214,"pgSharedCtaLink":214},"8952",{"value":267,"processed":268,"format":259},"\u003Cp>The following is a lengthy and quite technical blog post about obstacles overcome while developing a plugin to extract custom project metrics from Atlassian Jira 7.12.3.\u003C\u002Fp>\r\n\r\n\u003Cul>\r\n\t\u003Cli>Introduction\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"#section-8959\">Getting started\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-8964\">User Experience\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9008\">Connect the UI to Jira Data\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9019\">Unit Tests\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9026\">Wired Tests 1\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"#section-9035\">Wired Tests 2\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9048\">Accelerate the Cycle\u003C\u002Fa>\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n\r\n\u003Cp>&nbsp;\u003C\u002Fp>\r\n\r\n\u003Cp>You may have come to this page seeking a solution to one of the following error messages:\u003C\u002Fp>\r\n","\u003Cp>The following is a lengthy and quite technical blog post about obstacles overcome while developing a plugin to extract custom project metrics from Atlassian Jira 7.12.3.\u003C\u002Fp>\n\n\u003Cul>\n\t\u003Cli>Introduction\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"#section-8959\">Getting started\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-8964\">User Experience\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9008\">Connect the UI to Jira Data\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9019\">Unit Tests\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9026\">Wired Tests 1\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"#section-9035\">Wired Tests 2\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.zuehlke.com\u002Fde\u002Fnode\u002F5456#section-9048\">Accelerate the Cycle\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\n\n\u003Cp>You may have come to this page seeking a solution to one of the following error messages:\u003C\u002Fp>",{"__typename":270,"id":271,"pgSharedTitle":214,"pgCodeCode":272},"ParagraphPgCode","8953",{"value":273,"processed":274,"format":275},"\u003Cul>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nUnexpected exception parsing XML document from URL [\u003Ca href=\"http:\u002F\u002Fzuehlke.dpstage.ch\u002Fen\u002Fhow-to-develop-a-plugin-for-atlassian-jira\u002F0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\">bundle:\u002F\u002F....0:0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\u003C\u002Fa>]; nested exception is org.springframework.beans.FatalBeanException: Class [org.eclipse.gemini.blueprint.config.OsgiNamespaceHandler] for namespace [\u003Ca href=\"http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\">http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\u003C\u002Fa>] does not implement the [org.springframework.beans.factory.xml.NamespaceHandler] interface\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nConfiguration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [\u003Ca href=\"http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\">http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\u003C\u002Fa>]\r\nOffending resource: URL [\u003Ca href=\"http:\u002F\u002Fzuehlke.dpstage.ch\u002Fen\u002Fhow-to-develop-a-plugin-for-atlassian-jira\u002F0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\">bundle:\u002F\u002F....0:0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\u003C\u002Fa>]\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nCannot start plugin: Unresolved constraint in bundle ... [...]: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=COM.jrockit.reflect)\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nCannot start plugin: Unresolved constraint in bundle ... [...]: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=com.atlassian.tutorial.myPlugin.api) [caused by: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=org.springframework.osgi.service.exporter.support)]\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\n'...-tests' - '...'  failed to load. The plugin has been disabled.\r\nA likely cause is that it timed out during initialisation\r\nIt has the following missing service dependencies : ...\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nlang.Exception: No tests found in class [it....]\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nNo tests found\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>\r\n\t\u003Cpre>\r\nlang.NoClassDefFoundError: com\u002Fatlassian\u002Fjira\u002Futil\u002Fcollect\u002FMapBuilder\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n","\u003Cul>\n\t\u003Cli>\n\t\u003Cpre>\nUnexpected exception parsing XML document from URL [\u003Ca href=\"http:\u002F\u002Fzuehlke.dpstage.ch\u002Fen\u002Fhow-to-develop-a-plugin-for-atlassian-jira\u002F0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\">bundle:\u002F\u002F....0:0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\u003C\u002Fa>]; nested exception is org.springframework.beans.FatalBeanException: Class [org.eclipse.gemini.blueprint.config.OsgiNamespaceHandler] for namespace [\u003Ca href=\"http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\">http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\u003C\u002Fa>] does not implement the [org.springframework.beans.factory.xml.NamespaceHandler] interface\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\nConfiguration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [\u003Ca href=\"http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\">http:\u002F\u002Fwww.eclipse.org\u002Fgemini\u002Fblueprint\u002Fschema\u002Fblueprint\u003C\u002Fa>]\nOffending resource: URL [\u003Ca href=\"http:\u002F\u002Fzuehlke.dpstage.ch\u002Fen\u002Fhow-to-develop-a-plugin-for-atlassian-jira\u002F0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\">bundle:\u002F\u002F....0:0\u002FMETA-INF\u002Fspring\u002Fatlassian-plugins-component-imports.xml\u003C\u002Fa>]\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\nCannot start plugin: Unresolved constraint in bundle ... [...]: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=COM.jrockit.reflect)\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\nCannot start plugin: Unresolved constraint in bundle ... [...]: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=com.atlassian.tutorial.myPlugin.api) [caused by: Unable to resolve ....0: missing requirement [....0] osgi.wiring.package; (osgi.wiring.package=org.springframework.osgi.service.exporter.support)]\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\n'...-tests' - '...'  failed to load. The plugin has been disabled.\nA likely cause is that it timed out during initialisation\nIt has the following missing service dependencies : ...\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\nlang.Exception: No tests found in class [it....]\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\nNo tests found\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\t\u003Cli>\n\t\u003Cpre>\nlang.NoClassDefFoundError: com\u002Fatlassian\u002Fjira\u002Futil\u002Fcollect\u002FMapBuilder\u003C\u002Fpre>\n\t\u003C\u002Fli>\n\u003C\u002Ful>\n","code",{"__typename":254,"id":277,"ctaLink":214,"pgSharedText":278,"pgSharedTitle":214,"pgSharedCtaLink":214},"8955",{"value":279,"processed":280,"format":259},"\u003Cp>The article below records the steps I took to overcome the above issues. I don’t claim that it is the only way to solve them, just that it worked for me. Unfortunately, if you are new to Atlassian plugin development the learning curve can be very steep and the documentation can be hard to find. I hope I can save you a few hours or days of searching for the correct information. References to some relevant Atlassian documentation can be found at the end.\u003C\u002Fp>\r\n\r\n\u003Cp>My sincere thanks to several Zuhlke colleagues who worked on the solution with me: Mirko Sciachero, Giovanni Asproni, Michael Dickens and Michael Scott.\u003C\u002Fp>\r\n","\u003Cp>The article below records the steps I took to overcome the above issues. I don’t claim that it is the only way to solve them, just that it worked for me. Unfortunately, if you are new to Atlassian plugin development the learning curve can be very steep and the documentation can be hard to find. I hope I can save you a few hours or days of searching for the correct information. References to some relevant Atlassian documentation can be found at the end.\u003C\u002Fp>\n\n\u003Cp>My sincere thanks to several Zuhlke colleagues who worked on the solution with me: Mirko Sciachero, Giovanni Asproni, Michael Dickens and Michael Scott.\u003C\u002Fp>",{"__typename":282,"id":283,"caption":214,"captionSecondary":214,"pgSharedTitle":284,"pgMediaRefMedia":285,"secondaryImage":214,"edgeToEdge":214,"fullWidth":214},"ParagraphPgMedia","8956","A simple starter-for-ten Jira plugin.",{"id":286,"sharedMediaLegend":214,"mediaImage":287,"name":312},"3730",{"url":288,"width":289,"height":290,"alt":291,"title":214,"size":292,"mime":221,"variations":214,"mediaVariations":293,"infographVariations":306},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fagile-metrics_0.jpg",976,314,"agile metrics",31613,[294,298,299,302,305],{"name":295,"url":296,"width":238,"height":297},"FIXED_WIDTH_SMALL","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_small\u002Fpublic\u002Fimages\u002Fagile-metrics_0.jpg.webp?itok=wSnafIqy",290,{"name":295,"url":296,"width":238,"height":297},{"name":300,"url":301,"width":289,"height":290},"FIXED_WIDTH_MEDIUM","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_medium\u002Fpublic\u002Fimages\u002Fagile-metrics_0.jpg.webp?itok=3RdOSoHx",{"name":303,"url":304,"width":289,"height":290},"FIXED_WIDTH_LARGE","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_large\u002Fpublic\u002Fimages\u002Fagile-metrics_0.jpg.webp?itok=Rk8Z32L4",{"name":303,"url":304,"width":289,"height":290},[307,308,309,310,311],{"name":295,"url":296,"width":238,"height":297},{"name":295,"url":296,"width":238,"height":297},{"name":295,"url":296,"width":238,"height":297},{"name":300,"url":301,"width":289,"height":290},{"name":300,"url":301,"width":289,"height":290},"agile-metrics.jpg",{"__typename":314,"id":315,"caption":214,"imageRight":214,"pgSharedTitle":316,"pgSharedCtaLink":214,"textMedia":317,"pgSharedText":335},"ParagraphPgTextMedia","8957","A little architectural background",{"id":318,"sharedMediaLegend":214,"mediaImage":319,"name":334},"3731",{"url":320,"width":321,"height":322,"alt":323,"title":214,"size":324,"mime":325,"variations":214,"textMediaVariations":326},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fjava-vm.png",705,664,"Java VM",33600,"image\u002Fpng",[327,329,330,331,333],{"name":295,"url":328,"width":321,"height":322},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_small\u002Fpublic\u002Fimages\u002Fjava-vm.png.webp?itok=ysAl9Ys2",{"name":295,"url":328,"width":321,"height":322},{"name":295,"url":328,"width":321,"height":322},{"name":300,"url":332,"width":321,"height":322},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_medium\u002Fpublic\u002Fimages\u002Fjava-vm.png.webp?itok=0BZhd-ls",{"name":300,"url":332,"width":321,"height":322},"java-vm.png",{"value":336,"processed":337,"format":259},"\u003Cp>When I started this work, I knew nothing about the way the Jira server is built, but I soon discovered that I needed to be better informed if I was to have any chance of working out what was going wrong. The following diagram is a very simple-minded idea of the way in which dependencies are fulfilled within the web application.\u003C\u002Fp>\r\n\r\n\u003Cp>The plugin we wish to develop runs within the context of a multi-threaded web app (Jira).\u003C\u002Fp>\r\n","\u003Cp>When I started this work, I knew nothing about the way the Jira server is built, but I soon discovered that I needed to be better informed if I was to have any chance of working out what was going wrong. The following diagram is a very simple-minded idea of the way in which dependencies are fulfilled within the web application.\u003C\u002Fp>\n\n\u003Cp>The plugin we wish to develop runs within the context of a multi-threaded web app (Jira).\u003C\u002Fp>",{"__typename":254,"id":339,"ctaLink":214,"pgSharedText":340,"pgSharedTitle":214,"pgSharedCtaLink":214},"8958",{"value":341,"processed":342,"format":259},"\u003Cp>Integration\u002Fwired tests are run from a complementary plugin installed within the same environment.\u003C\u002Fp>\r\n\r\n\u003Cp>Both plugins have dependencies on multiple Java libraries. A Java application satisfies its dependencies at runtime by calling its class loader to obtain them. Standard class loaders delegate to their parents first, and only start searching the locations on their class path if the parent is unable to load the required resource or class.\u003C\u002Fp>\r\n\r\n\u003Cp>However, in the case of Jira it’s different. Jira wants to avoid every plugin loading copies of the same class, as this could easily increase the amount of memory used for class storage by a few orders of magnitude. It uses the OSGi framework, which comes with Spring, to provide a cache of already-loaded classes (called Felix) and to satisfy all dependencies from this cache.\u003C\u002Fp>\r\n\r\n\u003Cp>This has important implications for the plugin configuration. Felix only loads classes permitted by the plugin configuration. This configuration is composed of instructions in the&nbsp;atlassian-plugin.xml&nbsp;file (a resource), the project POM, and some Atlassian defaults computed at build time.\u003C\u002Fp>\r\n\r\n\u003Cp>The result is that it is very easy to encounter NoClassDefFound exceptions even though the required class is on the class path. Whenever this happens, it is worth checking the plugin configuration for missing package-import statements.\u003C\u002Fp>\r\n","\u003Cp>Integration\u002Fwired tests are run from a complementary plugin installed within the same environment.\u003C\u002Fp>\n\n\u003Cp>Both plugins have dependencies on multiple Java libraries. A Java application satisfies its dependencies at runtime by calling its class loader to obtain them. Standard class loaders delegate to their parents first, and only start searching the locations on their class path if the parent is unable to load the required resource or class.\u003C\u002Fp>\n\n\u003Cp>However, in the case of Jira it’s different. Jira wants to avoid every plugin loading copies of the same class, as this could easily increase the amount of memory used for class storage by a few orders of magnitude. It uses the OSGi framework, which comes with Spring, to provide a cache of already-loaded classes (called Felix) and to satisfy all dependencies from this cache.\u003C\u002Fp>\n\n\u003Cp>This has important implications for the plugin configuration. Felix only loads classes permitted by the plugin configuration. This configuration is composed of instructions in the&nbsp;atlassian-plugin.xml&nbsp;file (a resource), the project POM, and some Atlassian defaults computed at build time.\u003C\u002Fp>\n\n\u003Cp>The result is that it is very easy to encounter NoClassDefFound exceptions even though the required class is on the class path. Whenever this happens, it is worth checking the plugin configuration for missing package-import statements.\u003C\u002Fp>",{"__typename":254,"id":344,"ctaLink":214,"pgSharedText":345,"pgSharedTitle":348,"pgSharedCtaLink":214},"8959",{"value":346,"processed":347,"format":259},"\u003Ch3>Installing the tools\u003C\u002Fh3>\r\n\r\n\u003Cp>I am working on a MacBook Pro, so I have installed:\u003C\u002Fp>\r\n\r\n\u003Cul>\r\n\t\u003Cli>Java 8 JDK from&nbsp;\u003Ca href=\"https:\u002F\u002Fwww.oracle.com\u002Ftechnetwork\u002Fjava\u002Fjavase\u002Fdownloads\u002Fjdk8-downloads-2133151.html\">Oracle\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.apple.com\u002Fxcode\u002Ffeatures\u002F\">XCode\u003C\u002Fa>&nbsp;from the App Store (this provides, inter alia, a command-line&nbsp;\u003Ca href=\"https:\u002F\u002Fgit-scm.com\u002Fdownloads\">git client\u003C\u002Fa>, which you will need – but there are other ways to obtain one)\u003C\u002Fli>\r\n\t\u003Cli>IntelliJ IDEA direct from the&nbsp;\u003Ca href=\"https:\u002F\u002Fwww.jetbrains.com\u002Fidea\u002Fdownload\">Jetbrains site\u003C\u002Fa>&nbsp;(I prefer a paid-for license, but the free community edition would do)\u003C\u002Fli>\r\n\t\u003Cli>The&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdownloads\u002F\">Atlassian SDK\u003C\u002Fa>&nbsp;– it turns out that&nbsp;\u003Ca href=\"https:\u002F\u002Fdocs.brew.sh\u002FInstallation\">Homebrew\u003C\u002Fa>&nbsp;is the preferred way to&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdownloads\u002F#homebrew-os-x\">install this\u003C\u002Fa>, as the pkg installer makes unwarranted assumptions about your version of Java, Python and more\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n\r\n\u003Cp>&nbsp;\u003C\u002Fp>\r\n\r\n\u003Ch2>Start your project\u003C\u002Fh2>\r\n\r\n\u003Cp>The first thing to do is to decide how you’re going to version-control your work. I am using git with an in-house Bitbucket service, so I created a repository via the Web UI on bitbucket and cloned it to my workspace.\u003C\u002Fp>\r\n\r\n\u003Cp>Use the sequence of instructions in the&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fcreate-a-helloworld-plugin-project\u002F\">Hello World tutorial\u003C\u002Fa>&nbsp;to create a plugin skeleton – but select your values for Maven group ID, Maven artifact ID, Maven version and Java package according to your real needs. For example (your inputs in&nbsp;bold):\u003C\u002Fp>\r\n","\u003Ch3>Installing the tools\u003C\u002Fh3>\n\n\u003Cp>I am working on a MacBook Pro, so I have installed:\u003C\u002Fp>\n\n\u003Cul>\n\t\u003Cli>Java 8 JDK from&nbsp;\u003Ca href=\"https:\u002F\u002Fwww.oracle.com\u002Ftechnetwork\u002Fjava\u002Fjavase\u002Fdownloads\u002Fjdk8-downloads-2133151.html\">Oracle\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.apple.com\u002Fxcode\u002Ffeatures\u002F\">XCode\u003C\u002Fa>&nbsp;from the App Store (this provides, inter alia, a command-line&nbsp;\u003Ca href=\"https:\u002F\u002Fgit-scm.com\u002Fdownloads\">git client\u003C\u002Fa>, which you will need – but there are other ways to obtain one)\u003C\u002Fli>\n\t\u003Cli>IntelliJ IDEA direct from the&nbsp;\u003Ca href=\"https:\u002F\u002Fwww.jetbrains.com\u002Fidea\u002Fdownload\">Jetbrains site\u003C\u002Fa>&nbsp;(I prefer a paid-for license, but the free community edition would do)\u003C\u002Fli>\n\t\u003Cli>The&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdownloads\u002F\">Atlassian SDK\u003C\u002Fa>&nbsp;– it turns out that&nbsp;\u003Ca href=\"https:\u002F\u002Fdocs.brew.sh\u002FInstallation\">Homebrew\u003C\u002Fa>&nbsp;is the preferred way to&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdownloads\u002F#homebrew-os-x\">install this\u003C\u002Fa>, as the pkg installer makes unwarranted assumptions about your version of Java, Python and more\u003C\u002Fli>\n\u003C\u002Ful>\n\n\n\n\u003Ch2>Start your project\u003C\u002Fh2>\n\n\u003Cp>The first thing to do is to decide how you’re going to version-control your work. I am using git with an in-house Bitbucket service, so I created a repository via the Web UI on bitbucket and cloned it to my workspace.\u003C\u002Fp>\n\n\u003Cp>Use the sequence of instructions in the&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fcreate-a-helloworld-plugin-project\u002F\">Hello World tutorial\u003C\u002Fa>&nbsp;to create a plugin skeleton – but select your values for Maven group ID, Maven artifact ID, Maven version and Java package according to your real needs. For example (your inputs in&nbsp;bold):\u003C\u002Fp>","Getting started",{"__typename":270,"id":350,"pgSharedTitle":214,"pgCodeCode":351},"8960",{"value":352,"processed":353,"format":275},"\u003Cpre>\r\n$ \u003Cstrong>git clone ...\u002FmyPlugin.git\u003C\u002Fstrong>\r\n$ \u003Cstrong>atlas-create-jira-plugin\u003C\u002Fstrong>\r\nExecuting: \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fbin\u002Fmvn com.atlassian.maven.plugins:maven-jira-plugin:6.3.21:create -gs \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fconf\u002Fsettings.xml\r\nreadlink: illegal option -- f\r\nusage: readlink [-n] [file ...]\r\nWarning: JAVA_HOME environment variable is not set.\r\nJava HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0\r\n[INFO] Scanning for projects...\r\n[INFO] \r\n[INFO] Using the builder org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder with a thread count of 1\r\n[INFO]                                                                         \r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] Building Maven Stub Project (No POM) 1\r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] \r\n[INFO] --- maven-jira-plugin:6.3.21:create (default-cli) @ standalone-pom ---\r\n[INFO] Google Analytics Tracking is enabled to collect AMPS usage statistics.\r\n[INFO] Although no personal information is sent, you may disable tracking by adding &lt;allowGoogleTracking&gt;false&lt;\u002FallowGoogleTracking&gt; to the amps plugin configuration in your pom.xml\r\n[INFO] Sending event to Google Analytics: AMPS:jira - Create Plugin\r\n[INFO] using stable product version: 7.12.0\r\n[INFO] using stable data version: 7.12.0\r\nDefine value for groupId: : \u003Cstrong>com.zuhlke.training.jira\u003C\u002Fstrong>\r\nDefine value for artifactId: : \u003Cstrong>myPlugin\u003C\u002Fstrong>\r\nDefine value for version:  1.0.0-SNAPSHOT: : \r\nDefine value for package:  com.zuhlke.training.jira: : \r\nConfirm properties configuration:\r\ngroupId: com.zuhlke.training.jira\r\nartifactId: myPlugin\r\nversion: 1.0.0-SNAPSHOT\r\npackage: com.zuhlke.training.jira\r\n Y: : \r\n[INFO] Setting property: classpath.resource.loader.class =&gt; 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.\r\n[INFO] Setting property: velocimacro.messages.on =&gt; 'false'.\r\n[INFO] Setting property: resource.loader =&gt; 'classpath'.\r\n[INFO] Setting property: resource.manager.logwhenfound =&gt; 'false'.\r\n[INFO] Generating project in Batch mode\r\n[INFO] Archetype repository missing. Using the one from [com.atlassian.maven.archetypes:jira-plugin-archetype:5 -&gt; https:\u002F\u002Fmaven.atlassian.com\u002Fpublic] found in catalog internal\r\n[WARNING] The directory \u002FUsers\u002Fihu\u002FIdeaProjects\u002FmyPlugin already exists.\r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] BUILD SUCCESS\r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] Total time: 41.487 s\r\n[INFO] Finished at: 2018-11-22T11:17:01+00:00\r\n[INFO] Final Memory: 16M\u002F223M\r\n[INFO] ------------------------------------------------------------------------\r\n$ \u003Cstrong>ls -al myPlugin\u003C\u002Fstrong>\r\ntotal 40\r\ndrwxr-xr-x   8 ihu  staff   256 22 Nov 11:17 .\r\ndrwxr-xr-x   9 ihu  staff   288 22 Nov 11:15 ..\r\ndrwxr-xr-x  10 ihu  staff   320 22 Nov 11:15 .git\r\n-rw-r--r--   1 ihu  staff  3659 22 Nov 11:15 .gitignore\r\n-rw-r--r--   1 ihu  staff   310 22 Nov 11:17 LICENSE\r\n-rw-r--r--   1 ihu  staff   625 22 Nov 11:17 README\r\n-rw-r--r--   1 ihu  staff  1325 22 Nov 11:15 README.md\r\n-rw-r--r--   1 ihu  staff  7658 22 Nov 11:17 pom.xml\r\ndrwxr-xr-x   4 ihu  staff   128 22 Nov 11:17 src\r\n$ \u003Cstrong>cd myPlugin\r\n\u003C\u002Fstrong>$ \u003Cstrong>atlas-integration-test\u003C\u002Fstrong>\u003C\u002Fpre>\r\n","\u003Cpre>\n$ \u003Cstrong>git clone ...\u002FmyPlugin.git\u003C\u002Fstrong>\n$ \u003Cstrong>atlas-create-jira-plugin\u003C\u002Fstrong>\nExecuting: \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fbin\u002Fmvn com.atlassian.maven.plugins:maven-jira-plugin:6.3.21:create -gs \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fconf\u002Fsettings.xml\nreadlink: illegal option -- f\nusage: readlink [-n] [file ...]\nWarning: JAVA_HOME environment variable is not set.\nJava HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0\n[INFO] Scanning for projects...\n[INFO] \n[INFO] Using the builder org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder with a thread count of 1\n[INFO]                                                                         \n[INFO] ------------------------------------------------------------------------\n[INFO] Building Maven Stub Project (No POM) 1\n[INFO] ------------------------------------------------------------------------\n[INFO] \n[INFO] --- maven-jira-plugin:6.3.21:create (default-cli) @ standalone-pom ---\n[INFO] Google Analytics Tracking is enabled to collect AMPS usage statistics.\n[INFO] Although no personal information is sent, you may disable tracking by adding &lt;allowGoogleTracking&gt;false&lt;\u002FallowGoogleTracking&gt; to the amps plugin configuration in your pom.xml\n[INFO] Sending event to Google Analytics: AMPS:jira - Create Plugin\n[INFO] using stable product version: 7.12.0\n[INFO] using stable data version: 7.12.0\nDefine value for groupId: : \u003Cstrong>com.zuhlke.training.jira\u003C\u002Fstrong>\nDefine value for artifactId: : \u003Cstrong>myPlugin\u003C\u002Fstrong>\nDefine value for version:  1.0.0-SNAPSHOT: : \nDefine value for package:  com.zuhlke.training.jira: : \nConfirm properties configuration:\ngroupId: com.zuhlke.training.jira\nartifactId: myPlugin\nversion: 1.0.0-SNAPSHOT\npackage: com.zuhlke.training.jira\n Y: : \n[INFO] Setting property: classpath.resource.loader.class =&gt; 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.\n[INFO] Setting property: velocimacro.messages.on =&gt; 'false'.\n[INFO] Setting property: resource.loader =&gt; 'classpath'.\n[INFO] Setting property: resource.manager.logwhenfound =&gt; 'false'.\n[INFO] Generating project in Batch mode\n[INFO] Archetype repository missing. Using the one from [com.atlassian.maven.archetypes:jira-plugin-archetype:5 -&gt; https:\u002F\u002Fmaven.atlassian.com\u002Fpublic] found in catalog internal\n[WARNING] The directory \u002FUsers\u002Fihu\u002FIdeaProjects\u002FmyPlugin already exists.\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 41.487 s\n[INFO] Finished at: 2018-11-22T11:17:01+00:00\n[INFO] Final Memory: 16M\u002F223M\n[INFO] ------------------------------------------------------------------------\n$ \u003Cstrong>ls -al myPlugin\u003C\u002Fstrong>\ntotal 40\ndrwxr-xr-x   8 ihu  staff   256 22 Nov 11:17 .\ndrwxr-xr-x   9 ihu  staff   288 22 Nov 11:15 ..\ndrwxr-xr-x  10 ihu  staff   320 22 Nov 11:15 .git\n-rw-r--r--   1 ihu  staff  3659 22 Nov 11:15 .gitignore\n-rw-r--r--   1 ihu  staff   310 22 Nov 11:17 LICENSE\n-rw-r--r--   1 ihu  staff   625 22 Nov 11:17 README\n-rw-r--r--   1 ihu  staff  1325 22 Nov 11:15 README.md\n-rw-r--r--   1 ihu  staff  7658 22 Nov 11:17 pom.xml\ndrwxr-xr-x   4 ihu  staff   128 22 Nov 11:17 src\n$ \u003Cstrong>cd myPlugin\n\u003C\u002Fstrong>$ \u003Cstrong>atlas-integration-test\u003C\u002Fstrong>\u003C\u002Fpre>\n",{"__typename":254,"id":355,"ctaLink":214,"pgSharedText":356,"pgSharedTitle":214,"pgSharedCtaLink":214},"8961",{"value":357,"processed":358,"format":259},"\u003Cp>You can now open (no need to import) the workspace in \u003Cstrong>IDEA \u003C\u002Fstrong>in order to configure&nbsp;\u003Cstrong>README\u003C\u002Fstrong>.md&nbsp;and&nbsp;.\u003Cstrong>gitignore&nbsp;\u003C\u002Fstrong>files appropriately. NB the Atlassian tool&nbsp;atlas-create-jira-plugin&nbsp;will create a&nbsp;README&nbsp;file (without an extension) – not to be confused with the markdown file&nbsp;README.md, which github or bitbucket servers will display on the project’s home page.\u003C\u002Fp>\r\n\r\n\u003Cp>Before starting to modify anything, commit your initial checkpoint to version control. The easiest way is to open a terminal window in IDEA and enter the following two commands. Subsequent commits are more easily performed within the IDE, using CMD-K.\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\n$ git add .\r\n$ git commit -m 'Initial version'\u003C\u002Fpre>\r\n","\u003Cp>You can now open (no need to import) the workspace in \u003Cstrong>IDEA \u003C\u002Fstrong>in order to configure&nbsp;\u003Cstrong>README\u003C\u002Fstrong>.md&nbsp;and&nbsp;.\u003Cstrong>gitignore&nbsp;\u003C\u002Fstrong>files appropriately. NB the Atlassian tool&nbsp;atlas-create-jira-plugin&nbsp;will create a&nbsp;README&nbsp;file (without an extension) – not to be confused with the markdown file&nbsp;README.md, which github or bitbucket servers will display on the project’s home page.\u003C\u002Fp>\n\n\u003Cp>Before starting to modify anything, commit your initial checkpoint to version control. The easiest way is to open a terminal window in IDEA and enter the following two commands. Subsequent commits are more easily performed within the IDE, using CMD-K.\u003C\u002Fp>\n\n\n$ git add .\n$ git commit -m 'Initial version'",{"__typename":254,"id":360,"ctaLink":214,"pgSharedText":361,"pgSharedTitle":364,"pgSharedCtaLink":214},"8962",{"value":362,"processed":363,"format":259},"\u003Cul>\r\n\t\u003Cli>Make sure that the correct version of Java is used both to run your IDE and to run Jira (see above)\r\n\t\u003Cul>\r\n\t\t\u003Cli>If you need multiple versions of Java JDK on your machine, consider the use of&nbsp;\u003Ca href=\"http:\u002F\u002Fwww.jenv.be\u002F\">jEnv\u003C\u002Fa>&nbsp;to manage the version in use (sorry Windows users – not available for you)\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Configure IDEA via its preferences dialog to use atlas-mvn instead of the default bundled mvn executable\r\n\t\u003Cul>\r\n\t\t\u003Cli>Maven home directory: \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1 (or similar)\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>If not automatically set, configure the IDEA project SDK to match your version of Java\r\n\t\u003Cul>\r\n\t\t\u003Cli>Right-click the top level of the hierarchy under the Project Browser and select “open module settings”\u003C\u002Fli>\r\n\t\t\u003Cli>Alternatively, click the prompt at the top of any Java source window if it warns you that the project SDK has not been set\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Use the latest version of Jira both for the testkit and for Jira itself by adjusting the properties values at the bottom of the&nbsp;pom.xml&nbsp;file (I used 7.12.3 for both)\u003Cbr \u002F>\r\n\t&nbsp;\u003C\u002Fli>\r\n\t\u003Cli>Add JVM arguments to the maven-jira-plugin in the POM to increase the amount of heap\r\n\t\u003Cul>\r\n\t\t\u003Cli>See complete plugin configuration under “Final configuration” below\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Look out for the scope of dependencies in the Maven POM – most artifacts should be “provided” or possibly “test” (e.g. com.google.code.gson:gson should be “provided”)\r\n\t\u003Cul>\r\n\t\t\u003Cli>Search the target folder after executing&nbsp;\u003Cstrong>atlas-run,&nbsp;atlas-package&nbsp;or&nbsp;atlas-integration-tes\u003C\u002Fstrong>t&nbsp;to see which versions of libraries have been provided\u003C\u002Fli>\r\n\t\t\u003Cli>Only specify “compile” scope for packages that Jira does not provide, but that your main plugin depends on (atlassian-spring-scanner-annotations appears to be an exception to this rule)\u003C\u002Fli>\r\n\t\t\u003Cli>Give “runtime” scope to the dependency on atlassian-spring-scanner-runtime\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Add the following property values to the POM:\r\n\t\u003Cpre>\r\n    &lt;junit.version&gt;4.11&lt;\u002Fjunit.version&gt;\r\n    &lt;felix.framework.version&gt;4.2.1&lt;\u002Ffelix.framework.version&gt;\r\n    &lt;gemini.blueprint.version&gt;2.0.5.BUILD-atlassian-m002&lt;\u002Fgemini.blueprint.version&gt;\u003C\u002Fpre>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Set the version of the junit dependency to ${junit.version}\u003C\u002Fli>\r\n\t\u003Cli>Add the following dependencies to support integration testing:\r\n\t\u003Cul>\r\n\t\t\u003Cli>com.atlassian.plugins.rest:atlassian-rest-module:3.2.18:provided\u003C\u002Fli>\r\n\t\t\u003Cli>com.atlassian.sal:sal-api:3.1.0:provided\u003C\u002Fli>\r\n\t\t\u003Cli>org.eclipse.gemini.blueprint:gemini-blueprint-core:${gemini.blueprint.version}:provided\u003C\u002Fli>\r\n\t\t\u003Cli>com.atlassian.jira.tests:jira-testkit-client (just uncomment)\u003C\u002Fli>\r\n\t\t\u003Cli>com.atlassian.jira:jira-tests:${jira-version}:test\u003C\u002Fli>\r\n\t\t\u003Cli>org.slf4j:slf4j-log4j12:1.7.9:test\u003C\u002Fli>\r\n\t\t\u003Cli>junit:junit-dep:${junit-version}:test\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Do not let out-of-date tutorial documentation mislead you into adding a dependency on&nbsp;jira-func-test&nbsp;– this seems to be incompatible with Jira 7\u003C\u002Fli>\r\n\t\u003Cli>In IDEA, if the source folders are not identified automatically, you may have to right-click the POM file and select “Maven -&gt; import as Maven project” or “Maven -&gt; reimport”\u003Cbr \u002F>\r\n\t&nbsp;\u003C\u002Fli>\r\n\t\u003Cli>No Import-Package section should be needed in the main plugin’s&nbsp;atlassian-plugin.xml, because the imports are specified in the maven-jira-plugin configuration. However, you will need it in the test plugin (see below)\u003Cbr \u002F>\r\n\t&nbsp;\u003C\u002Fli>\r\n\t\u003Cli>The fastdev console (see illustration below) is deprecated – if you enable it in the POM, you get lots of verbose warnings.\r\n\t\u003Cul>\r\n\t\t\u003Cli>FastDev Console (FDC) would clash with QuickReload (qr) so you can only enable one or the other\u003C\u002Fli>\r\n\t\t\u003Cli>Only if you have enabled FDC will you be able to initiate integration tests from the&nbsp;\u003Ca href=\"http:\u002F\u002Flocalhost:2990\u002Fjira\u002Fplugins\u002Fservlet\u002Fit-test-console\">Web UI\u003C\u002Fa>&nbsp;– this is useful if you wish to remote-debug the plugin code on the server. This is the only circumstance under which I found it useful to enable FDC\u003C\u002Fli>\r\n\t\t\u003Cli>Whether you enable qr or FDC, running the&nbsp;atlas-package&nbsp;command will reload both the custom plugin and its plugin-tests plugin. However, in the FDC case, attempting to&nbsp;run&nbsp;the integration tests from the Web UI after a reload causes a server crash, while in the qr case it does nothing at all (the client receives a 404 error from the server because it tries to invoke a FastDev API)\u003C\u002Fli>\r\n\t\t\u003Cli>However, there is a workaround – see&nbsp;\u003Ca href=\"http:\u002F\u002Fzuehlke.dpstage.ch\u002Fblog\u002Fen\u002F?p=25399\">Speeding up Plugin Testing\u003C\u002Fa>&nbsp;at the end of this article.\u003C\u002Fli>\r\n\t\t\u003Cli>Import-Package may specify ‘;version=”0″‘ if you don’t care&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fmanaging-dependencies\u002F\">which version of the dependency\u003C\u002Fa>&nbsp;is required – normally you would not specify a version\u003Cbr \u002F>\r\n\t\t&nbsp;\u003C\u002Fli>\r\n\t\u003C\u002Ful>\r\n\t\u003C\u002Fli>\r\n\t\u003Cli>Import-Package should usually specify ‘;resolution:=”optional”‘ – this ensures that the plugin will not fail to load just because some dependency is not available at the time\u003C\u002Fli>\r\n\t\u003Cli>\u003Cbr \u002F>\r\n\tBefore you can use the IDE effectively, it’s necessary to execute&nbsp;\u003Cstrong>atlas-run\u003C\u002Fstrong>,&nbsp;\u003Cstrong>atlas-package\u003C\u002Fstrong>&nbsp;or&nbsp;\u003Cstrong>atlas-integration-test\u003C\u002Fstrong>&nbsp;at least once. This downloads the required dependencies using the Maven dependency resolution mechanism.\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n","\u003Cul>\n\t\u003Cli>Make sure that the correct version of Java is used both to run your IDE and to run Jira (see above)\n\t\u003Cul>\n\t\t\u003Cli>If you need multiple versions of Java JDK on your machine, consider the use of&nbsp;\u003Ca href=\"http:\u002F\u002Fwww.jenv.be\u002F\">jEnv\u003C\u002Fa>&nbsp;to manage the version in use (sorry Windows users – not available for you)\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>Configure IDEA via its preferences dialog to use atlas-mvn instead of the default bundled mvn executable\n\t\u003Cul>\n\t\t\u003Cli>Maven home directory: \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1 (or similar)\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>If not automatically set, configure the IDEA project SDK to match your version of Java\n\t\u003Cul>\n\t\t\u003Cli>Right-click the top level of the hierarchy under the Project Browser and select “open module settings”\u003C\u002Fli>\n\t\t\u003Cli>Alternatively, click the prompt at the top of any Java source window if it warns you that the project SDK has not been set\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>Use the latest version of Jira both for the testkit and for Jira itself by adjusting the properties values at the bottom of the&nbsp;pom.xml&nbsp;file (I used 7.12.3 for both)\u003Cbr>\n\t&nbsp;\u003C\u002Fli>\n\t\u003Cli>Add JVM arguments to the maven-jira-plugin in the POM to increase the amount of heap\n\t\u003Cul>\n\t\t\u003Cli>See complete plugin configuration under “Final configuration” below\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>Look out for the scope of dependencies in the Maven POM – most artifacts should be “provided” or possibly “test” (e.g. com.google.code.gson:gson should be “provided”)\n\t\u003Cul>\n\t\t\u003Cli>Search the target folder after executing&nbsp;\u003Cstrong>atlas-run,&nbsp;atlas-package&nbsp;or&nbsp;atlas-integration-tes\u003C\u002Fstrong>t&nbsp;to see which versions of libraries have been provided\u003C\u002Fli>\n\t\t\u003Cli>Only specify “compile” scope for packages that Jira does not provide, but that your main plugin depends on (atlassian-spring-scanner-annotations appears to be an exception to this rule)\u003C\u002Fli>\n\t\t\u003Cli>Give “runtime” scope to the dependency on atlassian-spring-scanner-runtime\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>Add the following property values to the POM:\n\t\n    &lt;junit.version&gt;4.11&lt;\u002Fjunit.version&gt;\n    &lt;felix.framework.version&gt;4.2.1&lt;\u002Ffelix.framework.version&gt;\n    &lt;gemini.blueprint.version&gt;2.0.5.BUILD-atlassian-m002&lt;\u002Fgemini.blueprint.version&gt;\n\t\u003C\u002Fli>\n\t\u003Cli>Set the version of the junit dependency to ${junit.version}\u003C\u002Fli>\n\t\u003Cli>Add the following dependencies to support integration testing:\n\t\u003Cul>\n\t\t\u003Cli>com.atlassian.plugins.rest:atlassian-rest-module:3.2.18:provided\u003C\u002Fli>\n\t\t\u003Cli>com.atlassian.sal:sal-api:3.1.0:provided\u003C\u002Fli>\n\t\t\u003Cli>org.eclipse.gemini.blueprint:gemini-blueprint-core:${gemini.blueprint.version}:provided\u003C\u002Fli>\n\t\t\u003Cli>com.atlassian.jira.tests:jira-testkit-client (just uncomment)\u003C\u002Fli>\n\t\t\u003Cli>com.atlassian.jira:jira-tests:${jira-version}:test\u003C\u002Fli>\n\t\t\u003Cli>org.slf4j:slf4j-log4j12:1.7.9:test\u003C\u002Fli>\n\t\t\u003Cli>junit:junit-dep:${junit-version}:test\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>Do not let out-of-date tutorial documentation mislead you into adding a dependency on&nbsp;jira-func-test&nbsp;– this seems to be incompatible with Jira 7\u003C\u002Fli>\n\t\u003Cli>In IDEA, if the source folders are not identified automatically, you may have to right-click the POM file and select “Maven -&gt; import as Maven project” or “Maven -&gt; reimport”\u003Cbr>\n\t&nbsp;\u003C\u002Fli>\n\t\u003Cli>No Import-Package section should be needed in the main plugin’s&nbsp;atlassian-plugin.xml, because the imports are specified in the maven-jira-plugin configuration. However, you will need it in the test plugin (see below)\u003Cbr>\n\t&nbsp;\u003C\u002Fli>\n\t\u003Cli>The fastdev console (see illustration below) is deprecated – if you enable it in the POM, you get lots of verbose warnings.\n\t\u003Cul>\n\t\t\u003Cli>FastDev Console (FDC) would clash with QuickReload (qr) so you can only enable one or the other\u003C\u002Fli>\n\t\t\u003Cli>Only if you have enabled FDC will you be able to initiate integration tests from the&nbsp;\u003Ca href=\"http:\u002F\u002Flocalhost:2990\u002Fjira\u002Fplugins\u002Fservlet\u002Fit-test-console\">Web UI\u003C\u002Fa>&nbsp;– this is useful if you wish to remote-debug the plugin code on the server. This is the only circumstance under which I found it useful to enable FDC\u003C\u002Fli>\n\t\t\u003Cli>Whether you enable qr or FDC, running the&nbsp;atlas-package&nbsp;command will reload both the custom plugin and its plugin-tests plugin. However, in the FDC case, attempting to&nbsp;run&nbsp;the integration tests from the Web UI after a reload causes a server crash, while in the qr case it does nothing at all (the client receives a 404 error from the server because it tries to invoke a FastDev API)\u003C\u002Fli>\n\t\t\u003Cli>However, there is a workaround – see&nbsp;\u003Ca href=\"http:\u002F\u002Fzuehlke.dpstage.ch\u002Fblog\u002Fen\u002F?p=25399\">Speeding up Plugin Testing\u003C\u002Fa>&nbsp;at the end of this article.\u003C\u002Fli>\n\t\t\u003Cli>Import-Package may specify ‘;version=”0″‘ if you don’t care&nbsp;\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fmanaging-dependencies\u002F\">which version of the dependency\u003C\u002Fa>&nbsp;is required – normally you would not specify a version\u003Cbr>\n\t\t&nbsp;\u003C\u002Fli>\n\t\u003C\u002Ful>\n\t\u003C\u002Fli>\n\t\u003Cli>Import-Package should usually specify ‘;resolution:=”optional”‘ – this ensures that the plugin will not fail to load just because some dependency is not available at the time\u003C\u002Fli>\n\t\u003Cli>\u003Cbr>\n\tBefore you can use the IDE effectively, it’s necessary to execute&nbsp;\u003Cstrong>atlas-run\u003C\u002Fstrong>,&nbsp;\u003Cstrong>atlas-package\u003C\u002Fstrong>&nbsp;or&nbsp;\u003Cstrong>atlas-integration-test\u003C\u002Fstrong>&nbsp;at least once. This downloads the required dependencies using the Maven dependency resolution mechanism.\u003C\u002Fli>\n\u003C\u002Ful>","Things to look out for",{"__typename":282,"id":366,"caption":214,"captionSecondary":214,"pgSharedTitle":214,"pgMediaRefMedia":367,"secondaryImage":214,"edgeToEdge":214,"fullWidth":214},"8963",{"id":368,"sharedMediaLegend":214,"mediaImage":369,"name":391},"3732",{"url":370,"width":371,"height":372,"alt":373,"title":214,"size":374,"mime":221,"variations":214,"mediaVariations":375,"infographVariations":385},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fatlassian-plugin-test-console.jpg",999,707,"Atlassian Plugin Test Console",66165,[376,379,380,382,384],{"name":295,"url":377,"width":238,"height":378},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_small\u002Fpublic\u002Fimages\u002Fatlassian-plugin-test-console.jpg.webp?itok=hzoDuQix",637,{"name":295,"url":377,"width":238,"height":378},{"name":300,"url":381,"width":371,"height":372},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_medium\u002Fpublic\u002Fimages\u002Fatlassian-plugin-test-console.jpg.webp?itok=Bh2Fs1QA",{"name":303,"url":383,"width":371,"height":372},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_large\u002Fpublic\u002Fimages\u002Fatlassian-plugin-test-console.jpg.webp?itok=VuciDodz",{"name":303,"url":383,"width":371,"height":372},[386,387,388,389,390],{"name":295,"url":377,"width":238,"height":378},{"name":295,"url":377,"width":238,"height":378},{"name":295,"url":377,"width":238,"height":378},{"name":300,"url":381,"width":371,"height":372},{"name":300,"url":381,"width":371,"height":372},"atlassian-plugin-test-console.jpg",{"__typename":314,"id":393,"caption":214,"imageRight":7,"pgSharedTitle":394,"pgSharedCtaLink":214,"textMedia":395,"pgSharedText":412},"8964","User Experience",{"id":396,"sharedMediaLegend":214,"mediaImage":397,"name":411},"3733",{"url":398,"width":399,"height":400,"alt":401,"title":214,"size":402,"mime":221,"variations":214,"textMediaVariations":403},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fnew-menu-entry-with-custom-icon-added-to-atlassian-jira.jpg",276,408,"New menu entry with custom icon added to Atlassian Jira",14175,[404,406,407,408,410],{"name":295,"url":405,"width":399,"height":400},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_small\u002Fpublic\u002Fimages\u002Fnew-menu-entry-with-custom-icon-added-to-atlassian-jira.jpg.webp?itok=-J1y8qRl",{"name":295,"url":405,"width":399,"height":400},{"name":295,"url":405,"width":399,"height":400},{"name":300,"url":409,"width":399,"height":400},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_medium\u002Fpublic\u002Fimages\u002Fnew-menu-entry-with-custom-icon-added-to-atlassian-jira.jpg.webp?itok=jewr8vFY",{"name":300,"url":409,"width":399,"height":400},"new-menu-entry-with-custom-icon-added-to-atlassian-jira.jpg",{"value":413,"processed":414,"format":259},"\u003Ch3>Customise the plugin\u003C\u002Fh3>\r\n\r\n\u003Cp>However, you can skip the creation of the “mySection” and “myItem” plugin modules.\u003C\u002Fp>\r\n\r\n\u003Cp>If you wish to include a Web UI in your plugin, which is the usual situation, the recommended way to create an initial landing page is to use the command&nbsp;atlas-create-jira-plugin-module&nbsp;to add plugin modules of type Web Item (25) and Web Panel (26). The web item comes first, even though it has to reference the web panel, which comes second.\u003C\u002Fp>\r\n","\u003Ch3>Customise the plugin\u003C\u002Fh3>\n\n\u003Cp>However, you can skip the creation of the “mySection” and “myItem” plugin modules.\u003C\u002Fp>\n\n\u003Cp>If you wish to include a Web UI in your plugin, which is the usual situation, the recommended way to create an initial landing page is to use the command&nbsp;atlas-create-jira-plugin-module&nbsp;to add plugin modules of type Web Item (25) and Web Panel (26). The web item comes first, even though it has to reference the web panel, which comes second.\u003C\u002Fp>",{"__typename":254,"id":416,"ctaLink":214,"pgSharedText":417,"pgSharedTitle":214,"pgSharedCtaLink":214},"9010",{"value":418,"processed":419,"format":259},"\u003Cp>Below, I specify&nbsp;jira.project.sidebar.plugins.navigation&nbsp;as the location where the new menu item should appear. This corresponds to the left-hand menu displayed when the user has navigated to a specific project. I have been unable to find a consolidated list of Jira user interface locations (called sections) where web items, pages, panels and so on can be inserted. Here are some you may find useful:\u003C\u002Fp>\r\n\r\n\u003Cul>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fgeneral-navigation-ui-locations\u002F\" target=\"_blank\">General Navigation UI Locations\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fproject-settings-ui-locations\u002F\" target=\"_blank\">Project Settings UI Locations\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fissue-view-ui-locations\u002F\" target=\"_blank\">Issue view UI Locations\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fcloud\u002Fjira\u002Fplatform\u002Fnew-issue-view-ui-locations\u002F\" target=\"_blank\">New issue view UI Locations\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fcloud\u002Fjira\u002Fplatform\u002Fadministration-ui-locations\u002F\" target=\"_blank\">Administration UI Locations\u003C\u002Fa>\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n\r\n\u003Cp>Atlassian also recommends a third-party utility called&nbsp;\u003Ca href=\"https:\u002F\u002Fmarketplace.atlassian.com\u002Fapps\u002F1211656\u002Fweb-fragment-finder?hosting=cloud&amp;tab=overview\" target=\"_blank\">Web fragments\u003C\u002Fa>&nbsp;to find sections\u002Flocations.\u003C\u002Fp>\r\n\r\n\u003Cp>Assuming you wish to use an icon that resembles those of the existing menu items, create this as e.g. arrows.png and store it in the folder src\u002Fmain\u002Fresources\u002Fimages before you begin. Note that it is extremely difficult to discover in Atlassian’s published documentation how to access an image you have added to the above folder. The correct URL for a Jira web resource is shown below.\u003C\u002Fp>\r\n","\u003Cp>Below, I specify&nbsp;jira.project.sidebar.plugins.navigation&nbsp;as the location where the new menu item should appear. This corresponds to the left-hand menu displayed when the user has navigated to a specific project. I have been unable to find a consolidated list of Jira user interface locations (called sections) where web items, pages, panels and so on can be inserted. Here are some you may find useful:\u003C\u002Fp>\n\n\u003Cul>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fgeneral-navigation-ui-locations\u002F\" target=\"_blank\">General Navigation UI Locations\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fproject-settings-ui-locations\u002F\" target=\"_blank\">Project Settings UI Locations\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fissue-view-ui-locations\u002F\" target=\"_blank\">Issue view UI Locations\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fcloud\u002Fjira\u002Fplatform\u002Fnew-issue-view-ui-locations\u002F\" target=\"_blank\">New issue view UI Locations\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fcloud\u002Fjira\u002Fplatform\u002Fadministration-ui-locations\u002F\" target=\"_blank\">Administration UI Locations\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>Atlassian also recommends a third-party utility called&nbsp;\u003Ca href=\"https:\u002F\u002Fmarketplace.atlassian.com\u002Fapps\u002F1211656\u002Fweb-fragment-finder?hosting=cloud&amp;tab=overview\" target=\"_blank\">Web fragments\u003C\u002Fa>&nbsp;to find sections\u002Flocations.\u003C\u002Fp>\n\n\u003Cp>Assuming you wish to use an icon that resembles those of the existing menu items, create this as e.g. arrows.png and store it in the folder src\u002Fmain\u002Fresources\u002Fimages before you begin. Note that it is extremely difficult to discover in Atlassian’s published documentation how to access an image you have added to the above folder. The correct URL for a Jira web resource is shown below.\u003C\u002Fp>",{"__typename":254,"id":421,"ctaLink":214,"pgSharedText":422,"pgSharedTitle":214,"pgSharedCtaLink":214},"8965",{"value":423,"processed":424,"format":259},"\u003Cp>Alternatively, define the icon as a SVG drawing embedded in a stylesheet, as the system icons are. Such icons are referenced from within&nbsp;\u003Cstrong>atlassian-plugin.xml\u003C\u002Fstrong>&nbsp;using a style reference, so you would omit the advanced setup from the web item in the dialog below, and instead edit the file directly afterwards. See below.\u003C\u002Fp>\r\n","\u003Cp>Alternatively, define the icon as a SVG drawing embedded in a stylesheet, as the system icons are. Such icons are referenced from within&nbsp;\u003Cstrong>atlassian-plugin.xml\u003C\u002Fstrong>&nbsp;using a style reference, so you would omit the advanced setup from the web item in the dialog below, and instead edit the file directly afterwards. See below.\u003C\u002Fp>",{"__typename":270,"id":426,"pgSharedTitle":214,"pgCodeCode":427},"8966",{"value":428,"processed":429,"format":275},"\u003Cpre>\r\n$ \u003Cstrong>atlas-create-jira-plugin-module\u003C\u002Fstrong> \r\nExecuting: \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fbin\u002Fmvn com.atlassian.maven.plugins:maven-jira-plugin:6.3.21:create-plugin-module -gs \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fconf\u002Fsettings.xml\r\nreadlink: illegal option -- f\r\nusage: readlink [-n] [file ...]\r\nWarning: JAVA_HOME environment variable is not set.\r\nJava HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0\r\n[INFO] Scanning for projects...\r\n[INFO] \r\n[INFO] Using the builder org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder with a thread count of 1\r\n[INFO]                                                                         \r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] Building myPlugin 1.0.0-SNAPSHOT\r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] \r\n[INFO] --- maven-jira-plugin:6.3.21:create-plugin-module (default-cli) @ myPlugin ---\r\nChoose Plugin Module:\r\n...\r\n25: Web Item\r\n26: Web Panel\r\n...\r\nChoose a number (1\u002F2\u002F3\u002F4\u002F5\u002F6\u002F7\u002F8\u002F9\u002F10\u002F11\u002F12\u002F13\u002F14\u002F15\u002F16\u002F17\u002F18\u002F19\u002F20\u002F21\u002F22\u002F23\u002F24\u002F25\u002F26\u002F27\u002F28\u002F29\u002F30\u002F31\u002F32\u002F33\u002F34): \u003Cstrong>25\u003C\u002Fstrong>\r\n[INFO] Google Analytics Tracking is enabled to collect AMPS usage statistics.\r\n[INFO] Although no personal information is sent, you may disable tracking by adding &lt;allowGoogleTracking&gt;false&lt;\u002FallowGoogleTracking&gt; to the amps plugin configuration in your pom.xml\r\n[INFO] Sending event to Google Analytics: AMPS:jira - Create Plugin Module - jira:Web Item\r\nEnter Plugin Module Name My Web Item: : \u003Cstrong>metrics-page-item\u003C\u002Fstrong>\r\nEnter Section (e.g. system.admin\u002Fglobalsettings): \u003Cstrong>jira.project.sidebar.plugins.navigation\u003C\u002Fstrong>\r\nEnter Link URL (e.g. \u002Fsecure\u002FCreateIssue!default.jspa): \u003Cstrong>\u002Fprojects\u002F${pathEncodedProjectKey}?selectedItem=com.atlassian.jira.jira-projects-plugin:metrics-page\u003C\u002Fstrong>\r\nShow Advanced Setup? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\r\nModule Key metrics--page--item: : \r\nModule Description The metrics-page-item Plugin: : \u003Cstrong>Menu item for Metrics Page\u003C\u002Fstrong>\r\ni18n Name Key metrics--page--item.name: : \r\ni18n Description Key metrics--page--item.description: : \r\nWeight 1000: : \r\nLink Id metrics--page--item-link: : \r\nEnter Label Key metrics--page--item.label: : \r\nEnter Label Value metrics-page-item: : \u003Cstrong>Agile Metrics\u003C\u002Fstrong>\r\nAdd Label Param? (Y\u002Fy\u002FN\u002Fn) N: : \r\nAdd Icon? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\r\nIcon Location (e.g. \u002Fimages\u002Ficons\u002Fprint.gif): \u003Cstrong>${baseurl}\u002Fdownload\u002Fresources\u002F${atlassian.plugin.key}:myPlugin-resources\u002Fimages\u002Farrows.png\u003C\u002Fstrong>\r\nIcon Width 16: : \u003Cstrong>32\u003C\u002Fstrong>\r\nIcon Height 16: : \u003Cstrong>32\u003C\u002Fstrong>\r\nAdd Tooltip? (Y\u002Fy\u002FN\u002Fn) N: : \r\nAdd Resource (Y\u002Fy\u002FN\u002Fn) N: : \r\nAdd Velocity Context Provider (Y\u002Fy\u002FN\u002Fn) N: : \r\nAdd Plugin Module Param? (Y\u002Fy\u002FN\u002Fn) N: : \r\nAdd Conditions? (Y\u002Fy\u002FN\u002Fn) N: : \r\n[INFO] Adding the following items to the project:\r\n[INFO]   [dependency: org.mockito:mockito-all]\r\n[INFO]   [module: web-item]\r\n[INFO]   i18n strings: 3\r\nAdd Another Plugin Module? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\r\nChoose Plugin Module:\r\n...\r\n25: Web Item\r\n26: Web Panel\r\n...\r\nChoose a number (1\u002F2\u002F3\u002F4\u002F5\u002F6\u002F7\u002F8\u002F9\u002F10\u002F11\u002F12\u002F13\u002F14\u002F15\u002F16\u002F17\u002F18\u002F19\u002F20\u002F21\u002F22\u002F23\u002F24\u002F25\u002F26\u002F27\u002F28\u002F29\u002F30\u002F31\u002F32\u002F33\u002F34): \u003Cstrong>26\u003C\u002Fstrong>\r\nEnter Plugin Module Name My Web Panel: : \u003Cstrong>metrics-page\u003C\u002Fstrong>\r\nEnter Location (e.g. system.admin\u002Fglobalsettings): \u003Cstrong>com.atlassian.jira.jira-projects-plugin:metrics-page\u003C\u002Fstrong>\r\nShow Advanced Setup? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\r\nModule Key metrics--page: : \r\nModule Description The metrics-page Plugin: : \u003Cstrong>Agile Metrics page\u003C\u002Fstrong>\r\ni18n Name Key metrics--page.name: : \r\ni18n Description Key metrics--page.description: : \r\nWeight 1000: : \r\nAdd Resource (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\r\nEnter Resource Name (leave blank to use namePattern): \u003Cstrong>view\u003C\u002Fstrong>\r\nEnter Resource Type download: : \u003Cstrong>velocity\u003C\u002Fstrong>\r\nEnter Location (path to resource file): \u003Cstrong>templates\u002Ftabpanels\u002Fmetrics-tab-panel.vm\u003C\u002Fstrong>\r\nAdd Resource Parameter? (Y\u002Fy\u002FN\u002Fn) N: :\r\nAdd Resource (Y\u002Fy\u002FN\u002Fn) N: : \r\nAdd Velocity Context Provider (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\r\nEnter Fully Qualified Context Provider Class com.zuhlke.training.jira.web.contextproviders.MyContextProvider: : \u003Cstrong>com.zuhlke.training.jira.impl.MetricsInfoImpl\u003C\u002Fstrong>\r\nAdd Conditions? (Y\u002Fy\u002FN\u002Fn) N: : \r\n[INFO] Adding the following items to the project:\r\n[INFO]   [dependency: org.mockito:mockito-all]\r\n[INFO]   [module: web-panel]\r\n[INFO]   i18n strings: 2\r\nAdd Another Plugin Module? (Y\u002Fy\u002FN\u002Fn) N: : \r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] BUILD SUCCESS\r\n[INFO] ------------------------------------------------------------------------\r\n[INFO] Total time: 03:38 min\r\n[INFO] Finished at: 2018-11-22T14:14:13+00:00\r\n[INFO] Final Memory: 26M\u002F378M\r\n[INFO] ------------------------------------------------------------------------\u003C\u002Fpre>\r\n","\u003Cpre>\n$ \u003Cstrong>atlas-create-jira-plugin-module\u003C\u002Fstrong> \nExecuting: \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fbin\u002Fmvn com.atlassian.maven.plugins:maven-jira-plugin:6.3.21:create-plugin-module -gs \u002FApplications\u002FAtlassian\u002Fatlassian-plugin-sdk-6.3.12\u002Fapache-maven-3.2.1\u002Fconf\u002Fsettings.xml\nreadlink: illegal option -- f\nusage: readlink [-n] [file ...]\nWarning: JAVA_HOME environment variable is not set.\nJava HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0\n[INFO] Scanning for projects...\n[INFO] \n[INFO] Using the builder org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder with a thread count of 1\n[INFO]                                                                         \n[INFO] ------------------------------------------------------------------------\n[INFO] Building myPlugin 1.0.0-SNAPSHOT\n[INFO] ------------------------------------------------------------------------\n[INFO] \n[INFO] --- maven-jira-plugin:6.3.21:create-plugin-module (default-cli) @ myPlugin ---\nChoose Plugin Module:\n...\n25: Web Item\n26: Web Panel\n...\nChoose a number (1\u002F2\u002F3\u002F4\u002F5\u002F6\u002F7\u002F8\u002F9\u002F10\u002F11\u002F12\u002F13\u002F14\u002F15\u002F16\u002F17\u002F18\u002F19\u002F20\u002F21\u002F22\u002F23\u002F24\u002F25\u002F26\u002F27\u002F28\u002F29\u002F30\u002F31\u002F32\u002F33\u002F34): \u003Cstrong>25\u003C\u002Fstrong>\n[INFO] Google Analytics Tracking is enabled to collect AMPS usage statistics.\n[INFO] Although no personal information is sent, you may disable tracking by adding &lt;allowGoogleTracking&gt;false&lt;\u002FallowGoogleTracking&gt; to the amps plugin configuration in your pom.xml\n[INFO] Sending event to Google Analytics: AMPS:jira - Create Plugin Module - jira:Web Item\nEnter Plugin Module Name My Web Item: : \u003Cstrong>metrics-page-item\u003C\u002Fstrong>\nEnter Section (e.g. system.admin\u002Fglobalsettings): \u003Cstrong>jira.project.sidebar.plugins.navigation\u003C\u002Fstrong>\nEnter Link URL (e.g. \u002Fsecure\u002FCreateIssue!default.jspa): \u003Cstrong>\u002Fprojects\u002F${pathEncodedProjectKey}?selectedItem=com.atlassian.jira.jira-projects-plugin:metrics-page\u003C\u002Fstrong>\nShow Advanced Setup? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\nModule Key metrics--page--item: : \nModule Description The metrics-page-item Plugin: : \u003Cstrong>Menu item for Metrics Page\u003C\u002Fstrong>\ni18n Name Key metrics--page--item.name: : \ni18n Description Key metrics--page--item.description: : \nWeight 1000: : \nLink Id metrics--page--item-link: : \nEnter Label Key metrics--page--item.label: : \nEnter Label Value metrics-page-item: : \u003Cstrong>Agile Metrics\u003C\u002Fstrong>\nAdd Label Param? (Y\u002Fy\u002FN\u002Fn) N: : \nAdd Icon? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\nIcon Location (e.g. \u002Fimages\u002Ficons\u002Fprint.gif): \u003Cstrong>${baseurl}\u002Fdownload\u002Fresources\u002F${atlassian.plugin.key}:myPlugin-resources\u002Fimages\u002Farrows.png\u003C\u002Fstrong>\nIcon Width 16: : \u003Cstrong>32\u003C\u002Fstrong>\nIcon Height 16: : \u003Cstrong>32\u003C\u002Fstrong>\nAdd Tooltip? (Y\u002Fy\u002FN\u002Fn) N: : \nAdd Resource (Y\u002Fy\u002FN\u002Fn) N: : \nAdd Velocity Context Provider (Y\u002Fy\u002FN\u002Fn) N: : \nAdd Plugin Module Param? (Y\u002Fy\u002FN\u002Fn) N: : \nAdd Conditions? (Y\u002Fy\u002FN\u002Fn) N: : \n[INFO] Adding the following items to the project:\n[INFO]   [dependency: org.mockito:mockito-all]\n[INFO]   [module: web-item]\n[INFO]   i18n strings: 3\nAdd Another Plugin Module? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\nChoose Plugin Module:\n...\n25: Web Item\n26: Web Panel\n...\nChoose a number (1\u002F2\u002F3\u002F4\u002F5\u002F6\u002F7\u002F8\u002F9\u002F10\u002F11\u002F12\u002F13\u002F14\u002F15\u002F16\u002F17\u002F18\u002F19\u002F20\u002F21\u002F22\u002F23\u002F24\u002F25\u002F26\u002F27\u002F28\u002F29\u002F30\u002F31\u002F32\u002F33\u002F34): \u003Cstrong>26\u003C\u002Fstrong>\nEnter Plugin Module Name My Web Panel: : \u003Cstrong>metrics-page\u003C\u002Fstrong>\nEnter Location (e.g. system.admin\u002Fglobalsettings): \u003Cstrong>com.atlassian.jira.jira-projects-plugin:metrics-page\u003C\u002Fstrong>\nShow Advanced Setup? (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\nModule Key metrics--page: : \nModule Description The metrics-page Plugin: : \u003Cstrong>Agile Metrics page\u003C\u002Fstrong>\ni18n Name Key metrics--page.name: : \ni18n Description Key metrics--page.description: : \nWeight 1000: : \nAdd Resource (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\nEnter Resource Name (leave blank to use namePattern): \u003Cstrong>view\u003C\u002Fstrong>\nEnter Resource Type download: : \u003Cstrong>velocity\u003C\u002Fstrong>\nEnter Location (path to resource file): \u003Cstrong>templates\u002Ftabpanels\u002Fmetrics-tab-panel.vm\u003C\u002Fstrong>\nAdd Resource Parameter? (Y\u002Fy\u002FN\u002Fn) N: :\nAdd Resource (Y\u002Fy\u002FN\u002Fn) N: : \nAdd Velocity Context Provider (Y\u002Fy\u002FN\u002Fn) N: : \u003Cstrong>y\u003C\u002Fstrong>\nEnter Fully Qualified Context Provider Class com.zuhlke.training.jira.web.contextproviders.MyContextProvider: : \u003Cstrong>com.zuhlke.training.jira.impl.MetricsInfoImpl\u003C\u002Fstrong>\nAdd Conditions? (Y\u002Fy\u002FN\u002Fn) N: : \n[INFO] Adding the following items to the project:\n[INFO]   [dependency: org.mockito:mockito-all]\n[INFO]   [module: web-panel]\n[INFO]   i18n strings: 2\nAdd Another Plugin Module? (Y\u002Fy\u002FN\u002Fn) N: : \n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 03:38 min\n[INFO] Finished at: 2018-11-22T14:14:13+00:00\n[INFO] Final Memory: 26M\u002F378M\n[INFO] ------------------------------------------------------------------------\u003C\u002Fpre>\n",{"__typename":254,"id":431,"ctaLink":214,"pgSharedText":432,"pgSharedTitle":214,"pgSharedCtaLink":214},"8967",{"value":433,"processed":434,"format":259},"\u003Cp>As an alternative to an icon element containing a link to a web resource image file, stylesheet icons are referenced like this (e.g. the “reports” icon):\u003C\u002Fp>\r\n","\u003Cp>As an alternative to an icon element containing a link to a web resource image file, stylesheet icons are referenced like this (e.g. the “reports” icon):\u003C\u002Fp>",{"__typename":270,"id":436,"pgSharedTitle":214,"pgCodeCode":437},"8968",{"value":438,"processed":439,"format":275},"\u003Cpre>\r\n&lt;param name=\"iconClass\" value=\"aui-icon-large agile-icon-report aui-iconfont-graph-line\"\u002F&gt;\u003C\u002Fpre>\r\n","\u003Cpre>\n&lt;param name=\"iconClass\" value=\"aui-icon-large agile-icon-report aui-iconfont-graph-line\"\u002F&gt;\u003C\u002Fpre>\n",{"__typename":254,"id":441,"ctaLink":214,"pgSharedText":442,"pgSharedTitle":445,"pgSharedCtaLink":214},"9008",{"value":443,"processed":444,"format":259},"\u003Ch3>Access to Jira Data\u003C\u002Fh3>\r\n\r\n\u003Cp>At this point we have to provide additional source files: the Velocity template and the context provider class mentioned in the module configuration above. Suggested initial, extremely simple implementations are shown below.\u003C\u002Fp>\r\n\r\n\u003Cp>The&nbsp;\u003Ca href=\"http:\u002F\u002Fvelocity.apache.org\u002Fengine\u002F\" target=\"_blank\">Velocity\u003C\u002Fa>&nbsp;template is a recipe for generating a chunk of HTML that the web application server inserts in a web page. As you can see in the example below, it obtains variable text from context variables. In this case, aside from the built-in variables such as&nbsp;\u003Cstrong>$i18n\u003C\u002Fstrong>, which is an object capable of returning localised text for any given key (and which should of course be used for all the field labels instead of hard-coding them), we need a variable named&nbsp;\u003Cstrong>$project\u003C\u002Fstrong>. The context provider class (in this example,&nbsp;\u003Cstrong>MetricsInfoImpl\u003C\u002Fstrong>) is named in the web-panel module defined earlier. It has to provide a public method&nbsp;getContextMap, which returns the context information in the form of key-value pairs. Values can be complex objects – Velocity is clever enough to dereference the required attribute or property by repeatedly calling&nbsp;get…&nbsp;until it has descended to the desired level (e.g.&nbsp;\u003Cstrong>$project.lead.name\u003C\u002Fstrong>).\u003C\u002Fp>\r\n","\u003Ch3>Access to Jira Data\u003C\u002Fh3>\n\n\u003Cp>At this point we have to provide additional source files: the Velocity template and the context provider class mentioned in the module configuration above. Suggested initial, extremely simple implementations are shown below.\u003C\u002Fp>\n\n\u003Cp>The&nbsp;\u003Ca href=\"http:\u002F\u002Fvelocity.apache.org\u002Fengine\u002F\" target=\"_blank\">Velocity\u003C\u002Fa>&nbsp;template is a recipe for generating a chunk of HTML that the web application server inserts in a web page. As you can see in the example below, it obtains variable text from context variables. In this case, aside from the built-in variables such as&nbsp;\u003Cstrong>$i18n\u003C\u002Fstrong>, which is an object capable of returning localised text for any given key (and which should of course be used for all the field labels instead of hard-coding them), we need a variable named&nbsp;\u003Cstrong>$project\u003C\u002Fstrong>. The context provider class (in this example,&nbsp;\u003Cstrong>MetricsInfoImpl\u003C\u002Fstrong>) is named in the web-panel module defined earlier. It has to provide a public method&nbsp;getContextMap, which returns the context information in the form of key-value pairs. Values can be complex objects – Velocity is clever enough to dereference the required attribute or property by repeatedly calling&nbsp;get…&nbsp;until it has descended to the desired level (e.g.&nbsp;\u003Cstrong>$project.lead.name\u003C\u002Fstrong>).\u003C\u002Fp>","Connect the UI to Jira Data",{"__typename":270,"id":447,"pgSharedTitle":448,"pgCodeCode":449},"9009","metrics-tab-panel.vm (in src\u002Fmain\u002Fresources\u002Ftemplates\u002Ftabpanels)",{"value":450,"processed":451,"format":275},"\u003Cpre>\r\n&lt;div class=\"mod-header\"&gt;\r\n    &lt;h3&gt;$i18n.getText('metrics--page--item.label')&lt;\u002Fh3&gt;\r\n    &lt;table&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Name&lt;\u002Ftd&gt;&lt;td&gt;$project.name&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Key&lt;\u002Ftd&gt;&lt;td&gt;$project.key&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Description&lt;\u002Ftd&gt;&lt;td&gt;$project.description&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Project Lead Key&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.key&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Project Lead Name&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.name&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Project Lead Display Name&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.displayName&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n        &lt;tr&gt;\r\n            &lt;td&gt;Project Lead Email Address&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.emailAddress&lt;\u002Ftd&gt;\r\n        &lt;\u002Ftr&gt;\r\n    &lt;\u002Ftable&gt;\r\n&lt;\u002Fdiv&gt;\u003C\u002Fpre>\r\n","\u003Cpre>\n&lt;div class=\"mod-header\"&gt;\n    &lt;h3&gt;$i18n.getText('metrics--page--item.label')&lt;\u002Fh3&gt;\n    &lt;table&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Name&lt;\u002Ftd&gt;&lt;td&gt;$project.name&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Key&lt;\u002Ftd&gt;&lt;td&gt;$project.key&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Description&lt;\u002Ftd&gt;&lt;td&gt;$project.description&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Project Lead Key&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.key&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Project Lead Name&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.name&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Project Lead Display Name&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.displayName&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n        &lt;tr&gt;\n            &lt;td&gt;Project Lead Email Address&lt;\u002Ftd&gt;&lt;td&gt;$project.lead.emailAddress&lt;\u002Ftd&gt;\n        &lt;\u002Ftr&gt;\n    &lt;\u002Ftable&gt;\n&lt;\u002Fdiv&gt;\u003C\u002Fpre>\n",{"__typename":270,"id":453,"pgSharedTitle":454,"pgCodeCode":455},"9011","MetricsInfo.java",{"value":456,"processed":457,"format":275},"\u003Ch3>&nbsp;\u003C\u002Fh3>\r\n\r\n\u003Cpre>\r\npackage com.zuhlke.training.jira.api;\r\n\r\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\r\nimport com.atlassian.jira.user.ApplicationUser;\r\n\r\nimport java.util.Map;\r\n\r\npublic interface MetricsInfo {\r\n    String PROJECT = \"project\";\r\n\r\n    Map getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper);\r\n}\u003C\u002Fpre>\r\n","\u003Ch3>&nbsp;\u003C\u002Fh3>\n\n\u003Cpre>\npackage com.zuhlke.training.jira.api;\n\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\nimport com.atlassian.jira.user.ApplicationUser;\n\nimport java.util.Map;\n\npublic interface MetricsInfo {\n    String PROJECT = \"project\";\n\n    Map getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper);\n}\u003C\u002Fpre>\n",{"__typename":254,"id":459,"ctaLink":214,"pgSharedText":460,"pgSharedTitle":463,"pgSharedCtaLink":214},"9012",{"value":461,"processed":462,"format":259},"\u003Cp>There are numerous ways to get at information in Jira. Where the desired information is related to a project, the&nbsp;\u003Cstrong>UserProjectHistoryManager\u003C\u002Fstrong>&nbsp;turns out to be the most straightforward. There is another class named&nbsp;\u003Cstrong>ProjectManager\u003C\u002Fstrong>, which is easier to use in some circumstances – particularly where you already know the project key or you want a list of all projects in the system. However, in this case, we just want the currently selected project.\u003C\u002Fp>\r\n","\u003Cp>There are numerous ways to get at information in Jira. Where the desired information is related to a project, the&nbsp;\u003Cstrong>UserProjectHistoryManager\u003C\u002Fstrong>&nbsp;turns out to be the most straightforward. There is another class named&nbsp;\u003Cstrong>ProjectManager\u003C\u002Fstrong>, which is easier to use in some circumstances – particularly where you already know the project key or you want a list of all projects in the system. However, in this case, we just want the currently selected project.\u003C\u002Fp>","MetricsInfoImpl.java",{"__typename":270,"id":465,"pgSharedTitle":214,"pgCodeCode":466},"9013",{"value":467,"processed":468,"format":275},"\u003Cpre>\r\npackage com.zuhlke.training.jira.impl;\r\n\r\nimport com.atlassian.jira.plugin.webfragment.contextproviders.AbstractJiraContextProvider;\r\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\r\nimport com.atlassian.jira.project.Project;\r\nimport com.atlassian.jira.user.ApplicationUser;\r\nimport com.atlassian.jira.user.UserProjectHistoryManager;\r\nimport com.atlassian.plugin.spring.scanner.annotation.component.Scanned;\r\nimport com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\nimport static com.atlassian.jira.security.Permissions.PROJECT_ADMIN;\r\n\r\n@Scanned\r\npublic class MetricsInfoImpl extends AbstractJiraContextProvider implements com.zuhlke.training.jira.api.MetricsInfo {\r\n\r\n    @ComponentImport\r\n    private final UserProjectHistoryManager userProjectHistoryManager;\r\n\r\n    public MetricsInfoImpl(UserProjectHistoryManager userProjectHistoryManager){\r\n        this.userProjectHistoryManager = userProjectHistoryManager;\r\n    }\r\n\r\n    @Override\r\n    public Map getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper) {\r\n        Map&lt;String, Object&gt; contextMap = new HashMap&lt;&gt;();\r\n\r\n        Project currentProject = userProjectHistoryManager.getCurrentProject(PROJECT_ADMIN, applicationUser);\r\n        if(null != currentProject) {\r\n            contextMap.put(PROJECT, currentProject);\r\n        }\r\n\r\n        return contextMap;\r\n    }\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\npackage com.zuhlke.training.jira.impl;\n\nimport com.atlassian.jira.plugin.webfragment.contextproviders.AbstractJiraContextProvider;\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\nimport com.atlassian.jira.project.Project;\nimport com.atlassian.jira.user.ApplicationUser;\nimport com.atlassian.jira.user.UserProjectHistoryManager;\nimport com.atlassian.plugin.spring.scanner.annotation.component.Scanned;\nimport com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static com.atlassian.jira.security.Permissions.PROJECT_ADMIN;\n\n@Scanned\npublic class MetricsInfoImpl extends AbstractJiraContextProvider implements com.zuhlke.training.jira.api.MetricsInfo {\n\n    @ComponentImport\n    private final UserProjectHistoryManager userProjectHistoryManager;\n\n    public MetricsInfoImpl(UserProjectHistoryManager userProjectHistoryManager){\n        this.userProjectHistoryManager = userProjectHistoryManager;\n    }\n\n    @Override\n    public Map getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper) {\n        Map&lt;String, Object&gt; contextMap = new HashMap&lt;&gt;();\n\n        Project currentProject = userProjectHistoryManager.getCurrentProject(PROJECT_ADMIN, applicationUser);\n        if(null != currentProject) {\n            contextMap.put(PROJECT, currentProject);\n        }\n\n        return contextMap;\n    }\n}\u003C\u002Fpre>\n",{"__typename":254,"id":470,"ctaLink":214,"pgSharedText":471,"pgSharedTitle":214,"pgSharedCtaLink":214},"9014",{"value":472,"processed":473,"format":259},"\u003Cp>The annotation\u003Cstrong>&nbsp;@Scanned\u003C\u002Fstrong>&nbsp;tells the Atlassian Spring scanner to scan the class at build time. From&nbsp;\u003Ca href=\"https:\u002F\u002Fwww.j-tricks.com\u002Ftutorials\u002Fatlassian-spring-scanner-and-nosuchbeandefinitionexception\">https:\u002F\u002Fwww.j-tricks.com\u002Ftutorials\u002Fatlassian-spring-scanner-and-nosuchbeandefinitionexception\u003C\u002Fa>:\u003C\u002Fp>\r\n\r\n\u003Cul>\r\n\t\u003Cli>Anything in the constructor needs the&nbsp;\u003Cstrong>@ComponentImport\u003C\u002Fstrong>&nbsp;annotation and the class needs a&nbsp;\u003Cstrong>@Scanned\u003C\u002Fstrong>&nbsp;annotation\u003C\u002Fli>\r\n\t\u003Cli>You can use the components using&nbsp;ComponentAccessor, without the annotations:\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n\r\n\u003Cpre>\r\n    YourComponent yourComponent = ComponentAccessor.getComponent(YourComponent.class);\u003C\u002Fpre>\r\n\r\n\u003Cul>\r\n\t\u003Cli>But make sure that your component class itself has the required annotations to publish the class as a component:&nbsp;\u003Cstrong>@Component\u003C\u002Fstrong>&nbsp;and&nbsp;\u003Cstrong>@ExportAsService\u003C\u002Fstrong>, as required.\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n\r\n\u003Cp>It is therefore probably a good idea to add the&nbsp;\u003Cstrong>@Scanned\u003C\u002Fstrong>&nbsp;annotation to the&nbsp;MyPluginComponentImpl&nbsp;class too, for the sake of consistency.\u003C\u002Fp>\r\n","\u003Cp>The annotation\u003Cstrong>&nbsp;@Scanned\u003C\u002Fstrong>&nbsp;tells the Atlassian Spring scanner to scan the class at build time. From&nbsp;\u003Ca href=\"https:\u002F\u002Fwww.j-tricks.com\u002Ftutorials\u002Fatlassian-spring-scanner-and-nosuchbeandefinitionexception\">https:\u002F\u002Fwww.j-tricks.com\u002Ftutorials\u002Fatlassian-spring-scanner-and-nosuchbeandefinitionexception\u003C\u002Fa>:\u003C\u002Fp>\n\n\u003Cul>\n\t\u003Cli>Anything in the constructor needs the&nbsp;\u003Cstrong>@ComponentImport\u003C\u002Fstrong>&nbsp;annotation and the class needs a&nbsp;\u003Cstrong>@Scanned\u003C\u002Fstrong>&nbsp;annotation\u003C\u002Fli>\n\t\u003Cli>You can use the components using&nbsp;ComponentAccessor, without the annotations:\u003C\u002Fli>\n\u003C\u002Ful>\n\n\n    YourComponent yourComponent = ComponentAccessor.getComponent(YourComponent.class);\n\n\u003Cul>\n\t\u003Cli>But make sure that your component class itself has the required annotations to publish the class as a component:&nbsp;\u003Cstrong>@Component\u003C\u002Fstrong>&nbsp;and&nbsp;\u003Cstrong>@ExportAsService\u003C\u002Fstrong>, as required.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>It is therefore probably a good idea to add the&nbsp;\u003Cstrong>@Scanned\u003C\u002Fstrong>&nbsp;annotation to the&nbsp;MyPluginComponentImpl&nbsp;class too, for the sake of consistency.\u003C\u002Fp>",{"__typename":270,"id":475,"pgSharedTitle":476,"pgCodeCode":477},"9015","Final configuration",{"value":478,"processed":479,"format":275},"\u003Ch3>Eventual content of&nbsp;\u003Cstrong>atlassian-plugin.xml\u003C\u002Fstrong>:\u003C\u002Fh3>\r\n\r\n\u003Cpre>\r\n&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n\r\n&lt;atlassian-plugin key=\"${atlassian.plugin.key}\" name=\"${project.name}\" plugins-version=\"2\"&gt;\r\n  &lt;plugin-info&gt;\r\n    &lt;description&gt;${project.description}&lt;\u002Fdescription&gt;\r\n    &lt;version&gt;${project.version}&lt;\u002Fversion&gt;\r\n    &lt;vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\"\u002F&gt;\r\n    &lt;param name=\"plugin-icon\"&gt;images\u002FpluginIcon.png&lt;\u002Fparam&gt;\r\n    &lt;param name=\"plugin-logo\"&gt;images\u002FpluginLogo.png&lt;\u002Fparam&gt;\r\n  &lt;\u002Fplugin-info&gt;\r\n  &lt;!-- add our i18n resource --&gt;\r\n  &lt;resource type=\"i18n\" name=\"i18n\" location=\"myPlugin\"\u002F&gt;\r\n  &lt;!-- add our web resources --&gt;\r\n  &lt;web-resource key=\"myPlugin-resources\" name=\"myPlugin Web Resources\"&gt;\r\n    &lt;dependency&gt;com.atlassian.auiplugin:ajs&lt;\u002Fdependency&gt;\r\n    &lt;resource type=\"download\" name=\"myPlugin.css\" location=\"\u002Fcss\u002FmyPlugin.css\"\u002F&gt;\r\n    &lt;resource type=\"download\" name=\"myPlugin.js\" location=\"\u002Fjs\u002FmyPlugin.js\"\u002F&gt;\r\n    &lt;resource type=\"download\" name=\"images\u002F\" location=\"\u002Fimages\"\u002F&gt;\r\n    &lt;context&gt;myPlugin&lt;\u002Fcontext&gt;\r\n  &lt;\u002Fweb-resource&gt;\r\n  &lt;web-item name=\"metrics-page-item\" i18n-name-key=\"metrics--page--item.name\" key=\"metrics--page--item\" section=\"jira.project.sidebar.plugins.navigation\" weight=\"1000\"&gt;\r\n    &lt;description key=\"metrics--page--item.description\"&gt;Menu item for Metrics Page&lt;\u002Fdescription&gt;\r\n    &lt;icon width=\"32\" height=\"32\"&gt;\r\n      &lt;link linkId=\"metrics--page--item-icon\"&gt;${baseurl}\u002Fdownload\u002Fresources\u002F${atlassian.plugin.key}:myPlugin-resources\u002Fimages\u002Farrows.png&lt;\u002Flink&gt;\r\n    &lt;\u002Ficon&gt;\r\n    &lt;label key=\"metrics--page--item.label\"\u002F&gt;\r\n    &lt;link linkId=\"metrics--page--item-link\"&gt;\u002Fprojects\u002F${pathEncodedProjectKey}?selectedItem=com.atlassian.jira.jira-projects-plugin:metrics-page&lt;\u002Flink&gt;\r\n  &lt;\u002Fweb-item&gt;\r\n\r\n  &lt;web-panel name=\"metrics-page\" i18n-name-key=\"metrics--page.name\" key=\"metrics--page\" location=\"com.atlassian.jira.jira-projects-plugin:metrics-page\" weight=\"1000\"&gt;\r\n    &lt;description key=\"metrics--page.description\"&gt;Agile Metrics page&lt;\u002Fdescription&gt;\r\n    &lt;context-provider class=\"com.zuhlke.training.jira.impl.MetricsInfoImpl\"\u002F&gt;\r\n    &lt;resource name=\"view\" type=\"velocity\" location=\"templates\u002Ftabpanels\u002Fmetrics-tab-panel.vm\"\u002F&gt;\r\n  &lt;\u002Fweb-panel&gt;\r\n&lt;\u002Fatlassian-plugin&gt;\u003C\u002Fpre>\r\n","\u003Ch3>Eventual content of&nbsp;\u003Cstrong>atlassian-plugin.xml\u003C\u002Fstrong>:\u003C\u002Fh3>\n\n\u003Cpre>\n&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n\n&lt;atlassian-plugin key=\"${atlassian.plugin.key}\" name=\"${project.name}\" plugins-version=\"2\"&gt;\n  &lt;plugin-info&gt;\n    &lt;description&gt;${project.description}&lt;\u002Fdescription&gt;\n    &lt;version&gt;${project.version}&lt;\u002Fversion&gt;\n    &lt;vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\"\u002F&gt;\n    &lt;param name=\"plugin-icon\"&gt;images\u002FpluginIcon.png&lt;\u002Fparam&gt;\n    &lt;param name=\"plugin-logo\"&gt;images\u002FpluginLogo.png&lt;\u002Fparam&gt;\n  &lt;\u002Fplugin-info&gt;\n  &lt;!-- add our i18n resource --&gt;\n  &lt;resource type=\"i18n\" name=\"i18n\" location=\"myPlugin\"\u002F&gt;\n  &lt;!-- add our web resources --&gt;\n  &lt;web-resource key=\"myPlugin-resources\" name=\"myPlugin Web Resources\"&gt;\n    &lt;dependency&gt;com.atlassian.auiplugin:ajs&lt;\u002Fdependency&gt;\n    &lt;resource type=\"download\" name=\"myPlugin.css\" location=\"\u002Fcss\u002FmyPlugin.css\"\u002F&gt;\n    &lt;resource type=\"download\" name=\"myPlugin.js\" location=\"\u002Fjs\u002FmyPlugin.js\"\u002F&gt;\n    &lt;resource type=\"download\" name=\"images\u002F\" location=\"\u002Fimages\"\u002F&gt;\n    &lt;context&gt;myPlugin&lt;\u002Fcontext&gt;\n  &lt;\u002Fweb-resource&gt;\n  &lt;web-item name=\"metrics-page-item\" i18n-name-key=\"metrics--page--item.name\" key=\"metrics--page--item\" section=\"jira.project.sidebar.plugins.navigation\" weight=\"1000\"&gt;\n    &lt;description key=\"metrics--page--item.description\"&gt;Menu item for Metrics Page&lt;\u002Fdescription&gt;\n    &lt;icon width=\"32\" height=\"32\"&gt;\n      &lt;link linkId=\"metrics--page--item-icon\"&gt;${baseurl}\u002Fdownload\u002Fresources\u002F${atlassian.plugin.key}:myPlugin-resources\u002Fimages\u002Farrows.png&lt;\u002Flink&gt;\n    &lt;\u002Ficon&gt;\n    &lt;label key=\"metrics--page--item.label\"\u002F&gt;\n    &lt;link linkId=\"metrics--page--item-link\"&gt;\u002Fprojects\u002F${pathEncodedProjectKey}?selectedItem=com.atlassian.jira.jira-projects-plugin:metrics-page&lt;\u002Flink&gt;\n  &lt;\u002Fweb-item&gt;\n\n  &lt;web-panel name=\"metrics-page\" i18n-name-key=\"metrics--page.name\" key=\"metrics--page\" location=\"com.atlassian.jira.jira-projects-plugin:metrics-page\" weight=\"1000\"&gt;\n    &lt;description key=\"metrics--page.description\"&gt;Agile Metrics page&lt;\u002Fdescription&gt;\n    &lt;context-provider class=\"com.zuhlke.training.jira.impl.MetricsInfoImpl\"\u002F&gt;\n    &lt;resource name=\"view\" type=\"velocity\" location=\"templates\u002Ftabpanels\u002Fmetrics-tab-panel.vm\"\u002F&gt;\n  &lt;\u002Fweb-panel&gt;\n&lt;\u002Fatlassian-plugin&gt;\u003C\u002Fpre>\n",{"__typename":254,"id":481,"ctaLink":214,"pgSharedText":482,"pgSharedTitle":214,"pgSharedCtaLink":214},"9016",{"value":483,"processed":484,"format":259},"\u003Cp>The full specification of the maven-jira-plugin in&nbsp;\u003Cstrong>pom.xml\u003C\u002Fstrong>:\u003C\u002Fp>\r\n","\u003Cp>The full specification of the maven-jira-plugin in&nbsp;\u003Cstrong>pom.xml\u003C\u002Fstrong>:\u003C\u002Fp>",{"__typename":270,"id":486,"pgSharedTitle":214,"pgCodeCode":487},"9017",{"value":488,"processed":489,"format":275},"\u003Cpre>\r\n&lt;plugin&gt;\r\n    &lt;groupId&gt;com.atlassian.maven.plugins&lt;\u002FgroupId&gt;\r\n    &lt;artifactId&gt;maven-jira-plugin&lt;\u002FartifactId&gt;\r\n    &lt;version&gt;${amps.version}&lt;\u002Fversion&gt;\r\n    &lt;extensions&gt;true&lt;\u002Fextensions&gt;\r\n    &lt;configuration&gt;\r\n        &lt;productVersion&gt;${jira.version}&lt;\u002FproductVersion&gt;\r\n        &lt;productDataVersion&gt;${jira.version}&lt;\u002FproductDataVersion&gt;\r\n        &lt;jvmArgs&gt;-Xms1g -Xmx1g -XX:MaxPermSize=1g -XX:-UseGCOverheadLimit -server&lt;\u002FjvmArgs&gt;\r\n        &lt;!--Uncomment to install TestKit backdoor in JIRA.--&gt;\r\n        &lt;pluginArtifacts&gt;\r\n            &lt;pluginArtifact&gt;\r\n                &lt;groupId&gt;com.atlassian.jira.tests&lt;\u002FgroupId&gt;\r\n                &lt;artifactId&gt;jira-testkit-plugin&lt;\u002FartifactId&gt;\r\n                &lt;version&gt;${testkit.version}&lt;\u002Fversion&gt;\r\n            &lt;\u002FpluginArtifact&gt;\r\n        &lt;\u002FpluginArtifacts&gt;\r\n        &lt;enableQuickReload&gt;true&lt;\u002FenableQuickReload&gt;\r\n        &lt;enableFastdev&gt;false&lt;\u002FenableFastdev&gt;\r\n        &lt;!-- See here for an explanation of default instructions: --&gt;\r\n        &lt;!-- https:\u002F\u002Fdeveloper.atlassian.com\u002Fdocs\u002Fadvanced-topics\u002Fconfiguration-of-instructions-in-atlassian-plugins --&gt;\r\n        &lt;instructions&gt;\r\n            &lt;Atlassian-Plugin-Key&gt;${atlassian.plugin.key}&lt;\u002FAtlassian-Plugin-Key&gt;\r\n            &lt;!-- Add package to export here --&gt;\r\n            &lt;Export-Package&gt;\r\n                &lt;!-- package name from your interface e.g. MyPluginComponent.java here --&gt;\r\n                path.to.api\r\n            &lt;\u002FExport-Package&gt;\r\n            &lt;!-- Add package import here --&gt;\r\n            &lt;Import-Package&gt;\r\n                org.springframework.osgi.*;resolution:=\"optional\",\r\n                org.eclipse.gemini.blueprint.*;resolution:=\"optional\",\r\n                *\r\n            &lt;\u002FImport-Package&gt;\r\n            &lt;!-- Ensure plugin is spring powered --&gt;\r\n            &lt;Spring-Context&gt;*&lt;\u002FSpring-Context&gt;\r\n        &lt;\u002Finstructions&gt;\r\n    &lt;\u002Fconfiguration&gt;\r\n&lt;\u002Fplugin&gt;\u003C\u002Fpre>\r\n","\u003Cpre>\n&lt;plugin&gt;\n    &lt;groupId&gt;com.atlassian.maven.plugins&lt;\u002FgroupId&gt;\n    &lt;artifactId&gt;maven-jira-plugin&lt;\u002FartifactId&gt;\n    &lt;version&gt;${amps.version}&lt;\u002Fversion&gt;\n    &lt;extensions&gt;true&lt;\u002Fextensions&gt;\n    &lt;configuration&gt;\n        &lt;productVersion&gt;${jira.version}&lt;\u002FproductVersion&gt;\n        &lt;productDataVersion&gt;${jira.version}&lt;\u002FproductDataVersion&gt;\n        &lt;jvmArgs&gt;-Xms1g -Xmx1g -XX:MaxPermSize=1g -XX:-UseGCOverheadLimit -server&lt;\u002FjvmArgs&gt;\n        &lt;!--Uncomment to install TestKit backdoor in JIRA.--&gt;\n        &lt;pluginArtifacts&gt;\n            &lt;pluginArtifact&gt;\n                &lt;groupId&gt;com.atlassian.jira.tests&lt;\u002FgroupId&gt;\n                &lt;artifactId&gt;jira-testkit-plugin&lt;\u002FartifactId&gt;\n                &lt;version&gt;${testkit.version}&lt;\u002Fversion&gt;\n            &lt;\u002FpluginArtifact&gt;\n        &lt;\u002FpluginArtifacts&gt;\n        &lt;enableQuickReload&gt;true&lt;\u002FenableQuickReload&gt;\n        &lt;enableFastdev&gt;false&lt;\u002FenableFastdev&gt;\n        &lt;!-- See here for an explanation of default instructions: --&gt;\n        &lt;!-- https:\u002F\u002Fdeveloper.atlassian.com\u002Fdocs\u002Fadvanced-topics\u002Fconfiguration-of-instructions-in-atlassian-plugins --&gt;\n        &lt;instructions&gt;\n            &lt;Atlassian-Plugin-Key&gt;${atlassian.plugin.key}&lt;\u002FAtlassian-Plugin-Key&gt;\n            &lt;!-- Add package to export here --&gt;\n            &lt;Export-Package&gt;\n                &lt;!-- package name from your interface e.g. MyPluginComponent.java here --&gt;\n                path.to.api\n            &lt;\u002FExport-Package&gt;\n            &lt;!-- Add package import here --&gt;\n            &lt;Import-Package&gt;\n                org.springframework.osgi.*;resolution:=\"optional\",\n                org.eclipse.gemini.blueprint.*;resolution:=\"optional\",\n                *\n            &lt;\u002FImport-Package&gt;\n            &lt;!-- Ensure plugin is spring powered --&gt;\n            &lt;Spring-Context&gt;*&lt;\u002FSpring-Context&gt;\n        &lt;\u002Finstructions&gt;\n    &lt;\u002Fconfiguration&gt;\n&lt;\u002Fplugin&gt;\u003C\u002Fpre>\n",{"__typename":254,"id":491,"ctaLink":214,"pgSharedText":492,"pgSharedTitle":495,"pgSharedCtaLink":214},"9018",{"value":493,"processed":494,"format":259},"\u003Cp>At this point, you should be able to execute&nbsp;atlas-clean&nbsp;followed by&nbsp;atlas-run&nbsp;or&nbsp;atlas-debug. Note that starting the server takes a minute or two, and you’ll probably notice a series of stack traces complaining about missing jar files. Provided that you don’t find the string “failed to load” in the output, there is nothing to worry about. Point your browser at&nbsp;\u003Ca href=\"http:\u002F\u002Flocalhost:2990\u002Fjira\">http:\u002F\u002Flocalhost:2990\u002Fjira\u003C\u002Fa>&nbsp;and log in with admin\u002Fadmin.\u003C\u002Fp>\r\n\r\n\u003Cp>If things are going wrong, check the log files in these locations:\u003C\u002Fp>\r\n\r\n\u003Cul>\r\n\t\u003Cli>target\u002Fcontainer\u002Ftomcat8x\u002Fcargo-jira-home\u002Flogs\u003C\u002Fli>\r\n\t\u003Cli>target\u002Fjira\u002Fhome\u002Flog\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n\r\n\u003Cp>Don’t forget to commit your work!\u003C\u002Fp>\r\n","\u003Cp>At this point, you should be able to execute&nbsp;atlas-clean&nbsp;followed by&nbsp;atlas-run&nbsp;or&nbsp;atlas-debug. Note that starting the server takes a minute or two, and you’ll probably notice a series of stack traces complaining about missing jar files. Provided that you don’t find the string “failed to load” in the output, there is nothing to worry about. Point your browser at&nbsp;\u003Ca href=\"http:\u002F\u002Flocalhost:2990\u002Fjira\">http:\u002F\u002Flocalhost:2990\u002Fjira\u003C\u002Fa>&nbsp;and log in with admin\u002Fadmin.\u003C\u002Fp>\n\n\u003Cp>If things are going wrong, check the log files in these locations:\u003C\u002Fp>\n\n\u003Cul>\n\t\u003Cli>target\u002Fcontainer\u002Ftomcat8x\u002Fcargo-jira-home\u002Flogs\u003C\u002Fli>\n\t\u003Cli>target\u002Fjira\u002Fhome\u002Flog\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>Don’t forget to commit your work!\u003C\u002Fp>","Start the Server",{"__typename":254,"id":497,"ctaLink":214,"pgSharedText":498,"pgSharedTitle":501,"pgSharedCtaLink":214},"9019",{"value":499,"processed":500,"format":259},"\u003Ch3>Automated Testing\u003C\u002Fh3>\r\n\r\n\u003Cp>So far, we have only used the very rudimentary unit and integration tests generated by the Atlassian tools. In order to perform proper test-driven development (TDD), these need to be extended. In general, there should be a thorough unit test of each custom Java module and a simpler integration test of the same, which tests the “happy day” scenarios as well as one or two of the principal failure modes.\u003C\u002Fp>\r\n\r\n\u003Cp>Of course the&nbsp;MetricsInfoImpl&nbsp;class was developed using TDD, although I presented the finished code above as a fait accompli.\u003C\u002Fp>\r\n\r\n\u003Ch4>Unit Tests\u003C\u002Fh4>\r\n\r\n\u003Cp>All unit tests are located in the&nbsp;\u003Cstrong>src\u002Ftest\u002Fjava\u003C\u002Fstrong> source&nbsp;folder under the package&nbsp;ut.path.to.your.package. In this example, the package name is&nbsp;\u003Cstrong>ut.com.zuhlke.training.jira\u003C\u002Fstrong>&nbsp;(generated automatically by the initial&nbsp;\u003Cstrong>atlas-create-jira-plugin\u003C\u002Fstrong>&nbsp;command). These tests run both under the IDE and under Maven whenever the software is built. I will present one happy-day test case to show how the whole thing works.\u003C\u002Fp>\r\n\r\n\u003Cp>The unit tests have to emulate the context in which the plugin will run. This makes the setup quite verbose. Jira uses the mockito framework to make it easy to mock the environment – you just need to know what parts of the environment to mock!\u003C\u002Fp>\r\n\r\n\u003Cp>Start by creating the Java class&nbsp;\u003Cstrong>MetricsInfoImplUnitTest\u003C\u002Fstrong>&nbsp;and writing a failing test:\u003C\u002Fp>\r\n","\u003Ch3>Automated Testing\u003C\u002Fh3>\n\n\u003Cp>So far, we have only used the very rudimentary unit and integration tests generated by the Atlassian tools. In order to perform proper test-driven development (TDD), these need to be extended. In general, there should be a thorough unit test of each custom Java module and a simpler integration test of the same, which tests the “happy day” scenarios as well as one or two of the principal failure modes.\u003C\u002Fp>\n\n\u003Cp>Of course the&nbsp;MetricsInfoImpl&nbsp;class was developed using TDD, although I presented the finished code above as a fait accompli.\u003C\u002Fp>\n\n\u003Ch4>Unit Tests\u003C\u002Fh4>\n\n\u003Cp>All unit tests are located in the&nbsp;\u003Cstrong>src\u002Ftest\u002Fjava\u003C\u002Fstrong> source&nbsp;folder under the package&nbsp;ut.path.to.your.package. In this example, the package name is&nbsp;\u003Cstrong>ut.com.zuhlke.training.jira\u003C\u002Fstrong>&nbsp;(generated automatically by the initial&nbsp;\u003Cstrong>atlas-create-jira-plugin\u003C\u002Fstrong>&nbsp;command). These tests run both under the IDE and under Maven whenever the software is built. I will present one happy-day test case to show how the whole thing works.\u003C\u002Fp>\n\n\u003Cp>The unit tests have to emulate the context in which the plugin will run. This makes the setup quite verbose. Jira uses the mockito framework to make it easy to mock the environment – you just need to know what parts of the environment to mock!\u003C\u002Fp>\n\n\u003Cp>Start by creating the Java class&nbsp;\u003Cstrong>MetricsInfoImplUnitTest\u003C\u002Fstrong>&nbsp;and writing a failing test:\u003C\u002Fp>","Unit Tests",{"__typename":270,"id":503,"pgSharedTitle":214,"pgCodeCode":504},"9020",{"value":505,"processed":506,"format":275},"\u003Cpre>\r\npackage ut.com.zuhlke.training.jira;\r\n\r\nimport com.zuhlke.training.jira.api.MetricsInfo;\r\nimport com.zuhlke.training.jira.impl.MetricsInfoImpl;\r\nimport org.junit.Test;\r\n\r\nimport java.util.Map;\r\n\r\nimport static org.junit.Assert.assertNotNull;\r\n\r\npublic class MetricsInfoImplUnitTest {\r\n\r\n    @Test\r\n    public void shouldRetrieveContextMapForProject() {\r\n\r\n        final MetricsInfo metricsInfo = new MetricsInfoImpl(userProjectHistoryManager);\r\n        final Map&lt;String, Object&gt; contextMap = metricsInfo.getContextMap(applicationUser, jiraHelper);\r\n\r\n        assertNotNull(\"contextMap should not be null\", contextMap);\r\n    }\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\npackage ut.com.zuhlke.training.jira;\n\nimport com.zuhlke.training.jira.api.MetricsInfo;\nimport com.zuhlke.training.jira.impl.MetricsInfoImpl;\nimport org.junit.Test;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.assertNotNull;\n\npublic class MetricsInfoImplUnitTest {\n\n    @Test\n    public void shouldRetrieveContextMapForProject() {\n\n        final MetricsInfo metricsInfo = new MetricsInfoImpl(userProjectHistoryManager);\n        final Map&lt;String, Object&gt; contextMap = metricsInfo.getContextMap(applicationUser, jiraHelper);\n\n        assertNotNull(\"contextMap should not be null\", contextMap);\n    }\n}\u003C\u002Fpre>\n",{"__typename":254,"id":508,"ctaLink":214,"pgSharedText":509,"pgSharedTitle":214,"pgSharedCtaLink":214},"9021",{"value":510,"processed":511,"format":259},"\u003Cp>This fails to compile, because the objects&nbsp;\u003Cstrong>userProjectHistoryManager,&nbsp;applicationUser&nbsp;and&nbsp;jiraHelper\u003C\u002Fstrong>&nbsp;are undefined. Add some mocks to satisfy these requirements (above the first test case):\u003C\u002Fp>\r\n","\u003Cp>This fails to compile, because the objects&nbsp;\u003Cstrong>userProjectHistoryManager,&nbsp;applicationUser&nbsp;and&nbsp;jiraHelper\u003C\u002Fstrong>&nbsp;are undefined. Add some mocks to satisfy these requirements (above the first test case):\u003C\u002Fp>",{"__typename":270,"id":513,"pgSharedTitle":214,"pgCodeCode":514},"9022",{"value":515,"processed":516,"format":275},"\u003Cpre>\r\n@Rule\r\npublic MockitoContainer mockitoContainer = MockitoMocksInContainer.rule(this);\r\n\r\n@Mock\r\nprivate UserProjectHistoryManager userProjectHistoryManager;\r\n\r\n@Mock\r\nprivate ApplicationUser applicationUser;\r\n\r\n@Mock\r\nprivate JiraHelper jiraHelper;\u003C\u002Fpre>\r\n\r\n\u003Cp>The IDE automatically adds the necessary imports:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport com.atlassian.jira.junit.rules.MockitoContainer;\r\nimport com.atlassian.jira.junit.rules.MockitoMocksInContainer;\r\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\r\nimport com.atlassian.jira.user.ApplicationUser;\r\nimport com.atlassian.jira.user.UserProjectHistoryManager;\r\nimport org.junit.Rule;\r\nimport org.mockito.Mock;\u003C\u002Fpre>\r\n","\u003Cpre>\n@Rule\npublic MockitoContainer mockitoContainer = MockitoMocksInContainer.rule(this);\n\n@Mock\nprivate UserProjectHistoryManager userProjectHistoryManager;\n\n@Mock\nprivate ApplicationUser applicationUser;\n\n@Mock\nprivate JiraHelper jiraHelper;\u003C\u002Fpre>\n\n\u003Cp>The IDE automatically adds the necessary imports:\u003C\u002Fp>\n\n\u003Cpre>\nimport com.atlassian.jira.junit.rules.MockitoContainer;\nimport com.atlassian.jira.junit.rules.MockitoMocksInContainer;\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\nimport com.atlassian.jira.user.ApplicationUser;\nimport com.atlassian.jira.user.UserProjectHistoryManager;\nimport org.junit.Rule;\nimport org.mockito.Mock;\u003C\u002Fpre>\n",{"__typename":254,"id":518,"ctaLink":214,"pgSharedText":519,"pgSharedTitle":214,"pgSharedCtaLink":214},"9023",{"value":520,"processed":521,"format":259},"\u003Cp>Run the test to prove that it fails. Surprisingly perhaps, it passes – because of the defensive code in the implementation class, which I inserted following the failure of this test. So let’s add another assertion:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nassertEquals(\"Project\", project, contextMap.get(PROJECT));\u003C\u002Fpre>\r\n\r\n\u003Cp>This adds another import and requires us to add another mock object. This one is provided in quite a different manner. Just above the test case, add the following:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nfinal String projectKey            = \"SAM\";\r\nfinal String projectName           = \"SampleProject\";\r\nfinal MockProject project          = new MockProject(10001L, projectKey, projectName);\u003C\u002Fpre>\r\n\r\n\u003Cp>The following imports are added automatically (you may have to respond to the IDE’s prompt with ALT-ENTER):\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport static com.zuhlke.training.jira.api.MetricsInfo.PROJECT;\r\nimport com.atlassian.jira.project.MockProject;\r\nimport static org.junit.Assert.assertEquals;\u003C\u002Fpre>\r\n\r\n\u003Cp>Rather satisfyingly, the test now runs and fails. The context map has returned null when the object with the key PROJECT was requested. So a little more initialisation is needed at the start of the test case:\u003C\u002Fp>\r\n","\u003Cp>Run the test to prove that it fails. Surprisingly perhaps, it passes – because of the defensive code in the implementation class, which I inserted following the failure of this test. So let’s add another assertion:\u003C\u002Fp>\n\n\nassertEquals(\"Project\", project, contextMap.get(PROJECT));\n\n\u003Cp>This adds another import and requires us to add another mock object. This one is provided in quite a different manner. Just above the test case, add the following:\u003C\u002Fp>\n\n\nfinal String projectKey            = \"SAM\";\nfinal String projectName           = \"SampleProject\";\nfinal MockProject project          = new MockProject(10001L, projectKey, projectName);\n\n\u003Cp>The following imports are added automatically (you may have to respond to the IDE’s prompt with ALT-ENTER):\u003C\u002Fp>\n\n\nimport static com.zuhlke.training.jira.api.MetricsInfo.PROJECT;\nimport com.atlassian.jira.project.MockProject;\nimport static org.junit.Assert.assertEquals;\n\n\u003Cp>Rather satisfyingly, the test now runs and fails. The context map has returned null when the object with the key PROJECT was requested. So a little more initialisation is needed at the start of the test case:\u003C\u002Fp>",{"__typename":270,"id":523,"pgSharedTitle":214,"pgCodeCode":524},"9024",{"value":525,"processed":526,"format":275},"\u003Cpre>\r\nMockito.when(userProjectHistoryManager.getCurrentProject(PROJECT_ADMIN, applicationUser)).thenReturn(project);\u003C\u002Fpre>\r\n","\u003Cpre>\nMockito.when(userProjectHistoryManager.getCurrentProject(PROJECT_ADMIN, applicationUser)).thenReturn(project);\u003C\u002Fpre>\n",{"__typename":254,"id":528,"ctaLink":214,"pgSharedText":529,"pgSharedTitle":214,"pgSharedCtaLink":214},"9025",{"value":530,"processed":531,"format":259},"\u003Cp>This adds another import:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport static com.atlassian.jira.security.Permissions.PROJECT_ADMIN;\u003C\u002Fpre>\r\n\r\n\u003Cp>The test now passes. Just to be sure that we can access properties of the returned object, add the following to the end of the test case:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nfinal Project contextProject = (Project) contextMap.get(PROJECT);\r\nassertEquals(\"Project Name\", project.getName(), contextProject.getName());\u003C\u002Fpre>\r\n\r\n\u003Cp>The IDE adds the import:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport com.atlassian.jira.project.Project;\u003C\u002Fpre>\r\n","\u003Cp>This adds another import:\u003C\u002Fp>\n\n\nimport static com.atlassian.jira.security.Permissions.PROJECT_ADMIN;\n\n\u003Cp>The test now passes. Just to be sure that we can access properties of the returned object, add the following to the end of the test case:\u003C\u002Fp>\n\n\nfinal Project contextProject = (Project) contextMap.get(PROJECT);\nassertEquals(\"Project Name\", project.getName(), contextProject.getName());\n\n\u003Cp>The IDE adds the import:\u003C\u002Fp>\n\n\nimport com.atlassian.jira.project.Project;",{"__typename":254,"id":533,"ctaLink":214,"pgSharedText":534,"pgSharedTitle":537,"pgSharedCtaLink":214},"9026",{"value":535,"processed":536,"format":259},"\u003Ch3>Integration Tests\u003C\u002Fh3>\r\n\r\n\u003Cp>All integration tests are located under the package&nbsp;it.path.to.your.implementation&nbsp;– in this case,&nbsp;it.com.zuhlke.training.jira. These tests can only be run using the command&nbsp;atlas-integration-test&nbsp;or using the deprecated FastDev Console (see above).\u003C\u002Fp>\r\n\r\n\u003Ch4>Configuration for wired tests\u003C\u002Fh4>\r\n\r\n\u003Cp>Before you can use the context provider class in a wired test, you will have to import it to the test plugin as a component. And before you can do that, you’ll have to add annotations to export it appropriately. Add the following annotations to the&nbsp;MetricsInfoImpl&nbsp;class:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\n@ExportAsService({MetricsInfo.class})\r\n@Named(\"metricsInfo\")\u003C\u002Fpre>\r\n\r\n\u003Cp>Add the following annotations to its constructor function:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\n@Inject\r\n@Autowired\u003C\u002Fpre>\r\n\r\n\u003Cp>The IDE will insert the following imports:\u003C\u002Fp>\r\n","\u003Ch3>Integration Tests\u003C\u002Fh3>\n\n\u003Cp>All integration tests are located under the package&nbsp;it.path.to.your.implementation&nbsp;– in this case,&nbsp;it.com.zuhlke.training.jira. These tests can only be run using the command&nbsp;atlas-integration-test&nbsp;or using the deprecated FastDev Console (see above).\u003C\u002Fp>\n\n\u003Ch4>Configuration for wired tests\u003C\u002Fh4>\n\n\u003Cp>Before you can use the context provider class in a wired test, you will have to import it to the test plugin as a component. And before you can do that, you’ll have to add annotations to export it appropriately. Add the following annotations to the&nbsp;MetricsInfoImpl&nbsp;class:\u003C\u002Fp>\n\n\n@ExportAsService({MetricsInfo.class})\n@Named(\"metricsInfo\")\n\n\u003Cp>Add the following annotations to its constructor function:\u003C\u002Fp>\n\n\n@Inject\n@Autowired\n\n\u003Cp>The IDE will insert the following imports:\u003C\u002Fp>","Wired Tests 1",{"__typename":270,"id":539,"pgSharedTitle":214,"pgCodeCode":540},"9027",{"value":541,"processed":542,"format":275},"\u003Cpre>\r\nimport com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;\r\nimport com.zuhlke.training.jira.api.MetricsInfo;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport javax.inject.Inject;\r\nimport javax.inject.Named;\u003C\u002Fpre>\r\n","\u003Cpre>\nimport com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;\nimport com.zuhlke.training.jira.api.MetricsInfo;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport javax.inject.Inject;\nimport javax.inject.Named;\u003C\u002Fpre>\n",{"__typename":254,"id":544,"ctaLink":214,"pgSharedText":545,"pgSharedTitle":214,"pgSharedCtaLink":214},"9028",{"value":546,"processed":547,"format":259},"\u003Cp>Then add a component-import statement to the file&nbsp;\u003Cstrong>src\u002Ftest\u002Fresources\u002Fatlassian-plugin.xml:\u003C\u002Fstrong>\u003C\u002Fp>\r\n","\u003Cp>Then add a component-import statement to the file&nbsp;\u003Cstrong>src\u002Ftest\u002Fresources\u002Fatlassian-plugin.xml:\u003C\u002Fstrong>\u003C\u002Fp>",{"__typename":270,"id":549,"pgSharedTitle":214,"pgCodeCode":550},"9029",{"value":551,"processed":552,"format":275},"\u003Cpre>\r\n&lt;component-import key=\"metricsInfo\" interface=\"com.zuhlke.training.jira.api.MetricsInfo\"\u002F&gt;\u003C\u002Fpre>\r\n","\u003Cpre>\n&lt;component-import key=\"metricsInfo\" interface=\"com.zuhlke.training.jira.api.MetricsInfo\"\u002F&gt;\u003C\u002Fpre>\n",{"__typename":254,"id":554,"ctaLink":214,"pgSharedText":555,"pgSharedTitle":214,"pgSharedCtaLink":214},"9030",{"value":556,"processed":557,"format":259},"\u003Ch4>Trivial Wired Test\u003C\u002Fh4>\r\n\r\n\u003Cp>We can now start using this class in wired tests. Start by creating the Java class&nbsp;\u003Cstrong>MetricsInfoImplWiredTest\u003C\u002Fstrong>. Annotate the class as follows:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\n@RunWith(AtlassianPluginsTestRunner.class)\u003C\u002Fpre>\r\n\r\n\u003Cp>This test runner requires a constructor with parameters corresponding to one or more of the component-imports quoted in the file&nbsp;\u003Cstrong>src\u002Ftest\u002Fresources\u002Fatlassian-plugin.xml\u003C\u002Fstrong>. If you’re using an IDE, auto-generate the constructor and then change its signature to add the parameters, binding them to private final fields:\u003C\u002Fp>\r\n","\u003Ch4>Trivial Wired Test\u003C\u002Fh4>\n\n\u003Cp>We can now start using this class in wired tests. Start by creating the Java class&nbsp;\u003Cstrong>MetricsInfoImplWiredTest\u003C\u002Fstrong>. Annotate the class as follows:\u003C\u002Fp>\n\n\n@RunWith(AtlassianPluginsTestRunner.class)\n\n\u003Cp>This test runner requires a constructor with parameters corresponding to one or more of the component-imports quoted in the file&nbsp;\u003Cstrong>src\u002Ftest\u002Fresources\u002Fatlassian-plugin.xml\u003C\u002Fstrong>. If you’re using an IDE, auto-generate the constructor and then change its signature to add the parameters, binding them to private final fields:\u003C\u002Fp>",{"__typename":270,"id":559,"pgSharedTitle":214,"pgCodeCode":560},"9031",{"value":561,"processed":562,"format":275},"\u003Cpre>\r\npublic MetricsInfoImplWiredTest(ApplicationProperties applicationProperties,\r\n        MetricsInfo metricsInfo) {\r\n    this.applicationProperties = applicationProperties;\r\n    this.metricsInfo = metricsInfo;\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\npublic MetricsInfoImplWiredTest(ApplicationProperties applicationProperties,\n        MetricsInfo metricsInfo) {\n    this.applicationProperties = applicationProperties;\n    this.metricsInfo = metricsInfo;\n}\u003C\u002Fpre>\n",{"__typename":254,"id":564,"ctaLink":214,"pgSharedText":565,"pgSharedTitle":214,"pgSharedCtaLink":214},"9032",{"value":566,"processed":567,"format":259},"\u003Cp>You should now have these imports:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport com.atlassian.plugins.osgi.test.AtlassianPluginsTestRunner;\r\nimport com.atlassian.sal.api.ApplicationProperties;\r\nimport org.junit.runner.RunWith;\u003C\u002Fpre>\r\n\r\n\u003Cp>Now use the IDE code generator to create your first test:\u003C\u002Fp>\r\n","\u003Cp>You should now have these imports:\u003C\u002Fp>\n\n\nimport com.atlassian.plugins.osgi.test.AtlassianPluginsTestRunner;\nimport com.atlassian.sal.api.ApplicationProperties;\nimport org.junit.runner.RunWith;\n\n\u003Cp>Now use the IDE code generator to create your first test:\u003C\u002Fp>",{"__typename":270,"id":569,"pgSharedTitle":214,"pgCodeCode":570},"9033",{"value":571,"processed":572,"format":275},"\u003Cpre>\r\n@Test\r\npublic void shouldHaveCorrectInterfaces() {\r\n    assertTrue(\"applicationProperties should be instance of ApplicationProperties\",\r\n            applicationProperties instanceof ApplicationProperties);\r\n    assertTrue(\"metricsInfo should be instance of MetricsInfo\",\r\n            metricsInfo instanceof MetricsInfo);\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\n@Test\npublic void shouldHaveCorrectInterfaces() {\n    assertTrue(\"applicationProperties should be instance of ApplicationProperties\",\n            applicationProperties instanceof ApplicationProperties);\n    assertTrue(\"metricsInfo should be instance of MetricsInfo\",\n            metricsInfo instanceof MetricsInfo);\n}\u003C\u002Fpre>\n",{"__typename":254,"id":574,"ctaLink":214,"pgSharedText":575,"pgSharedTitle":214,"pgSharedCtaLink":214},"9034",{"value":576,"processed":577,"format":259},"\u003Cp>The following imports are added:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport org.junit.Test;\r\nimport static org.junit.Assert.assertTrue;\u003C\u002Fpre>\r\n\r\n\u003Cp>This test case simply verifies that the parameters passed to the test class constructor conform to the expected type, proving that you have wired everything up correctly. To run the test, you need to make sure that the local Jira server is not running (type CTRL-D into its console if it is still up, and wait up to a minute for it to shut down). Then, at the root of your workspace, enter the command&nbsp;\u003Cstrong>atlas-integration-test\u003C\u002Fstrong>.\u003C\u002Fp>\r\n","\u003Cp>The following imports are added:\u003C\u002Fp>\n\n\nimport org.junit.Test;\nimport static org.junit.Assert.assertTrue;\n\n\u003Cp>This test case simply verifies that the parameters passed to the test class constructor conform to the expected type, proving that you have wired everything up correctly. To run the test, you need to make sure that the local Jira server is not running (type CTRL-D into its console if it is still up, and wait up to a minute for it to shut down). Then, at the root of your workspace, enter the command&nbsp;\u003Cstrong>atlas-integration-test\u003C\u002Fstrong>.\u003C\u002Fp>",{"__typename":254,"id":579,"ctaLink":214,"pgSharedText":580,"pgSharedTitle":583,"pgSharedCtaLink":214},"9035",{"value":581,"processed":582,"format":259},"\u003Ch3>Non-trivial wired test\u003C\u002Fh3>\r\n\r\n\u003Cp>Assuming that the test passes, add a second test. First define a constant just below the opening brace of the\u003Cstrong>&nbsp;MetricsInfoImplWiredTest&nbsp;\u003C\u002Fstrong>class:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nfinal static String userName      = \"admin\";\u003C\u002Fpre>\r\n\r\n\u003Cp>Below the first test case, add the second:\u003C\u002Fp>\r\n","\u003Ch3>Non-trivial wired test\u003C\u002Fh3>\n\n\u003Cp>Assuming that the test passes, add a second test. First define a constant just below the opening brace of the\u003Cstrong>&nbsp;MetricsInfoImplWiredTest&nbsp;\u003C\u002Fstrong>class:\u003C\u002Fp>\n\n\nfinal static String userName      = \"admin\";\n\n\u003Cp>Below the first test case, add the second:\u003C\u002Fp>","Wired Tests 2",{"__typename":270,"id":585,"pgSharedTitle":214,"pgCodeCode":586},"9036",{"value":587,"processed":588,"format":275},"\u003Cpre>\r\n@Test\r\npublic void shouldReturnProjectLead() {\r\n    final Map contextMap = metricsInfo.getContextMap(null, null);\r\n    final Project project = (Project) contextMap.get(MetricsInfo.PROJECT);\r\n    assertNotNull(\"Project project should not be null\", project);\r\n    assertEquals(\"Project Lead Name\", userName, project.getLeadUserName());\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\n@Test\npublic void shouldReturnProjectLead() {\n    final Map contextMap = metricsInfo.getContextMap(null, null);\n    final Project project = (Project) contextMap.get(MetricsInfo.PROJECT);\n    assertNotNull(\"Project project should not be null\", project);\n    assertEquals(\"Project Lead Name\", userName, project.getLeadUserName());\n}\u003C\u002Fpre>\n",{"__typename":254,"id":590,"ctaLink":214,"pgSharedText":591,"pgSharedTitle":214,"pgSharedCtaLink":214},"9037",{"value":592,"processed":593,"format":259},"\u003Cp>The assertTrue import is deleted and the following imports are added:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport com.atlassian.jira.project.Project;\r\nimport java.util.Map;\r\nimport static org.junit.Assert.*;\u003C\u002Fpre>\r\n\r\n\u003Cp>This test will fail: the context map is empty. To make the test pass, you will have to set up certain preconditions and pass the correct parameters to the&nbsp;\u003Cstrong>getContextMap\u003C\u002Fstrong>&nbsp;call. Firstly, you’ll have to get hold of the&nbsp;\u003Cstrong>UserProjectHistoryManager\u003C\u002Fstrong>&nbsp;object used by the module under test. So add a method to the MetricsInfo interface:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nUserProjectHistoryManager getUserProjectHistoryManager();\u003C\u002Fpre>\r\n\r\n\u003Cp>The following import is added to the interface class’s source file:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport com.atlassian.jira.user.UserProjectHistoryManager;\u003C\u002Fpre>\r\n\r\n\u003Cp>Implement the method in&nbsp;\u003Cstrong>MetricsInfoImpl.java\u003C\u002Fstrong>:\u003C\u002Fp>\r\n","\u003Cp>The assertTrue import is deleted and the following imports are added:\u003C\u002Fp>\n\n\nimport com.atlassian.jira.project.Project;\nimport java.util.Map;\nimport static org.junit.Assert.*;\n\n\u003Cp>This test will fail: the context map is empty. To make the test pass, you will have to set up certain preconditions and pass the correct parameters to the&nbsp;\u003Cstrong>getContextMap\u003C\u002Fstrong>&nbsp;call. Firstly, you’ll have to get hold of the&nbsp;\u003Cstrong>UserProjectHistoryManager\u003C\u002Fstrong>&nbsp;object used by the module under test. So add a method to the MetricsInfo interface:\u003C\u002Fp>\n\n\nUserProjectHistoryManager getUserProjectHistoryManager();\n\n\u003Cp>The following import is added to the interface class’s source file:\u003C\u002Fp>\n\n\nimport com.atlassian.jira.user.UserProjectHistoryManager;\n\n\u003Cp>Implement the method in&nbsp;\u003Cstrong>MetricsInfoImpl.java\u003C\u002Fstrong>:\u003C\u002Fp>",{"__typename":270,"id":595,"pgSharedTitle":214,"pgCodeCode":596},"9038",{"value":597,"processed":598,"format":275},"\u003Cpre>\r\n@Override\r\npublic UserProjectHistoryManager getUserProjectHistoryManager() {\r\n    return this.userProjectHistoryManager;\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\n@Override\npublic UserProjectHistoryManager getUserProjectHistoryManager() {\n    return this.userProjectHistoryManager;\n}\u003C\u002Fpre>\n",{"__typename":254,"id":600,"ctaLink":214,"pgSharedText":601,"pgSharedTitle":214,"pgSharedCtaLink":214},"9039",{"value":602,"processed":603,"format":259},"\u003Cp>Next, the test class needs a few more constants:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nfinal static String projectName   = \"SampleProject\";\r\nfinal static String projectKey    = \"SAM\";\r\nfinal static String projectType   = \"business\";\u003C\u002Fpre>\r\n\r\n\u003Cp>It also needs a few more fields:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nprivate final Backdoor backdoor;\r\nprivate final JIRAEnvironmentData environmentData;\r\nprivate final UserManager userManager;\r\nprivate final ApplicationUser applicationUser;\r\nprivate final JiraHelper jiraHelper;\r\nprivate final UserProjectHistoryManager userProjectHistoryManager;\u003C\u002Fpre>\r\n\r\n\u003Cp>These fields have to be initialised in the constructor (or inline):\u003C\u002Fp>\r\n","\u003Cp>Next, the test class needs a few more constants:\u003C\u002Fp>\n\n\nfinal static String projectName   = \"SampleProject\";\nfinal static String projectKey    = \"SAM\";\nfinal static String projectType   = \"business\";\n\n\u003Cp>It also needs a few more fields:\u003C\u002Fp>\n\n\nprivate final Backdoor backdoor;\nprivate final JIRAEnvironmentData environmentData;\nprivate final UserManager userManager;\nprivate final ApplicationUser applicationUser;\nprivate final JiraHelper jiraHelper;\nprivate final UserProjectHistoryManager userProjectHistoryManager;\n\n\u003Cp>These fields have to be initialised in the constructor (or inline):\u003C\u002Fp>",{"__typename":270,"id":605,"pgSharedTitle":214,"pgCodeCode":606},"9040",{"value":607,"processed":608,"format":275},"\u003Cpre>\r\nthis.environmentData       = new TestKitLocalEnvironmentData(); \u002F\u002F localtest.properties supplies defaults\r\nthis.backdoor              = new Backdoor(environmentData);\r\nthis.userManager           = ComponentAccessor.getUserManager();\r\nthis.applicationUser       = userManager.getUserByName(userName);\r\nthis.jiraHelper            = new JiraHelper();\r\nuserProjectHistoryManager  = metricsInfo.getUserProjectHistoryManager();\u003C\u002Fpre>\r\n","\u003Cpre>\nthis.environmentData       = new TestKitLocalEnvironmentData(); \u002F\u002F localtest.properties supplies defaults\nthis.backdoor              = new Backdoor(environmentData);\nthis.userManager           = ComponentAccessor.getUserManager();\nthis.applicationUser       = userManager.getUserByName(userName);\nthis.jiraHelper            = new JiraHelper();\nuserProjectHistoryManager  = metricsInfo.getUserProjectHistoryManager();\u003C\u002Fpre>\n",{"__typename":254,"id":610,"ctaLink":214,"pgSharedText":611,"pgSharedTitle":214,"pgSharedCtaLink":214},"9041",{"value":612,"processed":613,"format":259},"\u003Cp>The following imports are added:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport com.atlassian.jira.component.ComponentAccessor;\r\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\r\nimport com.atlassian.jira.testkit.client.Backdoor;\r\nimport com.atlassian.jira.testkit.client.JIRAEnvironmentData;\r\nimport com.atlassian.jira.testkit.client.util.TestKitLocalEnvironmentData;\r\nimport com.atlassian.jira.user.ApplicationUser;\r\nimport com.atlassian.jira.user.UserProjectHistoryManager;\r\nimport com.atlassian.jira.user.util.UserManager;\u003C\u002Fpre>\r\n\r\n\u003Cp>As the comment (above) says, you will have to create a few resources before this constructor can execute successfully.\u003C\u002Fp>\r\n\r\n\u003Ch4>src\u002Ftest\u002Fresources\u002Flocaltest.properties\u003C\u002Fh4>\r\n\r\n\u003Cpre>\r\njira.protocol = http\r\njira.host = localhost\r\njira.port = 2990\r\njira.context = \u002Fjira\r\njira.edition = all\r\n\r\njira.xml.data.location = ..\u002F..\u002F..\u002Ftest-classes\u002Fxml\u003C\u002Fpre>\r\n\r\n\u003Ch4>src\u002Ftest\u002Fresources\u002Fxml\u002F.keepme\u003C\u002Fh4>\r\n\r\n\u003Cp>This file is empty – it merely ensures that the&nbsp;\u003Cstrong>xml\u003C\u002Fstrong>&nbsp;directory is committed to source code control.\u003C\u002Fp>\r\n\r\n\u003Ch4>src\u002Ftest\u002Fresources\u002Fatlassian-plugin.xml\u003C\u002Fh4>\r\n\r\n\u003Cp>The plugin-tests plugin now requires more import-packages than its parent plugin. So add the following section to the&nbsp;\u003Cstrong>plugin-info\u003C\u002Fstrong>&nbsp;statement:\u003C\u002Fp>\r\n","\u003Cp>The following imports are added:\u003C\u002Fp>\n\n\nimport com.atlassian.jira.component.ComponentAccessor;\nimport com.atlassian.jira.plugin.webfragment.model.JiraHelper;\nimport com.atlassian.jira.testkit.client.Backdoor;\nimport com.atlassian.jira.testkit.client.JIRAEnvironmentData;\nimport com.atlassian.jira.testkit.client.util.TestKitLocalEnvironmentData;\nimport com.atlassian.jira.user.ApplicationUser;\nimport com.atlassian.jira.user.UserProjectHistoryManager;\nimport com.atlassian.jira.user.util.UserManager;\n\n\u003Cp>As the comment (above) says, you will have to create a few resources before this constructor can execute successfully.\u003C\u002Fp>\n\n\u003Ch4>src\u002Ftest\u002Fresources\u002Flocaltest.properties\u003C\u002Fh4>\n\n\njira.protocol = http\njira.host = localhost\njira.port = 2990\njira.context = \u002Fjira\njira.edition = all\n\njira.xml.data.location = ..\u002F..\u002F..\u002Ftest-classes\u002Fxml\n\n\u003Ch4>src\u002Ftest\u002Fresources\u002Fxml\u002F.keepme\u003C\u002Fh4>\n\n\u003Cp>This file is empty – it merely ensures that the&nbsp;\u003Cstrong>xml\u003C\u002Fstrong>&nbsp;directory is committed to source code control.\u003C\u002Fp>\n\n\u003Ch4>src\u002Ftest\u002Fresources\u002Fatlassian-plugin.xml\u003C\u002Fh4>\n\n\u003Cp>The plugin-tests plugin now requires more import-packages than its parent plugin. So add the following section to the&nbsp;\u003Cstrong>plugin-info\u003C\u002Fstrong>&nbsp;statement:\u003C\u002Fp>",{"__typename":270,"id":615,"pgSharedTitle":214,"pgCodeCode":616},"9042",{"value":617,"processed":618,"format":275},"\u003Cpre>\r\n&lt;bundle-instructions&gt;\r\n    &lt;Import-Package&gt;\r\n        com.atlassian.jira.plugin.webfragment.model;resolution:=\"optional\",\r\n        com.atlassian.jira.user.util;resolution:=\"optional\",\r\n        com.atlassian.jira.util.collect;resolution:=\"optional\",\r\n        com.atlassian.jira.util;resolution:=\"optional\",\r\n        org.springframework.osgi.*;resolution:=\"optional\",\r\n        org.eclipse.gemini.blueprint.*;resolution:=\"optional\",\r\n        *;version=\"0\";resolution:=\"optional\"\r\n    &lt;\u002FImport-Package&gt;\r\n&lt;\u002Fbundle-instructions&gt;\u003C\u002Fpre>\r\n","\u003Cpre>\n&lt;bundle-instructions&gt;\n    &lt;Import-Package&gt;\n        com.atlassian.jira.plugin.webfragment.model;resolution:=\"optional\",\n        com.atlassian.jira.user.util;resolution:=\"optional\",\n        com.atlassian.jira.util.collect;resolution:=\"optional\",\n        com.atlassian.jira.util;resolution:=\"optional\",\n        org.springframework.osgi.*;resolution:=\"optional\",\n        org.eclipse.gemini.blueprint.*;resolution:=\"optional\",\n        *;version=\"0\";resolution:=\"optional\"\n    &lt;\u002FImport-Package&gt;\n&lt;\u002Fbundle-instructions&gt;\u003C\u002Fpre>\n",{"__typename":254,"id":620,"ctaLink":214,"pgSharedText":621,"pgSharedTitle":214,"pgSharedCtaLink":214},"9043",{"value":622,"processed":623,"format":259},"\u003Cp>At this point, the test should still be failing for the same reason as before (because the project is null, not because of an error thrown in the constructor).\u003C\u002Fp>\r\n\r\n\u003Cp>You’re now equipped to initialise all the required bits and pieces to enable the test to pass. Add before and after functions for your tests like this:\u003C\u002Fp>\r\n","\u003Cp>At this point, the test should still be failing for the same reason as before (because the project is null, not because of an error thrown in the constructor).\u003C\u002Fp>\n\n\u003Cp>You’re now equipped to initialise all the required bits and pieces to enable the test to pass. Add before and after functions for your tests like this:\u003C\u002Fp>",{"__typename":270,"id":625,"pgSharedTitle":214,"pgCodeCode":626},"9044",{"value":627,"processed":628,"format":275},"\u003Cpre>\r\n@Before\r\npublic void setup() {\r\n    deleteSampleData();\r\n    \u002F* final long projectId = *\u002F backdoor.project().addProject(\r\n            projectName, projectKey, userName, projectType);\r\n    final Project project = ComponentAccessor.getProjectManager()\r\n            .getProjectByCurrentKey(projectKey);\r\n    userProjectHistoryManager.addProjectToHistory(applicationUser, project);\r\n}\r\n\r\n@After\r\npublic void cleanup() {\r\n    \u002F\u002F Comment out this line if you want sample data to remain in the system after the tests have run\r\n    deleteSampleData();\r\n}\r\n\r\nprivate void deleteSampleData() {\r\n    try {\r\n        backdoor.project().deleteProject(projectKey);\r\n    } catch (Throwable e) {\r\n        \u002F\u002F ignore\r\n    }\r\n}\u003C\u002Fpre>\r\n","\u003Cpre>\n@Before\npublic void setup() {\n    deleteSampleData();\n    \u002F* final long projectId = *\u002F backdoor.project().addProject(\n            projectName, projectKey, userName, projectType);\n    final Project project = ComponentAccessor.getProjectManager()\n            .getProjectByCurrentKey(projectKey);\n    userProjectHistoryManager.addProjectToHistory(applicationUser, project);\n}\n\n@After\npublic void cleanup() {\n    \u002F\u002F Comment out this line if you want sample data to remain in the system after the tests have run\n    deleteSampleData();\n}\n\nprivate void deleteSampleData() {\n    try {\n        backdoor.project().deleteProject(projectKey);\n    } catch (Throwable e) {\n        \u002F\u002F ignore\n    }\n}\u003C\u002Fpre>\n",{"__typename":254,"id":630,"ctaLink":214,"pgSharedText":631,"pgSharedTitle":214,"pgSharedCtaLink":214},"9045",{"value":632,"processed":633,"format":259},"\u003Cp>The following imports are added:\u003C\u002Fp>\r\n\r\n\u003Cpre>\r\nimport org.junit.After;\r\nimport org.junit.Before;\u003C\u002Fpre>\r\n\r\n\u003Cp>The test should now pass.\u003C\u002Fp>\r\n\r\n\u003Cp>NB (don’t do this now, but note for future use): if you add a constructor argument of type&nbsp;\u003Cstrong>MyPluginComponent\u003C\u002Fstrong>&nbsp;as well, you can call its public methods to provide additional help with the setup and teardown functions. You can also use the&nbsp;\u003Cstrong>ProjectManager\u003C\u002Fstrong>&nbsp;instance returned by the&nbsp;\u003Cstrong>ComponentAccessor\u003C\u002Fstrong>&nbsp;to create and delete project categories, issue types and more, which the backdoor will then be able to add to projects. For example (assuming you have declared constant strings for&nbsp;\u003Cstrong>categoryName\u003C\u002Fstrong>,\u003Cstrong>&nbsp;categoryDesc\u003C\u002Fstrong>,\u003Cstrong>&nbsp;testIssueType&nbsp;\u003C\u002Fstrong>and\u003Cstrong>&nbsp;testIssue\u003C\u002Fstrong>):\u003C\u002Fp>\r\n","\u003Cp>The following imports are added:\u003C\u002Fp>\n\n\nimport org.junit.After;\nimport org.junit.Before;\n\n\u003Cp>The test should now pass.\u003C\u002Fp>\n\n\u003Cp>NB (don’t do this now, but note for future use): if you add a constructor argument of type&nbsp;\u003Cstrong>MyPluginComponent\u003C\u002Fstrong>&nbsp;as well, you can call its public methods to provide additional help with the setup and teardown functions. You can also use the&nbsp;\u003Cstrong>ProjectManager\u003C\u002Fstrong>&nbsp;instance returned by the&nbsp;\u003Cstrong>ComponentAccessor\u003C\u002Fstrong>&nbsp;to create and delete project categories, issue types and more, which the backdoor will then be able to add to projects. For example (assuming you have declared constant strings for&nbsp;\u003Cstrong>categoryName\u003C\u002Fstrong>,\u003Cstrong>&nbsp;categoryDesc\u003C\u002Fstrong>,\u003Cstrong>&nbsp;testIssueType&nbsp;\u003C\u002Fstrong>and\u003Cstrong>&nbsp;testIssue\u003C\u002Fstrong>):\u003C\u002Fp>",{"__typename":270,"id":635,"pgSharedTitle":214,"pgCodeCode":636},"9046",{"value":637,"processed":638,"format":275},"\u003Cpre>\r\nfinal ProjectManager projectManager = ComponentAccessor.getProjectManager();\r\nfinal ProjectCategory projectCategory = projectManager.createProjectCategory(categoryName, categoryDesc);\r\nfinal long projectCategoryId = projectCategory.getId();\r\nbackdoor.project().setProjectCategory(projectId, projectCategoryId);\r\nfinal IssueTypeControl.IssueType issueType = backdoor.issueType().createIssueType(testIssueType);\r\nbackdoor.issues().createIssue(projectKey, testIssue, userName, \"2\", testIssueType);\u003C\u002Fpre>\r\n","\u003Cpre>\nfinal ProjectManager projectManager = ComponentAccessor.getProjectManager();\nfinal ProjectCategory projectCategory = projectManager.createProjectCategory(categoryName, categoryDesc);\nfinal long projectCategoryId = projectCategory.getId();\nbackdoor.project().setProjectCategory(projectId, projectCategoryId);\nfinal IssueTypeControl.IssueType issueType = backdoor.issueType().createIssueType(testIssueType);\nbackdoor.issues().createIssue(projectKey, testIssue, userName, \"2\", testIssueType);\u003C\u002Fpre>\n",{"__typename":254,"id":640,"ctaLink":214,"pgSharedText":641,"pgSharedTitle":214,"pgSharedCtaLink":214},"9047",{"value":642,"processed":643,"format":259},"\u003Cp>If you add issue types and project categories in the setup method, remember to remove them in the teardown method – or in this example, in&nbsp;\u003Cstrong>deleteSampleData()\u003C\u002Fstrong>.\u003C\u002Fp>\r\n","\u003Cp>If you add issue types and project categories in the setup method, remember to remove them in the teardown method – or in this example, in&nbsp;\u003Cstrong>deleteSampleData()\u003C\u002Fstrong>.\u003C\u002Fp>",{"__typename":254,"id":645,"ctaLink":214,"pgSharedText":646,"pgSharedTitle":649,"pgSharedCtaLink":214},"9048",{"value":647,"processed":648,"format":259},"\u003Ch3>Speeding up Plugin Testing\u003C\u002Fh3>\r\n\r\n\u003Cp>In order to avoid the need to re-start the entire application server for each integration test cycle, I developed a plugin for Jira 7 that allows the old FastDev test console to be used with the new QuickReload feature. This is based on&nbsp;\u003Ca href=\"https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002FQuestions\u002FAtlassian-Plugin-Test-Console-and-QuickReload\u002Fqaq-p\u002F371152\">a suggestion\u003C\u002Fa>&nbsp;from&nbsp;\u003Ca href=\"https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002Fuser\u002Fviewprofilepage\u002Fuser-id\u002F645660\">Christophe Merlotti\u003C\u002Fa>.\u003C\u002Fp>\r\n\r\n\u003Cp>Just insert the following under the existing pluginArtifact in the POM:\u003C\u002Fp>\r\n","\u003Ch3>Speeding up Plugin Testing\u003C\u002Fh3>\n\n\u003Cp>In order to avoid the need to re-start the entire application server for each integration test cycle, I developed a plugin for Jira 7 that allows the old FastDev test console to be used with the new QuickReload feature. This is based on&nbsp;\u003Ca href=\"https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002FQuestions\u002FAtlassian-Plugin-Test-Console-and-QuickReload\u002Fqaq-p\u002F371152\">a suggestion\u003C\u002Fa>&nbsp;from&nbsp;\u003Ca href=\"https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002Fuser\u002Fviewprofilepage\u002Fuser-id\u002F645660\">Christophe Merlotti\u003C\u002Fa>.\u003C\u002Fp>\n\n\u003Cp>Just insert the following under the existing pluginArtifact in the POM:\u003C\u002Fp>","Accelerate the Cycle",{"__typename":270,"id":651,"pgSharedTitle":214,"pgCodeCode":652},"9049",{"value":653,"processed":654,"format":275},"\u003Cpre>\r\n&lt;pluginArtifact&gt;\r\n    &lt;groupId&gt;com.zuhlke.testing&lt;\u002FgroupId&gt;\r\n    &lt;artifactId&gt;atlassian-plugin-test-console-enabler&lt;\u002FartifactId&gt;\r\n    &lt;version&gt;1.0.1&lt;\u002Fversion&gt;\r\n&lt;\u002FpluginArtifact&gt;\u003C\u002Fpre>\r\n","\u003Cpre>\n&lt;pluginArtifact&gt;\n    &lt;groupId&gt;com.zuhlke.testing&lt;\u002FgroupId&gt;\n    &lt;artifactId&gt;atlassian-plugin-test-console-enabler&lt;\u002FartifactId&gt;\n    &lt;version&gt;1.0.1&lt;\u002Fversion&gt;\n&lt;\u002FpluginArtifact&gt;\u003C\u002Fpre>\n",{"__typename":314,"id":656,"caption":214,"imageRight":214,"pgSharedTitle":214,"pgSharedCtaLink":214,"textMedia":657,"pgSharedText":674},"9050",{"id":658,"sharedMediaLegend":214,"mediaImage":659,"name":673},"3746",{"url":660,"width":661,"height":372,"alt":662,"title":214,"size":663,"mime":221,"variations":214,"textMediaVariations":664},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fatlassian-plugin-test-console-after-wired-tests-have-run.jpg",1000,"Atlassian Plugin Test Console after wired tests have run",68270,[665,668,669,670,672],{"name":295,"url":666,"width":238,"height":667},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_small\u002Fpublic\u002Fimages\u002Fatlassian-plugin-test-console-after-wired-tests-have-run.jpg.webp?itok=LG5YQ4C6",636,{"name":295,"url":666,"width":238,"height":667},{"name":295,"url":666,"width":238,"height":667},{"name":300,"url":671,"width":661,"height":372},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffixed_width_medium\u002Fpublic\u002Fimages\u002Fatlassian-plugin-test-console-after-wired-tests-have-run.jpg.webp?itok=4aVW37PF",{"name":300,"url":671,"width":661,"height":372},"atlassian-plugin-test-console-after-wired-tests-have-run.jpg",{"value":675,"processed":676,"format":259},"\u003Cp>The artifact is now in Maven Central. To use it, simply start the application server using&nbsp;\u003Cstrong>atlas-run\u003C\u002Fstrong>&nbsp;or&nbsp;\u003Cstrong>atlas-debug\u003C\u002Fstrong>&nbsp;and point your browser at&nbsp;\u003Ca href=\"http:\u002F\u002Flocalhost:2990\u002Fjira\u002Fplugins\u002Fservlet\u002Fit-test-console\">http:\u002F\u002Flocalhost:2990\u002Fjira\u002Fplugins\u002Fservlet\u002Fit-test-console\u003C\u002Fa>. Note that this supports plugin (wired) tests only; the unit tests should be run from the command line using&nbsp;\u003Cstrong>atlas-package\u003C\u002Fstrong>&nbsp;or equivalent, or from within your IDE.\u003C\u002Fp>\r\n","\u003Cp>The artifact is now in Maven Central. To use it, simply start the application server using&nbsp;\u003Cstrong>atlas-run\u003C\u002Fstrong>&nbsp;or&nbsp;\u003Cstrong>atlas-debug\u003C\u002Fstrong>&nbsp;and point your browser at&nbsp;\u003Ca href=\"http:\u002F\u002Flocalhost:2990\u002Fjira\u002Fplugins\u002Fservlet\u002Fit-test-console\">http:\u002F\u002Flocalhost:2990\u002Fjira\u002Fplugins\u002Fservlet\u002Fit-test-console\u003C\u002Fa>. Note that this supports plugin (wired) tests only; the unit tests should be run from the command line using&nbsp;\u003Cstrong>atlas-package\u003C\u002Fstrong>&nbsp;or equivalent, or from within your IDE.\u003C\u002Fp>",{"__typename":254,"id":678,"ctaLink":214,"pgSharedText":679,"pgSharedTitle":682,"pgSharedCtaLink":214},"9051",{"value":680,"processed":681,"format":259},"\u003Cp>All source code developed in the course of this workshop can be found under&nbsp;\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fzuhlke\u002Fexample-jira-plugin\">https:\u002F\u002Fgithub.com\u002Fzuhlke\u002Fexample-jira-plugin\u003C\u002Fa>.\u003C\u002Fp>\r\n\r\n\u003Cp>Resources you may find useful include:\u003C\u002Fp>\r\n\r\n\u003Cul>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-software-server\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-software-server\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-server\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-server\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fjava-apis\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fjava-apis\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.atlassian.com\u002Fsoftware\u002Fjira\u002Fdocs\u002Fapi\u002F7.12.3\u002F\">https:\u002F\u002Fdocs.atlassian.com\u002Fsoftware\u002Fjira\u002Fdocs\u002Fapi\u002F7.12.3\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fset-up-the-atlassian-plugin-sdk-and-build-a-project\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fset-up-the-atlassian-plugin-sdk-and-build-a-project\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fcreate-a-helloworld-plugin-project\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fcreate-a-helloworld-plugin-project\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ffaq\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ffaq\u003C\u002Fa>&nbsp;and child pages\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fmanaging-dependencies\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fmanaging-dependencies\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fweb-resource\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fweb-resource\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002FQuestions\u002FAtlassian-Plugin-Test-Console-and-QuickReload\u002Fqaq-p\u002F371152\">https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002FQuestions\u002FAtlassian-Plugin-Test-Console-and-QuickReload\u002Fqaq-p\u002F371152\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fadding-resources-to-your-project\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fadding-resources-to-your-project\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fdownloadable-add-on-resources\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fdownloadable-add-on-resources\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fplatform\u002Fmarketplace\u002Fdeclaring-marketing-assets-for-server-apps\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fplatform\u002Fmarketplace\u002Fdeclaring-marketing-assets-for-server-apps\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ftroubleshooting\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ftroubleshooting\u003C\u002Fa>\u003C\u002Fli>\r\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdependency-issues-during-plugin-initialisation\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdependency-issues-during-plugin-initialisation\u002F\u003C\u002Fa>\u003C\u002Fli>\r\n\u003C\u002Ful>\r\n","\u003Cp>All source code developed in the course of this workshop can be found under&nbsp;\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fzuhlke\u002Fexample-jira-plugin\">https:\u002F\u002Fgithub.com\u002Fzuhlke\u002Fexample-jira-plugin\u003C\u002Fa>.\u003C\u002Fp>\n\n\u003Cp>Resources you may find useful include:\u003C\u002Fp>\n\n\u003Cul>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-software-server\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-software-server\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-server\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fintegrating-with-jira-server\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fjava-apis\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fjava-apis\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.atlassian.com\u002Fsoftware\u002Fjira\u002Fdocs\u002Fapi\u002F7.12.3\u002F\">https:\u002F\u002Fdocs.atlassian.com\u002Fsoftware\u002Fjira\u002Fdocs\u002Fapi\u002F7.12.3\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fset-up-the-atlassian-plugin-sdk-and-build-a-project\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fset-up-the-atlassian-plugin-sdk-and-build-a-project\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fcreate-a-helloworld-plugin-project\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fcreate-a-helloworld-plugin-project\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ffaq\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ffaq\u003C\u002Fa>&nbsp;and child pages\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fmanaging-dependencies\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fmanaging-dependencies\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fweb-resource\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fweb-resource\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002FQuestions\u002FAtlassian-Plugin-Test-Console-and-QuickReload\u002Fqaq-p\u002F371152\">https:\u002F\u002Fcommunity.atlassian.com\u002Ft5\u002FQuestions\u002FAtlassian-Plugin-Test-Console-and-QuickReload\u002Fqaq-p\u002F371152\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fadding-resources-to-your-project\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fadding-resources-to-your-project\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fdownloadable-add-on-resources\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fjira\u002Fplatform\u002Fdownloadable-add-on-resources\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fplatform\u002Fmarketplace\u002Fdeclaring-marketing-assets-for-server-apps\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fplatform\u002Fmarketplace\u002Fdeclaring-marketing-assets-for-server-apps\u002F\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ftroubleshooting\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Ftroubleshooting\u003C\u002Fa>\u003C\u002Fli>\n\t\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdependency-issues-during-plugin-initialisation\u002F\">https:\u002F\u002Fdeveloper.atlassian.com\u002Fserver\u002Fframework\u002Fatlassian-sdk\u002Fdependency-issues-during-plugin-initialisation\u002F\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>","References",{"__typename":684,"id":685,"title":214,"pgContactRefContacts":686,"showEmail":214,"showPhone":214},"ParagraphPgContact","10591",[687],{"contactName":688,"contactProfession":689,"contactDesc":690,"contactLinkedinUrl":692,"sharedEmail":696,"sharedPhone":697,"sharedRefMedia":698},"Immo Huneke","Expert Software Engineer",{"value":691},"\u003Cp>Immo Huneke has been with Zühlke since February 2004. Since completing a BSc in Physics and later qualifying as CEng MBCS CITP, he has gathered wide-ranging experiences in the fields of telecoms, finance, industrial manufacturing (including medical devices) and transport, among others. His passion is agile object-oriented software development and in the continuous improvement of the methods, techniques and tools used to achieve it.\u003C\u002Fp>\r\n",{"title":693,"url":694,"internal":695},"","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fimmo-huneke-27b121\u002F",false,"immo.huneke@zuhlke.com","+44 20 7113 5347",{"id":699,"sharedMediaLegend":214,"mediaImage":700,"name":715},"300",{"url":701,"width":702,"height":702,"alt":703,"title":214,"size":704,"mime":221,"variations":214,"personVariations":705},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fimmo_hueneke-zuehlke_0.jpg",674,"Immo Huneke Zühlke",41618,[706,709,710,713,714],{"name":707,"url":708,"width":702,"height":702},"FOCAL_POINT11_SMALL","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_1_1_small\u002Fpublic\u002Fimages\u002Fimmo_hueneke-zuehlke_0.jpg.webp?itok=rA3ZZxxO",{"name":707,"url":708,"width":702,"height":702},{"name":711,"url":712,"width":702,"height":702},"FOCAL_POINT11_MEDIUM","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_1_1_medium\u002Fpublic\u002Fimages\u002Fimmo_hueneke-zuehlke_0.jpg.webp?itok=lgXtcSvk",{"name":711,"url":712,"width":702,"height":702},{"name":711,"url":712,"width":702,"height":702},"immo_hueneke-zuehlke_0.jpg",[717],{"id":718,"name":719},"855","All industries",[721,724],{"id":722,"name":723},"891","New technologies",{"id":725,"name":726},"21","Software engineering",{"processed":728},"\u003Cp>This is where the Jira plug-in mechanism comes in handy as it enables you to crunch through Jira’s repository for your own purposes.\u003C\u002Fp>",{"timestamp":730},1543147200,{"data":732},{"relatedContentView":733},{"id":734,"pageInfo":735,"results":738},"3dad2dac-900c-48b3-9380-0814b1c1f4f1",{"page":736,"pageSize":737,"total":737},0,3,[739,775,809],{"id":740,"path":741,"sharedTeaserTitle":742,"sharedTeaserRefMedia":743,"sharedTaxrefIndustries":762,"sharedTaxrefExpertises":766},"7284","\u002Fde\u002Finsights\u002Fkreislaufwirtschaft-fuer-sichere-ressourcen-und-lieferketten","Die Bedeutung der Kreislaufwirtschaft für sichere, unabhängige Lieferketten",{"id":744,"sharedMediaLegend":214,"mediaImage":745,"name":761},"6004",{"url":746,"width":747,"height":748,"alt":749,"title":214,"size":750,"mime":221,"variations":214,"relatedContentVariations":751},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fblog_sustainability_cities.jpg",500,375,"sustainability globus",83644,[752,755,756,759,760],{"name":224,"url":753,"width":747,"height":754},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_small\u002Fpublic\u002Fimages\u002Fblog_sustainability_cities.jpg.webp?itok=387bZp2n",334,{"name":224,"url":753,"width":747,"height":754},{"name":757,"url":758,"width":747,"height":754},"FOCAL_POINT32_MEDIUM","https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_medium\u002Fpublic\u002Fimages\u002Fblog_sustainability_cities.jpg.webp?itok=zizp9Qzv",{"name":757,"url":758,"width":747,"height":754},{"name":757,"url":758,"width":747,"height":754},"blog_sustainability_cities.jpg",[763],{"id":764,"name":765},"10","Industrie",[767,770,772],{"id":768,"name":769},"19","Digitale Strategien",{"id":722,"name":771},"Neue Technologien",{"id":773,"name":774},"608","Nachhaltigkeit",{"id":776,"path":777,"sharedTeaserTitle":778,"sharedTeaserRefMedia":779,"sharedTaxrefIndustries":797,"sharedTaxrefExpertises":801},"23586","\u002Fde\u002Finsights\u002Fdie-digitalisierung-bricht-durch-im-corporate-banking","Die Digitalisierung bricht durch im Corporate Banking",{"id":780,"sharedMediaLegend":214,"mediaImage":781,"name":796},"167393",{"url":782,"width":783,"height":784,"alt":785,"title":214,"size":786,"mime":221,"variations":214,"relatedContentVariations":787},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fcorporate-banking.jpg",648,359,"reflections in glass office facade at dusk",138407,[788,791,792,794,795],{"name":224,"url":789,"width":790,"height":784},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_small\u002Fpublic\u002Fimages\u002Fcorporate-banking.jpg.webp?itok=34JPPuzT",539,{"name":224,"url":789,"width":790,"height":784},{"name":757,"url":793,"width":790,"height":784},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_medium\u002Fpublic\u002Fimages\u002Fcorporate-banking.jpg.webp?itok=LwLlj8xu",{"name":757,"url":793,"width":790,"height":784},{"name":757,"url":793,"width":790,"height":784},"corporate-banking.jpg",[798],{"id":799,"name":800},"7","Banken & Finanzdienstleister",[802,805,808],{"id":803,"name":804},"129","Experience Design",{"id":806,"name":807},"944","User \u002F Customer Experience",{"id":722,"name":771},{"id":810,"path":811,"sharedTeaserTitle":812,"sharedTeaserRefMedia":813,"sharedTaxrefIndustries":829,"sharedTaxrefExpertises":832},"25721","\u002Fde\u002Finsights\u002Ftechnologie-innovation-und-oekosysteme-der-weg-zur-nachhaltigen-transformation","Technologie, Innovation und Ökosysteme als Treiber der nachhaltigen Transformation ",{"id":814,"sharedMediaLegend":214,"mediaImage":815,"name":828},"169593",{"url":816,"width":237,"height":817,"alt":818,"title":214,"size":819,"mime":221,"variations":214,"relatedContentVariations":820},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fimages\u002Fsustainability-transformation_1.jpg",1067,"hands holding a growing plant",160140,[821,823,824,826,827],{"name":224,"url":822,"width":226,"height":227},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_small\u002Fpublic\u002Fimages\u002Fsustainability-transformation_1.jpg.webp?itok=SlABA5_n",{"name":224,"url":822,"width":226,"height":227},{"name":757,"url":825,"width":232,"height":226},"https:\u002F\u002Fcms.zuehlke.com\u002Fsites\u002Fdefault\u002Ffiles\u002Fstyles\u002Ffocal_point_3_2_medium\u002Fpublic\u002Fimages\u002Fsustainability-transformation_1.jpg.webp?itok=ukpagGae",{"name":757,"url":825,"width":232,"height":226},{"name":757,"url":825,"width":232,"height":226},"sustainability-transformation.jpg",[830],{"id":718,"name":831},"Alle Branchen",[833,834,837,840,843,844],{"id":768,"name":769},{"id":835,"name":836},"887","Business Transformation",{"id":838,"name":839},"886","Tech Innovation",{"id":841,"name":842},"892","Ökosysteme",{"id":722,"name":771},{"id":773,"name":774}]