Showing posts with label Alchemy plugin. Show all posts
Showing posts with label Alchemy plugin. Show all posts

Monday 10 September 2018

Published Summary Alchemy Plugin

In my previous blog, we saw how Published Summary plugin was conceptualized, its use cases and how it works and its different functionalities.

In this Plugin, I wrote the C# code that interacts with Core Service and forwards the JSON response to be consumed on FrontEnd.

Based on the use cases and feature of this plugin I wrote the Endpoints on the following scenarios and provided the JSON.
  1. Get All Published Items from Publication, Structure Group, and Folder.
    • Let's say if we select Publication then the response will be all Published  Pages, Components, and Categories in that publication.
    • If we select this plugin from Structure group, then we will have all the published pages in the structure group and same goes for Folder selection all Published ComponentTemplates and Components.
  2. Publish Items that are selected by the user from the Plugin UI.
  3. Unpublish the Items that are select by the user from the Plugin UI.
  4. Summary Panel, where we'll have a Snapshot of how many items are published in a particular publication, basically a GroupBy of itemsType with the count.
  5. And Finally,  Get all Publication and Target Type.
Now, Let's look at the code at High Level

Get All Published Item

The GetAllPublishedItems method is the POST request method and deserialized JSON in C# modal.

JSON format is {'IDs':['tcm:14-65-4']}

 [HttpPost, Route("GetAllPublishedItems")]  
     public object GetAllPublishedItems(TcmIds tcmIDs)  
     {  
       GetPublishedInfo getFinalPublishedInfo = new GetPublishedInfo();  
       var multipleListItems = new List<ListItems>();  
       XmlDocument doc = new XmlDocument();  
       try  
       {  
         foreach (var tcmId in tcmIDs.IDs)  
         {  
           TCM.TcmUri iTcmUri = new TCM.TcmUri(tcmId.ToString());  
           XElement listXml = null;  
           switch (iTcmUri.ItemType.ToString())  
           {  
             case CONSTANTS.PUBLICATION:  
               listXml = Client.GetListXml(tcmId.ToString(), new RepositoryItemsFilterData  
               {  
                 ItemTypes = new[] { ItemType.Component, ItemType.ComponentTemplate, ItemType.Category, ItemType.Page },  
                 Recursive = true,  
                 BaseColumns = ListBaseColumns.Extended  
               });  
               break;  
             case CONSTANTS.FOLDER:  
               listXml = Client.GetListXml(tcmId.ToString(), new OrganizationalItemItemsFilterData  
               {  
                 ItemTypes = new[] { ItemType.Component, ItemType.ComponentTemplate },  
                 Recursive = true,  
                 BaseColumns = ListBaseColumns.Extended  
               });  
               break;  
             case CONSTANTS.STRUCTUREGROUP:  
               listXml = Client.GetListXml(tcmId.ToString(), new OrganizationalItemItemsFilterData()  
               {  
                 ItemTypes = new[] { ItemType.Page },  
                 Recursive = true,  
                 BaseColumns = ListBaseColumns.Extended  
               });  
               break;  
             case CONSTANTS.CATEGORY:  
               listXml = Client.GetListXml(tcmId.ToString(), new RepositoryItemsFilterData  
               {  
                 ItemTypes = new[] { ItemType.Category },  
                 Recursive = true,  
                 BaseColumns = ListBaseColumns.Extended  
               });  
               break;  
             default:  
               throw new ArgumentOutOfRangeException();  
           }  
           if (listXml == null) throw new ArgumentNullException(nameof(listXml));  
           doc.LoadXml(listXml.ToString());  
           multipleListItems.Add(TransformObjectAndXml.Deserialize<ListItems>(doc));  
         }  
         return getFinalPublishedInfo.FilterIsPublishedItem(multipleListItems).SelectMany(publishedItem => publishedItem, (publishedItem, item) => new { publishedItem, item }).Select(@t => new { @t, publishInfo = Client.GetListPublishInfo(@t.item.ID) }).SelectMany(@t => getFinalPublishedInfo.ReturnFinalList(@t.publishInfo, @t.@t.item)).ToList();  
       }  
       catch (Exception ex)  
       {  
         throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));  
       }  
     }  

Publish and UnPublish Items

The Publish and UnPublish method is the POST request method and deserialized JSON  into C# Model.

{"TcmIds":[{Id:"tcm:14-65-4",Target:"staging"},{Id:"tcm:14-77-64",Target:"staging"}]}


  #region Publishe the items  
     /// <summary>  
     /// Publishes the items.  
     /// </summary>  
     /// <param name="IDs">The i ds.</param>  
     /// <returns>System.Int32.</returns>  
     /// <exception cref="ArgumentNullException">result</exception>  
     [HttpPost, Route("PublishItems")]  
     public string PublishItems(PublishUnPublishInfoData IDs)  
     {  
       try  
       {  
         var pubInstruction = new PublishInstructionData()  
         {  
           ResolveInstruction = new ResolveInstructionData() { IncludeChildPublications = false },  
           RenderInstruction = new RenderInstructionData()  
         };  
         PublishTransactionData[] result = null;  
         var tfilter = new TargetTypesFilterData();  
         var allPublicationTargets = Client.GetSystemWideList(tfilter);  
         if (allPublicationTargets == null) throw new ArgumentNullException(nameof(allPublicationTargets));  
         foreach (var pubdata in IDs.IDs)  
         {  
           var target = allPublicationTargets.Where(x => x.Title == pubdata.Target).Select(x => x.Id).ToList();  
           if (target.Any())  
           {  
             result = Client.Publish(new[] { pubdata.Id }, pubInstruction, new[] { target[0] }, PublishPriority.Normal, null);  
             if (result == null) throw new ArgumentNullException(nameof(result));  
           }  
         }  
         return "Item send to Publish";  
       }  
       catch (Exception ex)  
       {  
         throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));  
       }  
     }  
     #endregion  
     #region Unpublish the items  
     /// <summary>  
     /// Uns the publish items.  
     /// </summary>  
     /// <param name="IDs">The i ds.</param>  
     /// <returns>System.Object.</returns>  
     /// <exception cref="ArgumentNullException">result</exception>  
     /// <exception cref="HttpResponseException"></exception>  
     [HttpPost, Route("UnPublishItems")]  
     public string UnPublishItems(PublishUnPublishInfoData IDs)  
     {  
       try  
       {  
         var unPubInstruction = new UnPublishInstructionData()  
         {  
           ResolveInstruction = new ResolveInstructionData()  
           {  
             IncludeChildPublications = false,  
             Purpose = ResolvePurpose.UnPublish,  
           },  
           RollbackOnFailure = true  
         };  
         PublishTransactionData[] result = null;  
         var tfilter = new TargetTypesFilterData();  
         var allPublicationTargets = Client.GetSystemWideList(tfilter);  
         if (allPublicationTargets == null) throw new ArgumentNullException(nameof(allPublicationTargets));  
         foreach (var tcmID in IDs.IDs)  
         {  
           var target = allPublicationTargets.Where(x => x.Title == tcmID.Target).Select(x => x.Id).ToList();  
           if (target.Any())  
           {  
             result = Client.UnPublish(new[] { tcmID.Id }, unPubInstruction, new[] { target[0] }, PublishPriority.Normal, null);  
             if (result == null) throw new ArgumentNullException(nameof(result));  
           }  
         }  
         return "Items send for Unpublish";  
       }  
       catch (Exception ex)  
       {  
         throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));  
       }  
     }  
     #endregion  

Summary Panel

The GetSummaryPanelData method is the POST request method and deserialized JSON in C# modal.

JSON format is {'IDs':['tcm:14-65-4']}


 #region Get GetSummaryPanelData  
     /// <summary>  
     /// Gets the analytic data.  
     /// </summary>  
     /// <returns>System.Object.</returns>  
     [HttpPost, Route("GetSummaryPanelData")]  
     public object GetSummaryPanelData(TcmIds tcmIDs)  
     {  
       try  
       {  
         GetPublishedInfo getFinalPublishedInfo = new GetPublishedInfo();  
         var multipleListItems = new List<ListItems>();  
         XmlDocument doc = new XmlDocument();  
         foreach (var tcmId in tcmIDs.IDs)  
         {  
           var listXml = Client.GetListXml(tcmId.ToString(), new RepositoryItemsFilterData  
           {  
             ItemTypes = new[] { ItemType.Component, ItemType.ComponentTemplate, ItemType.Category, ItemType.Page },  
             Recursive = true,  
             BaseColumns = ListBaseColumns.Extended  
           });  
           if (listXml == null) throw new ArgumentNullException(nameof(listXml));  
           doc.LoadXml(listXml.ToString());  
           multipleListItems.Add(TransformObjectAndXml.Deserialize<ListItems>(doc));  
         }  
         List<Item> finalList = new List<Item>();  
         foreach (var publishedItem in getFinalPublishedInfo.FilterIsPublishedItem(multipleListItems))  
           foreach (var item in publishedItem)  
           {  
             var publishInfo = Client.GetListPublishInfo(item.ID);  
             foreach (var item1 in getFinalPublishedInfo.ReturnFinalList(publishInfo, item)) finalList.Add(item1);  
           }  
         IEnumerable<Analytics> analytics = finalList.GroupBy(x => new { x.PublicationTarget, x.Type }).Select(g => new Analytics { Count = g.Count(), PublicationTarget = g.Key.PublicationTarget, ItemType = g.Key.Type, });  
         var tfilter = new TargetTypesFilterData();  
         List<ItemSummary> itemssummary = getFinalPublishedInfo.SummaryPanelData(analytics, Client.GetSystemWideList(tfilter));  
         return itemssummary;  
       }  
       catch (Exception ex)  
       {  
         throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));  
       }  
     }  
     #endregion  

Get All Publication and Target Type

 #region Get list of all publications  
     /// <summary>  
     /// Gets the publication list.  
     /// </summary>  
     /// <returns>List&lt;Publications&gt;.</returns>  
     [HttpGet, Route("GetPublicationList")]  
     public List<Publications> GetPublicationList()  
     {  
       GetPublishedInfo getPublishedInfo = new GetPublishedInfo();  
       XmlDocument publicationList = new XmlDocument();  
       PublicationsFilterData filter = new PublicationsFilterData();  
       XElement publications = Client.GetSystemWideListXml(filter);  
       if (publications == null) throw new ArgumentNullException(nameof(publications));  
       List<Publications> publicationsList = getPublishedInfo.Publications(publicationList, publications);  
       return publicationsList;  
     }  
     #endregion  
     #region Get List of all publication targets  
     /// <summary>  
     /// Gets the publication target.  
     /// </summary>  
     /// <returns>System.Object.</returns>  
     [HttpGet, Route("GetPublicationTarget")]  
     public object GetPublicationTarget()  
     {  
       var filter = new TargetTypesFilterData();  
       var allPublicationTargets = Client.GetSystemWideList(filter);  
       if (allPublicationTargets == null) throw new ArgumentNullException(nameof(allPublicationTargets));  
       return allPublicationTargets;  
     }  
     #endregion  


You can download the code from the Github.

Happy Coding and Keep Sharing !!!

SDL Web Hackathon 2018 - Published Summary Plugin

Published Summary Alchemy-Plug In project has been conceptualized and initiated for SDL Tridion Developer Summit, Amsterdam Hackathon for the year 2018.

The entry for Hackathon has been entered with the team name - "CB Ke Cheete" and for the project "Published Summary" Alchemy plugin.

Team Members 
The team - "CB ke Cheete" - having meanings as "Cheetahs of CB", comprises of three young and dynamic Content Bloom professionals. The team has following members:
  • Pankaj Gaur - Director, Content Bloom | SDL Certified Dev and BA | SDL Tridion/Web MVP
  • Hem Kant - Consultant, Content Bloom | SDL Certified Dev and BA | SDL Web MVP
  • Priyank Gupta - Consultant, Content Bloom | SDL Certified Dev

This plugin is intended to do the following:
  1. Get all items within a publication, folder or structure group published to one or more Publishing Target Types.
    • Let's say if we select Publication then the response will be all Published  PagesComponents, and Categories in that publication.
    • If we select this plugin from Structure group, then we will have all the published pages in the structure group and same goes for Folder selection all Published ComponentTemplates and Components.
  2. Republish, Unpublish and open a specific item.
  3. Republish or Unpublish multiple items on their respective Publishing Target Types.
  4. Export in CSV.
  5. Filter based on Publishing Target Types, Item Type (Component, Pages etc.), and Published Date Range.
  6. Sorting based on Title, Published Date, Published By, Targets, and TCM URIs.
  7. Searching.
  8. Summary of published building blocks across all publishing target types.

The Published Summary Alchemy Plug-in can help in following scenarios:
  1. You want to export a list of all published items from Tridion for a specific website (or for a specific structure group/folder within a website) in CSV format.
  2. You want to see/export all published items from Tridion for a specific Publishing Target Type and compare among all publishing target types for a specific CMS instance.
  3. You need to know, what all need to be published from Tridion in order to make a specific website up and running similar to an existing website.
  4. You need to know a summary of "how many" specific items are published from Tridion to individual publishing target types.
  5. You need to know about "delta" of published items across publishing target types of a CMS instance.
  6. You need to sync your non-live websites with the live websites in terms of content managed from Tridion.
Published Summary

Happy Coding and Keep Sharing !!!

Saturday 2 June 2018

SDL Tridion with IBM Watson Assistant

Well, in the past I've learned and worked on many CMS systems but got engaged with SDL Tridion and one thing I noticed that they all have all the common features available e;g. Targeting, Personalization, Experience manager (InLine Editing), Analytics, Content Management etc.

"It's time to make your CMS more intelligent."

 I would like to share my knowledge and findings on how we can use Artificial Intelligence and Machine Learning technique in SDL Tridion in the form of a ChatBot Assistant with the help of IBM Watson Assistant service. I've trained this BOT to help CMS user and answer most commonly and frequently asked questions.

Especially for non-technical users "Content Author" with their most frequently asked questions.  I trained this BOT to answer based on DXA implementation and on most common scenarios "it's still learning", but if you want you can educate it according to your CMS implementation.

  • "Who can give me access on CMS or any specific Publication "
  • "Where, I should create content as per the blueprint hierarchy"   
  • "Where I should create Templates"
  • "Where I should create schema"

To know more about IBM Watson Assistant please refer my blogs.


Today we are going to integrate IBM Waston Assistant sample ChatBot in SDL Tridion and for that, I have created an Alchemy Plugin. It as pretty simple and StraightForward. You can always extend the functionality, not limited to ChatBot only you will come to know once you start exploring it.

If you are new to Alchemy please visit us at WebStore and Alchemy4Tridion

Install Alchemy Plugin

Conversation between user and Watson

Happy coding and keep sharing !!!

Monday 29 May 2017

Universal WorkList Alchemy Plugin for SDL WEB

Centralized way of Managing all the tasks Universal Work List is used to manage ,respond to ,assign daily task or delegate tasks to peers.With the help of this plugin you will able to manage all our task and communication from CMS.

Features of this plugin

  1. Create Task and assign to peers.
  2. Update Task status and put your comments.
  3. Re-assign task you any user.
  4. Assign any task to yourself.
  5. Dashboard.
  6. Powered by SOLR 6.5
Let's go through the each screens one by one and understand how its works and its functionality. 


Landing page where you will see all the task assign to you and you can filter them as well to render data I have used JSgrid which gives all the feature such as pagination,sorting etc.
Landing page
 Here, I will have all the task assign to me rendering and I can create the task ,by clicking on the uniqueKey it will take user to Task update page, where user can update comments and status
Update Task
Here user can update comments ,update task status,Re-assign to other user . All the data is stored in SOLR. 

Next ,is how to create a task.In the create task panel you have 
  1. Task assign to field.
  2. Watcher or you can say second look.
  3. Priority is defined.
  4. Environment 
  5. Issue description.   
    Create Task
Happy Coding and keep Sharing !!!

Monday 22 May 2017

Alchemy Chat Plugin For SDL WEB

This plugin is used to communicate with all CMS user as in group or one to one chat.I have used JQuery,WEB API and SOLR to store all the chat data.
By default it will load top 100 messages based on datetime in your chat window for both group and one to one chat,with the help of this plugin we can easily communicate with all CMS users without any problem,most of the time we face this problem when our client or editors are only accessible via emails not on chat.

To use this plugin we need to install SOLR
  1. Download SOLR here .I have used SOLR version 6.5
  2. Create core in SOLR
  3. Update managed-Schema.xml and add following fields.
  4. Re-Start :- solr start -p <portNumber>
  5. Chat Plugin will load all the CMS users using coreservice.
  6. In the group window your message will be visible to all CMS users.
  7. In One2One only the participates. 

<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="Message" type="text_general" indexed="true" stored="true" required="false" multiValued="false" />
<field name="MessageId" type="string" indexed="true" stored="true" required="false" multiValued="false" />
<field name="User" type="text_general" indexed="true" stored="true" required="false" multiValued="false" />
<field name="TimeStamp" type="string" indexed="true" stored="true" required="false" multiValued="false" />
<field name="isPrivateChat" type="string" indexed="true" stored="true" required="false" multiValued="false" />
<field name="chatWith" type="string" indexed="true" stored="true" required="false" multiValued="false" />
<field name="privateChatID" type="string" indexed="true" stored="true" required="false" multiValued="true" />

Once all this is done install the plugin and start chatting with all the CMS users,or you can chat with any user one to one.
Install chat Plugin
Group chat with all CMS users.
Group chat data index in SOLR
One to one chat with CMS user.
One to One Chat
One to one chat data in SOLR

Happy Coding and keep sharing !!!!

Friday 5 May 2017

Alchemy Plugin for WEB8 Dashboard

This is in continuation of my previous post where we build Alchemy plugin to download and search CMS items.I have further enhanced that one and added new feature as CMS level dashboard where we have multiple data points options and multiple chart options to represent the data in pictorial form.

Let's see the new addition in this Plugin DASHBOARD.
Dashboards often provide at-a-glance views.A data dashboard is an information management tool that visually tracks, analyzes and displays key performance using pictorial representation of the data , with the help of this dashboard we can identity the usage of different CMS items based on date using charts.

Here,We have 3 filters.
Dashboard
Let's  run this and see how its looks like with all the charts and information which represent .Let's generate charts based on All the components created month wise using all three charts.
1.Bar and Column Chart.
Bar and Column chart
 2.All three charts representing the number of content created month wise in pictorial form we can have same information available year wise as well.
All three charts.
3.Similar to components right we can generate information for pages for month and year wise.
4. Schema utilization number of components created using any particular schema
Schema utilization
With the help of all these information when can do CMS DB forecasting, CMS clean-up activity,Content growth.I have used Core Service,JQuery and Google visualization API to create dashboard.

Let's see the enhancement in the existing report Plugin.

  1. Download button to export the search data in .csv format.
  2. <a> link to open any item in the CMS to verify or cross check.
  3. You can open any Item in same or new window.
  4. Improved UI
  5. JQuery searching to filter data
Anchor tag with UI improvement

Download and Data searching .
Happy coding and keep sharing !!!!

Monday 1 May 2017

Alchemy plugin to download report from SDL WEB8


We all have came a across from a situation where we need to deploy/port CMS items from one environment to another for that we have Content porter but we need to manually identity(list of items which got created or modified after a certain date) items which we need to migrate.We used Bundle Schema to clubbed all the items at one location but that is again what if we missed any item and as a result content porting might failed due to some or the other dependency.

Here, is an Alchemy plugin which will allow you to download report from CMS based on date.I have used coreService,jQuery to build this plugin and a .ASPX page which is for popup.

This plugin will help you in migration as well where you need to identify items from across CMS.


Steps 
1.    Download the Developer pack from visual studio gallery Link.
2.    Alchemy version that I have used can be downloaded from Alchemy4Tridion link.
3.    Create a project selecting "Starter Plugin Project" template change the project name .
4.    Build the project and navigate to the generated .a4t file.
5.    Drag and drop the file in alchemy window.

Alchemy Plugin Installed.
Let go to CME,
Plugin is installed and ready for use
Let run the plugin and get some records form CM Database.
Custom Popup to Download data

Using Jquery Table searching you can filter the records as well. It will look for the text in all the columns.With the help of Jquery searching you can filter data based on date as well.
Search



You,can download the sample code from here

Happy coding and keep sharing !!!!

Monday 14 March 2016

Get List of all users in WEB 8. A basic alchemy plugin


Basic Steps 
1.    Download the Developer pack from visual studio gallery Link.
2.    Alchemy version that I have used can be downloaded from Alchemy4Tridion Version: 0.8.0.0 link.
3.    Create a project selecting "Starter Plugin Project" template change the project name .
4.    Build the project and navigate to the generated .a4t file.
5.    Drag and drop the file in alchemy window.



You can read the basics of an Alchemy plugin in the documentation 

Lets update the files go to : PluginControllers.cs  go to action methods and using CoreService client we can get the list of all users created in CM.

public string GetUserList()
   {
      List<TridionUserDetails> AuthorList = new List<TridionUserDetails>();
      var filter = new UsersFilterData { IsPredefined = false };
      var users = Client.GetSystemWideList(filter);
       foreach (TrusteeData user in users)
            {
AuthorList.Add(new TridionUserDetails(user.Id, user.Description,user.Title, user.IsEditable));
            }
               
          }            
      var output = JsonConvert.SerializeObject(AuthorList);
           
      return output.ToString();
   }

public class TridionUserDetails
{
    private string tcmuri;
    private string name;
    private string title;
    private bool? isEditable;


    public TridionUserDetails(string tcmuri, string name, string title, bool? isEditable)
    {
        this.tcmuri = tcmuri;
        this.name = name;
        this.title = title;
        this.isEditable = isEditable;


    }

    public string Tcmuri
    {
        get { return tcmuri; }
        set { tcmuri = value; }
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    public string UserId
    {
        get { return title; }
        set { title = value; }
    }

    public bool? IsEditable
    {
        get { return isEditable; }
        set { isEditable = value; }
    }



}
Here, i have converted the output into JSON :) .later on you can consume this JSON on the popup window using Javascript e:g angular.js.

Command.JS file 

/**
 * Creates an anguilla command using a wrapper shorthand.
 *
 * Note the ${PluginName} will get replaced by the actual plugin name.
 */
Alchemy.command("${PluginName}", "GetUserList", {

    /**
     * If an init function is created, this will be called from the command's constructor when a command instance
     * is created.
     */
    init: function () { 
    },

    /**
     * Whether or not the command is enabled for the user (will usually have extensions displayed but disabled).
     * @returns {boolean}
     */
    isEnabled: function () {
        return true;
    }, 
    /**
     * Whether or not the command is available to the user.
     * @returns {boolean}
     */
    isAvailable: function () {
        return true;
    }, 
    /**
     * Executes your command. You can use _execute or execute as the property name.
     */
    execute: function () { 
       var progress = $messages.registerProgress("Getting ready to GetUserList...", null); 
        // This is the Promise pattern that the webapi proxy js exposes. Look at another example to
        // see how the callback method can also be used. Your WebAPI controller's route and route prefix
        // attributes controls how the namespace is generated.
        Alchemy.Plugins["${PluginName}"].Api.Service.GetUserList()
            .success(function (message) {
                //first arg in success is what's returned by your controller's action
                $messages.registerGoal(message);
            })
            .error(function (type, error) {
                // first arg is string that shows the type of error ie (500 Internal), 2nd arg is object representing
                // the error.  For BadRequests and Exceptions, the error message will be in the error.message property.
                $messages.registerError("There was an error", error.message);
            })
            .complete(function () {
                // this is called regardless of success or failure.
               // progress.finish();
            });
    }
});

If you wants to open pop-up then you need to create .aspx page in the views folder 

var url = "${ViewsUrl}/ListOfUsers.aspx?uri=" + message;
popup = $popup.create(url, "menubar=no,location=no,resizable=no,scrollbars=yes,status=no,width=800,height=800", null);
popup.open();

PluginCommandSet.CS

public PluginCommandSet()
{
     // we only need to add the name of our command
     AddCommand("GetUserList");
}

PluginContextMenuExtension.CS

public PluginContextMenuExtension()
        {
            AssignId = ""; 
            // Use this property to specify where in the context menu your items will go
            InsertBefore = Constants.ContextMenuIds.MainContextMenu.SendItemLink; 
            // Use AddItem() or AddSubMenu() to add items for this context menu 
            //       element id      title        command name
            AddItem("Get_User_List_cm", "GetUsersList", "GetUsersList"); 
            // Add a dependency to the resource group that contains the files/commands that this toolbar extension will use.
            Dependencies.Add<PluginResourceGroup>(); 
            // apply the extension to a specific view.
            Apply.ToView(Constants.Views.DashboardView);
        }

PluginResourceGroup.CS 

public PluginResourceGroup()
        {
            // only the filename of our JS files are needed
            AddFile("Command.js");
            // only the filename of our CSS files are needed
            AddFile("Styles.css");
            // add genertic type param to reference our command set
            AddFile<PluginCommandSet>();

            // Adds the web api proxy JS to this resource group... this allows us to call
            // our webapi service without any 3rd party libs.
            AddWebApiProxy();

            // AddWebApiProxy() includes Alchemy.Core as a dependency already... if not using
            // the proxy you can remove the comment from below.

            // Dependencies.AddAlchemyCore();
        }

PluginRibbonToolbarButton.CS

  public PluginRibbonToolbarButton()
        {
            // The unique identifier used for the html element created.
            AssignId = "GetUsersListButton";

            // The name of the command to execute when clicked
            Command = "GetUsersList";

            // The label of the button.
            Name = "GetUsersList";

            // The page tab to assign this extension to. See Constants.PageIds.
            PageId = Constants.PageIds.HomePage;

            // Option GroupId, put this into an existing group (not capable if using a .ascx Control)
            GroupId = Constants.GroupIds.HomePage.ShareGroup;

            // The tooltip label that will get applied.
            Title = "GetUsersList";

            // Add a dependency to the resource group that contains the files/commands that this toolbar extension will use.
            Dependencies.Add<PluginResourceGroup>();

            // apply the extension to a specific view.
            Apply.ToView(Constants.Views.DashboardView, "DashboardToolbar");
        }


And Image files in the Images folder and update the Style.css.i have added two files user16.png and user32.png


/**
 * Style the context menu button... #Get_User_List_cm is defined in our Context Menu Extension class' AssignId property
 */
.contextmenu #Get_User_List_cm> .image {
    background-image: url('${ImgUrl}user16.png');
}

/**
 * Style the ribbon tool bar button... #GetUsersListButton is defined in our Ribbon Toolbar Button class' AssignId property
 */
/* main icon in full ribbon mode */
.ribbontoolbar #GetUsersListButton.button .image, .ribbontoolbar #GetUsersListButton.button .image
{
    background-image: url('${ImgUrl}user32.png');
}

/* main icon in minimized ribbon mode */
.ribbontoolbar.minimized #GetUsersListButton.button .image, .ribbontoolbar.minimized #GetUsersListButton.button .image
{
    background-image: url('${ImgUrl}user16.png');
}

After Updating all the files built your project and navigate to the generated .a4t file 




Open Alchemy In CM and  Drag and drop the .a4t  file in alchemy window.



    Refresh the CM session  and go to Content Explorer 




      Here you can see new option is added which is GetUserList click on it and you will get the list of users

      





Happy Coding and keep Sharing !!!!