Sunday, October 29, 2023

The Restaurant of Mistaken Orders: A Tale of Sitecore Content Hub’s Transition to React


In the bustling digital metropolis, there resides a quaint little establishment known as Sitecore Content Hub. It's akin to the "Restaurant of Mistaken Orders," a place where the unexpected brews charm, yet also brews a concoction of technical quandaries. Much like the friendly staff at the restaurant, who serves dishes you didn't order with a smile, Sitecore embarked on a journey to spice up its menu by transitioning from the Knockout/Javascript fare to a more contemporary React cuisine.

However, the transition was akin to a chef persuading diners to swap their favorite vintage wine for a trendy cocktail. The mandate to move from version 4.x to 4.2 was presented as a necessary evolution to keep up with the digital gourmet landscape. Yet, this shift wasn't as palatable for many patrons, who found the React concoction a tad bit overwhelming to their established tastes. The hesitance was palpable, the reservations many.

Sitecore, being the avant-garde chef, was unsure about the compatibility of the old and new ingredients. The notion of a Mixmode - a blend of Knockout and React components - was akin to mixing Italian with Thai, a daring culinary adventure that could either tantalize the taste buds or result in a recipe for disaster. The promise was a full-fledged React feast, but the reality was a buffet where Knockout still found its place.

As clients gingerly navigated the transition, rolling up their sleeves to migrate components to React, the journey was akin to diners at the Restaurant of Mistaken Orders awaiting their surprise dish. The anticipation was laced with a dash of excitement and a sprinkle of dread.

The promise was a tech gourmet's delight with React's modern syntax and robust capabilities. Yet, as the migration unfolded, it was clear that Knockout was still holding fort in certain corners of the Content Hub, particularly the 'myorder' pages that seemingly had a penchant for the old world charm of Knockout.

The narrative of a full React transition seemed like a master-chef's dream, but the reality was more of a potluck where both React and Knockout found a place on the table. The insistence on transitioning seemed a tad over-seasoned, as the Content Hub couldn't quite bid adieu to Knockout.

As the tale unfolds, it's clear that the Sitecore Content Hub's transition journey had its share of quirky anecdotes, much like a dinner at the Restaurant of Mistaken Orders. The essence of the unexpected, the embrace of the old amidst the new, and the whimsical journey of navigating change, brewed a narrative that's both technically intriguing and gastronomically whimsical.

So, as we dine in the digital realm of Sitecore, the menu continues to evolve. The tale is a reminder that in the world of tech gourmet, the old and new often share a table, sometimes in harmony and sometimes in a playful tussle, much like the charming chaos of the Restaurant of Mistaken Orders.

In conclusion, the Sitecore Content Hub’s expedition from Knockout to React was much like a culinary adventure at a quirky eatery. There were surprises, and a few mistaken orders, but at the end of the day, the experience was a blend of the classic and contemporary, leaving patrons with a tale to reminisce and tech connoisseurs with lessons to mull over as they navigate the ever-evolving digital dining landscape.

Wednesday, October 25, 2023

Introduction

Welcome back to our quirky corner of the web, The Restaurant of Mistaken Orders! While we usually dish out code and tech tips with a side of humor, today we're serving up something different. Let's talk about a common post-pandemic pickle: the relentless online shopping habit. Think of it like ordering a simple pasta dish and ending up with a mysterious, yet intriguing, gourmet meal you never planned on trying.

Understanding the Habit: A Menu of Madness

The Pandemic Special

Picture this: the world went into lockdown, and our shopping habits were like ordering from a menu in the dark. We needed comfort, safety, and something to do. So, we turned to our screens, clicking 'Add to Cart' like it was the only dish available.

Why We Kept Coming Back

Online shopping, much like our restaurant's surprise orders, provides an unexpected thrill. Who doesn't love a package at the doorstep? It's like the chef sending out a complimentary dessert, except you pay for it, and it clutters your house.

Spotting the Excess: When You've Ordered Too Much

Signs You're Overstuffed

Do you remember the last time you impulsively added five different types of pasta to your cart, only to regret it later? That's what unnecessary online purchases feel like. Your digital shopping cart becomes a culinary misadventure you didn't plan for.

The Side Effects

Just as overeating can lead to a food coma, excessive online shopping can result in a financial hangover. You don't just lose money; you gain clutter and a hefty serving of buyer's remorse.

Culinary Strategies to Curb the Craving

1. Inventory Your Ingredients

Track your spending like a chef monitors their pantry. Know what you have, what you need, and what's simply taking up space.

2. Unsubscribe from Tempting Menus

Just as you wouldn't want a dessert menu waved under your nose on a full stomach, unsubscribe from those enticing retail emails.

3. Implement a "Cooling Off" Cooking Timer

Wait before you checkout. Let your shopping cart simmer for a day or two. You might find that the urge to buy that quirky spatula dissipates on its own.

4. Substitute Shopping with a Tastier Recipe

Find a hobby that's as satisfying as a perfectly cooked meal but doesn't cost as much as a five-star restaurant. Maybe it's time to cook up some new coding projects?

5. Understand the Emotional Eater Within

Sometimes, we shop online to fill an emotional void, much like stress eating. Recognizing these moments can help us find healthier ways to cope, like whipping up a real meal instead.

6. Setting Small Portion Goals

Cut back slowly. Don't try to go from a feast to a diet overnight. Reduce your online shopping orders bit by bit.

Extra Helpings of Support

Chew the Fat with Friends

Talk to people about your shopping habits. They might offer insights or join you in cutting back. It's like having a meal with friends instead of dining alone.

Professional Guidance: The Master Chef's Advice

If the habit feels too big to tackle, like trying to cook a five-course meal when you can barely boil water, seek professional advice.

Wrapping Up the Leftovers

Just like a meal at The Restaurant of Mistaken Orders, online shopping habits can surprise, delight, and sometimes overwhelm. Breaking free from this habit is about making mindful choices, understanding our impulses, and occasionally indulging in moderation.


As we close this unusual chapter in our tech-focused blog, remember: whether it's food, coding, or shopping, life's about savoring the experience, not overindulging in the distractions. Bon Appétit and Happy Coding!

Friday, October 20, 2023

A Modern Marvel: Serving "Applied Filters" by Default with React & TypeScript for Sitecore Content Hub

Ahoy, digital voyagers! 🚀 Ready to embark on a thrilling journey into the realm of React, TypeScript, and Sitecore Content Hub? Fasten your seatbelts, because today, we’re diving deep into the world of external components, and trust me, it's going to be a mouth-watering adventure.

The Backdrop: "Restaurant of Mistaken Orders"

Before we begin, let's set the stage with a metaphor. Have you ever dined at the "Restaurant of Mistaken Orders"? It's a unique place where servers intentionally mess up your order, all in the name of fun. But imagine if every time you visited, they remembered that you prefer your coffee with two sugars or that you despise pickles on your burger. They’d still make playful mistakes, but they'd never forget your most vital preferences. That's what we're aiming to achieve with our component: a delightful user experience that remembers your key choices.

The Culinary Delight: Why "Applied Filters"?

Just like a chef curates a menu tailored to the preferences of their patrons, digital platforms should cater to the unique tastes of their users. In the vast digital landscape of Sitecore Content Hub, users often find themselves sifting through a plethora of content. So, why not make life easier by serving them a plate of "Applied Filters" by default?

Benefits:

  1. Streamlined UX: Just as you wouldn't want to specify "no pickles" every single time you order, users don't want to set the same filters repeatedly.
  2. Efficiency: Time saved from repeatedly setting the same filters can be better spent on more critical tasks.
  3. Personalized Experience: Remembering user preferences creates a more tailored and enjoyable browsing journey.

The Recipe: A Deep Dive into the Code

Our main ingredient is the ApplySearchFilter component, a delightful concoction of React & TypeScript. This external component for Sitecore Content Hub is designed to apply search filters by default. Let's break down the recipe:

Ingredients:

  • React & TypeScript: Our main building blocks.
  • Sitecore Content Hub SDK: The secret sauce that allows us to interact with the Content Hub.
  • State Management: Using useState to keep track of how many times we've attempted to apply the filters.

Cooking Instructions:

  • We begin by setting up an event listener for when a search finishes.
  • On detecting the event, our component checks if the search matches our criteria.
  • If it’s a match, voilà! We apply our configured filters. But there's a twist: we ensure we don’t overdo it. Just like adding salt to a dish, after three attempts, we stop to prevent overwhelming the user.

Serving: Once our filters are applied, we provide feedback to the user, ensuring they're always in the know.

The Cherry on Top: Humor & Conclusion

Just like the playful servers at the "Restaurant of Mistaken Orders", our component brings a touch of humor to the otherwise serious world of digital content. By remembering user preferences and serving them "Applied Filters" by default, we enhance the user experience, ensuring that it's both efficient and delightful.

So, the next time you're sipping on that perfectly brewed coffee (with two sugars, of course) at the "Restaurant of Mistaken Orders", take a moment to appreciate the beauty of personalization. Whether it's in the culinary world or the digital realm, it's the little things that make all the difference.

Bon Appétit, and happy coding! 🍔🚀👩‍💻👨‍💻🎉

🔗 Full code can be found here: ch-apply-search-filter on GitHub

Tuesday, October 17, 2023

The Restaurant of Mistaken Orders: A Developer's Journey with Sitecore Content Hub



Welcome back to "The Restaurant of Mistaken Orders" blog! If you've dined with us before, you know we serve up hearty portions of technical insights related to Sitecore Content Hub. Today, we're diving deep into a developer's perspective, drawing parallels between the enigmatic world of content management and our whimsical restaurant metaphor. Grab a seat, order your favorite beverage, and let's dig in!

The Arrival: Embarking on the Sitecore Content Hub Journey

Just like entering a new restaurant for the first time, diving into a Sitecore Content Hub project can feel both exciting and a tad overwhelming. There's the allure of the unknown—new challenges, fresh interfaces, and the promise of streamlined content management. However, there's also the fear of the unfamiliar—where to start, how to navigate, and what pitfalls to avoid.

The Menu: Understanding the Features

Sitecore Content Hub, much like our restaurant's extensive menu, offers a smorgasbord of features. There's the Digital Asset Management (DAM) system, Product Content Management (PCM), and the Content Marketing Platform (CMP) to name just a few.

Digital Asset Management (DAM): Think of this as the restaurant's main course. It's where you store, organize, and retrieve media assets. Just like ensuring the steak is cooked to perfection, you'd want your assets to be easily accessible and in the right format.

Product Content Management (PCM): This is the side dish that complements the main course. PCM manages the detailed product information, ensuring consistency across platforms.

Content Marketing Platform (CMP): The dessert of the Content Hub. Here, you plan, create, and collaborate on content, ensuring it aligns with your brand's voice and goals.

The Order: Implementing the System

Here at "The Restaurant of Mistaken Orders", things might not always go as planned. Similarly, implementing Sitecore Content Hub can come with its set of challenges. Mistaken orders, in our context, are unforeseen issues or requirements that pop up during development.

Integration Challenges: Just like trying to pair wine with a dish, integrating Content Hub with other systems can be tricky. It's essential to understand the APIs and ensure seamless data flow.

Customization: Sometimes, the out-of-the-box features might not align with a business's unique requirements. It's akin to a customer wanting a bespoke dish. As developers, it's our job to whip up that custom solution without compromising the system's integrity.

Migration Woes: Migrating content from an old system to Sitecore Content Hub can be like trying to fit a square peg in a round hole. It requires meticulous planning, mapping, and testing to ensure data integrity.

The Service: User Training and Support

Implementing the system is just half the battle. Ensuring that users can effectively navigate and utilize Sitecore Content Hub is crucial. It's like training our waitstaff at the restaurant—they need to know the menu inside out, understand customer preferences, and handle mistaken orders with grace.

Hands-on Training: Just as we'd guide our customers through our eclectic menu, it's vital to offer hands-on training sessions for users. This ensures they're comfortable with the platform and can make the most of its features.

Documentation: A good restaurant always has a menu you can refer to. Similarly, detailed documentation, complete with screenshots and step-by-step guides, can be a lifesaver for users.

Ongoing Support: Mistakes can happen, both in our restaurant and on the platform. It's essential to provide a support system where users can raise concerns, report bugs, or seek clarifications.

The Check: Reviewing and Iterating

No restaurant visit is complete without reviewing the bill and perhaps giving feedback. Similarly, once Sitecore Content Hub is up and running, it's essential to gather feedback, analyze usage patterns, and continuously iterate to improve.

Feedback Channels: Establish channels where users can provide feedback. Whether it's a feature they'd like added or a glitch they've encountered, it's essential to listen and act.

Regular Audits: Just as a restaurant would periodically review its menu, conduct regular audits of the Content Hub. Ensure that the content is fresh, relevant, and organized.

Stay Updated: Sitecore regularly releases updates and patches. It's essential to stay updated to benefit from new features, security patches, and performance improvements.

The Farewell: Until Next Time!

Just as we'd hope our restaurant guests leave with their bellies full and hearts content, our goal with Sitecore Content Hub is to ensure a smooth, efficient, and productive experience for all users. While the journey might have its share of mistaken orders, with the right approach, tools, and mindset, it's a rewarding adventure.

Thank you for dining with us today at "The Restaurant of Mistaken Orders". We hope this developer's perspective has provided some food for thought. Until next time, keep coding, keep exploring, and bon appétit!


Tuesday, September 26, 2023

From the Kitchen of the Restaurant of Mistaken Orders: Whipping Up Faster Loading Speeds with 'From Path' in Sitecore Content Hub

restaurant of mistaken orders - sitecore content hub

Bonjour, tech gourmets! Chef Roel here, whisking up another delectable dish from the bustling kitchen of the Restaurant of Mistaken Orders. Today's special on the menu: A deep dive into the delightful world of Sitecore Content Hub, React components, and the ever-so-crucial choice between "From path" and "From asset". Grab your forks and let's dig in! 🍽️

The Appetizer: Setting the Table

Before we dive into the main course, let's set the table with some context. When working with Sitecore Content Hub and React, we often face the conundrum of how to link to an external React component. The two most popular choices are:

  • From asset: This method links directly to the asset.
  • From path: This method links to the component's path.

Now, I know what you're thinking: "Chef, isn't linking directly to the asset the quickest way to get to the dessert?" Ah, my discerning diners, this is where the plot thickens!

The Main Course: Why "From path" is the Crème de la Crème

1. Straight to the Source:

Using "From path" lets you link directly to the component's path. Think of it like having a shortcut to the freshest ingredients in your pantry. No more rummaging through aisles and shelves!

2. Speedy Service with Portal Assets:

When your React component resides in the Portal Assets, the serving time is even faster. Imagine this: You're hosting a grand feast and instead of fetching wine from the cellar in a distant vineyard (like an Azure environment), you grab a bottle from the wine rack right in your dining room. Swift, efficient, and oh-so-convenient!

A Side Dish: How to Measure Serving Speed

Now, for those of you who love to time your soufflés to perfection, here's a nifty trick to measure the speed difference between the two methods:

  1. Fire up your browser and head to your Sitecore Content Hub.
  2. Open Developer Tools (typically F12 or right-click > Inspect).
  3. Navigate to the 'Network' tab. This is where the magic happens!
  4. Reload your page.
  5. Sort the results by 'Time' to see how long each component takes to load.

Now, perform this for both "From asset" and "From path" (especially when linked from Portal Assets) and see the difference for yourself. Just like how some soups simmer slowly and others boil up quickly, you'll notice a delightful difference in loading times.

The Dessert: Wrapping Up with Some Sugar

So, dear diners, the next time you find yourself pondering the pathways in Sitecore Content Hub, remember our little culinary adventure today. "From path", especially from Portal Assets, is the way to go if you fancy a swift, efficient, and flavorsome experience.

As always, from the bustling stoves of the Restaurant of Mistaken Orders, we aim to serve you not just meals, but experiences. And remember, in the world of tech (and cooking), it's always the little details that spice things up!

Bon Appétit! 🍷🍝

Saturday, September 23, 2023

Ordering Up the Perfect Search Filter": A Deep Dive into Sitecore Content Hub's External Component

Welcome to the "Restaurant of Mistaken Orders"! 🍽️ Here, where unexpected combinations lead to delightful discoveries, today's special is a scrumptious walkthrough of an "external component" in Sitecore Content Hub. Our aim? To preset a search filter based on a user group.

Before we dive into the intricacies of our dish, allow me to present to you the recipe card. Our entire cooking procedure, ingredients, and secret techniques are documented in our digital cookbook, right here: Sitecore Content Hub External Component on GitHub. As we journey through this culinary adventure, we'll be referring to this repository. It's the source of our inspiration and the backbone of today's dish! 

With that said, a quick reminder: our restaurant thrives on the unexpected. Just as you might find a sprinkle of chocolate on your spaghetti (surprisingly delicious), expect a blend of technical details, humor, and chef anecdotes as we delve deeper.

1. The Ingredients: Our TypeScript Files

To cook up our search filter dish, we have the following TypeScript files as our main ingredients:

FilterDictionary.ts: This is like our recipe book. It defines how each user group correlates to a specific filter.

index.tsx: Our main entry point, just like the kitchen's bustling center!

HideSearchFilters.tsx & AddSearchFilters.tsx: These are the spices and seasonings, handling how filters appear or disappear based on user actions.

FilterConfig.ts: Think of this as the kitchen's guideline – setting the configurations for our filters.

2. Cooking Procedure: How it Works

a) Determining the User Group

First, our code checks which user group the logged-in user belongs to. Using our FilterDictionary.ts, it then fetches the corresponding preset filter for that group. Just like how I, as a chef, would pick a unique seasoning for each dish!

b) Setting the Filters

With the preset filter determined, the code utilizes the components HideSearchFilters.tsx and AddSearchFilters.tsx to adjust the search filters accordingly.

For example:

This process ensures that the user sees only the search filters relevant to their group.

3. Sourcing the Secret Sauce: Fetching toFilterRequest Values

Here's where our secret ingredient comes in: the toFilterRequest values. But how do we source these?

Accessing Browser Developer Tools: Just like opening the secret drawer of spices, open your browser and head to the Developer Tools.

Navigating to the Network Tab: Within Developer Tools, select the "Network" tab.

Filtering for Fetch/XHR: Make sure you have the "Fetch/XHR" option selected.

Performing a Search: On your Sitecore Content Hub, perform a search. In the Developer Tools, you'll notice an entry titled "search" on the left.

Extracting the Values: By setting the filter, you can now extract the required toFilterRequest values to use in your code.



This is similar to how I sometimes peek into other chefs' recipes to find that perfect spice mix. 😉

4. Final Plating

With everything set, our user will now experience a search filter uniquely tailored to their user group. A delightful experience that ensures content relevance and reduces unnecessary noise.

5. A Chef's Note

Remember, just as in our restaurant, it's all about experimentation. You might not get the desired taste in the first go, but with a pinch of persistence and a sprinkle of creativity, you'll cook up a masterpiece!

Wrapping Up

Thank you for dining with us today at the "Restaurant of Mistaken Orders"! We hope this detailed walkthrough has not only satisfied your tech cravings but also added a hint of fun to your coding journey. Until next time, keep experimenting and happy coding! 🍲👩‍🍳👨‍🍳

P.S. If you ever want to try spaghetti with chocolate sprinkles, let us know. We're always up for culinary adventures! 😉

Full source code available here: https://github.com/RoelRoozendaal/ch-facets-for-search

Wednesday, September 20, 2023

Sitecore Content Hub - User Group Display Component!


Hello there, fellow foodies of the coding world! Welcome to our delightful restaurant of mistaken orders, where our primary dish today is the spicy, flavorful, and ever so complex: "Sitecore Content Hub User Group Display Component!" Don your aprons and chef hats, and let's dive deep into the recipe of this code dish. 🍳

🍛 The Idea:

Before we stir the pot, let's discuss the idea. The code dish we're preparing serves to display the user groups that a user belongs to, right on their profile image. Imagine a pizza, and each slice is a user group. As a user, you can see which slices (or groups) you're a part of, just by looking at the pizza (your profile image). Savory, right?

User Profile Usergroups








🍜 Ingredients:

  • jQuery: The aromatic herb we'll be using to traverse and manipulate our HTML.
  • MutationObserver: The secret sauce ensuring that our ingredients (DOM elements) are ready before we start our culinary magic.
  • User Groups: The meaty chunks that we'll be refining to suit our taste.

🍳 Cooking Instructions:

Preparing the Meat (User Groups):

First, we filter out the basic flavors. We want our dish to stand out, so we're removing groups containing ".Base" or ".Role".

But wait! If our list contains the spicy "M.Consumer.US" flavor, we further refine by removing the "DML.Consumer.Base" group. It's all about balance!

Marinating the Meat:

To add some zest, we tweak the naming of our user groups. We remove any unwanted prefixes like "m" or "M." and replace any dot (.) with a space, making it more palatable.

Refining the Dish:

We don't want any overpowering flavors. Thus, we remove groups like "Everyone" or "TermsAndConditions".

To ensure uniqueness, we don't want duplicate ingredients. We get the unique groups and further refine them by removing words like "Base" or "Role".

Serving the Dish:

Now, we place our refined and unique user groups on the plate (or rather, into the HTML). Each group is garnished with a fancy icon and made clickable, though it doesn't lead anywhere right now (maybe to more recipes in the future? 😉).

Ensuring Perfect Temperature:

We wait for the right moment (using our secret sauce, the MutationObserver) to ensure our profile settings link is ready.

Once ready, we serve our dish hot! When you hover over the profile settings, our delicious user groups are displayed, and they hide when you move away.

🥘 Pro Tips from the Chef:

The kitchen can be unpredictable. Thus, we wrap parts of our code in try-catch to handle any unforeseen spills or burns (errors).

The waitForElm function is our oven timer. It ensures we don't start garnishing until our main dish (DOM element) is fully baked and ready.

🍰 Dessert:

After savoring this flavorful dish, remember: coding, like cooking, is an art. Sometimes it’s the mistaken orders that bring out the most delightful flavors. And while our restaurant might serve orders with a twist, it's always a gastronomic journey to remember!

The Code (code tab):

$(document).ready(function () {
var optionsUserGroups = options.userGroups.filter(x => !(x.includes(".Base") || x.includes(".Role")));

if (optionsUserGroups.includes("M.Consumer.US")) {
optionsUserGroups = jQuery.grep(optionsUserGroups, function (value) {
return value !== "M.Consumer.Base";
});
}

try {
var userGroups = optionsUserGroups.map(function (value) {
var newValue = value.replace(/m|M\./g, "");
return newValue ? newValue.replace(".", " ") : value;
});
} catch (err) {
console.log(`There was an error in Getting usergroups for user profile. ${err}`);
}

try {
if (userGroups.length > 0) {
var updatedUserGroupsArray = userGroups.filter(function (value) {
return !/Everyone|TermsAndConditions/.test(value);
});
}
} catch (err) {
console.log(`There was an error in Getting usergroups for user profile. ${err}`);
}

try {
if (updatedUserGroupsArray.length > 0) {
var uniqueUserGroups = [...new Set(updatedUserGroupsArray)];
var finalUserGroups = uniqueUserGroups.map(function (value) {
var newValue = value.replace(/Base|Role/g, "");
return newValue ? newValue.replace(".", " ") : value;
});
$.each(finalUserGroups, function (index, value) {
$(`<a class="dropdown-item" href="#"><li class="m-icon m-icon-people"></li>${value}</a>"`).insertAfter(".dropdown-divider");
});
}
} catch (err) {
console.log(`There was an error in Getting usergroups for user profile. ${err}`);
}

var selector = 'a[title*="Profile and settings"]';
waitForElm(selector).then((elem) => {
$(selector).click(function() {
$("#profile-groups-menu").hide();
$("#profile-groups-menu").addClass("hide");
});

$(selector).hover(function () {
$("#profile-groups-menu").show();
$("#profile-groups-menu").removeClass("hide");
}, function () {
$("#profile-groups-menu").hide();
$("#profile-groups-menu").addClass("hide");
});
});
});

function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
<!-- / TEMPLATE / -->
<style>
body,
html {
/* overflow-y: hidden; */
}

#profile-groups-menu {
position: fixed;
min-height: 200px;
min-width: 238px;
max-height: 800px;
width: 238px;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid #f9f9f9;
top: 5em;
right: 1em;
z-index: 10000 !important;
}

.dropdown-menu {
content: "";
}

.hide {
display: none;
}
</style>
<div id="profile-groups-menu" class="hide dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="#">
<h3><i class="m-icon m-icon-user-circle"></i>Group memberships</h3></a>
<div class="dropdown-divider"></div>
</div>

Sunday, September 17, 2023

Diving Deep into the Video Caption Editor's Integration with Azure Functions

Introduction

In our previous post, we introduced the Video Caption Editor for the Sitecore Content Hub and shed light on its importance in terms of efficiency and cost-saving. Today, we'll journey deeper into the technical aspects of this groundbreaking tool, especially its integration with Azure Functions.

Video Processing and Properties

Before sending video data to the Content Hub, it's crucial to obtain specific properties of the video that can aid in its correct representation and playback. This involves understanding the compression formats, such as the Apple ProRes 422 HQ.

Converting HTML to Images

One of the fascinating features of the Video Caption Editor is its capability to transform HTML content into images. This is particularly useful for generating overlays from HTML-based video captions. Leveraging powerful libraries such as SkiaSharp and HtmlAgilityPack, this feature provides high-fidelity conversions.

using HtmlAgilityPack;
using SkiaSharp;
using System.Collections.Generic;
using Topten.RichTextKit;

namespace Sitecore.CH.Implementation.AzFunctions.Model.Video
{
public class VideoHtmlToImage
{
public byte[] ConvertHtmlToImage(string html, int originalTextBoxWidth, int originalTextBoxHeight, int targetTextBoxWidth, int targetTextBoxHeight, SKColor backgroundColor, SKColor textColor, int videoWidth)
{
// Calculate aspect ratios
float originalAspectRatio = (float)originalTextBoxWidth / originalTextBoxHeight;
float targetAspectRatio = (float)targetTextBoxWidth / targetTextBoxHeight;

// Calculate scaled dimensions based on the aspect ratio
int scaledWidth, scaledHeight;
if (originalAspectRatio > targetAspectRatio)
{
scaledWidth = targetTextBoxWidth;
scaledHeight = (int)(targetTextBoxWidth / originalAspectRatio);
}
else
{
scaledWidth = (int)(targetTextBoxHeight * originalAspectRatio);
scaledHeight = targetTextBoxHeight;
}

// Create a new image surface
using (SKBitmap bitmap = new SKBitmap(targetTextBoxWidth, targetTextBoxHeight))
using (SKCanvas canvas = new SKCanvas(bitmap))
{
canvas.Clear(backgroundColor);
// Convert the HTML to a RichString
var richString = HtmlToRichString(html, textColor, videoWidth);
richString.MaxWidth = scaledWidth;
richString.MaxHeight = scaledHeight;
richString.Paint(canvas, new SKPoint(15, 20));

// Encode the bitmap as PNG
using (SKData data = SKImage.FromBitmap(bitmap). Encode(SKEncodedImageFormat.Png, 100))
{
return data.ToArray();
}
}
}

Dictionary<string, string> fontMap = new Dictionary<string, string>
{
{ "ql-font-timesnewroman", "Times New Roman" },
{ "ql-font-arial", "Arial" },
{ "ql-font-verdana", "Verdana" },
{ "ql-font-couriernew", "Courier New" },
{ "ql-font-georgia", "Georgia" },
{ "ql-font-comicsansms", "Comic Sans MS" },
{ "ql-font-consolas", "Consolas" },
{ "ql-font-impact", "Impact" },
{ "ql-font-lucidaconsole", "Lucida Console" },
{ "ql-font-lucidasansunicode", "Lucida Sans Unicode" },
{ "ql-font-microsoftsansserif", "Microsoft Sans Serif" },
{ "ql-font-palatinolinotype", "Palatino Linotype" },
{ "ql-font-segoe-ui", "Segoe UI" },
{ "ql-font-tahoma", "Tahoma" },
{ "ql-font-trebuchetms", "Trebuchet MS" },
{ "ql-font-symbol", "Symbol" },
{ "ql-font-webdings", "Webdings" },
{ "ql-font-wingdings", "Wingdings" }
};

// Additional methods and functionalities...
}
}

Asset Management in Content Hub

The core functionality revolves around managing video assets within the Content Hub. This encompasses creating, updating, and fetching assets. The Sitecore Content Hub SDK is at the heart of these operations, ensuring seamless integration.

Creating Asset Files

When introducing new video assets to the Content Hub, we utilize Azure Blob Storage for efficient storage and retrieval.

public async Task<(long ResponseId, long FileSize)> UploadVideoToContentHub(string videoBlobUrl, string videoExtension)
{
long responseId = 0;
long fileSize = 0;

try
{
_logger.LogInformation("Starting the video upload process...");
var uri = new Uri(videoBlobUrl, UriKind.Absolute);
var mClient = _mClientFactory.Client;
var cancellationTokenSource = new CancellationTokenSource();

using (var memoryStream = await MemoryStreamHelper. GetMemoryStreamFromUrlAsync(uri, cancellationTokenSource.Token))
{
string shortUniqueId = Guid.NewGuid().ToString().Substring(0, 7);
var contentType = VideoHelper.GetVideoContentType(videoExtension);
var name = $"video_caption_editor_{shortUniqueId}.{videoExtension}";
var uploadSource = new StreamUploadSource(memoryStream, contentType, name);
fileSize = memoryStream.Length;
var request = new UploadRequest(uploadSource, "AssetUploadConfiguration", "NewAsset")
{
ActionParameters = new Dictionary<string, object>
{
{ "FileSize", fileSize },
}
};

// Initiate upload and wait for its completion.
var response = await mClient.Uploads.UploadAsync(request, cancellationTokenSource.Token).ConfigureAwait(false);
responseId = (long)await mClient.LinkHelper.IdFromEntityAsync(response.Headers.Location).ConfigureAwait(false);
}
_logger.LogInformation("Video upload process completed successfully.");
return (responseId, fileSize);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
_logger.LogError(ex, "Authentication failed. Check your credentials or authorization token.");
throw new AuthenticationException("Authentication failed. Check your c redentials or authorization token.");
}
catch (Exception ex)
{
_logger.LogError(ex, $"File upload failed: {ex.Message}");
throw new VideoUploadException($"File upload failed: {ex.Message}");
}
}
}
}


// Additional methods and functionalities...

Updating Asset Files

The Video Caption Editor allows for updating existing assets. This is vital for making real-time edits to video captions and ensuring the Content Hub contains the most recent version.


public async Task<(long ResponseId, long FileSize)> UploadVideoToContentHub(IEntity asset, string fileName, string videoBlobUrl, string videoExtension)
{
long responseId = 0;
long fileSize = 0;
long assetId = asset.Id.Value;

try
{
_logger.LogInformation("Starting the video upload process...");
var uri = new Uri(videoBlobUrl, UriKind.Absolute);
var mClient = _mClientFactory.Client;
var cancellationTokenSource = new CancellationTokenSource();

using (var memoryStream = await MemoryStreamHelper.GetMemoryStreamFromUrlAsync(uri, cancellationTokenSource.Token))
{
var contentType = VideoHelper.GetVideoContentType(videoExtension);
var uploadSource = new StreamUploadSource(memoryStream, contentType, fileName);
fileSize = memoryStream.Length;
var request = new UploadRequest(uploadSource, "AssetUploadConfiguration", "NewMainFile")
{
ActionParameters = new Dictionary<string, object>
{
{ "AssetId", assetId},
{ "FileName", $"{assetId}_{fileName}" },
{ "FileSize", fileSize },
}
};

var response = await mClient.Uploads.UploadAsync(request, cancellationTokenSource.Token).ConfigureAwait(false);
responseId = (long)await mClient.LinkHelper.IdFromEntityAsync(response.Headers.Location).ConfigureAwait(false);
}
_logger.LogInformation("Video upload process completed successfully.");
return (responseId, fileSize);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
_logger.LogError(ex, "Authentication failed. Check your credentials or authorization token.");
throw new AuthenticationException("Authentication failed. Check your credentials or authorization token.");
}
catch (Exception ex)
{
_logger.LogError(ex, $"File upload failed: {ex.Message}");
throw new VideoUploadException($"File upload failed: {ex.Message}");
}
}


// Additional methods and functionalities...

Azure Batch Video Processing

For efficient video processing, especially when dealing with bulk operations, the Video Caption Editor integrates with Azure Batch. This provides scalability and ensures videos are processed in a timely manner without overloading resources.

BatchSharedKeyCredentials credentials = new BatchSharedKeyCredentials(batchAccountUrl, batchAccountName, batchAccountKey);
using (BatchClient batchClient = BatchClient.Open(credentials))
{
// Additional methods and functionalities...
            }

Conclusion

The Video Caption Editor, with its integration into the Sitecore Content Hub and Azure Functions, represents a significant advancement in DAM systems. Whether you're converting HTML captions to images, managing assets, or processing videos in batches, this tool ensures a streamlined and efficient workflow.

Stay tuned as we continue to explore more features and dive deeper into the world of DAM through the lens of our Video Caption Editor for Sitecore Content Hub.