Group Newsletter Studio newsletters by month in the Umbraco backend tree
One of our customer's websites uses the Newsletter Studio Umbraco package to manage their mailing lists but had an issue because they send a lot of emails (40+ a month) so the draft/sent/archive folders were getting pretty large. We needed a way of grouping them more logically. Markus got back to me pretty quickly on the forums suggesting that we implement a custom tree controller and shared the original source controller code here.
The way we've implemented it is fairly simple -and not overly efficient due to the way the data access works but I thought it worth sharing as a couple of others have asked for the code, first though here's a little screenshot:
Drafts are listed based on the created date, sent/archived generate folders based on the sent date as that felt most logical but it wouldn't be hard to change if you didn't like it. This was just a quick "get it out there ASAP" change so I'm sure the code can be cleared up. Welcome any comments.
Installation is simple, modify the `/config/trees.config` file:
<add title="Newsletters" type="YourProject.NewsletterStudio.CustomNewsletterTreeController, YourProject" iconopen="icon-message" iconclosed="icon-message" application="NewsletterStudio" alias="Newsletter" sortorder="5" initialize="true"></add>
Then you just need to add this class to your solution:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http.Formatting; using NewsletterStudio.Core.Extensions; using NewsletterStudio.Core.Interfaces.Data; using NewsletterStudio.Core.Model; using NewsletterStudio.Infrastucture; using NewsletterStudio.Infrastucture.Services; using NewsletterStudio.Umbraco; using umbraco.BusinessLogic.Actions; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.Trees; namespace YourProject.NewsletterStudio { [PluginController(NewsletterStudioApplication.Name)] [Tree( "NewsletterStudio" , "Newsletter" , "Newsletters" , "icon-message" , "icon-message" , sortOrder: 5)] public class CustomNewsletterTreeController : TreeController { private INewsletterRepository _newsletterRepository; private LocalizationService _localization; public CustomNewsletterTreeController() { _newsletterRepository = GlobalFactory.Current.NewsletterRepository; _localization = new LocalizationService(); } protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var node = base .CreateRootNode(queryStrings); node.Name = _localization.Get( "ns_newsletters" ); return node; } protected override TreeNodeCollection GetTreeNodes( string id, FormDataCollection queryStrings) { var nodes = new TreeNodeCollection(); if (id == "-1" ) { var nodeDraft = this .CreateTreeNode( "draft" , id, queryStrings, _localization.Get( "ns_draft" ), "icon-outbox" , _newsletterRepository.CountDraft() > 0); nodeDraft.RoutePath = "NewsletterStudio" ; nodes.Add(nodeDraft); var nodeSent = this .CreateTreeNode( "sent" , id, queryStrings, _localization.Get( "ns_sent" ), "icon-mailbox" , _newsletterRepository.CountSent() > 0); nodeSent.RoutePath = "NewsletterStudio" ; nodes.Add(nodeSent); var nodeArchive = this .CreateTreeNode( "archive" , id, queryStrings, _localization.Get( "ns_archive" ), "icon-file-cabinet" , _newsletterRepository.CountArchive() > 0); nodeArchive.RoutePath = "NewsletterStudio" ; nodes.Add(nodeArchive); } else { var statusParts = new [] { id }; var emailStatus = id; if (emailStatus.Contains( "-" )) { statusParts = emailStatus.Split( '-' ); emailStatus = statusParts.FirstOrDefault(); } // NOTE: This is messy as we're getting all items just to get the years/months but we don't have much of a choice atm IList letterList; switch (emailStatus) { case "draft" : letterList = _newsletterRepository.GetDraft(); break ; case "sent" : letterList = _newsletterRepository.GetSent(); break ; case "archive" : letterList = _newsletterRepository.GetArchived(); break ; default : letterList = new List(); break ; } // This is a year or month tree node if (statusParts.Length > 1) { var subPart = statusParts[1]; var year = Convert.ToInt32(statusParts[2]); var letterYears = letterList.Where(l => GetDateToGroupBy(emailStatus, l).Year == year); switch (subPart) { case "year" : var months = letterYears.GroupBy(l => GetDateToGroupBy(emailStatus, l).Month); foreach (var month in months.OrderBy(m => m.Key)) { var item = CreateTreeNode($ "{emailStatus}-month-{year}-{month.Key}" , id, queryStrings, CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month.Key), "icon-folder" , true ); nodes.Add(item); } break ; case "month" : var monthNumber = Convert.ToInt32(statusParts[3]); foreach (var newsletter in letterYears.Where(l => GetDateToGroupBy(emailStatus, l).Month == monthNumber).OrderBy(l => GetDateToGroupBy(emailStatus, l))) { var item = CreateTreeNode(newsletter.Id.ToString(), id, queryStrings, newsletter.Name, "icon-message" , false ); if (newsletter.Status == NewsletterStatus.Completed) { item.Icon = "icon-pie-chart" ; item.RoutePath = "NewsletterStudio/Newsletter/analytics/" + newsletter.Id; } else { if (newsletter.ScheduledSendDate.HasValue) item.CssClasses.Add( "ns-is-scheduled" ); } if (newsletter.Status == NewsletterStatus.Error) item.CssClasses.Add( "ns-has-error" ); nodes.Add(item); } break ; } } else { var years = letterList.GroupBy(l => GetDateToGroupBy(emailStatus, l).Year); foreach (var year in years.OrderBy(m => m.Key)) { var item = this .CreateTreeNode($ "{emailStatus}-year-{year.Key}" , id, queryStrings, year.Key.ToString(), "icon-folder" , true ); nodes.Add(item); } } } return nodes; } private DateTime GetDateToGroupBy( string emailStatus, Newsletter newsletter) { if (emailStatus == "draft" ) return newsletter.CreatedDate; return newsletter.SentDate ?? newsletter.CreatedDate; } protected override MenuItemCollection GetMenuForNode( string id, FormDataCollection queryStrings) { var menu = new MenuItemCollection(); if (id == "-1" || id == "draft" ) { menu.DefaultMenuAlias = ActionNew.Instance.Alias; menu.Items.Add(_localization.Get( "general_create" )); } else if (id.IsNumeric()) { menu.Items.Add(_localization.Get( "ns_copyToNew" )); menu.Items.Add(_localization.Get( "general_delete" )); } menu.Items.Add(_localization.Get( "actions_refreshNode" )); return menu; } } } |
Liked this post? Got a suggestion? Leave a comment