Commits

Julien Hoarau committed 897389f

Inject macro to Network tab on front page

  • Participants
  • Parent commits f6707dc

Comments (0)

Files changed (14)

 <?xml version="1.0" encoding="UTF-8"?>
 
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.atlassian.confluence.plugins</groupId>
     <artifactId>whotofollow</artifactId>
     <version>1.0-SNAPSHOT</version>
-
     <organization>
         <name>Example Company</name>
         <url>http://www.example.com/</url>
     </organization>
-
     <name>whotofollow</name>
     <description>This is the com.atlassian.confluence.plugins:whotofollow plugin for Atlassian Confluence.</description>
     <packaging>atlassian-plugin</packaging>
-
     <dependencies>
         <dependency>
             <groupId>com.atlassian.confluence</groupId>
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.10</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
-
     <build>
         <plugins>
             <plugin>
             </plugin>
         </plugins>
     </build>
-
     <properties>
         <confluence.version>4.3</confluence.version>
         <confluence.data.version>4.3</confluence.data.version>
         <ao.version>0.19.12</ao.version>
-        <amps.version>4.0-rc1</amps.version>
+        <amps.version>4.0</amps.version>
     </properties>
-
 </project>

File src/main/java/com/atlassian/confluence/plugins/whotofollow/ExampleMacro.java

-package com.atlassian.confluence.plugins.whotofollow;
-
-import java.util.Map;
-import java.util.List;
-
-import com.atlassian.confluence.content.render.xhtml.ConversionContext;
-import com.atlassian.confluence.macro.Macro;
-import com.atlassian.confluence.macro.MacroExecutionException;
-import com.atlassian.confluence.pages.PageManager;
-import com.atlassian.confluence.pages.Page;
-import com.atlassian.confluence.spaces.SpaceManager;
-import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
-import com.atlassian.user.User;
-import com.opensymphony.util.TextUtils;
-
-/**
- * This very simple macro shows you the very basic use-case of displaying *something* on the Confluence page where it is used.
- * Use this example macro to toy around, and then quickly move on to the next example - this macro doesn't
- * really show you all the fun stuff you can do with Confluence.
- */
-public class ExampleMacro implements Macro
-{
-
-    // We just have to define the variables and the setters, then Spring injects the correct objects for us to use. Simple and efficient.
-    // You just need to know *what* you want to inject and use.
-
-    private final PageManager pageManager;
-    private final SpaceManager spaceManager;
-
-    public ExampleMacro(PageManager pageManager, SpaceManager spaceManager)
-    {
-        this.pageManager = pageManager;
-        this.spaceManager = spaceManager;
-    }
-
-    /**
-     * This method returns XHTML to be displayed on the page that uses this macro
-     * we just do random stuff here, trying to show how you can access the most basic
-     * managers and model objects. No emphasis is put on beauty of code nor on
-     * doing actually useful things :-)
-     */
-    @Override
-    public String execute(Map<String, String> parameters, String body, ConversionContext context) throws MacroExecutionException
-    {
-        // in this most simple example, we build the result in memory, appending HTML code to it at will.
-        // this is something you absolutely don't want to do once you start writing plugins for real. Refer
-        // to the next example for better ways to render content.
-        StringBuffer result = new StringBuffer();
-
-        // get the currently logged in user and display his name
-        User user = AuthenticatedUserThreadLocal.getUser();
-        if (user != null)
-        {
-            String greeting = "<p>Hello <b>" + TextUtils.htmlEncode(user.getFullName()) + "</b>.</p>";
-            result.append(greeting);
-        }
-
-        //get the pages added in the last 55 days to the DS space ("Demo Space"), and display them
-        List<Page> list = pageManager.getRecentlyAddedPages(55, "DS");
-        result.append("<p>");
-        result.append("Some stats for <b>Demonstration Space</b>:");
-        result.append("<table class=\"confluenceTable\">");
-        result.append("<thead><tr><th class=\"confluenceTh\">Page</th><th class=\"confluenceTh\">Number of children</th></tr></thead>");
-        result.append("<tbody>");
-        for (Page page : list)
-        {
-            int numberOfChildren = page.getChildren().size();
-            String pageWithChildren = "<tr><td class=\"confluenceTd\">" + TextUtils.htmlEncode(page.getTitle()) + "</td><td class=\"confluenceTd\" style=\"text-align:right\">" + numberOfChildren + "</td></tr>";
-            result.append(pageWithChildren);
-        }
-        result.append("</tbody>");
-        result.append("</table>");
-        result.append("</p>");
-
-        // and show the number of all spaces in this installation.
-        String spaces = "<p>Altogether, this installation has <b>" + spaceManager.getAllSpaces().size() + "</b> spaces.</p>";
-        result.append(spaces);
-
-        // this concludes our little demo. Now you should understand the basics of code injection use in Confluence, and how
-        // to get a really simple macro running.
-
-        return result.toString();
-    }
-
-    @Override
-    public BodyType getBodyType()
-    {
-        return BodyType.NONE;
-    }
-
-    @Override
-    public OutputType getOutputType()
-    {
-        return OutputType.BLOCK;
-    }
-
-}

File src/main/java/com/atlassian/confluence/plugins/whotofollow/RecommendationsAction.java

+package com.atlassian.confluence.plugins.whotofollow;
+
+import com.atlassian.confluence.core.ConfluenceActionSupport;
+import com.atlassian.plugin.webresource.WebResourceManager;
+
+public class RecommendationsAction extends ConfluenceActionSupport
+{
+
+    private WebResourceManager webResourceManager;
+
+    public WebResourceManager getWebResourceManager()
+    {
+        return webResourceManager;
+    }
+
+    public void setWebResourceManager(WebResourceManager webResourceManager)
+    {
+        this.webResourceManager = webResourceManager;
+    }
+
+    @Override
+    public String doDefault() throws Exception
+    {
+        return SUCCESS;
+    }
+}

File src/main/java/com/atlassian/confluence/plugins/whotofollow/RecommendationsMacro.java

+package com.atlassian.confluence.plugins.whotofollow;
+
+import com.atlassian.confluence.content.render.xhtml.ConversionContext;
+import com.atlassian.confluence.macro.Macro;
+import com.atlassian.confluence.macro.MacroExecutionException;
+import com.atlassian.confluence.renderer.radeox.macros.MacroUtils;
+import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
+import com.atlassian.confluence.util.velocity.VelocityUtils;
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.plugin.webresource.WebResourceManager;
+import com.atlassian.renderer.RenderContext;
+import com.atlassian.renderer.v2.RenderMode;
+import com.atlassian.renderer.v2.macro.BaseMacro;
+import com.atlassian.renderer.v2.macro.MacroException;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.List;
+import java.util.Map;
+
+public class RecommendationsMacro extends BaseMacro implements Macro
+{
+    private final WebResourceManager webResourceManager;
+    private final RecommendationEngine recommendationEngine;
+
+    public RecommendationsMacro(WebResourceManager webResourceManager, RecommendationEngine recommendationEngine)
+    {
+        this.webResourceManager = webResourceManager;
+        this.recommendationEngine = recommendationEngine;
+    }
+
+    @Override
+    public String execute(Map<String, String> parameters, String body, ConversionContext conversionContext) throws MacroExecutionException
+    {
+        try
+        {
+            return execute(parameters, body, (RenderContext) null);
+        }
+        catch (MacroException e)
+        {
+            throw new MacroExecutionException(e);
+        }
+    }
+
+    @Override
+    public BodyType getBodyType()
+    {
+        return BodyType.NONE;
+    }
+
+    @Override
+    public OutputType getOutputType()
+    {
+        return OutputType.BLOCK;
+    }
+
+    @Override
+    public boolean hasBody()
+    {
+        return true;
+    }
+
+    @Override
+    public RenderMode getBodyRenderMode()
+    {
+        return RenderMode.NO_RENDER;
+    }
+
+    @Override
+    public String execute(Map parameters, String body, RenderContext renderContext) throws MacroException
+    {
+        String username = (String) parameters.get("username");
+        if (StringUtils.isBlank(username))
+        {
+            username = AuthenticatedUserThreadLocal.getUsername();
+        }
+
+        Map<String,Object> context = MacroUtils.defaultVelocityContext();
+
+        List<User> users = recommendationEngine.recommendFollowings(username);
+        context.put("recommendations", users);
+
+        webResourceManager.requireResource("confluence.web.resources:ajs");
+        webResourceManager.requireResource("com.atlassian.confluence.plugins.whotofollow:recommendations-macro-resources");
+        return VelocityUtils.getRenderedTemplate("/templates/recommendations-macro.vm", context);
+    }
+}

File src/main/java/com/atlassian/confluence/plugins/whotofollow/data/services/IUserNodeDaoService.java

 package com.atlassian.confluence.plugins.whotofollow.data.services;
 
+import com.atlassian.activeobjects.tx.Transactional;
 import com.atlassian.confluence.plugins.whotofollow.data.UserNode;
 import com.atlassian.crowd.embedded.api.User;
 
 import java.util.List;
 
+@Transactional
 public interface IUserNodeDaoService extends IDaoService<UserNode>
 {
     UserNode createOrUpdate(String username, String department, int globalPopularityScore, int departmentPopularityScore);

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

     <component-import key="settings-manager" interface="com.atlassian.confluence.setup.settings.SettingsManager"/>
     <component-import key="i18nbean-factory" interface="com.atlassian.confluence.util.i18n.I18NBeanFactory"/>
     <component-import key="space-permission-manager" interface="com.atlassian.confluence.security.SpacePermissionManager"/>
-
+    <component-import key="web-resource-manager" interface="com.atlassian.plugin.webresource.WebResourceManager"/>
 
     <component key="tx-processor" name="Transactional Annotation Processor"
                class="com.atlassian.activeobjects.external.TransactionalAnnotationProcessor">
         <entity>com.atlassian.confluence.plugins.whotofollow.data.UserNode</entity>
     </ao>
 
+    <!-- Macro -->
+    <macro key="recommendations-macro-old" name="recommendations" class="com.atlassian.confluence.plugins.whotofollow.RecommendationsMacro" state="enabled">
+        <category name="content"/>
+    </macro>
+
+    <xhtml-macro  key="recommendations-macro" name="recommendations" class="com.atlassian.confluence.plugins.whotofollow.RecommendationsMacro">
+        <parameters>
+            <parameter name="username" type="string" default="">
+                <option key="showNameInPlaceholder" value="false" />
+                <option key="showValueInPlaceholder" value="true" />
+            </parameter>
+        </parameters>
+    </xhtml-macro>
+
+    <web-resource key="recommendations-macro-resources" name="Recommendations CSS Resources">
+        <resource type="download" name="recommendations-macro.css" location="/includes/css/recommendations-macro.css"/>
+        <resource type="download" name="recommendations-macro.js" location="/includes/js/recommendations-macro.js"/>
+        <context>dashboard</context>
+    </web-resource>
+
+    <web-resource name="Injector Resource" key="injector-resource">
+        <resource name="injector.js" type="download" location="includes/js/injector.js"/>
+        <context>dashboard</context>
+    </web-resource>
+
+    <xwork key="recommendations-action" name="Recommendations Action">
+        <package name="recommendations" extends="default" namespace="/plugins/recommendations">
+            <default-interceptor-ref name="defaultStack"/>
+            <action name="get" class="com.atlassian.confluence.plugins.whotofollow.RecommendationsAction" method="doDefault">
+                <result name="success" type="velocity">/templates/recommendations-injected-macro.vm</result>
+            </action>
+        </package>
+    </xwork>
+
     <!-- REST -->
     <rest key="rest-service-resources" path="/whotofollow" version="1.0">
         <description>Provides the REST resources for WhoToFollow plugin.</description>

File src/main/resources/includes/css/recommendations-macro.css

+.recommendations-macro ul {
+    margin: 0;
+    padding: 0;
+}
+
+.recommendations-macro a:link, .recommendations-macro a:visited, .recommendations-macro a:focus, .recommendations-macro a:hover {
+    text-decoration: none;
+}
+
+div.recommendation {
+    position: relative;
+}
+
+.recommendation div.infos {
+    display: inline-block;
+    position: absolute;
+    margin-left: 5px;
+}
+
+.recommendation .infos h4 {
+    padding: 0;
+    margin: 0;
+}
+
+.recommendations-macro li.hidden {
+    display: none;
+}

File src/main/resources/includes/js/injector.js

+AJS.toInit(function($) {
+    var contextPath = Confluence.getContextPath();
+
+    var $networkPane = $("#network-pane");
+
+    AJS.safe.get(contextPath + "/plugins/recommendations/get.action", {}, function(data) {
+        $networkPane.prepend(data);
+        Confluence.Binder.userHover();
+    });
+});

File src/main/resources/includes/js/recommendations-macro.js

+AJS.toInit(function($) {
+    var contextPath = Confluence.getContextPath();
+
+
+    if (typeof AJS.followCallbacks == "undefined") AJS.followCallbacks = [];
+    AJS.followCallbacks.push(function(user) {
+        var $userLink = $("[data-username=" + user + "]");
+        updateRecommendations($userLink);
+    });
+
+    var $followLinks = $(".follow");
+    $followLinks.click(function(e) {
+        var $this = $(this);
+
+        var username = $this.attr("data-username");
+        var url = $this.hasClass("unfollow") ? "/unfollowuser.action" : "/followuser.action";
+
+        $this.removeClass("follow").addClass("unfollow").text("Unfollow");
+        AJS.safe.post(contextPath + url + "?username=" + encodeURIComponent(username) + "&mode=blank", {}, function() {
+
+            updateRecommendations($this);
+        });
+
+    });
+
+    function updateRecommendations($recommendationLink) {
+        var $recommendation = $recommendationLink.parents(".recommendation");
+
+        replaceFollowedByUnfollowedRecommendation($recommendation);
+    }
+
+    function replaceFollowedByUnfollowedRecommendation($recommendation) {
+        $recommendation.fadeOut(1000, function () {
+            var $listElement = $recommendation.parent();
+            var $listElements = $listElement.parent();
+
+            var $hiddenElements = $listElements.children(".hidden:first");
+            $listElement.remove();
+            $hiddenElements.removeClass("hidden");
+        });
+    }
+});

File src/main/resources/templates/recommendations-injected-macro.vm

+#requireResource("com.atlassian.confluence.plugins.whotofollow:recommendations-macro-resources")
+
+<html>
+<head>
+$action.webResourceManager.getResources()
+</head>
+<body>
+    #set ($username = $user.name)
+    $helper.renderConfluenceMacro("{recommendations:username=admin}")
+</body>
+</html>
+

File src/main/resources/templates/recommendations-macro.vm

+#* @vtlvariable name="recommendations" type="java.util.List<com.atlassian.crowd.embedded.api.User>" *#
+#requireResource("confluence.web.resources:ajs")
+#requireResource("confluence.macros.profile:network-resources")
+#requireResource("com.atlassian.confluence.plugins.whotofollow:recommendations-macro-resources")
+
+#set ($theme = "small")
+#set ($limit = 2)
+
+#if ($theme == 'small')
+<div class="network-macro follow-following follow-dashboard">
+    <h2 class="subheading">Who to follow</h2>
+    <ul class="avatars">
+        #foreach ($recommendation in $recommendations)
+            <li #if ($velocityCount > $limit) class="hidden" #end>
+                #logoBlock("~$recommendation.name")
+            </li>
+        #end
+    </ul>
+</div>
+#else
+<div class="recommendations-macro">
+    <h2>Who to follow</h2>
+    <ul>
+        #foreach ($recommendation in $recommendations)
+            <li #if ($velocityCount > $limit) class="hidden" #end>
+                <div class="recommendation">
+                    #logoBlock("~$recommendation.name")
+                    <div class="infos">
+                        <h4>$recommendation.displayName</h4>
+                        <span><a class="follow" href="#" data-username="$recommendation.name">Follow</a></span>
+                    </div>
+                </div>
+            </li>
+        #end
+    </ul>
+</div>
+#end

File src/test/java/com/atlassian/confluence/plugins/whotofollow/ExampleMacroTest.java

-package com.atlassian.confluence.plugins.whotofollow;
-
-import org.junit.Test;
-
-/**
- * Testing {@link com.atlassian.confluence.plugins.whotofollow.ExampleMacro}
- */
-public class ExampleMacroTest
-{
-    @Test
-    public void basic()
-    {
-        // add test here...
-    }
-}

File src/test/java/com/atlassian/confluence/plugins/whotofollow/RecommendationsMacroTest.java

+package com.atlassian.confluence.plugins.whotofollow;
+
+import org.junit.Test;
+
+/**
+ * Testing {@link RecommendationsMacro}
+ */
+public class RecommendationsMacroTest
+{
+    @Test
+    public void basic()
+    {
+        // add test here...
+    }
+}

File src/test/resources/confluence-app/WEB-INF/classes/velocity.properties

+#----------------------------------------------------------------------------
+# Velocity configuration file. Default values shown commented, changed
+# values uncommented. - brett
+#----------------------------------------------------------------------------
+
+#----------------------------------------------------------------------------
+# R U N T I M E  L O G
+#----------------------------------------------------------------------------
+
+#----------------------------------------------------------------------------
+#  LogSystem to use: log4j with category "velocity"
+#----------------------------------------------------------------------------
+# Log4J is configured in log4j.properties.
+#----------------------------------------------------------------------------
+runtime.log.logsystem.class = org.apache.velocity.runtime.log.SimpleLog4JLogSystem
+runtime.log.logsystem.log4j.category=velocity
+
+# prevent velocity from generating the log file.
+# runtime.log.logsystem.class = com.atlassian.confluence.util.velocity.NullVelocityLogSystem
+
+#----------------------------------------------------------------------------
+# This controls if Runtime.error(), info() and warn() messages include the
+# whole stack trace. The last property controls whether invalid references
+# are logged.
+#----------------------------------------------------------------------------
+
+runtime.log.error.stacktrace = false
+runtime.log.warn.stacktrace = false
+runtime.log.info.stacktrace = false
+#runtime.log.invalid.reference = true
+
+#----------------------------------------------------------------------------
+# T E M P L A T E  E N C O D I N G
+#----------------------------------------------------------------------------
+
+input.encoding=UTF-8
+output.encoding=UTF-8
+
+#----------------------------------------------------------------------------
+# F O R E A C H  P R O P E R T I E S
+#----------------------------------------------------------------------------
+# These properties control how the counter is accessed in the #foreach
+# directive. By default the reference $velocityCount will be available
+# in the body of the #foreach directive. The default starting value
+# for this reference is 1.
+#----------------------------------------------------------------------------
+
+#directive.foreach.counter.name = velocityCount
+#directive.foreach.counter.initial.value = 1
+
+#----------------------------------------------------------------------------
+# I N C L U D E  P R O P E R T I E S
+#----------------------------------------------------------------------------
+# These are the properties that governed the way #include'd content
+# is governed.
+#----------------------------------------------------------------------------
+
+#directive.include.output.errormsg.start = <!-- include error :
+#directive.include.output.errormsg.end   =  see error log -->
+
+#----------------------------------------------------------------------------
+# P A R S E  P R O P E R T I E S
+#----------------------------------------------------------------------------
+
+directive.parse.max.depth = 10
+
+#----------------------------------------------------------------------------
+# T E M P L A T E  L O A D E R S
+#----------------------------------------------------------------------------
+
+# use the webwork file and classpath loaders, as well as a custom classpath loader (defined below)
+resource.loader=hibernate,wwfile,confclass,confplugin
+
+# custom classpath loader (for mail templates)
+confclass.resource.loader.description=Confluence classpath loader
+confclass.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+
+# hibernate resource loader (for custom decorators)
+hibernate.resource.loader.class=com.atlassian.confluence.setup.velocity.HibernateResourceLoader
+hibernate.resource.loader.description=Hibernate loader
+hibernate.resource.loader.confluence.velocity13.compatibility=true
+hibernate.resource.loader.confluence.space.decorator.loader=true
+
+# debugging resource loader (sending messages to confluence log)
+debug.resource.loader.class=com.atlassian.confluence.setup.velocity.DebugResourceLoader
+debug.resource.loader.description=Debugging loader
+
+# dynamic plugin classpath loader (for plugin resources)
+confplugin.resource.loader.description=Confluence Dynamic Plugin classpath loader
+confplugin.resource.loader.class=com.atlassian.confluence.setup.velocity.DynamicPluginResourceLoader
+confplugin.resource.loader.confluence.velocity13.compatibility=true
+
+# set caching on for resource loaders (see com.opensymphony.webwork.views.velocity.VelocityManager)
+# comment in these lines to add template caching (faster)
+wwfile.resource.loader.cache=false
+confclass.resource.loader.cache=false
+hibernate.resource.loader.cache=false
+confplugin.resource.loader.cache=false
+file.resource.loader.modificationCheckInterval=-1
+
+#----------------------------------------------------------------------------
+# VELOCIMACRO PROPERTIES
+#----------------------------------------------------------------------------
+# global : name of default global library.  It is expected to be in the regular
+# template path.  You may remove it (either the file or this property) if
+# you wish with no harm.
+#----------------------------------------------------------------------------
+
+velocimacro.library = template/includes/macros-deprecated.vm, template/includes/macros.vm,\
+   template/includes/navlinkmacros.vm, template/includes/menu-macros.vm,\
+   template/includes/auimacros.vm
+
+#velocimacro.permissions.allow.inline = true
+velocimacro.permissions.allow.inline.to.replace.global = true
+#velocimacro.permissions.allow.inline.local.scope = false
+
+#velocimacro.context.localscope = false
+velocimacro.library.autoreload = true
+
+#----------------------------------------------------------------------------
+# INTERPOLATION
+#----------------------------------------------------------------------------
+# turn off and on interpolation of references and directives in string
+# literals.  ON by default :)
+#----------------------------------------------------------------------------
+
+#runtime.interpolate.string.literals = true
+
+#----------------------------------------------------------------------------
+# RESOURCE MANAGEMENT
+#----------------------------------------------------------------------------
+# Allows alternative ResourceManager and ResourceCache implementations
+# to be plugged in.
+#----------------------------------------------------------------------------
+
+resource.manager.class=com.atlassian.confluence.util.velocity.CompatibleVelocityResourceManager
+
+runtime.introspector.uberspect=com.atlassian.confluence.velocity.introspection.ConfluenceAnnotationBoxingUberspect
+
+userdirective=com.opensymphony.webwork.views.velocity.ParamDirective,com.opensymphony.webwork.views.velocity.TagDirective,com.opensymphony.webwork.views.velocity.BodyTagDirective,com.atlassian.confluence.setup.velocity.ApplyDecoratorDirective, com.atlassian.confluence.setup.velocity.ParamDirective, \
+com.atlassian.confluence.setup.velocity.RenderVelocityTemplateDirective,com.atlassian.confluence.setup.velocity.TrimDirective, com.atlassian.confluence.setup.velocity.HtmlSafeDirective, com.atlassian.confluence.setup.velocity.SkipLinkDirective, com.atlassian.confluence.setup.velocity.DisableAntiXssDirective
+
+# runtime.introspector.uberspect=com.atlassian.confluence.util.velocity.debug.UberspectDebugDecorator
+
+# CONF-15389 - recursion limit added in Velocity 1.6. Set to infinity
+velocimacro.max.depth=-1