Tagged: stimulus Toggle Comment Threads | Keyboard Shortcuts

  • kmitov 6:28 pm on April 7, 2021 Permalink |
    Tags: , , , rails-ujs, , stimulus   

    From a ticket to deploy in an hour. jQuery runs the scripts, DOM does not. 

    (Everyday Code – instead of keeping our knowledge in a README.md let’s share it with the internet)

    This is an article of a production incidents with Rails/Stimulus/Rails-ujs/DOM and jQuery.

    We made mistake that made us lose 31 registrations on the FLLCasts and a few more on the BuildIn3D platform. The stack involved includes Ruby on Rails with Rails-ujs, Stimulus, DOM and jQuery. I will enter into some details about how it occurred, why and how we resolved it in under an hour. I am writing this article mainly to share with my our team, but I am sure it could be useful for other teams.

    FLLCasts registration form as of April 2021

    The incident – people could not Sign Up on the platforms

    On the Sign up page there is a captach. Years ago we found our that the captcha helps us reduce invalid registartion.
    There is also one more thing on this platform – usernames. As the FLLCasts platforms is used by students and academies, one of the very useful features on the platform is the automatically generated username. Users type their names in the “Full name” field and a username is automatically generated for them. In this way there is a unique username for users because email is not unique – there could be many users with the same email.

    To generate a new username we send a request to the server and refresh form. We use Stimulus and rails-ujs for this.

    The is an “change->registrations#refreshForm” for the input field for the name. When the full name is changed the form is refreshed and a unique username is returned from the server.

    <%= user_form.fields_for :data, {parent_builder: user_form} do |data_form| %>
      <%= data_form.text_field :name_attribute,
        {
          icon_prepend: "icon-finance-067 u-line-icon-pro",
          autofocus: true,
          data: {
            registrations_target: "name",
            action: "change->registrations#refreshForm"
          },
          autocomplete: "off"
        }
      %>
    
      <%= data_form.email_field :email %>
    <% end %>

    Here is how it should work:

    How the form is supposed to work

    The incident was that when the form was refresh the captcha got lost and we were not showing it after that. This means that on the server we were checking the captcha, but there was not captcha on the client.

    The ticket

    A wild ticket appears from a user.

    Hi,
    
    We wanted to enroll our Kid for Personal B programme and we were trying to Sign Up to pay online , but we are unable to sign up using our details.
    We get an error message though we tried a few times.
    
    Please help to sort this out.
    
    Thanks
    
    Regards

    This is a really wild ticket. The user wants to buy and they can not sign up. 1,2,3 go.

    The problem

    We’ve made the following commit in the registration form

    --- a/app/views/devise/registrations/new.js.erb
    +++ b/app/views/devise/registrations/new.js.erb
    @@ -1 +1 @@
    -$("#<%= @form_id %>").replaceWith("<%= escape_javascript(render 'form') %>")
    \ No newline at end of file
    +document.getElementById("<%=form_id%>").outerHTML= '<%= escape_javascript(render "#{partial_name}") %>';
    \ No newline at end of file

    We are in the process of removing jQuery from the code. I saw this jQueyr call and decided to change it to a simple DOM call. It should be the same, right? No…

    jQuery executes scrips while document.outerHTML does not

    I knew this. But I did not consider it in this commit. What happens is that we receive the new form from the server and replace the form on the page with the new form received from the server that contains the generate username.

    But in this form (and this form only on the whole platform) there is a recaptach. This recaptcha is a JavaScript and when dynamically changing the DOM the JavaScript must be executed. Well, jQuery automatically executed this for us. document.outerHTML does not.

    Developing and RSpec system spec.

    First I deployed the fix to production. It took about 5 minutes.

    After that I developed the spec. This is how the spec looks like:

    scenario "shows recaptach when dynamically reloading the form", js: true  do
        with_recaptcha_enabled do
          visit_sign_up
    
          expect(page).to have_recaptcha
    
          name_field.set unique_name
          email_field.set "#{SecureRandom.hex(16)}@example.com"
    
          # This means we have reloaded with js because we've set the email field 
          # and this has change the focus and there was a fire event for the name_field.
          #
          # We are now waiting for the username to appear on the screen
          expect(page).to have_username_filled
    
          expect(page).to have_recaptcha
        end
      end

    We expect that there is a recaptcha before and after refreshing the form.

    What surprised us

    Looking at the data in the time frame this commit was on production – we’ve received 9 registrations on the platform. In the same previous period we’ve receive 40 registrations.

    This means that we’ve lost 31 registrations on the platform.

    What surprised us is that 9 people managed to register. The only way you could register on the platform during this period is to first enter your username. If you enter the username on your own we do not refresh the form as we don’t have to generate a username for you. This means that about 25% of the people start with their username and about 75% of the users at FLLCasts start the registration with their name or email.

    Good to know. Good to know.

    Conclusion

    Looking at the issue we could have prevented it in a number of ways. In a test environment it is difficult to have a recaptcha because there is no way to test that the recaptcha works. After all that is the whole purpose of a recaptcha – to prevent bots and a Selenium driver is exactly a bot. But it turned out we’ve missed it as a scenario in the specs. It is always a missing spec.

     
  • kmitov 1:34 pm on March 25, 2021 Permalink |
    Tags: , , , stimulus   

    Stimulus 1.1.1 to Stimulus 2.0.0 – practical cost 

    We just migrated Stimulus 1.1.1. to Stimulus 2.0.0 so I decided to share this with our whole team, but I thought this could be useful for the whole community.

    The practical cost is that this migration could be done in a few minutes per Stimulus controller. 10-20 controllers – you should be done in less than a day.

    Overview of the changes

    Here are a few examples of the changes that were committed for a single _form.html.erb

    html.erb

    # app/views/public_users_searches/_form.html.erb
    -            target: "public-users-searches.term",
    +            public_users_searches_target: "term",
    ...
    -    <%= f.submit t('public_users_searches.form.search'), data: {target: "public-users-searches.commit", value: t('public_users_searches.form.search')} do %>
    +    <%= f.submit t('public_users_searches.form.search'), data: {public_users_searches_target: "commit", value: t('public_users_searches.form.search')} do %>
    

    Conclusion

    If you are wondering whether you should migrate or not and how much it will cost probably you should migrate. It is not that expensive.

     
  • kmitov 5:41 am on October 6, 2020 Permalink |
    Tags: , progressive web application, pwa, , , stimulus   

    The path to a progressive web app – or how we skipped the whole JS Frameworks on the client thing. 

    I recently responded to a question in the Stimulus JS forum that prompted me to write this blog.

    About 6 months ago we decided to skip the whole “JSON response from server and JS framework on the client” stuff and we’ve never felt better. We significantly reduce the code base while delivering more features. We manage to do it with much less resources. Here is an outline of our path

    Progressive Web Application

    We had a few problems.

    1. Specs that were way to fragile and user experience that was way to fragile. Fragile specs that are failing from time to time are not a problem on their own. They are an indicator that the features also do not work in the client browsers from time to time.
    2. Too much and two difficult JS on the client. Although it might see that making a JSON request from the client to the server and generating the HTML from the response might seem as a good idea, it is not always a good idea. If we are to generate the HTML from a JSON, why don’t we ask the server for the HTML and be done with it? Yes, some would say it is faster, but we found out it is basically the same for the server to render ‘{“video_src”: “https://…&#8221;}’ or to render “<video src=’https://…&#8217;></video>’ . The drawback is that in the first scenario you must generate the video tag on the client and this means more work. Like twice the amount of work.

    So we said:

    Let’s deliver the platform to a browser that has NO JS at all, and if it has, we would enhance it here and there.

    How it worked out?

    In retrospective… best decision even. Just know that there is not JS in the browser and try to deliver your features. Specs got a lot faster and better. 1h 40 m compared to 31 minutes. They are not fragile. We have very little JS. The whole platform is much faster. We user one framework less. So, I see no drawbacks.

    First we made the decision not to have a JS framework on the client and to drop this idea as a whole. For our case it was just adding complexity and one more framework. This does not happen overnight, but it could happen. So we decide that there is no JS and the whole platform should work in the case of JS disabled on the browser (this bootstrap navigation menus are a pain in the a…). It should be a progressive web application (PWA).

    After this decisions we did not replace JSON with Ajax calls. We skipped most of them entirely. Some JSON requests could not be skipped, but we changed them as AJAX – for example “generating a username”. When users register they could choose a username, but to make it easier for them we generate one by default. When generating we must make sure it is a username that does not exists in the DB. For this we need to make a request to the server and this is one place we are using Stimulus to submit the username.

    A place that we still use JSON is with Datatables- it is just so convenient. There are also a few progress bars that are making some legacy JSON requests.

    Overall we have Ajax here and there, an a few JSON requests, but that’s it. Like 90-95% of the workflow is working with JS disabled.

    We even took this to the extreme. We are testing it with browsers with JS and browsers without JS. So a delete button on a browser without JS is not opening a confirmation. But with JS enabled the delete opens a confirmation. I was afraid this will introduce a lot of logic in the specs, but I am still surprised it did not. We have one method “js_agnostic_delete” with an if statement that check if JS is enabled and decides what to do.

    My point is that moving JSON to Ajax 1:1 was not for us. It would not pay off as we would basically be doing the same, but in another format. What really payed off and allowed us to reduce the code base with like 30-40%, increase the speed and make the specs not so fragile was to say – “let’s deliver our platform to a JS disabled browser, and if it has JS, than great.”

    To give you even more context this was a set of decisions we made in April 2020 after years of getting tired with JS on client. We are also quite experience with JS as we’ve build a pretty large framework for 3D that is running entirely in browser so it was not like a lack of knowledge and experience with JS on our side that brought us to these decisions. I think whole team grew up enough to finally do without JS.

     
c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel