Monday, October 27, 2014

New WorkXpress Platform Version released: 14.10.1010

Coming soon to a WorkXpress installation near you, Platform Version 14.10.1010.  At the end of this post are the full release notes, but I wanted to take a few minutes to highlight some of the new features that I find particularly exciting.

Improved Password Strength and Security

Our Application Developers have a wide array of Field Types to choose from, and many of these field types validate the data they store.  Some store numbers, some dates, email addresses, website URLs, and the list goes on.  One thing we haven't been able to provide, before now, is a consistent set of tools for defining what must go into a *password* for it to be valid for your users.  That is, until now.

You can now setup a password field to have: 

A minimum length.
A minimum number of uppercase letters.
A minimum number of lowercase letters.
A minimum number of digits.
A minimum number of special characters.

When you do this, and the User enters an invalid value, they will get an instantaneous prompt that their password isn't strong enough and the prompt will tell them the password restrictions you have setup!  

This validation is triggered as part of a new, super fast, UI-level Validation system.  We'll be transitioning all of our built in data validations to this new system soon, and hope to allow you to write your own validations against this system in 2015.

Meanwhile, in the backend, we've always employed a one-way encryption algorithm when storing our passwords.  This methodology allowed users to set their password, and for no one.. not even me.. to find out that password.  When you set your password to "thispasswordissecure", the best I could do is read something like 32dDS3412SADFT%321 (32d...321).  Obviously, not helpful, if I want to break into your account.

This is pretty cool, and the way this works is simple.  When you're changing your password, and you enter "thispasswordissecure", we apply some encryption/hashing magic to that string, get 32d...321, and store that in the database.  Then, when you login, you type "thispasswordissecure" we apply that same magic, and get 32d...321 again!  Assuming the stored value, and this current value match.. we know that you entered the correct password.  We don't know what it is, but we know you entered it correctly.

So, that's good.. but it's not great.  For instance, if I happened to set my password to "thispasswordissecure" also, then my stored value would also be 32d...321.  When I see that your hash and my hash match.. now I know your password!!  That's bad.

To combat this, we are generating a unique key for every user that has a password, different for each user, and we're using it as part of the encryption magic.  This key is called a "Salt".  Since my salt and salt are different, the same password doesn't even look alike in the database.  Problem solved.

To take things to the next level, instead of applying only 1 round of magic, we're doing it over 30,000 times.  This not only helps with the strength of the encryption, but it also means it takes about 0.8 seconds to do the encryption.  Yes, this will mean it will take a little extra time when logging in; but it also means that a brute force attacker who is trying to break your password, has to wait 0.8 seconds to find out his latest password attempt failed.  When they are going to have to try millions of possible values to get a hit.. that translates into a lot of seconds.

Intuit and Quickbooks Integration

Quickbooks.. that pesky accounting system that just won't go away!  Now you don't have to replace it with hours of building functionality in WorkXpress; instead.. simply integrate with Quickbooks online!

We have just released a pairing of integration tools to let you do just that: the Intuit Login Field Type, and a series of ESB Profile's for Browsing, Adding, Editing and Deleting records in many of the tables in Quickbooks, right from inside WorkXpress.

Your User will need to use the Intuit Field Type to get connected to Quickbooks Online.  Here's a Wiki Page that talks all about it.

Quickbooks has a SQL-like syntax for working with it's records, so you can sort, and filter when Browsing, Editing, and Deleting.  It's pretty sweet, IMHO.  If you plan to build an application with a Quickbooks Integration, you'll want to read the documentation carefully.

Facebook changes their API, so we change our calls!

So, this one isn't so much a new feature, as it is just a shout out to the power of the WorkXpress Platform.  Facebook recently changed part of the call we need to make for logging in through Facebook, and we were able to release a new Beta Platform Version to the clients who use Facebook within minutes!

Many of our built in features could easily be added to a lot of basic web apps.  Download the Facebook oAuth Docs, implement the login capability to their standards.. and you're done right?  Well.. sometimes.. but not usually.

See, connecting to, or logging in through, any web technology can be great, it can be consistent, work all the time, and never change.  But, the reality is that these things change from time to time.  These changes are generally not made available all that easily, and when you discover that something as important as signing in to your app is suddenly broken because Facebook changed something.. you're going to be running around panicking, trying to find a fix as fast as possible.

Enter WorkXpress.  We monitor the developer feeds of over 50 services we provide integrations for, on the look out for upcoming changes in these services, and new versions.  Our staff is dedicated to keeping the downtime because of changes in these services to an absolute minimum.  That's what happened here: Facebook changed their service, we responded with a changed to the data we were passing them, and the clients using Facebook Login were able update their platform, roll out, and go back to business as usual.  Chickens, with their heads still attached, so to speak :-)

And much more...

Here are the full patch notes.  At time of posting, this release is but a few days away.

Platform Version: 14.10.1009:

Action Type - Display Field (available in Evaluated Field Triggers)
- Added a setting for which field mode the chosen field will display under (View, Edit, or both). This allows an evaluated field to show one field when viewing a field and a different field when editing the field, for instance.

Action Type - Excel Read
- Fixed an issue with adding new Actions of this type.

Action Type - Read Email
- Updated IMAP implementation so it handles MIME Encoded Subjects properly, and does some encoding checking of the body of the emails that are coming in and corrects for this automatically.

Action Type - Reload Form
- Added options to "Reload the Current Tree Row", "Reload and Expand the Current Tree Row", "Reload the Tree Row from the Opening Page", and "Reload and Expand the Tree Row from the Opening Page".

Action Type - Save Field
- Fixed an issue with setting dynamic selection fields in the interface that use records from the page below.

Action Type - Send Fax
- Updated to include an option to record the page count of the sent fax in the application.
- Updated to read multiple files and fax them all in one, combined fax.

Action Type - Set Tenant
- Fixed typos in the configuration screens.

- Updated WX API Key generation to utilize improved password security.

- Facebook Login Integration
  * Updated the Log In button to request appropriate permissions for their newer oAuth API.
  * Updated the Log In button to request the permissions to post to the wall only when there are Actions that will post to the wall in the application.
  * Updated instructions for their Application approval process are on the wiki at

Browser Detection
- Improved our compatible browser detection method so it will be automatically updated nightly as new browsers are released.

Enterprise Services Bus (ESB)
- Google Calendar Event ESB Profiles
- Fixed an issue when the user did not select a default calendar to post events to.
- *New* Intuit Quickbooks ESB Profiles
- Added ESB Profiles for Browsing Records from, Adding Records to, Updating Records in, and Deleting Records from Quickbooks.

Expressions and the Expression Builder
- Added RSASHA1, RSASHA1_Base64, HMACSHA1 and HMACSHA1_Bas64 to the CONVERT function.
- Fixed an issue with the CONCAT wizard interface.
- Fixed an issue with the parameters of the DISTANCE_BETWEEN function in the advanced expression builder.
- Fixed an issue with the return type for sub expressions not loading their stored value (HTML or Text).

- Added the For Each Logic Control to all Form Validation Triggers.
- Added a quick search to the Wizard Step where existing fields are selected for attachment to a Form.

Form Type - List
- Added fixed table header support to list forms with a maximum height set so that the list headers are visible when the form is scrolled.
- Added drag and drop column resizing for Testing and Production Applications. These settings are stored per User.

Form Type - Map
- Fixed an issue with some elements going behind the map controls.
- Fixed the name of a step in the configuration of these forms.

Form Type - Page
- Add and Save Behaviors
  * Added options to "Reload the Tree Row Below" and "Reload and Expand the Tree Row Below".
  * Renamed "Close" to "Close Popup".
  * Added "Close Page", which works whenever the page has been opened indirectly.
  * Updated how the 'Close' button is rendered on pages so it's compatible with quirks in IE11.

Form Type - Tabs
- Added the ability to define Ribbon style Tab Forms with options that are defined dynamically, by records in the database. The content for each Tab in this style is defined by an expression.

Form Type - Tree
- Added "Expand All" and "Collapse All" controls.

Field Type - Address
- Fixed an issue with saving the longitude or latitude parts when geocoding is enabled.

Field Type - Brain Tree Credit Card
- Updated when the non-editable flag is set to only work when the transaction amount setting is enabled.

Field Type - Counter
- Fixed an issue with Counter fields and their actions causing the counting to pause/stop.

Field Type - Google Calendar Subscription
- Updated the interface to automatically select the default calendar to the first calendar the user chooses to subscribe to.

*New* Field Type - Intuit oAuth
- This Field Type allows authenticated Users to connect to their Intuit Account. This connection generates a key that is used during the new Intuit Quickbooks Enterprise Service Bus Profiles.

Field Type - Location
- Fixed issues with saving and searching the individual longitude and latitude parts.

*New* Field Type - Password
- Fields of this type are now available for software developers to create. Note that the Password used during login is still the "Password" field on the User Record; so these values will need to be copied there for them to be read during login.
- Added settings for password validation, to enforce password strength.
- Password storage security has been revamped and improved.

Field Type - Recurrence
- Fixed an issue with filtering a Recurrence Field inside a query.

Field Type - Selection
- Updated how parent selection field data is handled by database driven selection fields to better support a multi value selection field being a parent.
- Updated context detection inside selection field config fields to include the parent picker context inside evaluated filters.
- Fixed an issue with interface actions acting on dynamic selection fields that used their parent field in their query configuration.

Internationalization - Language Import
- Fixed an issue with trying to import a file that references blocks that have been deleted.

Relational Tables
- Updated the 'Edit a Relationship Table' Wizard so it appears in the handbook, and is searchable through the help search in the bottom right.

New WorkXpress Platform Version released: 14.06.964

Today I am pleased to announce that a new version of the WorkXpress Platform has been made public, version 14.06.964.  At the end of this post are the full release notes, but I wanted to take a few minutes to highlight some of the new features that I find particularly exciting.

Multi-Language Support

This is a feature that I'm surprised we managed to hit 14 years in business without needing to add.  We've been planning for it all along, but only needed to pull the trigger recently.

In WorkXpress Multi Language support breaks down into three categories, along clear lines in our platforms architecture: 

1. User Values
2. Application Values
3. Platform Values

User values are the values that your end users enter into the system.  When someone writes into a long text field the phrase "Hello, my name is Drew." it will appear as "Hello, my name is Drew" to everyone who view's that value, regardless of their language settings.  In an application that also supports Spanish and German, for them, this should read "Hola, mi nombre es Drew-." or "Hallo, mein Name ist Drew." respectively.  Handling translations for every User value in an application would be un-managable for your translator.  You'd be sending new files to be translated hourly, or daily, and it would be quite expensive.

Because browsers have come a long way in offering automatic translations for your end users, we decided not to implement User value translations.  Read more about how Chrome handles this.

Application values are the values that you, as a Software Developer, enter into the system when you create Tables, Fields and Forms.  When you create a field to store a Contact's First Name, you've just entered 2 values that could be translated: "Contact", and "First Name".  If you were writing an application in Spanish, you might call that table Contacto, and that field might be called "Primer Nombre".

Fortunately, these values are, comparatively, fixed in quantity.  When you begin building your application you will generate a high quantity of these as you're building out your Tables, Fields and Forms, but once you roll out your application for the first time, you're typically at a point where 80% of your application is complete, and won't be changing.  At this point, if you're planning to provide multi-language support, it's time to export your language values, get them translated, and then import the translated results.

Platform values are the values that the WorkXpress Engineers create to build your tools.  Fields like "Field Label", on Field, "Form Label" on Form, "Number of Columns" on Field Grid, are examples of Platform Values.  Translating the Platform Values would provide the tools to you in different languages so that you can see them in your native tongue.

We plan to add translations of the platform tools in the future (like, late 2015 future).

Skype Integration!

The Skype Integration comes in the form of two constructs in WorkXpress: A Skype Field Type (for your users to enter their Skype name into), and an "Initiate Skype Call" ESB Profile.  This ESB Profile can be triggered anywhere Actions are triggered, of course, and allows you to specify the Skype Name Fields of all Skpe Users to be involved in the call, and what kind of call it should be.  It can be a Chat, Video Chat, or Phone Call.

You can Call up to.. I think 10 Skype users at a time over video, and unlimited otherwise.

We've built some slick interfaces where the User sees a list of all of the Users who should be attending a meeting; and clicks "Start Skype Call" and a few seconds later, they are on a Video call with their meeting attendees!

And much more...

Below is the full patch notes, there is more in this release then I can detail.  Enjoy!

Platform Version: 14.06.964:

Action Type - Set Tenant

- Fixed an issue with defining a Tenant ID to change a record to as a non-numeric, or blank value.

Action Type - Web Service

- Added HTTP Authentication Support for REST Web Services.
Read more about this on the wiki.


- Fixed an issue with interface actions not working when the page was reloaded using a Reload Form action instead of a Reload Page action.

- Fixed an issue with interface actions when a Reload Page action was used as part of a List Form's inline add actions.

Application Settings

- Enabled international support for the support and sales contact phone number. These phone numbers appear on the login page.

- Added Multi-Language Support! You can now extract the translatable bits of your application into a CSV, provide that to a Translator to get their counterparts, and upload that updated CSV to your application. Once uploaded, when a User changes to that language, many labels and other pieces of your application will change to show the translations.

Read more about this on the Wiki.

Enterprise Service Bus

- New Services for pushing and pulling Google Calendars! This Service allows information about the Google Calendars themselves to be pulled/pushed into WorkXpress, for settings like Time Zones.
Read more about this on the Wiki.

- New Service for initiating Skype communication! Create an ESB Action with this service and you can enable Mass-Action Skype interactivity to start Video Calls, Phone Calls, or Text Chats with all of the People a User checks off on a list (for instance).

Read more about this on the Wiki.

- New Service that is triggered anytime Geocoding is performed. Actions under an ESB Profile for this service have access to three variables: Free Geocodes User, Premium Geocodes Used, and Geocoding Source Message. These three values can be used to increment simple counts for tracking Geocode usage, or to make a log of when, how many, and from what source, Geocodes were used.

Read more about this on the Wiki.

- New Service for generating Fedex Shipping Labels.

Read more about this on the Wiki.

- New Service for generating UPS Shipping Labels.

Read more about this on the Wiki.

- Added filtering to the "Past Queue" tab of the ESB Profile List.

- Fixed an issue with editing existing ESB Profile's through the Actions interface.

Expression Builder

- New Function - DISTANCE_BETWEEN_DRIVING. This Function calculates the distance between two points on Earth via driving directions.
Read more about this on the Wiki.

- New Function - TIME_BETWEEN_DRIVING. This Function calculates the approximate time to drive between two points on Earth.

Read more about this on the Wiki.

Field Type - Address

- Added placehoder text for the inputs to label them more clearly

Field Type - Recurrence

- Fixed an issue with saving recurrence fields that use a date to end on.

Field Type - Selection

- Fixed an issue with selection fields that were set to ignore tenancy not ignoring tenancy when displayed in the filter interface.

Field Type - Skype (New!)

- Added a new Field Type: Skype! This Field stores Skype usernames and allows Video Calls, Phone Calls, and Chats with one or many Skype Users to be initiated by a simple click (provided you have the Skype client loaded.)
Read more about this on the Wiki.

Form Type - List

- Added Multi-Column sorting. Enable User-driven Multi Column sorting in the list definition area.
Read more about this on the Wiki.

- Minor Sorting interface change: Click a column to sort by it (ASC), click it again to sort by it the other way (DESC), and a third time to reset the sort back to the default sort for the form.

- Fixed an issue with saving filters when using the popup interface

- Fixed an issue with loading a favorite with filters on a list.

Form Type - Matrix

- Fixed an issue with context when using Item Is and Tenant Is filters as part of the body query in the Matrix configuration.


- New Gauge Graphs are configurable when creating a Report.
Read more about this on the Wiki.


- Improved speed and stability in Internet Explorer by using non-mini-fied Javascript.
- Improved reliability when Internet Explorer aborts an AJAX call < 1ms after it begins.

Monday, January 17, 2011


Recently I was forwarded an article in Event Marketer Magazine where the author reviewed 5 products that have allowed "the last of the low-tech event sectors [to plug] in."  One of the products reviewed was built on the WorkXpress Platform, and is competing with no less than 4 software solutions built to serve the industry of Event Staffing, directly.  This level of success is very exciting to me personally because it shatters the depths of removal that our engineering team is constantly buried beneath.

A screenshot of the homepage of their WorkXpress product.

Feature development at WorkXpress is very abstract. On any given day I might make a change to how values of a certain field type sort, or are searched. I might add a new type of field, defining how it looks when edited, and viewed, how it's searched, etc. I might add a new type of action, adding power to our logic engine. Often times these additions and changes are done inside a contained development space so that I know what will go in, and can tweak what will come out. Seldom do I get to see how a builder uses this new feature, and even less often do I get to see a customer using this new feature in a way that impacts them. Unfortunately, without seeing how what we do impacts feet on the ground, there is a certain satisfaction missing from our role as engineers.

Granted, we hear things like "Tom from ServoLift told me yesterday that this past year was the best year they've ever had and in large part it's thanks to their WorkXpress product" from the support team, but that pale's in comparison to actually seeing a product built on our platform adding real value to a company's employee's day to day work.  Even that pale's in comparison to the satisfaction I got when I found out that not only does a product built on WorkXpress (EPS Tracker 2.0) power EventPro Strategy's day to day business, but it does it so well it was featured in a tech magazine about that industry.

Our vision is to build a platform that is business agnostic; anyone should be able to use it to build solutions that can solve any business need regardless of size or shape.  We believe that if we do a good enough job in delivering our vision then businesses will be free of the non-customizeable, 'You will run your business this way,' industry products that are out there now.  They will be able to produce software that's custom fit to their needs so they are free to run their business as they see fit on software that works with them instead of against them. 

The satisfaction of seeing that vision deliver so well to EPS that they not only have a solution that's helped them be more successful, but that it competes with industry specific products, is immense.

Friday, October 22, 2010

Speeding up MySQL Routines with C using UDF : Part 2 -> MySQL's Example udf_example.c

<- Part 1 : Intro

The first thing I did when researching UDF's was to head over to the MySQL Documentation and read about what was ahead of me. Makes sense, right? If only it were that simple :-)

This page in the MySQL Docs tells us that there's an example udf file that comes with the source code for MySQL.  So, I hopped over to their site, downloaded the source, and found the file.  sql/udf_example.c  Now, admittedly it has been a long time since I wrote any C, or even compiled any (with the exception of ./configure, make, make install type "install from source" stuff)... so I figured their example compile line would work :

dmclain@Percona:~$ gcc -shared -o udf_example.c
udf_example.c:127:23: error: my_global.h: No such file or directory
udf_example.c:128:20: error: my_sys.h: No such file or directory
udf_example.c:139:19: error: mysql.h: No such file or directory
udf_example.c:142: error: syntax error before ‘LOCK_hostname’
udf_example.c:142: warning: data definition has no type or storage class

.. Not even close.  As you can see, it was missing a bunch of include files, so I ended up adding -I/path/to/mysql/includes

dmclain@Percona:~$ gcc -shared -o udf_example.c -I/home/dmclain/mysql_5.1/include
In file included from ./udf_example2.c:127:
./my_global.h:80:23: error: my_config.h: No such file or directory
./my_global.h:602:26: error: my_attribute.h: No such file or directory
./my_global.h:639:21: error: my_dbug.h: No such file or directory
In file included from ./udf_example2.c:127:
./my_global.h:678: error: syntax error before ‘size_socket’
./my_global.h:678: warning: data definition has no type or storage class
./my_global.h:803:2: error: #error "please add -DSTACK_DIRECTION=1 or -1 to your CPPFLAGS"
./my_global.h:871:1: warning: "isnan" redefined
In file included from /usr/include/math.h:28,
 ------ Snip --------

Now keep in mind, I didn't actually install mysql from the source that I downloaded, I was just trying to compile the udf_example.c to use with my existing PerconaDB or MySQL installation.  I didn't want to mess up my existing installation.

So, after a few hours of banging my head against this.. I found this cool site : .  This is a group of folks who are trying to being a bit of order to a set of C functions that can be added to your MySQL installation in terms of the parameters, and naming conventions, etc.  What caught my eye mostly tho was the Skeleton package :

I downloaded and uncompressed this package which seems to include everything I need to compile an example C function for use in UDF in MySQL, which just returns it's own name, using commands I recognize!

dmclain@Percona:~$ autoreconf
dmclain@Percona:~$ ./configure
  -- Snip --
dmclain@Percona:~$ make
  -- Snip --
dmclain@Percona:~$ ls lib_mysqludf_skeleton/.libs
lib_mysqludf_skeleton.a  lib_mysqludf_skeleton.lai  lib_mysqludf_skeleton_la-lib_mysqludf_skeleton.o  lib_mysqludf_skeleton_la-mysqludf.o

Now you can use installdb.sql to register it.  I like to know what those kinds of scripts do tho, since I will need to replicate the registration process during the WorkXpress engine rollout that includes this work.  First it drops the function incase it already exists in your database, then then it adds your new function :

CREATE FUNCTION lib_mysqludf_skeleton_info RETURNS STRING SONAME '';

Notice there's no full path on the front of the SONAME. That's because MySQL has a directory where it puts plugins. You can ask your MySQL Server where *it* stores plugins with :

mysql> show variables like '%plugin%';
| Variable_name | Value                 |
| plugin_dir    | /usr/lib/mysql/plugin |
1 row in set (0.00 sec)

So I added a sym link from /usr/lib/mysql/plugin for my SO in my working area :

dmclain@Percona:~$ sudo ln -s /home/dmclain/lib_mysqludf_skeleton/.libs/  /usr/lib/mysql/plugin/

Then I restarted MySQL so it would find the new plugin; and ran the command :

mysql> select lib_mysqludf_skeleton_info();
| lib_mysqludf_skeleton_info() |
| lib_mysqludf_skeleton 0.0.1  |
1 row in set (0.00 sec)

So, this is step by step to download, compile, register and run the "Skeleton Info" function from

Now that I have a C function running in MySQL.. it's time to make one that does something I need it to do :-)

Speeding up MySQL Routines with C using UDF : Part 1

Most of you probably already know that you can write Routines in MySQL (5.1+) to allow you to encapsulate logic that you re-use into a simple to run command. An example...

In WorkXpress we store Address data in our databases as XML, since an address is just one field there's only one cell in the database to store Address 1, Address 2, City, State, Zip and Country. So, instead of creating a special kind of table to store this data, we decided to simply store it like this :

   <part id="street">1757 Canvasback Lane</part>
   <part id="street2"></part>
   <part id="street3"></part>
   <part id="city">Eagan</part>
   <part id="state">MN</part>
   <part id="zip_code">55122</part>
   <part id="country">United States</part>
   <part id="type">United States</part>

So, when we need to pull just the City out of an address because it was requested in an expression, I can use an XML XPATH function built into MySQL called extractvalue() and pull the value out and return it. The bold line is the key to this function, the rest is just structure/sanity checks :

FUNCTION `wx_Address_Us_GetCity`(input_value text) RETURNS varchar(64) CHARSET utf8 DETERMINISTIC
   IF input_value IS NULL THEN
      RETURN input_value;
      RETURN extractvalue(input_value, '/multi_part_field/part[@id="city"]');
   END IF;

With this function in place, you can run SQL like the below (assuming there is XML Address Data in your 'value' column) and it will pull out just the City :

SELECT wx_Address_Us_GetCity(value) FROM table;

This is great functionality, and it enabled us to do a lot of really cool stuff in the backend of WorkXpress with our Expression Builder.  We hit a wall, however, with the functionality of what you can do inside a MySQL FUNCTION, as declared above, and that's where UDF's come in.

The UDF features of MySQL allow you to write functions in C/C++, and hook them up into a MySQL function!  UDF Functions are not only more fully featured, but rumored to run more efficiently/quickly depending on the functionality you're looking to write into a function.

As part of our switch from MyISAM on MySQL Server 5.1 to XtraDB (a better version of InnoDB) on PerconaDB we will absolutely need to re-write 4 of the 96 functions in C, because of feature limitations to MySQL's Function language.  Because of the rumored speed improvements in using UDF C functions over MySQL's built in language, we're likely going to rewrite all 96.

I'll be sharing performance benchmarks, pitfalls and potholes I hit along the way and hopefully my success story when the functions have been converted and are ready for release into the WorkXpress PaaS Engine.

Part 2 : MySQL's Example - udf_example.c

Wednesday, October 28, 2009

WorkXpress goes in for some pastic surgery

Today the Engineering Team at WorkXpress took the bandages off of the Engine after a few rounds of plastic surgery; and it looks goood.

Below are some screen shots of before and after the changes :

Production Mode : No Tools, 'old and busted':

Production Mode : No Tools, 'new hotness':

Development Mode : With Tools, 'old and busted':

Development Mode : With Tools, 'new hotness':

Never, ever... touch.. the red button!

Monday, July 6, 2009

The Branding Spot : Week 3

The Week 3 review of how things are going with "The Branding Spot" has been posted. Please take a minute to check it out :