Wednesday, March 29, 2023

Talking to a typical customer about the road to composable - SUGCON 2023

Talking to a typical customer about the road to composable

I was very pleased to be able to present a new lightning talk at SUGCON this year in sunny Malaga. Where is usually go for technical topics, the idea this year was rather different so I am glad the response was pretty well. I had various good talks afterwards about it so I guess I must have somehow hit a spot. 
And of course, there were some cats 😸.

Description 

We've probably all seen sessions about moving to composable lately and we all know this is the road ahead but how do you talk about this to a customer that is used to his Sitecore editing environment and is sending mails with EXM.
In this talk I will explain how we handled this with a (B2B) customer that has been using Sitecore for over a decade. We brought them a story that (pl)eased them and opened the conversation to start the composable journey together. Taking into account procurement departments, IT departments, business owners and so on. Most of us have learned that for a company that is not willing to take a real big bang (hereby questioning not only products but also vendors and partners) it is not such an easy journey. The example I want to present in this talk is not the biggest customer or the most sexy solution - it is about one of many Sitecore customers that are looking at the future and are not sure what to do.
I'll present the roadmap we gave them - trying to give you ideas and/or open a discussion on how to start the journey for your customer as well.

Presentation @ Sugcon - Malaga March 24th 2023

Video

Videos are still in progress and will be uploaded to Youtube. I will edit this post and embed it when it's available. 

Slides

I can already share the slides - as some people requested them. Note that they don't come with all the animations which makes it less appealing and you'll miss some cats but if you were there you'll get the ideas (again).




PS: if other Sitecore UserGroups are interested, I can present this remotely of you want. That way you do get all the extra fun. Just reach out on Twitter or Slack. It's a "lightning" talk so it takes 15 minutes (well, maybe a minute more) - that is without Q&A... 


Wednesday, February 15, 2023

XMCloud SxaStarter local setup - solr-init issue

 XMCloud - solr-init error on a local container setup with SxaStarter 


Headless SXA

I wanted to try the new headless SXA. As this can be installed on Sitecore 10.3 I installed that version locally and installed SXA on top (tip: a good blog post from Dan about this setup). That went fine, but I bumped into issues with SXA. I asked on StackExchange and Slack but nobody seemed to know the answer (if you do, please answer the question on SSE). I was going to open a support ticket but as this was just a test... well, you know... I'll do that when I need it in a project.

I heard there were differences between SXA in Sitecore 10.3 and the version in XM Cloud. So I decided to try that one.

XM Cloud

As XM Cloud is sort of a SAAS solution there is some discussion about the fact why people would install it locally and it does make sense not to do is - but I was in test and mess-around mode so let's do that on a local setup (just because we can).

As I'm no expert (yet) in containers nor nextJS and such, I was looking for a simple way to do this. With the information I gathered during various sessions about XM Cloud, that should be possible.

The setup 

I found a blog post from Serge van den Oever who already succeeded in such a setup and documented it very well - thanks Serge for the very informative blog.

The setup seemed to go very well...  it takes a while to download all the images but at the end, I had a site that started and I could create the SXA site. Even the creation of the rendering container went fine - some issues mentioned in Serges post seem to be gone, some are still there. But I ended up with a running environment. 

Even Headless SXA worked. So the issues I had on my local XM were not present in this XM Cloud version. Hooray, cheers, all happy...   so why am I writing this post?

The issue

Of course, there had to be an issue. The next day the containers wouldn't start anymore. The solr was going crazy and that means nothing works.  Well in fact, the solr-init won't run because the solr isn't healthy. 

I started two tracks: as I had another laptop I tried the same installation there.. and at the same time started searching on Sitecore StackExchange, Google...    The second installation on the other laptop worked. Once 😞  The result was the same: solr-init would fail when trying to restart the containers. 

In the meantime my search quest led to a blog post from Jeremy Davis that actually didn't sound very hopeful - as you can read in his blog he had the same issue with Sitecore containers (not XMCloud) and already tried several things. So I was not going to try all of those again, but on my machine his workaround "docker network prune" didn't work.

But he does also mention an alternative provided by Rob Ahnemann. So that is a third Sitecore MVP bumping into the same issue 😨  Rob notes that the issue could be solved by removing the Zookeeper - so actually using Solr standalone instead of SolrCloud. This sounds reasonable, but we would need a change to the solr-init. As I'm no Solr expert (and neither a container expert) I'm very glad Rob provided us a full fledged solution on docker hub. 

So, let's try this. If you want to do this as well, read his post to understand the options for the solr-init image. His example is for XM - so we have to make a few small changes to get this working for XMCloud. It will result in a solr-config yml section like this:
solr-init:
    isolation: ${ISOLATION}
    image: rahnemann/solr-init:1.0-ltsc2019
    environment:
      TOPOLOGY: xm-sxa
      SITECORE_SOLR_CONNECTION_STRING: http://solr:8983/solr
      SOLR_CORE_PREFIX_NAME: ${SOLR_CORE_PREFIX_NAME}
      ADDITIONAL_SITECORE_CORES: _horizon_index
    volumes:
      - type: bind
        source: ${LOCAL_DATA_PATH}\solr
        target: c:\solr
So we are using the image from rahnemann here, we kept the topology xm-sxa as that fits our purpose but we have to add an additional Sitecore core for (oh boy) "Horizon". This is the Pages editor (which is not Horizon, but actually is).

You will also need to change the solr mode and the solr connectionstring:
solr:
    ...
    environment:
      SOLR_MODE: standalone
cm:
    ...
    environment:
      ...
      Sitecore_ConnectionStrings_Solr.Search: http://solr:8983/solr
    ...
After applying these changes, I can now run the containers again. More than once. 

It doesn't feel like a very decent solution. And I guess lots of people will not have these issues - but as some people did and blogged about it I would also assume there are more out there who actually do bump into this and maybe this post about my experience can save you some time of you do.

Tuesday, July 5, 2022

Extending the Edge endpoint in Sitecore JSS

Sitecore JSS & the Edge endpoint

I was working on a project with Sitecore JSS, NextJS and the Edge endpoint - which is the default when you use the starter template from Sitecore and apparently the way to go now. If you read some of my previous you might know we had some issues getting it all to work but in the end it all worked like a charm. 

But we are used to tweak/improve Sitecore to our specific needs...  so I wanted to add a custom field (the tokened treelist as described before). If I now query items with such a field I get those values as strings - the field is not handled like a normal TreeList. I read in the documentation that extending the schema is not supported as it would break compatibility with Experience Edge - so that would mean we cannot use this custom field. So I was curious and wanted to try anyway.

I found 2 options to get this done. Well, to be honest.. one that actually worked and one that should work but I didn't have enough time to investigate fully. 

Let's start with that last one.

SchemaExtender

A SchemaExtender should be able to add or modify types to the completed schema. There is some documentation on the Sitecore docs but it's rather limited. I managed to get some code working to extend the options for an image field. However, in the (limited) time I had I couldn't get the extra custom TreeList to work. So if anyone knows how to do this, I would really appreciate a blogpost. Maybe I'll add this question to Sitecore StackExchange as well.

I can share the code to extend the images though:

public class ImageSchemaExtender : Sitecore.Services.GraphQL.Schemas.SchemaExtender
{
  public ImageSchemaExtender()
  {
    ExtendType<Sitecore.Services.GraphQL.EdgeSchema.GraphTypes.FieldTypes.ImageFieldGraphType>("ImageField", type =>
    {
      ExtendField(type, "src", field =>
      {
        field.Arguments.Add(new QueryArgument<BooleanGraphType>
        {
          Name = "ratio",
          Description = "Ignore ratio"
        });
        
        field.Resolver = new FuncFieldResolver<Field, object>(context => GetUrl(context.Source, context.Arguments));
      });
    });
  }

  protected string GetUrl(ImageField field, Dictionary<string, object> arguments)
  {
    var mediaItem = field.MediaItem;
    if (mediaItem == null)
      return (string)null;
    var options = new MediaUrlBuilderOptions();
    if (arguments.TryGetValue("maxWidth", out var obj1) && int.TryParse(obj1?.ToString(), out var result1))
      options.MaxWidth = result1;
    if (arguments.TryGetValue("maxHeight", out var obj2) && int.TryParse(obj2?.ToString(), out var result2))
      options.MaxHeight = result2;
    if (arguments.TryGetValue("ratio", out var obj3) && bool.TryParse(obj3?.ToString(), out var result3))
      options.IgnoreAspectRatio = result3;
    return MediaManager.GetMediaUrl(mediaItem, options);
  }
}
<sitecore>
  <api>
    <GraphQL>
      <endpoints>
        <edge>
          <extenders hint="list:AddExtender">
            <imageExtender type="Project.Platform.Foundation.Images.ImageSchemaExtender, Project.Platform" />
          </extenders>
        </edge>
      </endpoints>
    </GraphQL>
  </api>
</sitecore>
You can see here that we can extend the ImageFieldGraphType. As mentioned - if anyone reading this knows how to create such an extender that would extend the schema in a way that a custom TreeList field works just like the ootb one I would love to read that solution 😉


FieldTypeFactoryStore

Other endpoints seem to have options to plug your code in through config so I tried to find out where the Edge endpoint defines how a field should be mapped to a piece of code. And I found that part in a class called FieldTypeFactoryStore. 

Problem with this class is that it is clearly not meant to be extended. As I'm not sure if adding custom fields is actually supported this might make sense. But just for being curious I tried and it can work. Create your own version of this class and register it.. that can do it. Although I believe it is not a good idea.

using System.Collections.Generic;
using Sitecore.Services.GraphQL.Content.TemplateGeneration.FieldMapping;
using Sitecore.Services.GraphQL.EdgeSchema.GraphTypes.FieldTypes;
using Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping;

namespace Project.Platform.Foundation.Images
{
  public class FieldTypeFactoryStore : IFieldTypeFactoryStore
  {
    private readonly Dictionary<string, IFieldTypeFactory> fieldTypes;

    public FieldTypeFactoryStore()
    {
      fieldTypes = new Dictionary<string, IFieldTypeFactory>();
      fieldTypes.Add("Checkbox", new GenericFieldTypeFactory<CheckboxFieldGraphType>());
      fieldTypes.Add("Date", new GenericFieldTypeFactory<DateFieldGraphType>());
      fieldTypes.Add("Datetime", new GenericFieldTypeFactory<DateFieldGraphType>());
      fieldTypes.Add("Image", (IFieldTypeFactory)new GenericFieldTypeFactory<ImageFieldGraphType>());
      fieldTypes.Add("Integer", new GenericFieldTypeFactory<IntegerFieldGraphType>());
      fieldTypes.Add("Number", new GenericFieldTypeFactory<NumberFieldGraphType>());
      fieldTypes.Add("Checklist", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Multilist", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Multilist with Search", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Name Value List", new GenericFieldTypeFactory<NameValueListFieldGraphType>());
      fieldTypes.Add("Name Lookup Value List", new GenericFieldTypeFactory<NameValueListFieldGraphType>());
      fieldTypes.Add("Treelist", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("TreelistEx", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Droplink", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("Droptree", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("General Link", new GenericFieldTypeFactory<LinkFieldGraphType>());
      fieldTypes.Add("General Link with Search", new GenericFieldTypeFactory<LinkFieldGraphType>());
      fieldTypes.Add("Rich Text", new GenericFieldTypeFactory<RichTextFieldGraphType>());
      fieldTypes.Add("lookup", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("reference", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("tree", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("default", new GenericFieldTypeFactory<ItemFieldGraphType>());
      ....
    }

    public IFieldTypeFactory GetFieldFactory(string fieldType)
    {
      fieldTypes.TryGetValue(fieldType, out var fieldTypeFactory);
      return fieldTypeFactory;
    }

    public IFieldTypeFactory Default => fieldTypes["default"];
  }
}
<sitecore>
  <services>
    <register serviceType="Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping.IFieldTypeFactoryStore, Sitecore.Services.GraphQL.EdgeSchema" implementationType="Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping.DefaultFieldTypeFactoryStore, Sitecore.Services.GraphQL.EdgeSchema">
	  <patch:attribute name="implementationType">Project.Platform.Foundation.Images.FieldTypeFactoryStore,Project.Platform</patch:attribute>
    </register>
  </services>
</sitecore>

So..  I'm afraid I didn't really get it working - yet.  But sharing it anyway as we/you can also learn from attempts that were not completely successful. If anything changes I'll keep you posted. I anyone who reads this has more information, please keep me posted as well...

Future - the cloud

What this means in the future with XM Cloud..  I don't know. I was told it should be possible so maybe it will. Or maybe it won't. It makes sense that you can't change how the Edge works - but as I don't know the details about it, it's not (yet) clear what will be possible and what not. Room for more blog posts 🙂


Monday, June 6, 2022

Why Sitecore Send is your new mail friend - SUGCON 2022

 Why Sitecore Send is your new mail friend?

The videos from the presentations at SUGCON 2022 in Budapest are live so it's time to share the goodies. 

You can find the full playlist of all videos on Sugcon Youtube channel. My lightning talk -an introduction to Sitecore Send- is embedded:


You can also check the slides:

Enjoy.

And hopefully see you (live) at a next Sitecore event...

Friday, April 22, 2022

Sitecore CLI login to multiple environments

 Sitecore CLI login to multiple environments

This post is maybe just for my own reference - but if it helps anyone else it was worth writing it. It's also on Sitecore StackExchange as that will be better indexed probably.

The problem

I was using the Sitecore CLI to login to multiple environments from my local machine. I could successfully login to my local (containerized) instance with a interactive login. I could also login to a remote instance (on Sitecore Managed Cloud). That login was non-interactive.  However, after a login to a remote environment, the local login didn't work anymore.

I was following the official documentation on Sitecore CLI login.

The error

Error while getting client credentials token: invalid_client

 

The solution

Make sure to always provide the --client-credentials argument. Even though it defaults to false and is not mentioned in the docs in the interactive login command, it does fix the issue. If it was set to true before, you need to reset it to false. So for an interactive login, use:

dotnet sitecore login --authority https://<Sitecore identity server> --cm http://<Sitecore instance> --allow-write true --client-credentials false

 



Tuesday, April 19, 2022

Sugcon 2022

SUGCON 2022 - Budapest 

Three years after the last in-person SUGCON (London 2019) we were finally able to meet the Sitecore Community again in real life. On stage, off stage, on a boat or in a lounge chair, with a beer or a wodka (or two).  It was great an great events deserve a blog post. I'm not going to write about all the sessions I saw - they were recorded and you will be able to see all of them on the Youtube channel of Sugcon. There was a lot of good content, so I would suggest to watch once they are available.  If you can't wait there is already a nice summary in the blog of Adam which is definitely worth reading. 


The journey

Travel arrangements were not always that easy this year. Lots of people had issues with covid regulations, flight plans, airport strikes and so on but at least we got there. Some of us didn't -we all know why- and that is a real pity.  I really hope you can all make it next time - as that would also mean the world has turned into a better place again. 

But back to the conference. Or the journey, because I had almost forgotten a Sitecore conference starts with the travel pictures and a whatsapp group going crazy with people seeking fellow travelers, transportation, or just beer 😀 Due to circumstances my flight schedule brought me in Munich. Fate, I guess, as this made me meet and greet an ex-colleague and other attendants even before the final flight. A good way to start a conference - as meeting people is at least as important as the sessions. 

As a speaker on the conference I had the privilege to be invited to the speaker event - a great initiative, thank you organizing team. You've probably seen the beautiful pictures on social media of Budapest by night. And we spend the rest of our first day as it should on such an event.. having drinks with the wonderful and enthusiastic group of SUG-DE, having drinks with community dinosaurs (no names mentioned) and other members of our amazing Sitecore community - special thanks to our Polish friends for the bottles 😋.    


The city

People who got the chance to visit the city of Budapest were not disappointed. The weather was very nice so lots of attendants were attracted to at least get a glimpse of Budapest center. Getting some fresh air is always a good idea and it helps to keep focused during the conference (or get the wodka out of your system).

And even Kevin was there...


The event

But of course we were no tourists - we were there for the first in-person SUGCON in years. So once more thanks to the organizers for making it happen and thank to the sponsors to support this. I think  a gigantic weight fell of Tamas' shoulders when he finally was able to start this hometown event. 

As I mentioned I'm not going to share lots of pictures of slides here - you can find those on social media or you can watch the full recordings on Youtube anytime soon. 

However, if you start the event with the CEO and CPO of Sitecore the tone is set and you know this is going to be good.



This is a community events of course. So a special mention maybe for a few more real community efforts. First of all the Sitecore StackExchange. A great place to ask questions in order to create a place that stores a great amount of Q&A based Sitecore knowledge. And it can't happen without you. And you. And all of you.


Another initiative that is definitely worth mentioning is the Sitecore Lunch. You might have seen this passing by on Twitter or on Slack and for those who can't join the American version (on Friday evening for European folks so that is not so easy to manage) there is an alternative on its way - check the European Sitecore Lunch...

There were lots more sessions.. I also liked the "lightning sessions" - a shorter format but in my opinion a great way to get some topics on the agenda that might not fill a full 45' slot. Really enjoyed giving one of those myself (on Sitecore Send). 

It was fun to be able to present in-person again. I've done some presentations virtually (on Sugcon and smaller usergroups) but the smaller the distance between speaker and audience the easier it is to get feedback and discuss your topic with other people. And that is what matters.. a good session should inform, trigger the audience and get a conversation started.


Best for last

Saving the best for last is a commonly used strategy on any event. The last slots in the agenda on this Sugcon were for a very interesting introduction into the possibilities of Sitecore Discover - one of the last acquisitions and probably not yet very known - and an even more interesting overview of what lies ahead: the roadmap. What is Sitecore working on? What can be we expect in the near future? No need to explain that a tag cloud would scream the words composable and headless but to make sure we all realize what Sitecore means by that Roger Connolly (VP of product management) showed us this slide: 
Sitecore composable editor

For me this is one of the most important slides of the conference - and I'm not alone with that thought

So we heard (and saw) a lot about XM cloud - and OrderCloud, Content Hub, CDP ... - but it's when you see editing environments that will be composable as well you can imagine Sitecore is really going this way and yes, that looks promising and it makes me hopeful for Symposium. (all under the assumption that it will actually work and this is not just another Horizon - oh yes, I said it).

Yes, promising. Let's continue this conversation end of October in Chicago!
 
 


Friday, March 18, 2022

Sitecore Managed Cloud with SXA and JSS

 Sitecore Managed Cloud with JSS and NextJS

First of all: thanks to David Szöke who recently also wrote an article on Sitecore Managed Cloud saving me a lot of time as I now have to write a lot less - thanks for sharing David! 👍
And if you haven't read it yet, you should.

Before we continue, one small step back: if you don't know what Managed Cloud is and you need to get acquainted the documentation is (like always) a good start. 
Sitecore Managed Cloud is a hosting service that enables customers to deploy Sitecore Experience Manager (XM), Sitecore Experience Platform (XP), and Sitecore Experience Commerce (XC) on Microsoft Azure. It is an alternative to running the Sitecore platform on premises or via the public cloud. With Sitecore Managed Cloud, Sitecore actively monitors, manages, and maintains both the infrastructure and the applications.


So now you know everything about Managed Cloud. You might want to learn a bit about Kubernetes and using kubectl as well, but that is not the focus here. I would like to share some more experiences (and issues) we recently had in our project with SXA, JSS and NextJS that we are hosting on this Managed Cloud. Combined with the documentation and the article from David that should get you going.


Installing JSS - SXA - Horizon

In our project we are using SXA (with SPE), JSS and Horizon. In Managed Cloud these are not installed for you, but it is well documented on how to do this. You will need to make changes to json and yaml files in the Application repository - one tip: get enough sleep so you are focused.. a typo is easily made (and not always easy to find.. ). Also note space matters, and tab is no space.

Although following some documentation seems very simple, we did run into some issues. There is a mechanism that will make sure the init tasks don't override stuff that was already initialized and this blocked the installation of SXA because the Solr indexes could not be created. We contacted Sitecore Support and got a fix for the issue. A new start script was provided for our solr-init container. We added it to the solution and with a small adjustment to the DockerFile we added it into a new image that could be used to execute the installation.
ARG PARENT_IMAGE
ARG SXA_IMAGE

FROM ${SXA_IMAGE} as sxa
FROM ${PARENT_IMAGE}

COPY --from=sxa C:\module\solr\cores-sxa.json C:\data\cores-sxa.json

COPY Start.ps1 c:\

Now we were all set and ready to go. The rendering host was added and the application seemed to work. Except...

JSS sc_apikey

We have some components that need to request data from the Sitecore GraphQL endpoint at the client (e.g. to load "more" data in a list). This didn't work - the same queries did work in the rendering. Luckily the error was pretty clear and said that the sc_apikey was not provided. Which was weird because we could see the key in the header of the request. It also worked perfectly on our local containers so we assumed the issue was related to the infrastructure. 

As a workaround Sitecore Support suggested to include the sc_apikey in the querystring instead. 

We were already using a custom graphql client factory to build the url so that was a rather easy task. We did build this factory also because we already encountered issues with the needed environment variables and their availability on clients/servers, differences between local setups and development environments in the cloud and mainly because the url for the GraphQL endpoint on the rendering host is the Content Delivery but on the client it has to be your rendering host (forwarding it to the CD). Just a tip that such a factory might be a good idea if you also need to use the sc_apikey on the client.

The querystring solution works. And the header issue was related to nginx blocking headers with underscores. There is a fix available through Support though - so you can go back to using the header instead of the querystring.


JSS editing secret

When you deploy to the Managed Cloud you should use the KeyVault that is provided for you to store all kind of secrets and environment variables. Lots of them are already in there - provided by Sitecore. But you will need to add some yourself if you are using additional modules like JSS. One of those secrets is the JSS editing secret. This has to be added to the Content Management (CM) and the rendering host. A good developer is by default lazy and tends to start copy/pasting...  but in this case that was not such a good idea as apparently in the CM Sitecore called the parameter SITECORE_JSS_EDITING_SECRET and in the rendering host it has to be called JSS_EDITING_SECRET.  Just a small difference but it does break the editors and it can take some time to find out why. But now you are warned and shouldn't make that same mistake :)


Kibana

This might sound like a sunny Brazilian beach but it's actually the place to find your logs - based on Elastic Search. You'll find the information in the Sitecore docs, your url and login information in the secrets and you're good to go. 

If you are not used to this environment it can be a bit tricky to find what you are looking for. First of all: find the navigation - and go to "Discover" in the "Analytics section.  
You should get a lot of entries that might make no sense so you need to start filtering. The servicename is a good place to start: those will be like "sitecore-xm1-cm" and so on. The pod_name can also be useful if you know it. Best way for me was start simple and check the results to see which fields are available and can be used to filter further to get the logs needed.



Conclusion

Don't be afraid of the Managed Cloud. I hope I saved you some time with this blog. Read Davids article as well. Call Support when needed. And enjoy the result when it's all working.