Showing posts with label error. Show all posts
Showing posts with label error. Show all posts

Sunday, 5 May 2024

Einstein Copilot - AI + (most of your) Data + CRM?

Image created by DALL-E 3 based on a prompt from Bob Buzzard

Introduction

Einstein Copilot from Salesforce went GA around a week ago (25th April 2024, for future readers), which changes my attitude to it. When things are in preview/pilot/beta, I'll take a bit of additional effort tracking down issues if it means I get my hands on things earlier. Once a feature goes GA though, the gloves are off - real money is changing hands to use these features so we hold them to a higher standard.

As the title of this post suggests, my first disappointment post-GA was that I can't use all my data across Generative AI - specifically Tasks and Events, part of the Salesforce Activities feature. This won't come as a surprise to old hands in the Salesforce ecosystem - hands up those who remember using text fields to capture IDs because custom lookups weren't available! More recently, they still aren't supported by the User Interface API, which means some of the standard LWC functionality won't work for them, and it's slightly concerning that an idea to add this capability has been open for 3+ years without even a comment from Salesforce. Yes, Activities are really Tasks, Events and Calendars on each others shoulders in a long overcoat, but this was the choice Salesforce made and they need to own it

There are workarounds for most areas of the platform where support for Activities is not as complete as it could be, but that isn't the case for the first issue that I encountered.

Prompt Template Parameters

Activities can't be specified as parameters to Prompt Builder. The docs suggest they should be available, as they are standard objects:

My use case was I wanted to send an Event record to an LLM, which contained details of a meeting that had taken place with a customer. The record includes the intended outcome, notes from the meeting, and any comments from company attendees after they've had a short period of time to reflect.  The LLM would then recommend the best course of action based on the meeting.This would then be used to create a Copilot action, so my users didn't could focus on doing the next step rather than figuring it out.

Searching for Event in the resource picker gave me pause for thought:


Maybe it's under Activity/Activities? Sadly not:


For the sake of completeness I also checked for Task - same result:


Now this doesn't mean that Activities can't be used with Prompt Builder - I can access the Open Activities and Activity History if I specify another object like Opportunity as the resource:



As this is the only way to specify a record to a Prompt Template, there aren't any workarounds to handle this, or none that don't suck anyway.

I could pass the Who and What information to the template and try to figure out the Event - for example, if the Event was with a Contact to talk about an Opportunity, I could pass the IDs for those in and use Apex/Flow grounding to retrieve the Events that match these IDs and hope I can figure out which one the user is interested in. If there are a number of them, or the user doesn't want something simple like the last one, I'm highly likely to pick the wrong one and give them unwanted advice about it.

I could make the users populate a lookup to a custom object and then pass the ID of that into the Prompt Template. The downside to this is my users have to do extra work in order to make the AI assistant useful - the exact opposite of how it's supposed to work. I'd also have to do a data fix to create custom object records for the recent history of Events.

Rendering Lists of Activities

One of the nice features of Copilot is if my custom action returns a list of sobjects in the correct format, it will apply Lightning Design System formatting:


So I decided to create a custom action that retrieves the Tasks that I should be focusing on - this has a bunch of rules in the action to determine which Tasks are actually important, as anyone who creates a Task for me will naturally mark it as Urgent and High Priority.

I'm able to retrieve the list of Tasks, but Copilot can't render them:


and check out that error message - Copilot fails to render the information provided and suggests you talk to your admin about it! I predict frustration all round!

Undeterred, I changed my code to return a list of custom Apex class instances that mirrored a task, so a property for Id, a property for Subject etc. Slightly better, in that it briefly rendered some text and then errored again.

After quite a bit of trial and error I was able to get a list of custom Apex class instances to render - I had to either remove the Id of the task, or put it into a String property that had any name other than Id. It looked awful by comparison, and I had no way to generate a link to any of the Tasks, as I'd had to put a false nose and dark glasses on the Id. The user could copy and paste the Id, but it seemed like the introduction of an AI assistant was once again leading to more work for the User, not less. So here there is a workaround, but it's not a great user experience.

My suspicion regarding this problem is that if Copilot can detect that there is either a Task or the Id of a Task in the data, it tries to pull some information about the Task sObject and then errors. Maybe via the User Interface API, given this is rendering on the screen, which doesn't support Tasks.

Conclusion

I'm really surprised at this gap, as Tasks and Events are pretty fundamental to Customer Relationship Management. It's either quite the oversight or, left out because the APIs don't support them in. the hope that nobody notices. It's all well and good having every web and commerce interaction with your customer in Data Cloud, but if you can't ask questions of your standard CRM objects, then we really aren't getting the AI + Data + CRM that we've been promised will change our lives.

Related Posts



Saturday, 2 October 2021

Transaction Boundaries in Salesforce (Apex and Flow)

Introduction

Winter 22 introduces the capability to roll back pending record changes when a flow element fails at run time. I had two reactions to this - first, that's good; second, how come it didn't always do that? It took me back to the (very few) days when I did Visual Basic work and found out that boolean expressions didn't short circuit - it's something that has been such an integral part of other technologies that I've worked with, it never occurred to me that this would be different.

The original idea for this post was to call out the roll back only applies to the current transaction, and tie in how Apex works as a comparison. But then it occurred to me that in the Salesforce ecosystem there are many people teaching themselves Apex who could probably use a bit more information about how transactions work, So, in the words of Henry James wrote in his obituary for George du Maurier (referring to the novel Trilby):

"the whole phenomenon grew and grew till it became, at any rate for this particular victim, a fountain of gloom and a portent of woe"

Transactions

A transaction is a collection of actions that are applied as a single unit. Transactions ensure the integrity of the database in the face of exceptions, errors, crashes and power failures. The characteristics of a transaction are known by the acronym ACID:

  • Atomic - the work succeeds entirely or fails entirely. Regardless of what happens in terms of failure, the database cannot be partially updated.
  • Consistent - when a transaction has completed, the database is in a valid state in terms of all rules and constraints. This doesn't guarantee the data is correct, just that it is legal from the database perspective.
  • Isolated - the changes made during a transaction are not visible to other users/requests until the transaction completes. 
  • Durable - once a transaction completes, the changes made are permanent. 

Transactions in Apex


Apex is different to a number of languages that I've used in the past, as it is tightly coupled with the database. For this reason you don't have to worry about transaction management unless you want to take control. When a user makes a request that invokes your code, for example a Lightning Web Component calling an @AuraEnabled method, your code is already in a transaction context. 

When a request completes without error, the transaction is automatically committed and all changes made during the request are applied permanently to the database.  This also causes work such as sending emails and publishing certain types of platform events to take place. Sending an email about work that didn't actually happen doesn't make a lot of sense, although this caused us plenty of angst in the past as we tried to find ways to log to an external system that a transaction had failed (Publish Immediately platform events finally gave us a mechanism to achieve this).

When a request encounters an error, the transaction is automatically rolled back and all changes made during the request are discarded. Often the user receives an ugly stack trace, which is where you might decide that you need a bit more control over the transaction.

Catching Exceptions


By catching an exception, you can interrogate the exception object to find out what actually happened and formulate a nice error message to send to the user. However, unless you surface this message to the user, you have changed the behaviour of the transaction, maybe without realising it. For example, if you catch an exception and return a message encapsulating what happened:
Account acc=new Account(Name='TX Test');
Contact cont=new Contact(FirstName='Keir', LastName='Bowden');
String result='SUCCESS';
try {
   insert acc;
   cont.AccountId=acc.Id;
   insert cont;
}
catch (Exception e) {
    result='Error ' + e.getMessage();
}

return result;
In my dev org, I have a validation rule set up that one of email or phone must be defined, so the insert of the contact fails and I see a fairly nice error message:


Unfortunately, this isn't the only issue with my code - as I caught the exception, the request didn't fail so the transaction wasn't automatically rolled back. While from the user's perspective the request failed, the first part of it succeeded and an account named TX Test was created:


and every time the user retries the request, I'll get another TX Test account and they will get another error message.  

Taking Back Control


Luckily Apex has my back on this, and if I want to (and I know what I'm doing) I can take control of the transaction and apply some nuance to it, rather than everything succeeding or failing.

Savepoints allow you to create what can be thought of as sub or nested transactions. A savepoint identifies a point within a transaction that can be rolled back to, undoing all work after that point but retaining all work prior to that point.  Changing my snippet from above to use a savepoint and rollback, I can ensure that all of my changes succeed or fail :
SavePoint preAccountSavePoint=Database.setSavePoint();
Account acc=new Account(Name='TX Test');
Contact cont=new Contact(FirstName='Keir', LastName='Bowden');
String result='SUCCESS';
try {
   insert acc;
   cont.AccountId=acc.Id;
   insert cont;
}
catch (Exception e) {
    result='Error ' + e.getMessage();
    Database.rollback(preAccountSavePoint);
}

return result;
The user's experience remains the same - they receive a friendly error message that the insert failed.  This time, though, there is no need to rid my database of the troublesome account. Prior to carrying out any DML I have created a Savepoint, and in my exception handler I rollback to that Savepoint, undoing all of the work in between - the insert of the account that was successful.  Note that rolling back the transaction has no effect on my local variables - prior to the return statement I have an account in memory that has an Id assigned from the database, but that doesn't exist any more. Of course this also leaves any other work that happened in the transaction outside of my code in place, which may or may not be what I want, so taking control of transactions shouldn't be done lightly.

Savepoints are also useful if there are a number of courses of action that are equally valid for your code to take. You set a Savepoint and try option 1, if that doesn't work you rollback and try option 2 and so on. This is pretty rare in my experience, as usually there is a specific requirement around a user action, but it does happen, like when option 2 is writing the details of how and why option 1 failed.
 
You can also set multiple Savepoints, each rolling back less and less work, and choose how far rollback to in the event of an error. Your co-workers probably won't thank you for this, and are more likely to see it as combining Circles of Hell into your very own Inferno.  If you do choose to go this route, note that when you rollback to a savepoint, any that you created since then are no longer valid, so you can't switch between different versions of the database at will.

Savepoints only work in the current transaction, so if you execute a batch job that requires 10 batches to process all of the records, rolling back batch 10 has no effect on batches 1-9, as those took place in their own transactions which completed successfully. 

Rolling Back Flow Transactions

After that lengthy diversion into Apex, hopefully you'll understand why I expect everything to rollback the transaction automatically when there is an error - it's been a long time since it has been my problem.  Per the Winter 22 release notes, flow doesn't do this :

Previously, when a transaction ended, its pending record changes were saved to the database even if a flow element failed in the transaction

and bear in mind that is still the case - what has been added is a new Roll Back Records element you can use in a fault path. Why wasn't this changed to automatically roll back on error? For the same reason that Visual Basic didn't start short circuiting boolean expressions - there's a ton of existing solutions out there and some of them will rely on flow working this way. While it's not ideal, nor is introducing a breaking change to existing customer automation. 

Something else to bear in mind is that this rolls back the current transaction, not necessarily all of the work that has taken place in the flow. Per the Flows in Transactions Salesforce Help article, a flow transaction ends when a Screen, Local Action or Pause element is executed. Continuing with my Account and Contact approach, if you've inserted the Account and then use a Screen element to ask the user for the Contact name, the Account is committed to the database and will persist regardless of what happens to your attempt to create a Contact.  Much like the batch Apex note above, rolling back the second (Contact) transaction has no effect on the first (Account) transaction as that has already completed successfully.

Enjoying Transactions?

Try distributed transactions:

And then scale them up across many microservices.

Many years ago I used to have to manage transactions myself, and in one particular case I had to work on distributing transactions across a number of disparate systems. It was interesting and challenging work, but I don't miss it!

Related Posts

Saturday, 15 November 2014

Visualforce, Required Fields and HTML5

A few weeks ago I encountered some unexpected behaviour around action regions and required fields which after some investigation turned out to be a consequence of using the HTML5 doctype. In this post I’ll present an example of the use of an action region in conjunction with required fields and show how it is impacted by HTML5.

Regular Form

As a very simple example, I have a page that allows the user to enter the name of an account and some additional information in a table.  The user can click the ‘Add Row’ button to add a new row to the additional information:

Screen Shot 2014 09 27 at 08 07 03

The markup for this page is a regular apex:form (the controller isn’t relevant to this post, but is available in the Resources section at the end of this post):

<apex:page controller="RequiredCtrl" tabstyle="Account">
  <apex:pageMessages id="msgs"/>
  <apex:form>
    <apex:pageBlock mode="maindetail">
      <apex:pageBlockButtons location="top">
        <apex:commandButton value="Save" action="{!save}" />
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Account Information">
        <apex:pageBlockSectionItem>
          <apex:outputlabel value="Name"/>
          <apex:inputfield value="{!Acc.Name}" />
        </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Related Information" columns="1">
        <apex:pageBlockSectionItem>
          <apex:commandButton value="Add Row" action="{!addRow}"/>
        </apex:pageBlockSectionItem>
        <apex:pageBlockTable value="{!rows}" var="row">
          <apex:column headerValue="Value 1">
            <apex:inputText value="{!row.val1}" />
          </apex:column>
          <apex:column headerValue="Value 2">
            <apex:inputText value="{!row.val1}" />
          </apex:column>
          <apex:column headerValue="Value 3">
            <apex:inputText value="{!row.val3}" />
          </apex:column>
        </apex:pageBlockTable>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Clicking the 'Add Row’ button causes the post back to fail as there is a required field missing:

Screen Shot 2014 09 27 at 07 51 34

Adding an Action Region

An action region demarcates a limited section of the form to submit back with the post back. Note that this also requires a rerender component to turn the post back into an Ajax request.  By enclosing my table in an output panel containing an action region, the required field no longer has an impact as it is outside the action region (note that I’m also rerendering my page messages component, as without that any errors will be swallowed as detailed in this post):

<apex:outputPanel id="rows">
  <apex:actionRegion>
    <apex:pageBlockSection title="Related Information" columns="1">
      <apex:pageBlockSectionItem>
        <apex:commandButton value="Add Row" action="{!addRow}" rerender="rows,msgs"/>
      </apex:pageBlockSectionItem>
     ...
    </apex:pageBlockSection>
  </apex:actionRegion>
</apex:outputPanel>

I can now click the ‘Add Row’ button and a new row is generated without errors:

Screen Shot 2014 09 27 at 08 00 34

Using the HTML5 Doctype

If I then decide I’d like to take advantage of some HTML5 features and turn on the HTML5 doctype on the page:

<apex:page controller="RequiredCtrl" tabstyle="Account" doctype="html-5.0">
  ...
</apex:page>

Clicking the ‘Add Row’ button suddenly fails again, but with a different style of error decorator:

Screen Shot 2014 09 27 at 08 01 32

Digging into the rendered HTML shows that an HTML5 specific attribute has been generated:

<input id="..." required="required" size="20" type="text" value="">

this attribute is handled directly by the browser, which obviously has no idea that there is a bunch of JavaScript lying in wait to trap the post back and turn it into an Ajax request, so it just checks the input element and blocks the post back if it is blank.

immediate=“true"

 The same behaviour can be seen when the immediate attribute is set to true for a command button, action function etc., for exactly the same reasons described above.

Workarounds

  • The easiest workaround, if you aren’t relying on any HTML5 features, is to turn off the HTML5 doctype.
  • If you need HTML5 features, make the field non-required by specifying the required attribute as false in your Visualforce markup if you can (you can’t do this with standard object names unfortunately)
  • Manage validation and error messages yourself - see the Field Level Error Messages … links in the Resources section.
  • If neither of the above are suitable, use JavaScript to remove the required attribute from the rendered HTML. This is pretty fragile though, as it requires your JavaScript to know which fields are part of an action region and which aren’t.

Resources

Saturday, 11 August 2012

The Importance of Page Messages

This week's blog is inspired by a lengthy thread that I was involved in on the Visualforce Discussion Board.  This concerned an action method tied to an onchange event not being called.  I was able to track down what the problem was thanks to the <apex:pageMessages/> component, which displays all error messages associated with the page.

Here's an example of how this component can help track down problems when rerendering parts of a page.  The following page simply presents a button to invoke an action method that increments the counter:

<apex:page controller="PageMessages">
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Count  = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

The controller contrives to produce an error rather than increment the counter:

public with sharing class PageMessages {
 public Integer countVal {get; set;}
 private Boolean fakeError=true;
 
 public PageMessages()
 {
  countVal=1;
 }
 
 public void click()
 {
  if (fakeError)
  {
   ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Fake Error'));
  }
  else
  {
   countVal++;
  }
 }
}

The page when rendered is as follows:




Opening the page in a browser window: and clicking the 'Click Me' button results in no change to the count value, and as the rerender attribute turns the command into an AJAX request, it looks like clicking the button has no effect whatsoever.  In this case I can turn on debug logging and see that an error message is being added to the page:

14:37:19.021 (21244000)|CODE_UNIT_STARTED|[EXTERNAL]|01p80000000cOqp|PageMessages invoke(click)
14:37:19.021 (21302000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:6
14:37:19.021 (21322000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:524
14:37:19.021 (21338000)|METHOD_ENTRY|[1]|01p80000000cOqp|PageMessages.PageMessages()
14:37:19.021 (21345000)|STATEMENT_EXECUTE|[1]
14:37:19.021 (21357000)|SYSTEM_MODE_ENTER|false
14:37:19.021 (21370000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:5
14:37:19.021 (21376000)|STATEMENT_EXECUTE|[1]
14:37:19.021 (21385000)|SYSTEM_MODE_EXIT|false
14:37:19.021 (21394000)|METHOD_EXIT|[1]|PageMessages
14:37:19.021 (21443000)|SYSTEM_MODE_ENTER|false
14:37:19.021 (21450000)|HEAP_ALLOCATE|[12]|Bytes:5
14:37:19.021 (21456000)|STATEMENT_EXECUTE|[11]
14:37:19.021 (21465000)|STATEMENT_EXECUTE|[13]
14:37:19.021 (21469000)|STATEMENT_EXECUTE|[14]
14:37:19.021 (21612000)|HEAP_ALLOCATE|[14]|Bytes:10
14:37:19.021 (21689000)|SYSTEM_METHOD_ENTRY|[14]|ApexPages.addMessage(ApexPages.Message)
14:37:19.021 (21697000)|ENTERING_MANAGED_PKG|
14:37:19.021 (21728000)|VF_PAGE_MESSAGE|Fake Error
14:37:19.021 (21738000)|SYSTEM_METHOD_EXIT|[14]|ApexPages.addMessage(ApexPages.Message)
14:37:19.021 (21746000)|SYSTEM_MODE_EXIT|false
14:37:19.021 (21775000)|CODE_UNIT_FINISHED|PageMessages invoke(click)

Its a lot easier just to add an <apex:pagemessages/> component to the page and rerender that as well:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Count  = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

which displays the error upon clicking the button:


Another scenario where its pretty much impossible to locate the problem without an  <apex:pageMessages/> component is where there is a required field on the page:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Text1 = " />
      <apex:inputText value="{!text}" required="true"/>
      <br/>
      <apex:outputLabel value="Count = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

This time the controller doesn't fake an error, the counter value is simply updated:

public with sharing class PageMessages {
 public Integer countVal {get; set;}
 public String text {get; set;}
 
 public PageMessages()
 {
  countVal=1;
 }
 
 public void click()
 {
  countVal++;
 }
}

As the required component isn't an <apex:inputField/>, there is no decoration of the element to indicate that it is required.  Clicking the 'Click Me' button once again appears to do nothing.


Checking the log this time reveals nothing that can help.  The click method simply isn't being called:

25.0 APEX_CODE,FINEST;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WORKFLOW,INFO
15:06:55.036 (36399000)|EXECUTION_STARTED
15:06:55.036 (36462000)|CODE_UNIT_STARTED|[EXTERNAL]|06680000000Tmbw|VF: /apex/kab_tutorial__PageMessage1
15:06:55.046 (46438000)|CODE_UNIT_STARTED|[EXTERNAL]|01p80000000cOqp|PageMessages <init>
15:06:55.046 (46470000)|SYSTEM_MODE_ENTER|true
15:06:55.047 (47466000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:8
15:06:55.047 (47490000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:552
15:06:55.047 (47513000)|METHOD_ENTRY|[1]|01p80000000cOqp|PageMessages.PageMessages()
15:06:55.047 (47539000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47659000)|SYSTEM_MODE_ENTER|false
15:06:55.047 (47678000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:5
15:06:55.047 (47694000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47707000)|SYSTEM_MODE_EXIT|false
15:06:55.047 (47720000)|METHOD_EXIT|[1]|PageMessages
15:06:55.047 (47763000)|VARIABLE_SCOPE_BEGIN|[5]|this|KAB_TUTORIAL.PageMessages|true|false
15:06:55.047 (47808000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:12
15:06:55.047 (47845000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:12
15:06:55.047 (47889000)|VARIABLE_ASSIGNMENT|[5]|this|{}|0x549e58c6
15:06:55.047 (47910000)|SYSTEM_MODE_ENTER|false
15:06:55.047 (47921000)|HEAP_ALLOCATE|[2]|Bytes:5
15:06:55.047 (47935000)|STATEMENT_EXECUTE|[1]
15:06:55.047 (47944000)|HEAP_ALLOCATE|[2]|Bytes:5
15:06:55.047 (47956000)|STATEMENT_EXECUTE|[2]
15:06:55.047 (47974000)|STATEMENT_EXECUTE|[3]
15:06:55.047 (47991000)|STATEMENT_EXECUTE|[6]
15:06:55.048 (48000000)|STATEMENT_EXECUTE|[7]
15:06:55.048 (48042000)|METHOD_ENTRY|[7]|01p80000000cOqp|KAB_TUTORIAL.PageMessages.__sfdc_countVal(Integer)
15:06:55.048 (48098000)|HEAP_ALLOCATE|[2]|Bytes:12
15:06:55.048 (48122000)|HEAP_ALLOCATE|[2]|Bytes:12
15:06:55.048 (48142000)|VARIABLE_ASSIGNMENT|[-1]|this|{}|0x549e58c6
15:06:55.048 (48155000)|HEAP_ALLOCATE|[2]|Bytes:8
15:06:55.048 (48190000)|VARIABLE_ASSIGNMENT|[-1]|value|1
15:06:55.048 (48205000)|HEAP_ALLOCATE|[2]|Bytes:8
15:06:55.048 (48231000)|VARIABLE_ASSIGNMENT|[2]|this.countVal|1|0x549e58c6
15:06:55.048 (48260000)|METHOD_EXIT|[7]|01p80000000cOqp|KAB_TUTORIAL.PageMessages.__sfdc_countVal(Integer)
15:06:55.048 (48275000)|SYSTEM_MODE_EXIT|false
15:06:55.048 (48292000)|CODE_UNIT_FINISHED|PageMessages <init>
15:06:55.048 (48814000)|VF_SERIALIZE_VIEWSTATE_BEGIN|06680000000Tmbw
15:06:55.050 (50548000)|VF_SERIALIZE_VIEWSTATE_END
15:06:55.440 (54027000)|CUMULATIVE_LIMIT_USAGE
15:06:55.440|LIMIT_USAGE_FOR_NS|KAB_TUTORIAL|
  Number of SOQL queries: 0 out of 100
  Number of query rows: 0 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 0 out of 150
  Number of DML rows: 0 out of 10000
  Number of script statements: 3 out of 200000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 10
  Number of Email Invocations: 0 out of 10
  Number of fields describes: 0 out of 100
  Number of record type describes: 0 out of 100
  Number of child relationships describes: 0 out of 100
  Number of picklist describes: 0 out of 100
  Number of future calls: 0 out of 10

15:06:55.440|CUMULATIVE_LIMIT_USAGE_END

15:06:55.054 (54063000)|CODE_UNIT_FINISHED|VF: /apex/kab_tutorial__PageMessage1
15:06:55.054 (54077000)|EXECUTION_FINISHED

In fact what has happened is the client side validation has failed and the page is simply redrawn. However, at this point its very easy to start doubting your code and rewriting it randomly - especially if its more complex than my simple example.  Simply adding an <apex:pageMessages/> component that is rerendered avoids all this:

<apex:page controller="PageMessages">
  <apex:pageMessages id="msgs"/>
  <apex:form >
    <apex:pageBlock title="Page Messages Test 1">
      <apex:pageBlockButtons >
         <apex:commandButton value="Click Me" action="{!click}" rerender="counter,msgs"/>
      </apex:pageBlockButtons>
      <apex:outputLabel value="Text1 = " />
      <apex:inputText value="{!text}" required="true"/>
      <br/>
      <apex:outputLabel value="Count = " />
      <apex:outputText value="{!countVal}" id="counter"/>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Clicking the button this time shows the validation error.


As an aside,  due to the required element not being an <apex:inputField/>, the error message is not particularly helpful.  For details on how to improve this, take a look at one of my older posts.