<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[An EmmTi Blog]]></title><description><![CDATA[Some random and not-so-random stuff I came across today and couldn't help keep silent about!]]></description><link>https://emmti.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1663511058112/DAww9AnlB.png</url><title>An EmmTi Blog</title><link>https://emmti.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 10 Apr 2026 20:34:34 GMT</lastBuildDate><atom:link href="https://emmti.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Tiny Elf - Transferring ownership of automated triggers]]></title><description><![CDATA[Three years after writing the original version of Tiny Elf, a new requirement has come up for me to transfer ownership of automated triggers. So this is just a blog post with some boring instructions 🤗

Tiny Elf runs on a time-driven trigger. Unfort...]]></description><link>https://emmti.com/tiny-elf-transfer-ownership</link><guid isPermaLink="true">https://emmti.com/tiny-elf-transfer-ownership</guid><category><![CDATA[google sheets]]></category><category><![CDATA[automation]]></category><category><![CDATA[slack]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Thu, 09 Oct 2025 08:26:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759998220518/4e440e5f-e397-413a-8e23-288e37e5f8f7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Three years after <a target="_blank" href="https://emmti.com/tiny-elf-a-slack-bot-with-google-sheets-and-typescript">writing the original version of Tiny Elf</a>, a new requirement has come up for me to transfer ownership of automated triggers. So this is just a blog post with some boring instructions 🤗</p>
<blockquote>
<p>Tiny Elf runs on a time-driven trigger. Unfortunately, the script run by one user does not have the permission to delete triggers set by another user. Thus, the original owner of the trigger needs to delete the trigger they have set up.</p>
</blockquote>
<h2 id="heading-original-owner-deleting-the-trigger">Original Owner - Deleting the trigger</h2>
<p>This needs to be done by the person who has triggers currently automating Tiny Elf.</p>
<ol>
<li><p>Open your <code>Tiny Elf</code> Spreadsheet</p>
</li>
<li><p>Go to the <code>Tiny Elf</code> menu and click on <code>Delete trigger (Pauses automated notifications)</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759995824581/90f33397-3c5c-4c2c-8ae3-d4394c5124d0.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Once the trigger is successfully deleted, you should get a notification saying <code>Trigger has been deleted.</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759995971818/cc00c064-6830-4ac5-a3ea-6cf071ac0202.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h3 id="heading-original-owner-verifying-if-your-trigger-is-deleted">Original Owner - Verifying if your trigger is deleted</h3>
<ol>
<li><p>Open your <code>Tiny Elf</code> Apps Script project.</p>
<ul>
<li><p>This can be done by going to your <code>Tiny Elf</code> Spreadsheet.</p>
</li>
<li><p>Click <code>Extensions</code> → <code>Apps Scripts</code></p>
</li>
<li><p>Then <code>Tiny Elf - Rostering bot</code> Apps Script project will open in a new window.</p>
</li>
</ul>
</li>
<li><p>On the left, click <strong>Triggers</strong> alarm. You should see any triggers you set here.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759994086451/068bef5d-fa9c-42df-8599-3e81cbdc224f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>You should not see any scripts <code>Owned by</code> Me. That’s SUCCESS! 💪</p>
</li>
<li><p>If you do see any such triggers, click on the meatball menu and hit <code>Delete trigger</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759994462205/c1a18325-41d3-48de-8c4f-c728c4b2df71.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-new-owner-setting-up-automated-notifications">New Owner - Setting up automated notifications</h2>
<ol>
<li>If you have been pointed at a <code>Tiny Elf</code> Spreadsheet and asked to take ownership of its triggers, the first step is authorising <code>Tiny Elf</code>. Do this by clicking on the <code>Tiny Elf</code> menu → <code>Authorise Tiny Elf</code>.</li>
</ol>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759996583174/f91ce958-47e4-486e-bce5-1b62d8e3150b.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li><p>Click <code>OK</code> on the next pop-up.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759996733100/f3b5086d-d8cd-4db3-9aaa-913eb8d63f91.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Choose an Account in the next pop-up.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759996834480/1266c20e-9453-476d-bda9-eb127a0c01b1.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Click <code>Continue</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759996935495/912a172d-7291-48c7-a51f-1e224b59004f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><code>Select All</code> in the next screen and click <code>Continue</code> at the bottom of the screen.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759997021138/325cf0c9-7b9d-4c39-b0e3-9dc98d2c06f2.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>You should see a success message. Click <code>OK</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759997128268/2f653f72-248e-49ff-b6d4-b0ed92c8cdde.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>You should now see the full <code>Tiny Elf</code> menu with four menu items.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759997213383/245c09cf-a53f-49b0-8fb2-d864b12791de.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Verify your trigger settings in the <code>Settings</code> sheet.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759997263587/2576be4d-262a-4bc1-847f-ff10d16a7790.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Click <code>Set/reset automated trigger</code> in the <code>Tiny Elf</code> menu.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759997330971/48217336-d019-4f61-90a4-de1f910e9ee5.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>You should get a message saying <code>Success</code> - <code>Trigger has been set\reset.</code> Click <code>Ok</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759997408066/a0de024d-a5be-47a3-90bd-1b0542bb3ccc.png" alt class="image--center mx-auto" /></p>
<p>Congratulations! That’s it. You have now transferred ownership of the automated trigger to yourself. 🌟</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[I dug a trench and unearthed some Software Engineering principles]]></title><description><![CDATA[Disclaimer: These are my thoughts as an individual and do not necessarily represent my workplace or any other organisations I'm involved with.
It's been 21 years since I started in the Software world, albeit as an intern. Having wanted to become a So...]]></description><link>https://emmti.com/i-dug-a-trench-and-unearthed-some-software-engineering-principles</link><guid isPermaLink="true">https://emmti.com/i-dug-a-trench-and-unearthed-some-software-engineering-principles</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[skills]]></category><category><![CDATA[Experience ]]></category><category><![CDATA[quality]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Thu, 03 Aug 2023 08:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1690433142729/fb658fc4-e81a-44a0-847a-6fc24450219e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Disclaimer</strong>: <em>These are my thoughts as an individual and do not necessarily represent my workplace or any other organisations I'm involved with.</em></p>
<p>It's been 21 years since I started in the Software world, albeit as an intern. Having wanted to become a Software Engineer (without knowing precisely what that meant) from a young age, I can say this was a dream come true. Digging a trench (a physical one, not a metaphorical trench) to install a smart doorbell brought a few practices in the Software industry into perspective.</p>
<h2 id="heading-digging-a-trench">Digging a trench</h2>
<blockquote>
<p><strong><em>Trench, n</em></strong><br />gen. A long, narrow ditch or furrow cut out of the ground. Also figurative.<br />Oxford English Dictionary, s.v. “trench, n., sense I.3”, July 2023. <a target="_blank" href="https://doi.org/10.1093/OED/4609488321">https://doi.org/10.1093/OED/4609488321</a></p>
</blockquote>
<p>Anyone can dig a trench. Similarly, anyone can write code. But let's look at the differences between how an expert and an amateur digs a trench.</p>
<p>It's just a hole. It's not rocket science you might say! But you need to consider tooling, skills, experience, efficiency and quality.</p>
<h3 id="heading-tooling">Tooling</h3>
<p>You need the right tools to dig a trench. This could be a spade, an auger, a mattock, a shovel or even a power tool. It needs to fit the space you have to work in and the size of the trench you need.</p>
<p>While digging my trench, I started with a spade and soon realised it did not do the job. Then it was off to Bunnings to buy a mattock and another trip to buy an auger. It was a surprise that I escaped any serious bodily harm while using the auger with a power drill.</p>
<p>I needed to have the right tools to dig a trench and I was unprepared.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691036268614/a1849e38-af36-478a-9d06-bd653e2ea7fa.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-skills">Skills</h3>
<p>I have dug holes for planting before without any formal training. But this trench needed to be 10 metres long, 10 centimetres wide and a metre deep. It needed to be dug right next to a concrete driveway.</p>
<p>But I believe this would have been done well had I learnt the necessary skills as a builder's apprentice. So it was all trial and error as I went about digging this trench. YouTube videos helped to some extent, but they did not replace the need for some form of formal training.</p>
<h3 id="heading-experience">Experience</h3>
<p>I had never dug a trench before. So I had no experience as to what I should be doing. The ground was rock hard (pun intended) and I had no clue as to which tools to use first.</p>
<p>As I went about digging the trench I came across tree roots and concrete which I didn't anticipate and had no idea how to deal with.</p>
<h3 id="heading-efficiency">Efficiency</h3>
<p>I recruited my wife and son to help on this project and it took us over a weekend to dig this trench. There were multiple trips to Bunnings, stops for YouTube instructional videos and breaks when we were totally clueless and frustrated.</p>
<p>Remember, it was a 10m x 1m X 10cm trench and it took 48 hours to dig from start to finish.</p>
<h3 id="heading-quality">Quality</h3>
<p>The quality of the trench was a huge compromise. Digging to a metre deep was too hard and it ended up being just 40 cm deep and even a little less in certain places. The width was larger and smaller in several places depending on which tool was used. Fortunately, as we were covering up the trench once the conduit was laid, it was not a big concern. But it would be a concern if anyone ever dug up the same area.</p>
<p>When it was too hard to cut through the roots, we tunnelled through it. That meant we had to use a flexible conduit in that location rather than the hard conduit used elsewhere.</p>
<p>If my Builder friend ever saw this trench, he's going to faint. 😮</p>
<h2 id="heading-trench-software-engineering">Trench ≠ Software Engineering</h2>
<p>So what has digging a trench got to do with Software Engineering? I would take those same principles of tooling, skills, experience, efficiency and quality, and apply them to Software Engineering practices. These are by no means the only considerations when building software, but are the parallel lessons I took away while digging that trench.</p>
<h2 id="heading-writing-software">Writing Software</h2>
<blockquote>
<p><strong>Software Engineering, n.</strong><br />The practice or process of designing, developing, and maintaining software; the field of study concerned with this.<br /><em>Oxford English Dictionary, s.v. “software engineering, n.”, July 2023.</em> <a target="_blank" href="https://doi.org/10.1093/OED/2334731322"><em>https://doi.org/10.1093/OED/2334731322</em></a></p>
</blockquote>
<p>Anyone can write code. Even a toddler writes code these days with tools such as Scratch. But would it turn out to be like my trench? Let's draw some parallels to it.</p>
<h3 id="heading-tooling-1">Tooling</h3>
<p>You can write code in a simple text editor such as Notepad or an Integrated Development Environment (IDE) such as Visual Studio or VS Code. While Notepad would allow you to write code that would work, it would not help with syntax checking, IntelliSense, debugging and other advanced features provided by an IDE. Having the right tool aids productivity.</p>
<p>Also, tools such as ReSharper, Snyk and GitHub Co-pilot give the developer an edge. Similarly for other disciplines in Software Engineering, tools such as Azure DevOps, JIRA, Figma and Package Managers make all the difference.</p>
<h3 id="heading-skills-1">Skills</h3>
<p>Before I started to program professionally I used physical paperbacks such as "GW Basic - User's Guide and Reference", "Microsoft Visual Basic 5 - Step by Step" and other bulky books to learn programming in addition to what I learnt in high school.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691040012938/2846b19c-531c-4501-af05-f44bd81d9bbc.jpeg" alt="Head First Design Patterns book on top of another bulky book" class="image--center mx-auto" /></p>
<p>As I went into a professional setting in 2002, MSDN was a popular resource and Microsoft Certified Professional study courses led the way in learning new languages and skills. I was doing my degree in Computer Science while working and these resources helped me gain skills quickly.</p>
<p>Currently, I would recommend Pluralsight, Microsoft Learn, and specific Platform certifications for Developers to skill up in their areas of interest.</p>
<p>While a degree in Computer Science or a related field can really set you up to win in Software Engineering, I have seen several smart and enthusiastic people transition from other careers by following a good boot camp. So don't fret if you don't have a degree in CS, there are other ways to skill up.</p>
<p>I'm a firm believer in certifications. But certifications do not trump experience. Certifications are a way to keep up to date on technology and receive the minimum knowledge on that particular version of a platform or tool. Getting a certificate does not mean that you are an "expert".</p>
<h3 id="heading-experience-1">Experience</h3>
<p>An experienced developer with a few years under their belt works differently from someone who's new to the workforce. They know certain things inherently based on the years they have dedicated to a technology or a platform. For example, while I can read and understand PHP, I don't call myself an expert on the LAMP stack as I would do with ASP.NET on the Microsoft stack.</p>
<p>But the years of experience do not count if a developer does not upskill and keep up with the latest changes in the technology they specialise in. So experience and skills go hand in hand as you see in the graph below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691020550469/acae2fce-0986-4f59-96c9-5dd75935288e.png" alt="Graph showing Skill level vs Years of Experience" class="image--center mx-auto" /></p>
<p>The above graph shows how a developer who is learning continuously skills up compared to a developer who has stopped that continuous learning or only learns on the job (pertaining to tasks they are assigned).</p>
<h3 id="heading-efficiency-1">Efficiency</h3>
<p>Long-term efficiency is the name of the game. Remember you are running a marathon and not a sprint. So be efficient, but pace yourself.</p>
<p>One of my mentors (Hi Andy 👋) says, "Efficiency is how fast would you build a website/software for your girlfriend, wife or partner". It's a labour of love but done in the shortest possible time. You deliver a quality output but you are super efficient as you go about it. In the world of Agile, you would be super agile in this instance.</p>
<p>If you are efficient, that gives you the time and space to learn new things on the job as 70% of a developer's learning is on the job.</p>
<h3 id="heading-quality-1">Quality</h3>
<p>Quality is not only ticking some checkboxes to say that your output passes the listed acceptance criteria. The end product should be something you are proud of. It should be something you can show off to the world. It should do what the label promised and do it well.</p>
<p>Your end product should be functional, reliable, performant, secure, and maintainable. This would involve rigorous testing, continuous improvement, and adherence to industry best practices to keep that quality.</p>
<h3 id="heading-bringing-it-all-together">Bringing it all together</h3>
<p>Having the best tools in the trade is not going to make you better at Software Engineering. Nor are textbook skills. You need a combination of tools, skills and experience to produce quality output efficiently.</p>
<h2 id="heading-sensible-decisions">Sensible Decisions</h2>
<p>Do I have more tools in my garden shed? I do! But that doesn't make me a better trench digger. Similarly, having the best Software tools doesn't make me a better Software Engineer.</p>
<p>Have I gained skills in digging a trench? Yes, YouTube videos do count. But am I skilled in it to dig another trench? I would say No! Similarly, learning new skills as a Software Engineer doesn't make you an expert. But it does make you a better beginner.</p>
<p>Have I gained some experience in digging a trench? Definitely. But would I dig another trench? I don't think so. I will leave it to the professionals. Similarly, I would be more comfortable working with a tech stack I'm experienced in rather than another tech stack I have passing knowledge of.</p>
<p>You pay more for the right tooling, skills and experience. You go to the experts because you need a quality output that is produced efficiently. If you find the right experts or that brighter agency💡, you get it done right the first time.</p>
<p>So next time when you need to dig a trench or get some Software developed think of Tooling, Skills and Experience that deliver Quality Efficiently. You don't want to end up with an expensive auger and a half dug hole.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691036290111/0077db68-2aa5-499f-994b-efe4c4473e61.jpeg" alt class="image--center mx-auto" /></p>
<p><em>P.S. - I couldn't resist adding this recent video from my smart doorbell (which is the reason I dug a trench that became the inspiration for this article)</em></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/lLICea0b4cU">https://youtu.be/lLICea0b4cU</a></div>
]]></content:encoded></item><item><title><![CDATA[Happy Birthday, Luminary - H5YR!]]></title><description><![CDATA[Dear Luminary,
Happy 24th Birthday! I'm glad to be celebrating with you. 🥂
It's with great joy and excitement that I pen this letter to you. As I reflect on this 24th birthday milestone, I can't help but feel a deep sense of gratitude for the incred...]]></description><link>https://emmti.com/happy-birthday-luminary-h5yr</link><guid isPermaLink="true">https://emmti.com/happy-birthday-luminary-h5yr</guid><category><![CDATA[work culture]]></category><category><![CDATA[digital agency]]></category><category><![CDATA[Melbourne]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Sat, 01 Jul 2023 12:51:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688287532297/8f9339ce-e902-43a9-a2d8-0c6c024ba098.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dear Luminary,</p>
<p>Happy 24th Birthday! I'm glad to be celebrating with you. 🥂</p>
<p>It's with great joy and excitement that I pen this letter to you. As I reflect on this 24th birthday milestone, I can't help but feel a deep sense of gratitude for the incredible journey we've shared together.</p>
<p><a target="_blank" href="https://www.luminary.com">Luminary</a> is not just a great digital agency, it's a collection of awesome human beings doing great things together. You have been more than just a workplace to me; you have been a source of inspiration, growth, and fulfillment.</p>
<p>I joined Luminary eight years ago, with an eagerness to contribute my skills and make a meaningful impact in the software industry. Little did I know that this would mark the beginning of an incredible chapter in my career of 21 years. Over the past eight years, or approximately one-third of Luminary's lifetime, I have witnessed firsthand the remarkable growth and success of this organization.</p>
<p>I started as a Senior developer, eager to learn and make a difference. With each passing year, Luminary provided me with opportunities to step up and take on greater responsibilities. This allowed me to be promoted to the role of Technical Lead, and eventually, I reached the position of Technical Director. Luminary has not only recognized my potential but has also nurtured and supported my passions, enabling me to evolve both professionally and personally.</p>
<p>I am grateful for the owners of Luminary, <a target="_blank" href="https://www.luminary.com/marty">Marty Drill</a> (CEO and Founder), <a target="_blank" href="https://www.luminary.com/adam">Adam Griffith</a> (Managing Director), and <a target="_blank" href="https://www.luminary.com/andy">Andy Thompson</a> (CTO), who have created an environment that fosters innovation, collaboration, and growth. Their leadership and vision have been instrumental in shaping Luminary into the vibrant and dynamic organization it is today. I would also like to extend my heartfelt thanks to the leadership team, all our team members, both past and present, who have contributed their talents and expertise to make Luminary a powerhouse in the digital industry. Each individual brings a unique brilliance to our collective effort, and I am fortunate to work with such bright minds on a daily basis.</p>
<p>One of our proudest achievement this year was receiving the prestigious <a target="_blank" href="https://www.luminary.com/blog/unicef-and-luminary-take-out-top-gong-at-australian-web-awards">overall site of the year award in the Australian Web Awards</a> for the website we built for UNICEF Australia. This recognition is a testament to the tireless dedication and passion that each Luminary team member puts into their work. We strive to make a difference in the digital landscape and create experiences that leave a positive impact on people's (and especially children's) lives.</p>
<p>In the words of Confucius 孔子, "Choose a job you love, and you'll never have to work a day in your life." I can honestly say that I am yet to work a day at Luminary. The passion, enthusiasm, and commitment that permeate our workspace make it feel less like a job and more like a fulfilling journey. The challenging projects, the creative problem-solving, and the opportunity to shape the digital world all fuel my love for what I do.</p>
<p>However, as our MD Adam said, it's important to acknowledge that it isn't roses every day. We are all human, and we experience our fair share of ups and downs. Yet, it is during these moments of challenge that Luminary truly shines. We come together as a team, supporting and encouraging one another, finding solutions, and emerging stronger than before. This camaraderie and resilience make Luminary not just a workplace but a family.</p>
<p>Talking about Family, my wife Suzie and son Jordan are also a part of that Luminary family. They are known and have made connections over the years. As my colleague Sarah said, when the kids (who are teenagers now) meet up once or twice a year, it's like meeting your cousins who live far away. It's the small things which make Luminary great.</p>
<p>It's also that outward look which you foster as a BCorp - People, Planet and Profit makes you unique among your peers, Luminary! As Liam, our Engagement Director, said, with Luminary you don't experience the stress that hinders some other agencies.</p>
<p>As we celebrate your 24th birthday, Luminary, I am filled with gratitude for the incredible journey we have shared. You have provided me with a platform to learn, grow, and make a meaningful impact in the digital world. Here's to many more years of success, innovation, and brightening the digital landscape.</p>
<p>Happy Birthday, Luminary - H5YR! <em>(For the uninitiated, it means</em> <a target="_blank" href="https://community.umbraco.com/learn-about-the-community/h5yr/"><em>High Five You Rock!</em></a><em>)</em></p>
<p>With warmest regards,</p>
<p>Emmanuel</p>
]]></content:encoded></item><item><title><![CDATA[Content delivery API in Umbraco 12 RC 1 - Did I say Headless?]]></title><description><![CDATA[Umbraco 12 Release Candidate 1 was released yesterday, 17 May 2023. There were quite a few features in this release as described in the release notes. But what I was most excited about was the Content Delivery API.
As a member of the Umbraco Headless...]]></description><link>https://emmti.com/headless-content-delivery-api-in-umbraco-12-rc-1</link><guid isPermaLink="true">https://emmti.com/headless-content-delivery-api-in-umbraco-12-rc-1</guid><category><![CDATA[Umbraco]]></category><category><![CDATA[headless cms]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Thu, 18 May 2023 12:49:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684409554855/7d1202ed-5a0f-4696-acec-dfcc2580d226.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Umbraco 12 Release Candidate 1 was released yesterday, 17 May 2023. There were quite a few features in this release as described in the <a target="_blank" href="https://umbraco.com/blog/umbraco-12-release-candidate">release notes</a>. But what I was most excited about was the Content Delivery API.</p>
<p>As a member of the <a target="_blank" href="https://umbraco.com/blog/meet-the-headless-community-team/">Umbraco Headless Community Team</a>, I was aware of this and we as a team had made several suggestions on the direction of this feature.</p>
<h1 id="heading-documentation">Documentation</h1>
<p>The official <a target="_blank" href="https://docs.umbraco.com/umbraco-cms/v/12.latest/reference/content-delivery-api">Content Delivery API</a> documentation is pretty comprehensive and is a good place to start.</p>
<h1 id="heading-installing-12rc1-on-your-local-machine">Installing 12RC1 on your local machine</h1>
<p>If you want to try it out on your local machine, the <a target="_blank" href="https://psw.codeshare.co.uk/?InstallUmbracoTemplate=true&amp;UmbracoTemplateVersion=12.0.0-rc1&amp;Packages=&amp;UserEmail=admin%40example.com&amp;ProjectName=MyProject&amp;CreateSolutionFile=true&amp;SolutionName=MySolution&amp;UseUnattendedInstall=true&amp;DatabaseType=SQLite&amp;UserPassword=1234567890&amp;UserFriendlyName=Administrator&amp;IncludeStarterKit=true&amp;StarterKitPackage=Umbraco.TheStarterKit&amp;OnelinerOutput=false">Package Script Writer</a> by Paul Seal is an excellent resource. Within a few minutes, you will have Umbraco 12 RC 1 installed and ready to test out on your local machine.</p>
<h1 id="heading-installing-12rc1-on-an-azure-web-app">Installing 12RC1 on an Azure Web App</h1>
<p>But of course, I wanted to try this out on Azure and share it with the devs at Luminary. I've got this nifty script that I run on an Azure Cloud Shell. In around 20 minutes I get a fully functional Umbraco 12 RC 1 with the Content Delivery API enabled.</p>
<pre><code class="lang-bash">wget https://www.speedycmsinstall.com/scripts/azure/v12rc1/create-umbraco-v12rc1-demo.sh
chmod +x create-umbraco-v12rc1-demo.sh
./create-umbraco-v12rc1-demo.sh
</code></pre>
<p><em>You can fork the bash script from</em> <a target="_blank" href="https://github.com/emmanueltissera/speedycmsinstall/tree/main/scripts/azure/v12rc1"><em>GitHub</em></a> <em>and tinker with it for your own needs. If you need more details on this script, you can read all about it in</em> <a target="_blank" href="https://archive.24days.in/umbraco-cms/2021/scripting-azure-deployment/"><em>Scripting an Azure deployment of Umbraco 9</em></a><em>.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684409299455/2f1dc0c1-d2fc-4b04-b055-8c8395ca8130.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-starter-kit-installed">Starter Kit installed</h2>
<p>My bash script also installed the "Starter Kit" which gave me some content to play with while testing out the new Content Delivery API.</p>
<h1 id="heading-trying-out-the-content-delivery-api">Trying out the Content Delivery API</h1>
<p>I tried the most obvious choices for testing out the content delivery API. Note that the following URLs may not work in the future as this test environment is ephemeral.</p>
<h2 id="heading-retrieving-all-the-content">Retrieving all the content</h2>
<p>Retrieving all the content was easy enough.</p>
<pre><code class="lang-bash">curl --location <span class="hljs-string">"https://umbracov12rc1-demo-19175.azurewebsites.net/umbraco/delivery/api/v1/content"</span>
</code></pre>
<p>But it seemed I was missing something. I know that my name was included in this Starter Kit. But the Delivery API did not return that item to me.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"total"</span>: <span class="hljs-number">30</span>,
    <span class="hljs-attr">"items"</span>: [
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Home"</span>,
            <span class="hljs-attr">"route"</span>: {
                <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/"</span>,
                <span class="hljs-attr">"startItem"</span>: {
                    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
                    <span class="hljs-attr">"path"</span>: <span class="hljs-string">"home"</span>
                }
            },
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"home"</span>,
            <span class="hljs-attr">"properties"</span>: {
                <span class="hljs-attr">"heroHeader"</span>: <span class="hljs-string">"Umbraco Demo"</span>,
                <span class="hljs-comment">// removed for brevity</span>
            },
            <span class="hljs-attr">"cultures"</span>: {}
        },
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Products"</span>,
            <span class="hljs-comment">// removed for brevity</span>
        },
        <span class="hljs-comment">// removed for brevity</span>
        {
            <span class="hljs-comment">// item #10</span>
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Biker Jacket"</span>,
            <span class="hljs-comment">// removed for brevity</span>
        }
    ]
}
</code></pre>
<p>Since it said that there were 30 items in total, I tried the <code>take</code> query parameter as follows:</p>
<pre><code class="lang-bash">curl --location <span class="hljs-string">"https://umbracov12rc1-demo-19175.azurewebsites.net/umbraco/delivery/api/v1/content?take=30"</span>
</code></pre>
<p>Voila! I can now see 30 items and myself in the JSON results.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"total"</span>: <span class="hljs-number">30</span>,
    <span class="hljs-attr">"items"</span>: [
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Home"</span>,
            <span class="hljs-comment">// removed for brevity</span>
        },
        <span class="hljs-comment">// removed for brevity</span>
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"People"</span>,
            <span class="hljs-comment">// removed for brevity</span>
        },
        <span class="hljs-comment">// removed for brevity</span>
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Emmanuel Tissera"</span>,
            <span class="hljs-attr">"route"</span>: {
                <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people/emmanuel-tissera/"</span>,
                <span class="hljs-comment">// removed for brevity</span>
            },
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"6374bda9-034a-4109-a837-72ed6a325f11"</span>,
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"person"</span>,
            <span class="hljs-attr">"properties"</span>: {
                <span class="hljs-attr">"seoMetaDescription"</span>: <span class="hljs-string">""</span>,
                <span class="hljs-attr">"keywords"</span>: [],
                <span class="hljs-attr">"umbracoNavihide"</span>: <span class="hljs-literal">false</span>,
                <span class="hljs-attr">"photo"</span>: [
                    {
                        <span class="hljs-attr">"id"</span>: <span class="hljs-string">"77db56ab-2f79-4eb2-82fd-eb7266e6b366"</span>,
                        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Emmanuel Tissera"</span>,
                        <span class="hljs-attr">"mediaType"</span>: <span class="hljs-string">"Image"</span>,
                        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"/media/vvlnw31z/emmanuel_tissera.jpg"</span>,
                        <span class="hljs-attr">"extension"</span>: <span class="hljs-string">"jpg"</span>,
                        <span class="hljs-attr">"width"</span>: <span class="hljs-number">1600</span>,
                        <span class="hljs-attr">"height"</span>: <span class="hljs-number">1200</span>,
                        <span class="hljs-attr">"bytes"</span>: <span class="hljs-number">772875</span>,
                        <span class="hljs-attr">"properties"</span>: {},
                        <span class="hljs-attr">"focalPoint"</span>: <span class="hljs-literal">null</span>,
                        <span class="hljs-attr">"crops"</span>: []
                    }
                ],
                <span class="hljs-attr">"department"</span>: [
                    <span class="hljs-string">"Australia"</span>,
                    <span class="hljs-string">"mvp"</span>
                ],
                <span class="hljs-attr">"email"</span>: <span class="hljs-string">""</span>,
                <span class="hljs-attr">"twitterUsername"</span>: <span class="hljs-string">""</span>,
                <span class="hljs-attr">"facebookUsername"</span>: <span class="hljs-string">""</span>,
                <span class="hljs-attr">"linkedInUsername"</span>: <span class="hljs-string">""</span>,
                <span class="hljs-attr">"instagramUsername"</span>: <span class="hljs-string">""</span>
            },
            <span class="hljs-attr">"cultures"</span>: {}
        },
        <span class="hljs-comment">// removed for brevity</span>
        {
            <span class="hljs-comment">// Item #30</span>
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Contact"</span>,
            <span class="hljs-comment">// removed for brevity</span>
        }
    ]
}
</code></pre>
<p>⚠️ I believe the Content Delivery API is missing a pagination block to let consumers know how many records have been returned and how they should reach for the next set.</p>
<h2 id="heading-retrieve-a-particular-item-via-id">Retrieve a particular item via ID</h2>
<p>Request:</p>
<pre><code class="lang-bash">curl --location <span class="hljs-string">"https://umbracov12rc1-demo-19175.azurewebsites.net/umbraco/delivery/api/v1/content/item/6374bda9-034a-4109-a837-72ed6a325f11"</span>
</code></pre>
<p>Response:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Emmanuel Tissera"</span>,
    <span class="hljs-attr">"route"</span>: {
        <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people/emmanuel-tissera/"</span>,
        <span class="hljs-attr">"startItem"</span>: {
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
            <span class="hljs-attr">"path"</span>: <span class="hljs-string">"home"</span>
        }
    },
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"6374bda9-034a-4109-a837-72ed6a325f11"</span>,
    <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"person"</span>,
    <span class="hljs-attr">"properties"</span>: {
        <span class="hljs-attr">"seoMetaDescription"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"keywords"</span>: [],
        <span class="hljs-attr">"umbracoNavihide"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">"photo"</span>: [
            {
                <span class="hljs-attr">"id"</span>: <span class="hljs-string">"77db56ab-2f79-4eb2-82fd-eb7266e6b366"</span>,
                <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Emmanuel Tissera"</span>,
                <span class="hljs-attr">"mediaType"</span>: <span class="hljs-string">"Image"</span>,
                <span class="hljs-attr">"url"</span>: <span class="hljs-string">"/media/vvlnw31z/emmanuel_tissera.jpg"</span>,
                <span class="hljs-attr">"extension"</span>: <span class="hljs-string">"jpg"</span>,
                <span class="hljs-attr">"width"</span>: <span class="hljs-number">1600</span>,
                <span class="hljs-attr">"height"</span>: <span class="hljs-number">1200</span>,
                <span class="hljs-attr">"bytes"</span>: <span class="hljs-number">772875</span>,
                <span class="hljs-attr">"properties"</span>: {},
                <span class="hljs-attr">"focalPoint"</span>: <span class="hljs-literal">null</span>,
                <span class="hljs-attr">"crops"</span>: []
            }
        ],
        <span class="hljs-attr">"department"</span>: [
            <span class="hljs-string">"Australia"</span>,
            <span class="hljs-string">"mvp"</span>
        ],
        <span class="hljs-attr">"email"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"twitterUsername"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"facebookUsername"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"linkedInUsername"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"instagramUsername"</span>: <span class="hljs-string">""</span>
    },
    <span class="hljs-attr">"cultures"</span>: {}
}
</code></pre>
<p>🌟Worked as expected.</p>
<h2 id="heading-retrieve-a-particular-item-via-path">Retrieve a particular item via Path</h2>
<p>Request:</p>
<pre><code class="lang-bash">curl --location <span class="hljs-string">"https://umbracov12rc1-demo-19175.azurewebsites.net/umbraco/delivery/api/v1/content/item/people/emmanuel-tissera"</span>
</code></pre>
<p>Response:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Emmanuel Tissera"</span>,
    <span class="hljs-attr">"route"</span>: {
        <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people/emmanuel-tissera/"</span>,
        <span class="hljs-attr">"startItem"</span>: {
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
            <span class="hljs-attr">"path"</span>: <span class="hljs-string">"home"</span>
        }
    },
    <span class="hljs-comment">// removed for brevity - same as above</span>
}
</code></pre>
<p>💪Worked as expected and I just needed to only know the path of the content item I was hoping to retrieve.</p>
<h2 id="heading-retrieve-draftpreview-content">Retrieve draft/preview content</h2>
<p>There are times when you need to preview draft content and the Content Delivery API does a great job with this.</p>
<pre><code class="lang-bash">curl --location <span class="hljs-string">"https://umbracov12rc1-demo-19175.azurewebsites.net//umbraco/delivery/api/v1/content/item/6374bda9-034a-4109-a837-72ed6a325f11"</span> --header <span class="hljs-string">"Preview: true"</span> --header <span class="hljs-string">"Api-Key: my-secret"</span>
</code></pre>
<p>Response:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Emm Tissera"</span>,
    <span class="hljs-attr">"route"</span>: {
        <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people/emmanuel-tissera/"</span>,
        <span class="hljs-attr">"startItem"</span>: {
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
            <span class="hljs-attr">"path"</span>: <span class="hljs-string">"home"</span>
        }
    },
    <span class="hljs-comment">// removed for brevity</span>
    <span class="hljs-attr">"properties"</span>: {
        <span class="hljs-comment">// removed for brevity</span>
        <span class="hljs-attr">"email"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"twitterUsername"</span>: <span class="hljs-string">"dAmazingNut"</span>,
        <span class="hljs-attr">"facebookUsername"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"linkedInUsername"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"instagramUsername"</span>: <span class="hljs-string">""</span>
    },
    <span class="hljs-attr">"cultures"</span>: {}
}
</code></pre>
<p>👀As you can see there is preview content where I have changed the name of the content item and added a <code>twitterUsername</code>. These changes are not visible on the public API.</p>
<h2 id="heading-adding-a-new-document-type">Adding a new Document Type</h2>
<p>To just test how easy this was, I added a new Document Type called <code>Pet</code>. I populated the details of my two dogs and made this request.</p>
<pre><code class="lang-bash">curl --location <span class="hljs-string">"https://umbracov12rc1-demo-19175.azurewebsites.net//umbraco/delivery/api/v1/content/?filter=contentType:pet"</span>
</code></pre>
<p>Without writing a single line of code in the CMS back office, I get this JSON response.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"total"</span>: <span class="hljs-number">2</span>,
    <span class="hljs-attr">"items"</span>: [
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Shadow"</span>,
            <span class="hljs-attr">"route"</span>: {
                <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people/shadow/"</span>,
                <span class="hljs-attr">"startItem"</span>: {
                    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
                    <span class="hljs-attr">"path"</span>: <span class="hljs-string">"home"</span>
                }
            },
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"2b9116f6-a45f-404d-8ef2-64900148eca9"</span>,
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"pet"</span>,
            <span class="hljs-attr">"properties"</span>: {
                <span class="hljs-attr">"petName"</span>: <span class="hljs-string">"Shadow"</span>,
                <span class="hljs-attr">"breed"</span>: <span class="hljs-string">"Staffie"</span>,
                <span class="hljs-attr">"isCuddley"</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-attr">"dateOfBirth"</span>: <span class="hljs-string">"2016-10-11T00:00:00Z"</span>
            },
            <span class="hljs-attr">"cultures"</span>: {}
        },
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Athena"</span>,
            <span class="hljs-attr">"route"</span>: {
                <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people/athena/"</span>,
                <span class="hljs-attr">"startItem"</span>: {
                    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"ca4249ed-2b23-4337-b522-63cabe5587d1"</span>,
                    <span class="hljs-attr">"path"</span>: <span class="hljs-string">"home"</span>
                }
            },
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"64bf848f-06f3-4856-b730-84d4dbbeab08"</span>,
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"pet"</span>,
            <span class="hljs-attr">"properties"</span>: {
                <span class="hljs-attr">"petName"</span>: <span class="hljs-string">"Athena"</span>,
                <span class="hljs-attr">"breed"</span>: <span class="hljs-string">"Staffie"</span>,
                <span class="hljs-attr">"isCuddley"</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-attr">"dateOfBirth"</span>: <span class="hljs-string">"2019-02-11T00:00:00Z"</span>
            },
            <span class="hljs-attr">"cultures"</span>: {}
        }
    ]
}
</code></pre>
<h1 id="heading-the-verdict">The Verdict</h1>
<p>That was a quick whirlwind tour of the new Umbraco Content Delivery API which is a part of the core offering and I'm loving it. I need to explore the localisation features and see if it passes my <a target="_blank" href="https://emmti.com/gilt-or-guilt-localisation-crimes">GILT or GUILT – Localisation crimes</a> test.</p>
<p>I couldn't see the swagger documentation on Azure and that's something I will explore further.</p>
<p>The team working on the Content Delivery API have done a great job in the first release candidate and I'm hoping they will fix the <a target="_blank" href="https://docs.umbraco.com/umbraco-cms/v/12.latest/reference/content-delivery-api#current-limitations">current limitations</a> they have listed to make this product shine.</p>
<h2 id="heading-provide-your-own-feedback">Provide your own feedback</h2>
<p>If you find issues that are not already <a target="_blank" href="https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aopen+is%3Aissue+label%3Aaffected%2Fv12">reported</a>, please report them on the <a target="_blank" href="https://github.com/umbraco/Umbraco-CMS/issues/new?assignees=&amp;labels=type%2Fbug&amp;template=01_bug_report.yml&amp;title=v12%20RC1">GitHub tracker</a> by following the link or selecting “🐛 Bug Report” when creating a new issue. That reminds me that I should file a suggestion for that pagination issue I encountered.</p>
<h1 id="heading-public-release">Public release</h1>
<p>I'm looking forward to the public release of Umbraco 12, which is targeted for June 29, 2023.</p>
<h1 id="heading-the-possibilities">The Possibilities</h1>
<p>With the Content Delivery API in place, a developer could create new Document types and have Content Authors editing adding and updating content even before the front-end has been built.</p>
<p>This allows for a composable architecture where the content is just another API. With the Content Delivery API in place, Umbraco has placed itself in a strong position in that hybrid headless space.</p>
]]></content:encoded></item><item><title><![CDATA[Tiny Elf - a Slack bot with Google sheets and Typescript]]></title><description><![CDATA[Tiny Elf is a Slack Bot driven by a Google Spreadsheet. It rosters team members as hosts to a recurring meeting on a round-robin basis after checking their availability on their Google calendars. When rostered, Tiny Elf sends out reminders to a commo...]]></description><link>https://emmti.com/tiny-elf-a-slack-bot-with-google-sheets-and-typescript</link><guid isPermaLink="true">https://emmti.com/tiny-elf-a-slack-bot-with-google-sheets-and-typescript</guid><category><![CDATA[slack]]></category><category><![CDATA[google sheets]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[automation]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Sun, 30 Oct 2022 21:27:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667165247363/zzN2qJSh6.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Tiny Elf is a Slack Bot driven by a Google Spreadsheet. It rosters team members as hosts to a recurring meeting on a round-robin basis after checking their availability on their Google calendars. When rostered, Tiny Elf sends out reminders to a common Slack channel tagging the rostered team member. It rotates team members based on the last time they were rostered and keeps track of it on the Google spreadsheet.</p>
<h1 id="heading-setup-in-five-steps">Setup in five steps</h1>
<p>Your Tiny Elf bot can be set up in five easy steps.</p>
<ol>
<li>Make your copy of the <a target="_blank" href="https://docs.google.com/spreadsheets/d/1DD5DTImXiwTc_KZrLlKt6f5tGkJ5xfnFW_THF-jI6XY/">Tiny Elf spreadsheet</a></li>
<li>Change the team data</li>
<li>Change the settings</li>
<li>Authorise the script</li>
<li>Set the trigger</li>
</ol>
<p>You are done 🎉 </p>
<p>Now, wait for the next team member to be rostered 🔔</p>
<h1 id="heading-features">Features</h1>
<p>Tiny Elf comes with a few nifty features.</p>
<ul>
<li>The sheet owner can change settings directly from the <code>settings</code> sheet. No need to touch the Google Apps Script files.</li>
<li>The bot can check if the event exists before sending out notifications. This helps with skipping notifications for meetings that are not held on a daily/weekly basis.</li>
<li>When rostering team members, the bot checks if they are available; have accepted the event invitation; they are available and have accepted the invite. It can also skip the check. </li>
<li>You can set and reset your automated trigger specifying when you want it to run.</li>
<li>Allows you to send a notification up to 7 days ahead or on the date of the meeting.</li>
<li>Allows you to specify custom messages to be sent via Slack with dynamic replacements for certain values.</li>
</ul>
<h1 id="heading-open-source">Open-source</h1>
<p>The Tiny Elf source code is available on <a target="_blank" href="https://github.com/emmanueltissera/tinyelf">GitHub</a> with an MIT license. It can be extended and customised to suit your unique requirements. Bug fixes or enhancements that benefit others are welcome. Please submit your pull requests. 😊</p>
<h1 id="heading-the-nitty-gritty-in-the-spreadsheet">The nitty gritty in the spreadsheet</h1>
<p>Tiny Elf runs off a Spreadsheet which has team data and all the settings. The <a target="_blank" href="https://docs.google.com/spreadsheets/d/1DD5DTImXiwTc_KZrLlKt6f5tGkJ5xfnFW_THF-jI6XY/">Tiny Elf spreadsheet</a> is shared publicly and a copy can be made for your use.</p>
<h2 id="heading-the-team-sheet">The Team sheet</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667158102896/qEPGKRu6w.png" alt="Spreadsheet with six columns and sample data for 10 team members" /></p>
<p>The Team sheet contains data that is spread across the following six columns.</p>
<ul>
<li>Name</li>
<li>Email address</li>
<li>Slack member ID</li>
<li>Enabled</li>
<li>Roster for days</li>
<li>Last host date</li>
</ul>
<h2 id="heading-the-settings-sheet">The Settings sheet</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667158200691/lgoxTdzVj.png" alt="Spreadsheet with various settings to configure Tiny Elf" /></p>
<p>The Settings sheet contains the following settings and each setting is explained in detail.</p>
<h3 id="heading-calendar-event-details">Calendar event details</h3>
<ul>
<li>Event title </li>
<li>Event start hour</li>
<li>Event start minute</li>
<li>Event end hour</li>
<li>Event end minute</li>
<li>Check event exists</li>
<li>Roster team members when</li>
</ul>
<h3 id="heading-automated-daily-reminder-trigger-settings">Automated daily reminder trigger settings</h3>
<ul>
<li>Trigger hour</li>
<li>Trigger minute</li>
<li>Trigger days before</li>
<li>Trigger on days</li>
</ul>
<h3 id="heading-slack-message-settings">Slack message settings</h3>
<ul>
<li>Slack Webhook URL</li>
<li>Team member notification summary</li>
<li>Team member notification body</li>
<li>Team member notification footer</li>
<li>Busy notification summary</li>
<li>Busy notification</li>
</ul>
<h2 id="heading-the-instructions-and-license-and-distribution-sheets">The 'Instructions' and 'License and distribution'  sheets</h2>
<p>These two sheets contain enough details for an end-user to use Tiny Elf without looking at the source code.</p>
<h1 id="heading-some-real-life-scenarios">Some real-life scenarios</h1>
<p>Tiny Elf is already in use across three teams and here are some ways they use it. Note that each team runs their unique copy of the Tiny Elf Spreadsheet but uses the same code. The data and settings are unique to each scenario.</p>
<p>The settings for each scenario are mentioned here as examples/inspiration. <em>Note that only significant changes are mentioned here for brevity.</em></p>
<h2 id="heading-weekday-meetings-with-a-friday-special">Weekday meetings with a Friday special</h2>
<p>Here is how the first team who uses Tiny Elf has configured it. The majority of their team attends meetings daily and some of them only on Fridays.</p>
<p><strong>The setup</strong></p>
<p><strong>Roster for days</strong> - <code>Mon, Tue, Wed, Thu, Fri</code> is set for those who attend daily and <code>Fri</code> for those who attend only on Fridays.</p>
<p><strong>Trigger days before</strong> is set as <code>0</code> since this notification goes out each weekday.</p>
<p><strong>Trigger on days</strong> is set as <code>Mon, Tue, Wed, Thu, Fri</code> so that it runs every weekday.</p>
<p><strong>Team member notification body</strong>  is set as </p>
<pre><code class="lang-plaintext">Hi $[slackHandle] :wave:
You are rostered to host *Hello World* today. Here 
are &lt;link to our wiki|instructions&gt; for you to host it!
Give us a shout here if you can't make it.
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667159039707/Sm9JsR6dd.png" alt="Tiny Elf Message sent to a team member" /></p>
<h2 id="heading-monday-to-thursday-meetings">Monday to Thursday meetings</h2>
<p>The second team who uses Tiny Elf has configured it to run only from Monday to Thursday. </p>
<p><strong>The setup</strong></p>
<p><strong>Roster for days</strong> is set as <code>Mon, Tue, Wed, Thu</code> for all team members. </p>
<p><strong>Trigger days before</strong> is set as <code>0</code> since this notification goes out on the day the script is triggered.</p>
<p><strong>Trigger on days</strong> is set as <code>Mon, Tue, Wed, Thu</code> so that it runs every weekday excluding Friday.</p>
<p><strong>Team member notification body</strong>  is set as </p>
<pre><code class="lang-plaintext">Hi $[slackHandle] :wave:
You are rostered to host *FED standup* today. You can lead with:
• What we did yesterday
• What we’re doing today
• Does anyone want to bring anything up
Here are some &lt;link to our wiki|tips&gt; for you to host it! 
Give us a shout here if you can't make it.
</code></pre>
<p><em>Note that you can use the Unicode character • (hex 0x2022) instead of markdown as the <a target="_blank" href="https://api.slack.com/reference/surfaces/formatting#block-formatting">Slack documentation</a> suggests.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667159777484/SLnO75fYq.png" alt="Tiny Elf Message sent to a team member" /></p>
<h2 id="heading-meet-every-other-friday-notify-on-wednesday">Meet every other Friday - Notify on Wednesday</h2>
<p>The third team has a meeting every other Friday and wanted the notification for the host to go out two days in advance.</p>
<p><strong>The setup</strong></p>
<p><strong>Roster for days</strong> is set as <code>Fri</code> for all Team members. </p>
<p><strong>Enabled</strong> is set as <code>No</code> for one team member as they were on extended leave. This prevented Tiny Elf from picking them as the host.</p>
<p><strong>Check event exists</strong> is set as <code>Yes</code>. This checks to see if the event exists on the calendar before the script is run.</p>
<p><strong>Trigger days before</strong> is set as <code>2</code> since this notification goes out on Wednesday.</p>
<p><strong>Trigger on days</strong> is set as <code>Wed</code> so that it runs every Wednesday only. But as it checks for the calendar event, notifications are sent out only every other week.</p>
<h2 id="heading-slack-message-with-instructions-on-your-intranet">Slack message with instructions on your intranet</h2>
<p>The Slack notification can contain links to your intranet or company wiki. Use the Slack format of <code>&lt;https://intranet-address.com/page|text description of link&gt;</code> to include links in your notification.</p>
<h2 id="heading-notify-on-a-private-channel">Notify on a private channel</h2>
<p>The notifications can be sent to a private Slack channel. The only requirement is that the person setting up the incoming webhook has access to that private channel. Once the webhook URL is configured, Tiny Elf works the same as it would on a public channel.</p>
<h1 id="heading-history-and-how-tiny-elf-came-to-be">History and how Tiny Elf came to be</h1>
<p>We have got a team of 15+ developers who meet every Monday to Thursday and this increases to 20+ developers every Friday. We wanted to give each team member a chance to host the meeting to take away a central authority and give each individual an opportunity to grow by chairing and running a meeting.</p>
<p>But this was easier said than done since our virtual meeting software (Zoom and Google Meet) didn't natively support rostering. So it became a manual task to keep track of who was rostered and who was next. Going alphabetically by name didn't help since some team members could not to attend all meetings due to other commitments or meetings at the same time.</p>
<p>Hmmm... What if I could automate this process and give the assigned host a heads-up?</p>
<h2 id="heading-automation-version-1">Automation - version 1</h2>
<p>As I was already maintaining the list of hosts in a Google spreadsheet, I wrote some JavaScript on the Google Script Platform to perform some automation. The code essentially checked each team member's Google calendar for any overlapping meetings and if there were none, it sent out a notification on a shared Slack channel asking them to be the host. It also recorded the last date they hosted the meeting so that it could rotate them after all the others have had a go at hosting the meeting. It also incorporated some smarts to assign certain team members only on a Friday.</p>
<h2 id="heading-automation-version-11">Automation - version 1.1</h2>
<p>This automation was working so well that a second team requested this bot to help them assign hosts from Monday to Thursday. That was easy enough, but I had to make changes to the source code as almost everything was hard coded. This included the meeting time, title and message templates.</p>
<h2 id="heading-automation-version-12">Automation - version 1.2?</h2>
<p>Tiny Elf got enough notice that a third team now requested the bot for their meeting. But that meeting was held every other Friday. Also, they wanted notifications to go out on a Wednesday.</p>
<p>Another copy? Another go at changing the source code?</p>
<h1 id="heading-typescript-to-the-rescue">TypeScript to the rescue</h1>
<p>Luckily, I had started <a target="_blank" href="https://emmti.com/starting-out-with-typescript">learning TypeScript</a> and this was a project I was converting from vanilla JavaScript to TypeScript.</p>
<h1 id="heading-next-steps">Next steps</h1>
<p>I have already <a target="_blank" href="https://github.com/emmanueltissera/tinyelf/issues/1">opened an issue</a> for myself to automate integration testing as it does take time when I have to do it manually.</p>
<p>When reviewing the draft of this post, my wife asked if messages could be sent as an SMS or a WhatsApp message. Tiny Elf wouldn't be a Slack bot then - but it's something worth thinking about.</p>
<h1 id="heading-resources">Resources</h1>
<p>As I was learning TypeScript and grappling with Google Scripts, these resources were of immense help and I have borrowed some code/direction from them.</p>
<ul>
<li><a target="_blank" href="https://www.august.com.au/blog/how-to-send-slack-alerts-from-google-sheets-apps-script/">How to send Slack alerts from Google Sheets / Google Apps Script</a> by Rowan Barnes</li>
<li><a target="_blank" href="https://github.com/iansan5653/gas-ts-template">Google Apps &amp; TypeScript Project Template</a> by Ian Sanders</li>
<li><a target="_blank" href="https://bobbyhadz.com/?q=typescript">TypeScript tips</a> from Borislav Hadzhiev</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[C# String extensions to TypeScript prototype extensions]]></title><description><![CDATA[Microsoft C# extension methods is a great way to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. 
My requirements
I needed two extension methods to do the following:

A metho...]]></description><link>https://emmti.com/c-string-extensions-to-typescript-prototype-extensions</link><guid isPermaLink="true">https://emmti.com/c-string-extensions-to-typescript-prototype-extensions</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[C#]]></category><category><![CDATA[extensions]]></category><category><![CDATA[prototype]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Thu, 13 Oct 2022 12:00:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Nw8bwn1Kd6o/upload/v1665661984045/R3xfZf_HS.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Microsoft <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods">C# extension methods</a> is a great way to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. </p>
<h1 id="heading-my-requirements">My requirements</h1>
<p>I needed two extension methods to do the following:</p>
<ul>
<li>A method to convert "Yes"/"No" to it's equivalent boolean value</li>
<li>A method to convert a date string to a Date object (or the minimum Date if it's a blank or malformed date string)</li>
</ul>
<h1 id="heading-my-c-class">My C# class</h1>
<p>So I wrote my C# extension methods as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">StringExtensions</span> {
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">ToBool</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> <span class="hljs-keyword">string</span> <span class="hljs-keyword">value</span></span>)</span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">value</span>.ToLower() == <span class="hljs-string">"yes"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> DateTime <span class="hljs-title">ToDate</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> <span class="hljs-keyword">string</span> <span class="hljs-keyword">value</span></span>)</span>{
        <span class="hljs-keyword">return</span> DateTime.TryParse(<span class="hljs-keyword">value</span>, <span class="hljs-keyword">out</span> DateTime result) 
                    ? result : DateTime.MinValue;
    }    
}
</code></pre>
<p>You can see a working <a target="_blank" href="https://dotnetfiddle.net/JDPqpZ">.NET Fiddle</a> below:</p>
<iframe width="100%" height="475" src="https://dotnetfiddle.net/Widget/9g5Akq"></iframe>


<p><span></span></p>
<h1 id="heading-equivalent-typescript-extensions">Equivalent TypeScript extensions</h1>
<p>So I wrote the equivalent extensions in the <a target="_blank" href="https://www.typescriptlang.org/play?#code/MoFwTglgdg5gdABzAexKgnggpnNAhZZAGywEMoACAXgoDMBXKAYxAmUoAoBKCgbwFgAUBREUwWEPTCUQACwgBnXMgAyyAO5YwAYVIKs3alRoAidFgUmA3EIC+NwUNCRYiFGhCYcaACKkQWNR0jCxsnDwCwqLiktIUcooUAISmJhQA-BRQWOoUfgEc+TgIpGD6HAlKaM7QMNxcPABcWTl5-gYmAAwAjAC0AFLkvQBMnT0mXA62QkJFbqgY2Mp+6ACy7HIAmmRgQQzMrOwUhpGiYhJSMvJVqshMpCRFNbAcAORYUL0A4nivADR8IRnM4AE1I6Garyg9AAtloIEx-hQYRtZJCFLJkGAQEjzKVIdC4ZBEUDgSJbFw4OIEERSEwDAB6CgMmAA169V6TOwzQRMdgKYg4IjIOoAA14AB0TNsFFK4AAVZAEYjcWwUYCY+hEEHnWIyMD0HAUACCLHoDwoADcHobmgASXjSiwmZTKkjkVWirm8-mCuDCsWSkwAOWQcsVbtV6s12t1lzoD30cBNZot1qItooDpDyBd+EI7qgnu9fKgApI-pFHHFUu6w16AHkWCNOsNhuHkEUoxrkFqdTF43XG83Rm3k6bJGmbVh7Y6h02QC223nO+1uOlluD1lAtjtiw5S+WhVWayYO12uGqe3243E+oNPqMeuPU0QrdPZyYVxeN74t6jtlKfcgA">TypeScript playground</a></p>
<pre><code class="lang-javascript">...
String.prototype.toBoolean = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.toLowerCase() === <span class="hljs-string">"yes"</span>;
};

<span class="hljs-built_in">String</span>.prototype.toDate = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span> != <span class="hljs-string">""</span> ? <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.parse(<span class="hljs-built_in">this</span>.toString())) : 
        <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-string">"01-Jan-2001"</span>);
}
...
</code></pre>
<p>Interesting, this code gives a compile time error. But still runs 😱</p>
<blockquote>
<p>Property 'toBoolean' does not exist on type 'String'.</p>
<p>Property 'toDate' does not exist on type 'String'.</p>
</blockquote>
<h1 id="heading-how-did-i-fix-it">How did I fix it?</h1>
<p>I needed to declare an interface for <code>String</code> first as you can see in my <a target="_blank" href="https://www.typescriptlang.org/play?ssl=17&amp;ssc=1&amp;pln=10&amp;pc=1#code/JYOwLgpgTgZghgYwgAgMpiqA5sg3gWAChkTkwB7AIXPIBsI4QAKASgC5kAjG+xgbiKky5ACJxIrDmMjIAPsgCuIACYQYoCMoGEAvkSKhIsRCmkoCxUhTEBPALLlwACwCaDKJOQBnDNm17CInRMECwAOgAHKHIKMBsIiDCKajoGEGQAXmQYJQQwYEdkVjxBUigIMAUodLAnYC8k8gAZcgB3aABhOC8IYoz+5AAiGwgvQf9tIN9QyOjY+MTrcRQsnJA8gvTiiyFyyuqyOq9kAEIswcHkAH5kEAhW5DMmM0i4KB6mWvrG4OxWFhYyA4dweT0GAAYAIwAWgAUoxoQAmcFQwYsfz6QgvKIxGILRq2BzONxvTLZXL5QrbUokPZVGpHRotBBwehmX6hJgAcggIGhAHFKFyADQlSxCEjKOA2DhckAKAC20GACBFyAVjlqsq8TnIUDAapGb1l8qVmFVNIlOhYYXKEVoJiYAHpkE6sKKudCueiiAEiAhHF5UmFaOQsEwAAa4AA6gzcXljYQAKlQeKwdGhdQpaMpkHSDhgFIlkABBPIKVnIABurKLHAAJLg46NBo0UrxmCwdBGfYQAyAg-QQ2HIzHBgA5ciJlPt9OZ8jZ3P59LwWg9MKl8uVmu0OvIRsT8it5I8NLpnvafuDxKh8NR2OQxHQgDyeSR4MRiOnomWc9QWZzPMKnpZBHxfN9kU-Dcy0qbdawgBsmzA18wHfT9jx-CQWCuAlpSJWoSQ8LsL39QNg1vUdY2-J4u3nRcgP2dIYXhPlkShaCt1oat4MQi5cKwnClnsTVXHcc90SAA">playground script</a> and the code below:</p>
<pre><code class="lang-javascript">interface <span class="hljs-built_in">String</span> {
    toBoolean(): boolean;
    toDate(): <span class="hljs-built_in">Date</span> | <span class="hljs-literal">undefined</span>;
}

<span class="hljs-built_in">String</span>.prototype.toBoolean = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.toLowerCase() === <span class="hljs-string">"yes"</span>;
};

<span class="hljs-built_in">String</span>.prototype.toDate = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span> != <span class="hljs-string">""</span> ? <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.parse(<span class="hljs-built_in">this</span>.toString())) : 
        <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-string">"01-Jan-2001"</span>);
}
...
</code></pre>
<p>This TypeScript code compiled and ran without any issues. Note that when you do this in your local environment, add the <code>interface</code> code to a <code>String.d.ts</code> file to keep it neat.</p>
<p>This <a target="_blank" href="https://stackoverflow.com/a/53392268">SO Answer</a> helped me to write the code. </p>
<h1 id="heading-why-did-it-work">Why did it work?</h1>
<p>This <a target="_blank" href="https://stackoverflow.com/a/39877446">SO Answer</a> gives the explanation as to why adding an interface works:</p>
<blockquote>
<p>Declaring the new member so it can pass type-checking. You need to declare an interface with the same name as the constructor/class you want to modify and put it under the correct declared namespace/module. This is called scope augmentation.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[C# List<T> Class to TypeScript extends Array<T>]]></title><description><![CDATA[After programming in C# for almost 19 years, I've decided to try my hand at TypeScript. Still thinking Object Oriented Programming concepts and C#, I've run into a few doozies I thought I'd document.
I tried extending my custom class from a List<T> w...]]></description><link>https://emmti.com/c-listt-class-to-typescript-extends-arrayt</link><guid isPermaLink="true">https://emmti.com/c-listt-class-to-typescript-extends-arrayt</guid><category><![CDATA[noob]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[C#]]></category><category><![CDATA[runtime]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Wed, 12 Oct 2022 09:51:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568232744/aOm1Ccouq.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After programming in C# for almost 19 years, I've decided to try my hand at TypeScript. Still thinking Object Oriented Programming concepts and C#, I've run into a few doozies I thought I'd document.</p>
<p>I tried extending my custom class from a <code>List&lt;T&gt;</code> with <code>T</code> being a custom class.</p>
<h2 id="heading-my-c-class">My C# Class</h2>
<p>Below is my <code>Team</code> class in C#. At runtime, I expect to get a count of 2 for the number of enabled team members with my sample dataset.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Team</span>: <span class="hljs-title">List</span>&lt;<span class="hljs-title">TeamMember</span>&gt; {

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Team</span>(<span class="hljs-params">List&lt;List&lt;<span class="hljs-keyword">string</span>&gt;&gt; rows</span>)</span> {
        <span class="hljs-keyword">this</span>.AddRange(rows.Where(x =&gt; !<span class="hljs-keyword">string</span>.IsNullOrEmpty(x[<span class="hljs-number">0</span>]))
          .Select(x =&gt; <span class="hljs-keyword">new</span> TeamMember(x)));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;TeamMember&gt; <span class="hljs-title">getEnabledUsers</span>(<span class="hljs-params"></span>)</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.FindAll(member =&gt; member.enabled);
    }
}
</code></pre>
<p>You can see a complete working sample in <a target="_blank" href="https://dotnetfiddle.net/cRFaD8">this fiddle</a>.</p>
<iframe width="100%" height="400" src="https://dotnetfiddle.net/Widget?Languages=CSharp&amp;CSharp_FiddleId=uCZE2u"></iframe>


<h2 id="heading-typescript-with-a-runtime-error">TypeScript with a runtime error</h2>
<p>I converted that C# class to a TypeScript class. This <code>Team</code> class compiles without any issues. But if you run it on <a target="_blank" href="https://www.typescriptlang.org/play?#code/MYGwhgzhAEAqCmYC2BZeSBG8BO0DeAsAFDSnQB2y8AXNBAC7YCW5A5gNzFnTyUYjwAJrQwB7UQLDlORLmWCjyDbAFdg9UdgAU2UQHcAamBAr4EWspasA2gF0AlPjndS9ABZMIAOkpJ40AF5oXUNjUwhrAAZbGRcyd08vXjB+IUDg-SMTM2sARlsvDQAZfRwAYUh4LUcA2ugAIgBPM3rYsgBfYk7ZIlBIGARkHgAPel5BGABBbGwwRoAeQdR0LGwAPice7gUlRjUNbRDzaGnZhdO5+cs2NbXHQhI4uhUABxxqtrijrwAzJhAxod9IENiEorZoABCIL1er2ZxPUheJBgF46YEBDYJbwvFQQNxacjwPRwRDLTDvEL2amfaDdBGseD0ACifAEggAqhAcBBqtBaBcFks0BT1ptEQB6CVwAASAEkAMrQADqAHkAEoAaSVAFpgkyVNhyNBVRgAFbwdReABuYTMWmx9l+-0BWj8opB0HdqySbKE9lp3GwBqN0GxzoB729OE90ewvpS7IDCO69JIAno0EEYHoYHS1ms9WV-wginqABoGgBNFq2csIxHcQsKxTkRoV6u1+uPRsuQuTFSCYPtyv1AByonqdYbveghY7sNsMWIGbDZNy6SJJKWWmzueTPVXY2QACZ0sekLkvIyWX7OdzsLyD8QdqWBF4QKJWFp6tYLyeP14Vh3AhBU3FEFQQEEfV6ENY0AJOdQVGMaBbWyWh6mgABqNdT0Ath3ADIA">TypeScript 
 playground</a>, you will see a runtime error.</p>
<pre><code class="lang-javascript">...
class Team <span class="hljs-keyword">extends</span> <span class="hljs-built_in">Array</span>&lt;TeamMember&gt; {

    <span class="hljs-keyword">constructor</span>(rows: Array&lt;Array&lt;string&gt;&gt;) {
        <span class="hljs-built_in">super</span>();
        rows.filter(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row[<span class="hljs-number">0</span>] != <span class="hljs-string">""</span>)
            .map(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> <span class="hljs-built_in">this</span>.push(<span class="hljs-keyword">new</span> TeamMember(row)));
    }

    getEnabledUsers() : <span class="hljs-built_in">Array</span>&lt;TeamMember&gt; {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.filter(<span class="hljs-function"><span class="hljs-params">member</span> =&gt;</span> member.enabled);
    }
}
...
</code></pre>
<h3 id="heading-misleading-runtime-error">Misleading runtime error</h3>
<p>The error on the playground or even if you run it locally says:</p>
<blockquote>
<p>[ERR]: "Executed JavaScript Failed:" </p>
<p>[ERR]: rows.filter is not a function </p>
</blockquote>
<p>So that means that I have done something wrong in the constructor where <code>rows.filter</code> is. Trying to debug what happens in the constructor made me waste a few hours on a Sunday.</p>
<p>But, if I take away the <code>getEnabledUsers()</code> method, this error goes away. 🤯</p>
<p>Reading through multiple SO questions and blogs etc finally led me to <code>Object.values</code>. The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values">Object.values()</a> method returns an array of a given object's own enumerable property values, in the same order as that provided by a for...in loop.</p>
<h2 id="heading-typescript-which-works">TypeScript which works</h2>
<p><code>Object.values</code> allowed me to reference <code>this</code> as an array in my <code>Team</code> class. So I changed <code>return this.filter(member =&gt; member.enabled);</code> to <code>return Object.values(this).filter(member =&gt; member.enabled);</code>. This made my code work at Runtime and gave me the necessary output as seen in this <a target="_blank" href="https://www.typescriptlang.org/play?#code/MYGwhgzhAEAqCmYC2BZeSBG8BO0DeAsAFDSnQB2y8AXNBAC7YCW5A5gNzFnTyUYjwAJrQwB7UQLDlORLmWCjyDbAFdg9UdgAU2UQHcAamBAr4EWspasA2gF0AlPjndS9ABZMIAOkpJ40AF5oXUNjUwhrAAZbGRcyd08vXjB+IUDg-SMTM2sARlsvDQAZfRwAYUh4LUcA2ugAIgBPM3rYsgBfYk7ZIlBIGARkHgAPel5BGABBbGwwRoAeQdR0LGwAPice7gUlRjUNbRDzaGnZhdO5+cs2NbXHQhI4uhUABxxqtrijrwAzJhAxod9IENiEorZoABCIL1er2ZxPUheJBgF46YEBDYJbwvFQQNxacjwPRwRDLTDvEL2amfaDdBGseD0ACifAEggAqhAcBBqtBaBcFks0BT1ptEdgmSpsORoAB5DAAK3g6i8ADcwmYtNj7L9-oCtH5RSDoEbVkk2UJ7LTuAB6W1wAASAEkAMrQAAicuZ7oAcnLYNAAOpygBKAGloABaYJSmXQbF6gHvM04E2p7AWlLs60I7r0kgCejQQRgehgdLWaz1IP-CCKeoAGgaAE0WrZGwjEdxq67FORGk3W+3O49uy5q5MVIJJYPm-VfaJ6h2u+PoNWh7DbDFiEWE2TcukiSSllpS+Xcz092NkAAmdI3pC5LyMlmWznc7C8y-EHb1gReCAoisFo9TWI+t6AbwrDuBCrpuKIKggIIsb0NKsqQSc6gqMY0AatktD1NAADU+53lBbDuNaQA">playground link</a>.</p>
<p>Here's the <code>Team</code> class for comparison</p>
<pre><code class="lang-javascript">...
class Team <span class="hljs-keyword">extends</span> <span class="hljs-built_in">Array</span>&lt;TeamMember&gt; {

    <span class="hljs-keyword">constructor</span>(rows: Array&lt;Array&lt;string&gt;&gt;) {
        <span class="hljs-built_in">super</span>();
        rows.filter(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row[<span class="hljs-number">0</span>] != <span class="hljs-string">""</span>)
            .map(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> <span class="hljs-built_in">this</span>.push(<span class="hljs-keyword">new</span> TeamMember(row)));
    }

    getEnabledUsers() : <span class="hljs-built_in">Array</span>&lt;TeamMember&gt; {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.values(<span class="hljs-built_in">this</span>).filter(<span class="hljs-function"><span class="hljs-params">member</span> =&gt;</span> member.enabled);
    }
}
...
</code></pre>
<h3 id="heading-is-using-objectvalues-the-correct-thing-to-do">Is using <code>Object.values()</code> the correct thing to do?</h3>
<p>Yes, using <code>Object.values()</code> fixed the runtime error. But it was extra computation and memory utilised to create an array from an already existing array. So IMHO, it seems like doubling the work.</p>
<h3 id="heading-whats-the-real-error">What's the real error?</h3>
<p>Thanks to my colleague Ynze who tracked down this FAQ answer from Microsoft on <a target="_blank" href="https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work">"Why doesn't extending built-ins like Error, Array, and Map work?"</a>.</p>
<p>So essentially, it's not supported in TypeScript for now due to how an <code>Array</code> for example uses ECMAScript 6's <code>new.target</code> to adjust the prototype chain.</p>
<h2 id="heading-typescript-best-practice">TypeScript - Best Practice?</h2>
<p>Speaking with another front-end developer colleague Ben resulted in him sharing the following code as better way of defining this <code>Team</code> class. You can see a working version on this <a target="_blank" href="https://www.typescriptlang.org/play?#code/MYGwhgzhAEAqCmYC2BZeSBG8BO0DeAsAFDSnQB2y8AXNBAC7YCW5A5gNzFnTyUYjwAJrQwB7UQLDlORLmWCjyDbAFdg9UdgAU2UQHcAamBAr4EWspasA2gF0AlPjndS9ABZMIAOkpJ40AF5oXUNjUwhrAAZbGRcyd08vXjB+IUDg-SMTM2sARlsvDQAZfRwAYUh4LUcA2ugAIgBPM3rYsgBfYk7ZIlBIGARkJx7uP0wcGCDyeD1oAEFsbDBGgB5B1HQsbAA+aplnaAUlRjUNbRDzecXllYWl1cs2be3HQhI4jL1vADMmEHocDp9IFtp8orZoABCIL1er2A4faBeJBgAAOQNmAVBCW8Yy23lRKggbi001m6zQ43O+nstLapG6B1Y8HoAFE+AJBABVCATarQWh3G4UzY4UFvRHYFkqbDkaA45Gi7A-P4A7R4nAg6Aa7BJDlCez06DdRkkAT0aCCMD0MDpazWeoAdT+EEU9QANA0AJotWzuhGIsgOgDKinIjQ93t9-vegZcDrmKkEUojnvqADlRPU-QG4w7I7DbDFiOb5YgkLl0mS4OWtFabYbiCWWWXkAAmdIA5C5LzMtn67m85V7Ju9RSugReECiVhaerWLtINtT3isdwQ4NuUQqECCYLS2XQZfzdQqYzQABuYRoDWgAGpW0uV2x3IagA">playground link</a>.</p>
<pre><code class="lang-javascript">...
class Team {

    members = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>&lt;TeamMember&gt;();

    <span class="hljs-keyword">constructor</span>(rows: Array&lt;Array&lt;string&gt;&gt;) {
        rows.filter(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row[<span class="hljs-number">0</span>] != <span class="hljs-string">""</span>)
            .map(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> <span class="hljs-built_in">this</span>.members.push(<span class="hljs-keyword">new</span> TeamMember(row)));
    }

    getEnabledUsers() : <span class="hljs-built_in">Array</span>&lt;TeamMember&gt; {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.members.filter(<span class="hljs-function"><span class="hljs-params">member</span> =&gt;</span> member.enabled);
    }
}
...
</code></pre>
<h2 id="heading-verdict">Verdict</h2>
<p>TypeScript is not like C#. So proceed with caution 😉
Hopefully this article will help the next TypeScript noob!</p>
]]></content:encoded></item><item><title><![CDATA[Starting out with TypeScript]]></title><description><![CDATA[I have been working with the Microsoft Tech Stack for the last 20 years. Specifically in the .NET and C# space.  But two years ago, I started writing JavaScript in the back end. Specifically on the Google Scripts Platform (GSP).
Google Scripts Platfo...]]></description><link>https://emmti.com/starting-out-with-typescript</link><guid isPermaLink="true">https://emmti.com/starting-out-with-typescript</guid><category><![CDATA[GSP]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Mon, 10 Oct 2022 09:52:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/w7sIj-M5Xyc/upload/v1665652182568/9_qEhYBQl.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been working with the Microsoft Tech Stack for the last 20 years. Specifically in the .NET and C# space.  But two years ago, I started writing JavaScript in the back end. Specifically on the Google Scripts Platform (GSP).</p>
<h1 id="heading-google-scripts-platform">Google Scripts Platform</h1>
<p>I have been playing around with the <a target="_blank" href="https://developers.google.com/apps-script">Google Scripts Platform (GSP)</a> both at home, for my parish during COVID lockdowns and at work. With the power of collecting data via Google Forms, storing data in Google Sheets and visualising data in either Google Sheets or Docs or even exported PDFs, GSP is pretty powerful. I wrote quite a bit of JavaScript to automate so many things. <em>(Maybe I will blog about them gradually with some useful code snippets in the future.)</em></p>
<h1 id="heading-why-typescript">Why TypeScript?</h1>
<p>So after writing C# for around 20 years, why did I want to learn TypeScript?</p>
<ul>
<li>Write JavaScript code that compiles</li>
<li>Build and share some nifty <a target="_blank" href="https://developers.google.com/apps-script">GCP</a> utilities</li>
<li>Build a static website with <a target="_blank" href="https://nextjs.org/">Next.js</a></li>
<li>Build a static website with <a target="_blank" href="https://www.gatsbyjs.com/">Gatsby</a></li>
</ul>
<h1 id="heading-the-transition">The Transition</h1>
<p>This is how I started on my journey from C# to JavaScript to TypeScript. </p>
<h2 id="heading-javascript-to-typescript">JavaScript to TypeScript</h2>
<p>Coming from a very OOP (Object Oriented Programming) background, writing code in JavaScript felt a bit dirty. Don't get me wrong - JavaScript is a pretty powerful language and how it works on GSP is terrific. But I missed the strict types and compile-time error-checking capabilities of C#. </p>
<p>I heard about TypeScript a long time ago, but that was people complaining about how hard it was.  But in recent times, that sentiment has changed and developers want to work with <a target="_blank" href="https://www.typescriptlang.org">TypeScript</a>.</p>
<p><a target="_blank" href="https://devblogs.microsoft.com/typescript/ten-years-of-typescript/">TypeScript turned 10 years old</a> as I completed learning the basics of it. I started off with <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html">learning TypeScript coming from a C# background</a> and then moved on to the <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/intro.html">TypeScript Handbook</a>. </p>
<p>I would recommend going through the handbook from end to end to grasp the concepts and the differences from other languages such as C#. Variable scoping and function overloading were a few things that I learnt along the way that were completely different from other programming languages I have used.</p>
<h2 id="heading-typescript-in-practice">TypeScript in Practice</h2>
<p>Once set up properly, the intellisense in an IDE (Integrated Development Environment) like Visual Studio Code makes writing and compiling TypeScript a breeze. In addition, using a unit testing framework such as <a target="_blank" href="https://jestjs.io/">Jest</a> makes your code so much more robust.</p>
<h1 id="heading-next-steps">Next steps</h1>
<p>I want to quickly utilise TypeScript in frameworks like NextJS and Gatsby. But I wanted to complete something useful for others and utilise my newly acquired TypeScript knowledge before I went there. So you will see a few nifty little utilities I wrote on GSP with JavaScript converted to TypeScript.</p>
<p>You will also see some weird errors/issues (and my fixes to those) as I encounter them.</p>
<p>Wish me luck and let's see how I go!</p>
]]></content:encoded></item><item><title><![CDATA[Umbraco for Enterprise Clients]]></title><description><![CDATA[What are enterprise customers looking for? How does Umbraco score against their needs? In this blog post, in my role as Technical Director at Luminary (a Umbraco Gold Partner) and as a dedicated Umbraco Community member, I take a look at Umbraco HQ's...]]></description><link>https://emmti.com/umbraco-for-enterprise-clients</link><guid isPermaLink="true">https://emmti.com/umbraco-for-enterprise-clients</guid><category><![CDATA[DXP]]></category><category><![CDATA[Umbraco]]></category><category><![CDATA[Heartcore]]></category><category><![CDATA[enterprise software]]></category><category><![CDATA[Enterprise Content Management Market]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Fri, 30 Sep 2022 07:32:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664522757892/hHvQvyWp2.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What are enterprise customers looking for? How does Umbraco score against their needs? In this blog post, in my role as Technical Director at Luminary (a Umbraco Gold Partner) and as a dedicated Umbraco Community member, I take a look at Umbraco HQ's plan to cater to enterprise customers from an agency perspective. Specifically, I dive into Luminary's experiences with enterprise customers and how Umbraco scores based on their requirements, how this affects the broader Umbraco ecosystem, as well as Luminary's recommendations to marketers, IT managers, and solutions architects. </p>
<p>Read the full blog post <em>(9 min read)</em> - <a href="https://umbraco.com/blog/umbraco-for-enterprise-clients/">Umbraco for Enterprise Clients: An agency perspective</a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a href="https://umbraco.com/blog/">umbraco.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Inspiration to move to Hashnode]]></title><description><![CDATA[I had read many articles on medium.com and dev.to. The articles published on those platforms looked slick. But on Medium, the articles were behind a paywall. So whenever the urge came for me to switch from my old drab Blog on Blogger.com, I resisted ...]]></description><link>https://emmti.com/inspiration-to-move-to-hashnode</link><guid isPermaLink="true">https://emmti.com/inspiration-to-move-to-hashnode</guid><category><![CDATA[Hashnode]]></category><category><![CDATA[Blogger]]></category><category><![CDATA[migration]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Sun, 18 Sep 2022 06:51:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/82TpEld0_e4/upload/v1663482076987/r40O68MMt.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I had read many articles on medium.com and dev.to. The articles published on those platforms looked slick. But on Medium, the articles were behind a paywall. So whenever the urge came for me to switch from my old drab Blog on Blogger.com, I resisted the idea and put it off.</p>
<p>As my day job culminates with building websites on a CMS (<strong>C</strong>ontent <strong>M</strong>anagement <strong>S</strong>ystem), I was also tempted to roll out an open-source CMS such as Umbraco and build my own blog on it. It's easy enough. But then I would need to host the CMS and take care of upgrades. So once again, I just continued on Blogger.com.</p>
<p>This was until I came across my former colleague <a target="_blank" href="https://thaitran.dev/">Thai Tran's Blog</a>. I was impressed by what I saw and as I scrolled to the bottom, I came across the Hashnode tagline.</p>
<blockquote>
<p>Powered by Hashnode - a blogging community for software developers.</p>
</blockquote>
<p>That sounded perfect. That was way back in June 2022.</p>
<p>Fast forward to 17 September 2022, I had ten days of annual leave booked in and a few tasks on my to-do list. One of which was to consolidate articles across different websites where I had written as a guest, MVP or employee.</p>
<p>I went back to <a target="_blank" href="https://thaitran.dev/">Thai's Blog</a> and scrolled down to the footer to remind myself what he was using.</p>
<p>I read the <a target="_blank" href="hashnode.com">hashnode.com homepage</a> and was convinced this was the way to go. Some of the features I really liked were:</p>
<ul>
<li>No Ads</li>
<li>No Paywall</li>
<li>Map your custom domain</li>
<li>Free SSL</li>
<li>Markdown for authoring</li>
</ul>
<p>What I couldn't find information on was how Hashnode was monetized or its plans for monetisation. I had to dig a bit further to figure this one out. But Hashnode's co-founder <a target="_blank" href="https://hashnode.com/@sandeep">Sandeep Panda</a> says the following in this <a target="_blank" href="https://hashnode.com/post/whats-next-with-hashnode-cjwpbpwzb005thfs14onwae24#:~:text=PRO%20features%20and%20monetization%20plans%3A%20Although%20we%20have%20raised%20sufficient%20funds%20to%20run%20Hashnode%2C%20we%20are%20looking%20to%20introduce%20a%20revenue%20model%20for%20long%20term%20viability.%20We%20have%20a%20few%20ideas%20in%20mind%20and%20I%27ll%20share%20them%20in%20a%20separate%20post.">public question to the Hashnode team</a>.</p>
<blockquote>
<p>PRO features and monetization plans: Although we have raised sufficient funds to run Hashnode, we are looking to introduce a revenue model for long term viability. We have a few ideas in mind and I'll share them in a separate post.</p>
</blockquote>
<p>So I signed up for an account and started to migrate my old blog posts from Google Blogger to Hashnode. While doing that, I thought it would be good to document my journey to inspire others as as I was inspired by Thai. </p>
]]></content:encoded></item><item><title><![CDATA[.NET Core 3.1 approaches end of life]]></title><description><![CDATA[Microsoft will cease to support and maintain .NET Core 3.1 by 13 December 2022. Beyond this date, .NET core 3.1 will no longer receive security patches or bug fixes. All websites and custom applications written on this technology will be vulnerable t...]]></description><link>https://emmti.com/net-core-31-approaches-end-of-life</link><guid isPermaLink="true">https://emmti.com/net-core-31-approaches-end-of-life</guid><category><![CDATA[.net core]]></category><category><![CDATA[Security]]></category><category><![CDATA[EOL]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Fri, 09 Sep 2022 01:23:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663550432096/BwMpWKDyG.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Microsoft will cease to support and maintain .NET Core 3.1 by 13 December 2022. Beyond this date, .NET core 3.1 will no longer receive security patches or bug fixes. All websites and custom applications written on this technology will be vulnerable to security and performance issues if not updated to the latest LTS Release .NET 6.</p>
<p>Read the full blog post <em>(7 min read)</em> - <a target="_blank" href="https://www.luminary.com/blog/net-core-3-1-approaches-end-of-life">.NET Core 3.1 approaches end of life </a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a target="_blank" href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Umbraco cDXP targets enterprise market]]></title><description><![CDATA[At Codegarden 2022, Umbraco HQ revealed ambitious plans to market Umbraco cDXP (composable Digital Experience Platform) to lower enterprise and upper midmarket company segments. As an attendee of the Umbraco Gold Partner Summit and Developer conferen...]]></description><link>https://emmti.com/umbraco-cdxp-targets-enterprise-market</link><guid isPermaLink="true">https://emmti.com/umbraco-cdxp-targets-enterprise-market</guid><category><![CDATA[Umbraco]]></category><category><![CDATA[Data Center]]></category><category><![CDATA[cDXP]]></category><category><![CDATA[CodeGarden]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Fri, 01 Jul 2022 01:37:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663551515934/IW_zaZjsz.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At Codegarden 2022, Umbraco HQ revealed ambitious plans to market Umbraco cDXP (composable Digital Experience Platform) to lower enterprise and upper midmarket company segments. As an attendee of the Umbraco Gold Partner Summit and Developer conference, I share some of my insights.</p>
<p>Read the full blog post <em>(5 min read)</em> - <a target="_blank" href="https://www.luminary.com/blog/umbraco-cdxp-targets-enterprise-market">Umbraco cDXP targets enterprise market</a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a target="_blank" href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Why you should be modelling content]]></title><description><![CDATA[Content modelling is a critical part of setting a headless CMS up for success. I take a deep dive into how it works.
Read the full blog post (8 min read) - Why you should be modelling content ↗️

This is a stub for my blog post on luminary.com/blog ↗...]]></description><link>https://emmti.com/why-you-should-be-modelling-content</link><guid isPermaLink="true">https://emmti.com/why-you-should-be-modelling-content</guid><category><![CDATA[headless cms]]></category><category><![CDATA[omnichannel ]]></category><category><![CDATA[Content modelling]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Fri, 22 Apr 2022 12:02:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663589000896/sFNLXGYNg.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Content modelling is a critical part of setting a headless CMS up for success. I take a deep dive into how it works.</p>
<p>Read the full blog post <em>(8 min read)</em> - <a target="_blank" href="https://www.luminary.com/blog/why-you-should-be-modelling-content">Why you should be modelling content</a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a target="_blank" href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Scripting an Azure deployment of Umbraco 9]]></title><description><![CDATA[Unattended installs in Umbraco 9 have brought in endless possibilities. Let's take a deep dive into how we can use this capability to spin up a new Umbraco instance on Azure with a bit of Azure CLI magic thrown in.
Read the full blog post (12 min rea...]]></description><link>https://emmti.com/scripting-an-azure-deployment-of-umbraco-9</link><guid isPermaLink="true">https://emmti.com/scripting-an-azure-deployment-of-umbraco-9</guid><category><![CDATA[Umbraco]]></category><category><![CDATA[Azure CLI]]></category><category><![CDATA[deployment]]></category><category><![CDATA[Scripting]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Wed, 01 Dec 2021 13:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663726782365/y7Y9PXZK5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Unattended installs in Umbraco 9 have brought in endless possibilities. Let's take a deep dive into how we can use this capability to spin up a new Umbraco instance on Azure with a bit of Azure CLI magic thrown in.</p>
<p>Read the full blog post <em>(12 min read)</em> - <a href="https://24days.in/umbraco-cms/2021/scripting-azure-deployment/">Scripting an Azure deployment of Umbraco 9</a> ↗️</p>
<p>Or listen to it on YouTube. Read by <a href="https://twitter.com/CodeSharePaul">Paul Seal</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=ZjwX_NlXacU">https://www.youtube.com/watch?v=ZjwX_NlXacU</a></div>
<hr />
<p><em>This is a stub for my blog post on <a href="https://24days.in/umbraco-cms/2021">24days.in/umbraco-cms/2021</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Luminary achieves Microsoft Cloud Platform Gold Partner status]]></title><description><![CDATA[Luminary has achieved Microsoft Cloud Platform Gold Partner status, highlighting our capability to deliver cloud applications and services on Microsoft Azure.
Read the full blog post (3 min read) - Luminary achieves Microsoft Cloud Platform Gold Part...]]></description><link>https://emmti.com/luminary-achieves-microsoft-cloud-platform-gold-partner-status</link><guid isPermaLink="true">https://emmti.com/luminary-achieves-microsoft-cloud-platform-gold-partner-status</guid><category><![CDATA[Microsoft]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Microsoft Partner]]></category><category><![CDATA[Cloud Applications]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Mon, 15 Nov 2021 11:18:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663589890528/ScByJJNG7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Luminary has achieved Microsoft Cloud Platform Gold Partner status, highlighting our capability to deliver cloud applications and services on Microsoft Azure.</p>
<p>Read the full blog post <em>(3 min read)</em> - <a target="_blank" href="https://www.luminary.com/blog/luminary-microsoft-cloud-platform-gold-partner">Luminary achieves Microsoft Cloud Platform Gold Partner status</a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a target="_blank" href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Choosing a DXP: Kentico Xperience or Umbraco CMS]]></title><description><![CDATA[Kentico Xperience and Umbraco are both very popular .NET DXP or CMS platforms. They bear many similarities, to the point that many digital agencies specialise in both platforms due to the similar skillsets required for implementation and support. Her...]]></description><link>https://emmti.com/choosing-a-dxp-kentico-xperience-or-umbraco-cms</link><guid isPermaLink="true">https://emmti.com/choosing-a-dxp-kentico-xperience-or-umbraco-cms</guid><category><![CDATA[Umbraco]]></category><category><![CDATA[Kentico]]></category><category><![CDATA[DXP]]></category><category><![CDATA[cms]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Sun, 24 Oct 2021 13:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/IFLgWYlT2fI/upload/v1665014130416/eZ_6Dhckb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kentico Xperience and Umbraco are both very popular .NET DXP or CMS platforms. They bear many similarities, to the point that many digital agencies specialise in both platforms due to the similar skillsets required for implementation and support. Here's our in-depth comparison and guide to why you might choose one or the other for your project.</p>
<p>Read the full blog post (7 min read) - <a href="https://www.luminary.com/blog/choosing-dxp-kentico-xperience-or-umbraco-cms">Choosing a DXP: Kentico Xperience or Umbraco CMS</a> ↗️</p>
<hr />
<p><em>This is a stub for a blog post written by <a href="https://www.luminary.com/andy">Andy</a> and me on <a href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[How To Choose A Headless CMS]]></title><description><![CDATA[There is an array of Headless CMSes out there. In this article, we delve into headless CMS features to satisfy your content editors, marketers and yourself as a developer. For the experience headless practitioner, this could be a checklist to see wha...]]></description><link>https://emmti.com/how-to-choose-a-headless-cms</link><guid isPermaLink="true">https://emmti.com/how-to-choose-a-headless-cms</guid><category><![CDATA[headless cms]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Mon, 05 Jul 2021 14:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/cJxxfSEbO8Y/upload/v1663724519233/EE_RI9vEC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is an array of Headless CMSes out there. In this article, we delve into headless CMS features to satisfy your content editors, marketers and yourself as a developer. For the experience headless practitioner, this could be a checklist to see what’s new out there. For those starting out on their headless journey, this could be a guide on what to look for.</p>
<p>Read the full blog post <em>(18 min read)</em> - <a href="https://www.smashingmagazine.com/2021/07/how-to-choose-a-headless-cms/">How To Choose A Headless CMS</a> ↗️</p>
<hr />
<p><em>This is a stub for my article on <a href="https://www.smashingmagazine.com/author/emmanuel-tissera/">smashingmagazine.com</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Rated H for Headless]]></title><description><![CDATA[Is your client or in-house Marketing team ready to use Umbraco Heartcore? Are they mature enough to use a Headless CMS? Would their unique requirements be better served by a self-hosted Umbraco instance or Umbraco Cloud?
Find out how to rate the digi...]]></description><link>https://emmti.com/rated-h-for-headless</link><guid isPermaLink="true">https://emmti.com/rated-h-for-headless</guid><category><![CDATA[Umbraco]]></category><category><![CDATA[Heartcore]]></category><category><![CDATA[headless cms]]></category><category><![CDATA[CodeGarden]]></category><category><![CDATA[Digital maturity]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Wed, 09 Jun 2021 14:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663931769524/sjNIM3asj.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Is your client or in-house Marketing team ready to use Umbraco Heartcore? Are they mature enough to use a Headless CMS? Would their unique requirements be better served by a self-hosted Umbraco instance or Umbraco Cloud?</p>
<p>Find out how to rate the digital experience maturity of your client and to guide them to the best solution.</p>
<h2 id="heading-the-trailer">The trailer</h2>
<p>The trailer I posted on LinkedIn and Twitter before the talk.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/lJKCz7ATKZE">https://youtu.be/lJKCz7ATKZE</a></div>
<h2 id="heading-the-talk">The talk</h2>
<p>This is the talk I gave at Umbraco Codegarden 2021.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/FRzScCQAjYk">https://youtu.be/FRzScCQAjYk</a></div>
<h2 id="heading-q-and-a-unconference-session">Q and A (Unconference session)</h2>
<p>This is the combined Q&amp;A session for all the talks in that time slot</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/3V0t_JqBIpQ">https://youtu.be/3V0t_JqBIpQ</a></div>
<h2 id="heading-about-codegarden-2021">About Codegarden 2021</h2>
<p>Umbraco Codegarden 2021 was a virtual tech conference held on June 9-11th 2021 and organised by Umbraco, the company behind the open-source Umbraco CMS. Codegarden is an annual conference held in Odense, Denmark and the home of Umbraco, but due to the corona pandemic was held virtually for the first time in 2021.</p>
<p>Over the course of 3 days, 2000+ attendees watched experts from all over the world, and all different areas of tech, come together to discuss everything new and upcoming with Umbraco, as well as the trends in the industry. Codegarden 2021 also introduced Unconference sessions for the first time - where speakers and hosts could get together to discuss the topics and ideas from the previous talks. Talks covered everything from accessibility to DevOps, best practices to career development, UX/Ul to Cloud hosting and SO much more.</p>
]]></content:encoded></item><item><title><![CDATA[A/B testing, personalisation and marketing automation with Umbraco 8]]></title><description><![CDATA[A/B testing, personalisation and marketing automation with Umbraco 8 were the main highlights at the February Australian Umbraco Meetup.
Read the full blog post (1 min read) and watch the video  - A/B testing, personalisation and marketing automation...]]></description><link>https://emmti.com/ab-testing-personalisation-and-marketing-automation-with-umbraco-8</link><guid isPermaLink="true">https://emmti.com/ab-testing-personalisation-and-marketing-automation-with-umbraco-8</guid><category><![CDATA[personalisation]]></category><category><![CDATA[UMarketingSuite]]></category><category><![CDATA[Umbraco]]></category><category><![CDATA[ab testing]]></category><category><![CDATA[Meetup]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Wed, 10 Feb 2021 13:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665013571533/VbM01ktLd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A/B testing, personalisation and marketing automation with Umbraco 8 were the main highlights at the February Australian Umbraco Meetup.</p>
<p>Read the full blog post (1 min read) and watch the video  - <a href="https://www.luminary.com/blog/a-b-testing-personalisation-marketing-automation-umbraco-8">A/B testing, personalisation and marketing automation with Umbraco 8</a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item><item><title><![CDATA[Packages in Umbraco 8]]></title><description><![CDATA[The December 2020 Melbourne Umbraco meetup was all about packages in Umbraco 8. Authors of different packages such as uSync, Plumber and a few others gave lightning demos/talks on their plugins.
Read the full blog post(3 min read) - Packages in Umbra...]]></description><link>https://emmti.com/packages-in-umbraco-8</link><guid isPermaLink="true">https://emmti.com/packages-in-umbraco-8</guid><category><![CDATA[Umbraco]]></category><category><![CDATA[plugins]]></category><category><![CDATA[Meetup]]></category><dc:creator><![CDATA[Emmanuel Tissera]]></dc:creator><pubDate>Thu, 10 Dec 2020 13:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665013212281/bobGpzCtb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The December 2020 Melbourne Umbraco meetup was all about packages in Umbraco 8. Authors of different packages such as uSync, Plumber and a few others gave lightning demos/talks on their plugins.</p>
<p>Read the full blog post(3 min read) - <a href="https://www.luminary.com/blog/packages-in-umbraco-8">Packages in Umbraco 8</a> ↗️</p>
<hr />
<p><em>This is a stub for my blog post on <a href="https://www.luminary.com/blog?blog-listing={author=emmanuel-tissera}">luminary.com/blog</a> ↗️</em></p>
]]></content:encoded></item></channel></rss>