\n \n \n","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We now have a template that is loaded when the customer is in checkout. So this would be the time when we start writing our first part of the directive. To begin, we'll write a simple directive to see how we can get started with it. We switch back to the script.phtml file and add this code to their:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"This section takes care of initializing the directive we are building. You can now add this directive to a field to which you want to add it. You can add it as follows:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"This will initialize our char limit on the actual model. We still need the model directive to define that it is actually a model. The directive we build will only support the defined model.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"As you can see, our directive consists of two parts, it has the modifier after the directive itself: .1500ms and the value 7 .","spans":[],"direction":"ltr"},{"type":"paragraph","text":"First, let's focus on retrieving the value. We do this by adding it to the directive code:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"This code results in the case where we have defined the directive in the input element we created earlier to 7. Now let's retrieve the modifier:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Again based on the input from earlier, this will now give us 1500. This is how to extract the data you define in the elements. Next we need to find what the model has defined for us. To support the model directory in its functionality, we need to have the path it has defined in it. We do this by adding the following:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Now we have a script that gives us the path we defined in the model itself. We can now start using this path to create our functionality. The first step we need to take is to take control of the actions of the elements. Since the core of the library does this with Alpine's x-model-directive, we will do the same. It creates this model-directive pragmatically in JavaScript, like this:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Now that that part is complete, we have a component that can be connected. As you can see, we have inserted return component.$wire.get(path);. This ensures that the magewire model is connected to the alpine model. Otherwise, one will just overwrite the other and not work coherently.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Next, we are going to use the actual input data from the clients. Let's start by reading the input and checking if it exceeds the character limit:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Now we have actually created our first real feature with the method data. For the next step, we also need to implement debounce. Since the scope of the debounce code from magewire blocks us from reusing it, I took the liberty of copying it and using it in this directive. By doing this, we have also completed the final step of implementing the directive in our codebase. So here is the completed directive:","spans":[],"direction":"ltr"},{"type":"preformatted","text":"","spans":[],"direction":"ltr"},{"type":"heading1","text":"Conclusion","spans":[],"direction":"ltr"},{"type":"paragraph","text":"In conclusion, improving the performance and functionality of a specific VAT number field within Hyva Checkout required a customized approach beyond the available standard options. By examining and experimenting with the debounce and other modifiers, it became clear that neither fully met the customer's needs due to the complexity of server-side validation with third-party services. Therefore, a custom directive was created that provided a more sophisticated solution by combining character limit enforcement with debounced input handling. This approach ensured that the customer's requirements were met, providing a smoother and more efficient user experience while maintaining robust validation processes.","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"link_type":"Document"}},"id":"blog_content$8c1f51f1-bc53-4aa7-a689-3ee543ea7b0b","slice_type":"blog_content","slice_label":null}]}},{"id":"ZsxXOxMAAB4ASx7z","uid":"hyva-development-kopieer-altijd-eerst-de-default-theme","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZsxXOxMAAB4ASx7z%22%29+%5D%5D","tags":[],"first_publication_date":"2024-08-26T10:31:32+0000","last_publication_date":"2024-08-26T10:56:08+0000","slugs":["hyva-altijd-eerst-het-default-thema-kopieren"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZsxZshMAAB4ASyKr","type":"blog","lang":"en-us","uid":"hyva-opinion-always-copy-the-default-theme"}],"data":{"Title":[{"type":"heading1","text":"Hyva: Always copy the default theme first","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":1024,"height":1024},"alt":"Hyva development on multiple devices","copyright":null,"url":"https://images.prismic.io/elgentos-next/ZsxYEUaF0TcGJY8M_0_1.jpeg?auto=format,compress","id":"ZsxYEUaF0TcGJY8M","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2024-07-15","author":"Wouter Steenmeijer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[],"primary":{"title":"Hyva: Always copy the default theme first","image":{"dimensions":{"width":1024,"height":1024},"alt":"Hyva ","copyright":null,"url":"https://images.prismic.io/elgentos-next/ZsxYEUaF0TcGJY8M_0_1.jpeg?auto=format,compress","id":"ZsxYEUaF0TcGJY8M","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Wouter Steenmeijer","date":"2024-07-15"},"id":"hero$f826fc8a-27f7-45bd-be2b-7fb32c4505b9","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"First, a little background. According to the documentation you can find here, a good practice is to start by setting up your Hyva theme with the theme structure such as: Hyva/reset, Hyva/default-theme, Vendor/child-theme. This can be very useful if you think the TTM is very long and you expect multiple theme upgrades during the process, that you have very limited capacity to upgrade Hyva during development or your client just doesn't care about the design :)","spans":[{"start":61,"end":65,"type":"hyperlink","data":{"link_type":"Web","url":"https://docs.hyva.io/hyva-themes/building-your-theme/index.html","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"I am willing to argue that in almost all cases it is better to just start duplicating the Hyva/default theme and rename the theme. This has many advantages, such as a clean tailwind.config.js, all elements are no longer styled, so it's easy to see if you need to work on that or not.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"But, the most important benefit is that this way you ensure that no branding elements leak into the new design. For example, if Hyva uses \"text-red-600,\" you want to make sure that this color is exactly the color your client uses in their branding strategy. Of course, the same goes for font size, line spacing, headings, border colors, etc. My personal experience is that the store just doesn't feel \"finished\" if this doesn't exactly match the client's brand book.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Yes. This will take some extra time during development when Hyva rolls out a major theme update. However, with tools such as our upgrade helper, you will scroll through each modified file with great ease.","spans":[{"start":132,"end":146,"type":"hyperlink","data":{"link_type":"Web","url":"https://elgentos.com/blog/tool-magento-upgrade-gui","target":"_blank"}}],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$9ca196b1-036d-4c5d-89ac-4939c4bc0a39","slice_type":"blog_content","slice_label":null}]}},{"id":"Zel96BEAAO8dYnJu","uid":"betere-conversie-met-ai-gedreven-productaanbevelingen","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Zel96BEAAO8dYnJu%22%29+%5D%5D","tags":[],"first_publication_date":"2024-03-07T08:52:17+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhJ8RRIAAJM0r63d","type":"blog","lang":"en-us","uid":"better-conversion-with-ai-driven-product-recommendations"}],"data":{"Title":[{"type":"heading1","text":"Better conversion with AI-driven product recommendations","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":2048,"height":2048},"alt":"AI Product Recommendations","copyright":null,"url":"https://images.prismic.io/elgentos-next/ZemAKHUurf2G3Lou_ai-product-recommendations.webp?auto=format,compress","id":"ZemAKHUurf2G3Lou","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2024-03-07","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[],"primary":{"title":"Better conversion with AI-driven product recommendations","image":{"dimensions":{"width":2048,"height":2048},"alt":"AI Product Recommendations","copyright":null,"url":"https://images.prismic.io/elgentos-next/ZemAKHUurf2G3Lou_ai-product-recommendations.webp?auto=format,compress","id":"ZemAKHUurf2G3Lou","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2024-03-07"},"id":"hero$998f5601-bc66-4c91-a9e5-c14d6b7dc51e","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Are you about to significantly improve the shopping experience in your online shop, without having to dive deep into product recommendations yourself? We have developed an appropriate solution for this: an advanced Magento 2 extension that uses AI to generate product recommendations. This extension not only makes it easier for you to recommend related and up-sells products, but it can also significantly increase your sales.","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"The benefits of AI recommendations","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Imagine no more time spent manually setting product recommendations. The AI takes this completely out of your hands, meaning you have more time left over for other important tasks. Using your customer data such as pages visited, shopping carts, historical orders and favorites, this technology provides highly relevant recommendations. This leads to a personalized shopping experience for your customers, increasing the likelihood that they will return.","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"What does our Magento extension offer?","spans":[],"direction":"ltr"},{"type":"list-item","text":"Shopping cart analysis: Recommendations are made based on what customers plan to buy.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Customer behavior mapping: The AI analyzes customer behavior - from browsing to adding to favorites and purchase history - to better understand what they are looking for.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Promote features: Products with certain features get a boost in recommendations.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Selective filtering: Undesirable products, such as those with low margins, can be filtered out of the recommendations.","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"Practical applications","spans":[],"direction":"ltr"},{"type":"paragraph","text":"With our extension you can include:","spans":[],"direction":"ltr"},{"type":"list-item","text":"Create a personalized homepage: Make each visit to the homepage unique to the user.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Suggest alternative products: Offer alternatives or replacements for discontinued products.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Smart search with filters: Let customers find products based on specific search criteria.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Send personalized emails: Improve the shopping experience with customized product recommendations in emails.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Recommend popular and trending products: Recommendations are tailored to individual customer preferences.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Upselling and cross-selling: Encourage customers to buy a more expensive version of a product or consider complementary products.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Show favorite brands: Present brands that match the customer's preferred history.","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"Customer case: Carnavalskleding","spans":[],"direction":"ltr"},{"type":"paragraph","text":"An excellent example of the effectiveness of our AI recommendations can be seen at Carnavalskleding. With our extension, they offer smart, customized product recommendations in the shopping cart. This has led to higher customer satisfaction and an increase in average order value.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Read more about this case; Case Carnavalskleding.nl","spans":[{"start":26,"end":50,"type":"hyperlink","data":{"link_type":"Web","url":"https://elgentos.com/cases/carnavalskleding","target":"_self"}}],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"Ready for the next step? Are you ready to transform your Magento shop with AI-driven product recommendations? Our extension makes it easier than ever to increase sales, save time and provide an unforgettable shopping experience that keeps customers coming back. Let's work together and take your shop to the next level!","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$67717e0e-7c08-445e-ad95-a41d37423118","slice_type":"blog_content","slice_label":null}]}},{"id":"Zehs9xIAAKkhJ3fD","uid":"proces-vanuit-de-project-manager","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Zehs9xIAAKkhJ3fD%22%29+%5D%5D","tags":[],"first_publication_date":"2024-03-06T13:18:25+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhJ9AxIAALQ0r7Ee","type":"blog","lang":"en-us","uid":"process-from-the-project-managers-point-of-view"}],"data":{"Title":[{"type":"heading1","text":"The process from the project manager's point of view","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":1024,"height":1024},"alt":"The process from the project manager's point of view","copyright":null,"url":"https://images.prismic.io/elgentos-next/ZemBe3Uurf2G3LpM_project-management-process.webp?auto=format,compress","id":"ZemBe3Uurf2G3LpM","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2024-03-07","author":"Anja Koop","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[],"primary":{"title":"The process from the project manager's point of view","image":{"dimensions":{"width":1024,"height":1024},"alt":"The process from the project manager's point of view","copyright":null,"url":"https://images.prismic.io/elgentos-next/ZemBe3Uurf2G3LpM_project-management-process.webp?auto=format,compress","id":"ZemBe3Uurf2G3LpM","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Anja Koop","date":"2024-03-07"},"id":"hero$f747e541-ee41-4616-a18a-e21ef0162e28","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Facilitate a streamlined process that results in successful and timely release of high quality products.As a project manager, we are not only the guardian of schedule and progress, but also the motivator that drives teams to peak performance.","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"We create an environment where creativity, collaboration and efficiency thrive.By setting achievable goals, facilitating effective communication and removing obstacles, we ensure that the development team can work unhindered. ","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"We are like the engine that keeps the Scrum framework running, resulting in a structured approach and optimized lead time.The ultimate goal is clear to us: maximum releases of high-quality products. ","spans":[],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"Our role as project manager is not only focused on achieving milestones, but also on ensuring the quality of each release. By inspiring the team to strive for continuous improvement, we strive for an iterative development cycle where each release is better than the last!","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$50d8496d-00dd-42f9-b2c5-9f1c2eb54240","slice_type":"blog_content","slice_label":null}]}},{"id":"Zc9F3BIAAJ8Y8O5W","uid":"hulp-bij-magento-performance-problemen","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Zc9F3BIAAJ8Y8O5W%22%29+%5D%5D","tags":[],"first_publication_date":"2024-02-16T11:24:14+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhJ9vxIAALw0r7Re","type":"blog","lang":"en-us","uid":"help-with-magento-performance-problems"}],"data":{"Title":[{"type":"heading1","text":"Help with Magento performance problems","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":1792,"height":1024},"alt":"Performance problems","copyright":null,"url":"https://images.prismic.io/elgentos-next/65cf45cb9be9a5b998b5ec46_Performance-problemen.webp?auto=format,compress","id":"Zc9Fy5vppbmYtexG","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2024-02-15","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[],"primary":{"title":"Help with Magento performance problems","image":{"dimensions":{"width":1792,"height":1024},"alt":"Performance problems","copyright":null,"url":"https://images.prismic.io/elgentos-next/65cf45cb9be9a5b998b5ec46_Performance-problemen.webp?auto=format,compress","id":"Zc9Fy5vppbmYtexG","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2024-02-15"},"id":"hero$20ead0de-1848-4566-ae4e-edf02e25df2b","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"heading2","text":"The key to a faster Magento shop","spans":[],"direction":"ltr"},{"type":"paragraph","text":"In the world of e-commerce, speed is obviously very important. A slow webshop can scare away customers and hurt sales. At elgentos, we understand this like no other and are specialized in solving performance problems of Magento webshops. Our expertise is based on years of experience regarding the most common causes of slow Magento webshops.","spans":[],"direction":"ltr"},{"type":"heading2","text":"Common causes and our approach","spans":[],"direction":"ltr"},{"type":"paragraph","text":"In our practice, we regularly encounter the same issues. This allows us to act quickly and efficiently. A few examples:","spans":[],"direction":"ltr"},{"type":"list-item","text":"External links: Often it is the external links that cause delays. We analyze and optimize these connections for better performance.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Poor FPC configuration: Full Page Caching (FPC) is crucial for fast load times. However, improper configurations of Fastly or Varnish can backfire. We ensure optimal setup. We have also used our knowledge of Varnish to improve the default implementation of Varnish in Magento, in collaboration with experts from Adobe and Varnish itself.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Unnecessary cache clearing: Excessive cache emptying can affect loading speed. We implement strategies to minimize this.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Bad extensions: Not all Magento extensions are equally good. We identify and replace, or improve underperforming extensions. Often these are extensions from the large well-known Magento extension parties. We prefer small (self-written) extensions that do exactly what they are supposed to do over one-size-fits-all extensions with dozens of options you don't use.","spans":[],"direction":"ltr"},{"type":"heading2","text":"Advanced tools for in-depth analysis","spans":[],"direction":"ltr"},{"type":"paragraph","text":"At elgentos, we deploy state-of-the-art tools to identify and solve performance problems. Some of the tools we use include:","spans":[],"direction":"ltr"},{"type":"list-item","text":"Blackfire: for in-depth performance analysis and bottleneck detection.","spans":[],"direction":"ltr"},{"type":"list-item","text":"New Relic: for real-time monitoring and analyzing user experiences.","spans":[],"direction":"ltr"},{"type":"list-item","text":"Xdebug: for debugging and profiling code to find inefficiencies.","spans":[{"start":29,"end":37,"type":"em"}],"direction":"ltr"},{"type":"list-item","text":"Sentry: for monitoring Magento exceptions and error messages and aggrandizing metadata to get the problem statement clearer.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"These tools allow us to quickly find the cause of the problem and implement the corresponding solutions.","spans":[],"direction":"ltr"},{"type":"heading2","text":"The benefits of performance optimization","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Fixing performance issues provides significant benefits. A faster shop not only means a better user experience and more conversions, but it can also lead to lower hosting costs. By using server resources more efficiently, costs can be saved. For example, at one client we reduced hosting costs from 6,000 euros per month to 1,000 euros per month.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Elgentos is at the forefront when it comes to solving Magento performance problems. Our expertise, combined with advanced tools, allows us to address the unique challenges of each Magento webshop. By focusing on common causes and addressing them efficiently, we provide faster, smoother and cost-effective e-commerce solutions. ","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$1a5d3e10-8d35-4111-a051-4d83d4f26710","slice_type":"blog_content","slice_label":null}]}},{"id":"Zc9ULRIAAH0a8TDH","uid":"bartrack-magento-2-koppeling-magazijnbeheer","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Zc9ULRIAAH0a8TDH%22%29+%5D%5D","tags":[],"first_publication_date":"2024-02-16T12:25:19+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhJ-ShIAAL40r7bE","type":"blog","lang":"en-us","uid":"bartrack-magento-2-extension-warehouse-management"}],"data":{"Title":[{"type":"heading1","text":"Streamline warehouse management with the BarTrack Magento 2 extension","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":1792,"height":1024},"alt":"BarTrack Magento 2 extension","copyright":null,"url":"https://images.prismic.io/elgentos-next/65cf541b9be9a5b998b5ed27_BarTrack-header.webp?auto=format,compress","id":"Zc9UG5vppbmYte0n","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2024-02-09","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[],"primary":{"title":"Streamline warehouse management with the BarTrack Magento 2 extension","image":{"dimensions":{"width":1792,"height":1024},"alt":"BarTrack Magento 2 extension","copyright":null,"url":"https://images.prismic.io/elgentos-next/65cf541b9be9a5b998b5ed27_BarTrack-header.webp?auto=format,compress","id":"Zc9UG5vppbmYte0n","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2024-02-09"},"id":"hero$1dbad73f-9c2d-44d9-a363-24cf07169d4f","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"In the world of e-commerce and warehouse management, efficiency is very important. With this in mind, we developed a Magento 2 extension link for our client Delftechniek for their customers using BarTrack. This extension not only makes warehouse inventory management easier, but also improves the shopping experience for these users.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"What is BarTrack?","spans":[{"start":0,"end":16,"type":"strong"}],"direction":"ltr"},{"type":"paragraph","text":"BarTrack is an inventory management system/app for wholesalers that uses barcodes. It allows customers to quickly and efficiently order products from the wholesalers they purchase from. This is done by scanning barcodes with a special app, available for both iOS and Android. But how does this technology integrate with Magento 2's e-commerce environment? Through the Magento extension we developed!","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Use of BarTrack by Delftechniek","spans":[{"start":0,"end":38,"type":"strong"}],"direction":"ltr"},{"type":"paragraph","text":"Delftechniek, a prominent player in the industrial, construction, marine, and installation engineering sectors, uses BarTrack to make it easier for their customers to order. With the Magento 2 extension, BarTrack is seamlessly integrated into their online platform. This provides customers with a unique functionality: the ability to print all product barcodes from their warehouse from the product page in their 'My Account' section in order to then order quickly.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Functionality of the extension","spans":[{"start":0,"end":31,"type":"strong"}],"direction":"ltr"},{"type":"paragraph","text":"Once a customer enables the BarTrack option, a \"Print Barcode\" button becomes visible on product pages. With a simple click, customers can then print the barcodes of products in their warehouse. These barcodes can then be affixed to pick locations in the warehouse.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Warehouse employees can then scan the barcodes with the BarTrack app, and the products are automatically added to the shopping cart. This purchasing process makes restocking and managing warehouse inventory easier and more accurate than ever before.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Integration of new products","spans":[{"start":0,"end":31,"type":"strong"}],"direction":"ltr"},{"type":"paragraph","text":"A nice feature of this extension is the ability to automatically add new products to Magento. When products exist within BarTrack but not (yet) in Magento, the extension ensures that these products are automatically added from Delftechniek's ERP and placed in the shopping cart. This offers huge time savings for Delftechniek, which can then enrich this product data and make the products available to non-BarTrack users as well.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Delftechniek's BarTrack Magento 2 extension marks a great step forward in the integration of e-commerce and warehouse management. This innovation provides a seamless, efficient and user-friendly experience for both the merchant and the end user. With this technology, Delftechniek is putting itself on the map as a forward-thinking company shaping the future of online shopping and warehouse management.","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$6bf1e779-1a8a-47c1-bf6f-296cbfb1af26","slice_type":"blog_content","slice_label":null}]}},{"id":"ZW3exhAAACMA-yV7","uid":"google-analytics-4-met-serverside-purchase-events","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZW3exhAAACMA-yV7%22%29+%5D%5D","tags":[],"first_publication_date":"2023-12-04T15:35:19+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKAlhIAAOU0r8Dq","type":"blog","lang":"en-us","uid":"google-analytics-4-with-serverside-purchase-events"}],"data":{"Title":[{"type":"heading1","text":"Our GA4 & GTM config - the way to 100% accurate data","spans":[]}],"image":{"dimensions":{"width":1024,"height":1024},"alt":"AI Generated - Prompt: create an hero blog image that show the following logos google, google tag manager and elgentos on a server configuration","copyright":null,"url":"https://images.prismic.io/elgentos-next/ef3dc1a6-29bf-4aea-a746-5b8a85eaae65_DALL%C2%B7E+2023-12-04+16.20.47+-+A+digital+artwork+of+600x400+pixels+including+the+Elgentos+logo_+the+Google+logo%2C+the+Google+Tag+Manager+logo%2C+and+a+less+futuristic+server.+Additiona.png?auto=compress,format","id":"ZW3xnRAAACUA-3og","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2023-12-05","author":"Wouter Steenmeijer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Our GA4 & GTM config - the way to 100% accurate data","image":{"dimensions":{"width":1024,"height":1024},"alt":"AI Generated - Prompt: create an hero blog image that show the following logos google, google tag manager and elgentos on a server configuration","copyright":null,"url":"https://images.prismic.io/elgentos-next/ef3dc1a6-29bf-4aea-a746-5b8a85eaae65_DALL%C2%B7E+2023-12-04+16.20.47+-+A+digital+artwork+of+600x400+pixels+including+the+Elgentos+logo_+the+Google+logo%2C+the+Google+Tag+Manager+logo%2C+and+a+less+futuristic+server.+Additiona.png?auto=compress,format","id":"ZW3xnRAAACUA-3og","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Wouter Steenmeijer","date":"2023-12-04"},"id":"hero$19b29751-5c3c-4053-bed2-bbafd2e9f80b","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"With the introduction of GA4, a lot has changed in the field of website analytics. The biggest difference, of course, is that GA4 is event driven and not session driven but event driven. The main reason for this change is that it is becoming increasingly difficult for Google to get a complete data picture of the user. For example, the fact that iOS blocks 3rd party tracking makes it harder to send the right data to your analytics platform. Also, make use of adblockers is still rising, for example.","spans":[]},{"type":"paragraph","text":"Practice","spans":[{"start":0,"end":11,"type":"strong"}]},{"type":"paragraph","text":"In this article, I want to go deeper into our standard GA4 and GTM configuration. We use the following extensions for this purpose:","spans":[]},{"type":"paragraph","text":"- Yireo GoogleTagManager - https://github.com/yireo/Yireo_GoogleTagManager2","spans":[{"start":2,"end":24,"type":"strong"},{"start":27,"end":75,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/yireo/Yireo_GoogleTagManager2","target":"_blank"}}]},{"type":"paragraph","text":"- Elgentos Server side analytics - https://github.com/elgentos/magento2-serversideanalytics","spans":[{"start":2,"end":32,"type":"strong"},{"start":35,"end":91,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/magento2-serversideanalytics","target":"_blank"}}]},{"type":"paragraph","text":"","spans":[]},{"type":"paragraph","text":"Of course, both extensions are free to download from github.","spans":[]},{"type":"paragraph","text":"In addition, it is important to have a server side container of GTM installed. I will not elaborate on this in this article, this is also normally beyond our scope, but here we can help. A server side container is important because you can ensure that gtm.js is downloaded from your own domain. The \"initiator\" of sending data is then within your own domain. The data can then also be sent to your own domain. For this, you create for example the CNAME marketing.JEDOMEINNAAM.com. All data is now 1st party and you have also fixed iOS tracking. More information can be found here: https://developers.google.com/tag-platform/tag-manager/server-side","spans":[{"start":671,"end":737,"type":"hyperlink","data":{"link_type":"Web","url":"https://developers.google.com/tag-platform/tag-manager/server-side","target":"_blank"}}]},{"type":"paragraph","text":"","spans":[]},{"type":"paragraph","text":"The Yireo GTM extension creates events in the datalayer. You can fire these in your GTM configuration. By default, these events are:","spans":[]},{"type":"list-item","text":"view_item ","spans":[{"start":0,"end":10,"type":"em"}]},{"type":"list-item","text":"view_item_list ","spans":[{"start":0,"end":15,"type":"em"}]},{"type":"list-item","text":"select_item","spans":[{"start":0,"end":11,"type":"em"}]},{"type":"list-item","text":"add_to_cart","spans":[{"start":0,"end":11,"type":"em"}]},{"type":"list-item","text":"remove_from_cart","spans":[{"start":0,"end":16,"type":"em"}]},{"type":"list-item","text":"view_cart","spans":[{"start":0,"end":9,"type":"em"}]},{"type":"list-item","text":"start_checkout","spans":[{"start":0,"end":14,"type":"em"}]},{"type":"list-item","text":"add_payment_info","spans":[{"start":0,"end":16,"type":"em"}]},{"type":"list-item","text":"add_shipping_info","spans":[{"start":0,"end":17,"type":"em"}]},{"type":"list-item","text":"purchase","spans":[{"start":0,"end":8,"type":"em"}]},{"type":"paragraph","text":"These events must all be created in GTM and forwarded to GA4 and/or other endpoints.","spans":[]},{"type":"paragraph","text":"Often our customers need customization on this. For example, if we want to track a reorder flow from the homepage or account page.","spans":[]},{"type":"paragraph","text":"Our ambition is that at least the purchase data is 100% correct in Google Analytics. For GA1 & UA we had already created a serverside extension that sends the purchase event serverside to GA when the order was paid.","spans":[]},{"type":"paragraph","text":"For GA4, this was of course the intention again. In this version we have also taken headless projects into account. The most important thing is to store a GA userID and a GA sessionId as soon as possible. We used the following flow:","spans":[]},{"type":"paragraph","text":"- User visits the site and adds an item to the cart","spans":[]},{"type":"paragraph","text":"- Currently, the extension looks to see if a user_id and/or session_id is present","spans":[]},{"type":"paragraph","text":"- These are stored in a separate table with a quote_id reference","spans":[]},{"type":"paragraph","text":"- When the order is placed, the table is updated with the order_id","spans":[]},{"type":"paragraph","text":"- When the order is paid, it is sent to GA4 with the measurement protocol","spans":[]},{"type":"paragraph","text":"We have built a feature that ensures that if there is no session_id present, due to use of an adblocker for example then it will be filled with a fallback session_id. This can be a range or a default id.","spans":[]},{"type":"paragraph","text":"","spans":[]},{"type":"paragraph","text":"Future","spans":[{"start":0,"end":8,"type":"strong"}]},{"type":"paragraph","text":"Currently, in server side analytics, we are only bottoming the purchase event. I could well imagine expanding this as the use case arises.","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$8bf9e017-8c17-4d44-a2f7-c9c76fa66e5e","slice_type":"blog_content","slice_label":null}]}},{"id":"ZczIsBIAAMzKAfr7","uid":"dutchlabelshop-wint-mm23nyc-tapestry-award","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZczIsBIAAMzKAfr7%22%29+%5D%5D","tags":[],"first_publication_date":"2024-02-14T14:10:52+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKBYxIAAAk1r8R6","type":"blog","lang":"en-us","uid":"dutchlabelshop-wins-mm23nyc-tapestry-award"}],"data":{"Title":[{"type":"heading1","text":"Our client Dutch Label Shop wins the MM23NYC Tapestry Award!","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":2560,"height":1707},"alt":"The Tapestry Award is received in New York on stage by Peter Jaap and Arjen","copyright":null,"url":"https://images.prismic.io/elgentos-next/65ccca6f9be9a5b998b5cf9e_dutchlabelshop-award.jpg?auto=format,compress","id":"ZczKb5vppbmYtc-e","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2023-10-11","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[],"primary":{"title":"Our client Dutch Label Shop wins the MM23NYC Tapestry Award!","image":{"dimensions":{"width":2560,"height":1707},"alt":"The Tapestry Award is received in New York on stage by Peter Jaap and Arjen","copyright":null,"url":"https://images.prismic.io/elgentos-next/65ccca6f9be9a5b998b5cf9e_dutchlabelshop-award.jpg?auto=format%2Ccompress&rect=364%2C0%2C1741%2C1161&w=2560&h=1707","id":"ZczKb5vppbmYtc-e","edit":{"x":364,"y":0,"zoom":1.47,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2023-10-11"},"id":"hero$e6443ed8-c9e2-4ed1-9586-55bfb0fca750","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"During Meet Magento New York 2023, our client Dutch Label Shop was awarded the prestigious Tapestry Award. This achievement is a testament to their exceptional expertise and innovation in the e-commerce industry.","spans":[],"direction":"ltr"},{"type":"heading2","text":"Innovative product solutions","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Dutch Label Shop distinguishes itself in its niche by offering custom apparel labels in extraordinarily low quantities - starting at just 30 pieces per order! This is a remarkable achievement when you consider that the norm in the industry is thousands of pieces. Their commitment to quality, combined with competitive pricing, sets them apart in a market dominated by high-volume competitors.","spans":[],"direction":"ltr"},{"type":"heading2","text":"🛍️ A seamless and customized shopping experience","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Thanks to a long-standing partnership with our Magento expertise, Dutch Label Shop offers a user-friendly online store integrated with custom product designers. With these React-based designers, customers can create personalized labels with unique fonts, symbols and colors, or upload their own logos for instant quotes.","spans":[],"direction":"ltr"},{"type":"heading2","text":"🚀 Custom order management system","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The OMS is much more than its name suggests. Developed after an extensive visit to Dutch Label Shop's production facilities, it governs production, logistics, quality assurance and customer service. It is the operational heart of Dutch Label Shop and reflects our deep understanding of their internal processes.","spans":[],"direction":"ltr"},{"type":"heading2","text":"💡Content revolution with Prismic","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The implementation of the headless CMS Prismic was a game-changer. It enabled Dutch Label Shop's content creators to quickly produce landing pages, blogs and informative articles while maintaining brand consistency.","spans":[],"direction":"ltr"},{"type":"heading2","text":"🤝 A partnership defined by growth","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Our 8-year journey with Dutch Label Shop has been incredibly rewarding. From their first Magento 1 build to the transition to Magento 2, we have been privileged to support their evolution into a multi-million dollar business. This journey included the integration of partners such as Adyen, Avalara, Hyvä , Klaviyo, Prismic and Yotpo, which were crucial to expanding their reach into niche markets and platforms such as Etsy.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Take a look for yourself at www.dutchlabelshop.com !","spans":[{"start":24,"end":47,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.dutchlabelshop.com","target":"_self"}}],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$acb81517-9c5f-413b-98b9-e64ff1fc7d20","slice_type":"blog_content","slice_label":null}]}},{"id":"ZOyxPhIAACMAj5Zu","uid":"magento-ontwikkeling-stroomlijnen-met-cicd","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZOyxPhIAACMAj5Zu%22%29+%5D%5D","tags":[],"first_publication_date":"2023-08-28T14:38:02+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZOywqRIAACAAj5SG","type":"blog","lang":"en-us","uid":"streamlining-magento-development-with-continuous-integration"}],"data":{"Title":[{"type":"heading1","text":"Streamline Magento development with CI/CD","spans":[]}],"image":{"dimensions":{"width":1024,"height":500},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/ebe1f224-b0c8-46a1-95be-4adca1a1eed1_magento-superwoman.png?auto=compress,format&rect=0,125,1024,500&w=1024&h=500","id":"ZOywdBIAACMAj5Qw","edit":{"x":0,"y":-125,"zoom":1,"background":"transparent"}},"date":"2023-08-28","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Streamline Magento development with CI/CD","image":{"dimensions":{"width":1024,"height":800},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/ebe1f224-b0c8-46a1-95be-4adca1a1eed1_magento-superwoman.png?auto=compress,format&rect=0,43,1024,800&w=1024&h=800","id":"ZOywdBIAACMAj5Qw","edit":{"x":0,"y":-43,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2023-08-28"},"id":"hero$3a6aacfc-fac8-43bb-8703-ecb696e462c8","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"In the rapidly evolving world of e-commerce, staying ahead of the competition requires not only a powerful platform, but also efficient development practices. This is where Continuous Integration (CI) and Continuous Deployment (CD) come in. These practices have transformed software development by minimizing cost, improving reliability and facilitating collaboration. In the field of Magento development, CI/CD has proven to be a game-changer, enabling agencies to streamline their processes and deliver exceptional results. In this blog post, we explore the importance of CI/CD for Magento development and introduce a comprehensive solution designed to revolutionize the way Magento agencies work.","spans":[]},{"type":"heading2","text":"Benefit 1: Saving costs","spans":[]},{"type":"paragraph","text":"Traditional approaches to software development often involve long development cycles, manual testing and separate implementation phases. This not only consumes valuable time, but also leads to higher development costs. CI/CD for Magento offers a solution by automating the integration, testing and implementation processes. This means errors are detected earlier in the development cycle, reducing the need for costly bug fixes later. With automated testing and deployment, developers can focus on creating new features rather than troubleshooting, ultimately saving time and resources.","spans":[]},{"type":"paragraph","text":"Benefit 2: Reliability and stability","spans":[]},{"type":"paragraph","text":"In the world of e-commerce, downtime is not an option. CI/CD ensures a more stable and reliable development environment by automating testing procedures and enforcing a consistent deployment process. With zero downtime deployments, changes can be seamlessly integrated into the live environment without disrupting the customer experience. In addition, the push-only deployment approach eliminates the risk of code manipulation on production servers, improving security and stability.","spans":[]},{"type":"heading2","text":"Benefit 3: Efficient workflow","spans":[]},{"type":"paragraph","text":"The integrated nature of CI/CD in Magento development promotes collaboration between developers, testers and operational teams. Changes are automatically tested in a controlled environment, reducing the chance of introducing bugs at the production site. This promotes a smoother workflow where problems are identified and resolved early, preventing bottlenecks and improving overall efficiency.","spans":[]},{"type":"heading2","text":"Introducing MageCICD: the ultimate Magento CI/CD solution","spans":[]},{"type":"paragraph","text":"We are proud to introduce an advanced solution tailored specifically for Magento agencies looking to harness the power of CI/CD. Our solution is equipped with a range of features designed to enhance your development process:","spans":[]},{"type":"heading3","text":"Test steps in the pipeline:","spans":[]},{"type":"list-item","text":"End-to-end testing with Cypress: Provide comprehensive testing of the user experience of your Magento application.","spans":[]},{"type":"list-item","text":"Linting for JSON/XML/PHP: Maintain code quality and consistency.","spans":[]},{"type":"list-item","text":"Static testing: Use tools such as phpcs, phpmd, phpstan, phpcpd, pdepend and phpmetrics to detect problems early on.","spans":[]},{"type":"heading3","text":"Performance steps in the pipeline:","spans":[]},{"type":"list-item","text":"Lighthouse: Evaluate the performance and accessibility of your Magento website.","spans":[]},{"type":"list-item","text":"Sitespeed: Analyze site speed and optimize load times.","spans":[]},{"type":"list-item","text":"Smoke Tests: Perform basic tests to verify core functionality.","spans":[]},{"type":"heading3","text":"Security and debugging steps in the pipeline:","spans":[]},{"type":"list-item","text":"Security notifications: Stay up to date with security patches and notifications.","spans":[]},{"type":"list-item","text":"OWASP ZAP checklist: Ensuring compliance with security standards.","spans":[]},{"type":"list-item","text":"Sentry release: Create Sentry releases automatically for effective debugging.","spans":[]},{"type":"heading3","text":"Customized implementation:","spans":[]},{"type":"list-item","text":"Apply custom Varnish VCLs: Customize caching configurations to suit your needs.","spans":[]},{"type":"list-item","text":"Clear opcache after deployment: Prevent problems caused by outdated code in the opcache.","spans":[]},{"type":"list-item","text":"Support for semaio/Magento2-ConfigImportExport: Simplify configuration management.","spans":[]},{"type":"list-item","text":"Auto-upgrade Magento and Composer packages: Keep your platform and dependencies up-to-date.","spans":[]},{"type":"heading3","text":"Review environment and collaboration:","spans":[]},{"type":"list-item","text":"Deploy to separate review environments: Test changes in isolation before going live.","spans":[]},{"type":"list-item","text":"Custom hostname and domain: Mimics production environment for accurate testing.","spans":[]},{"type":"list-item","text":"Database import and administrator creation: Setting up realistic test environments.","spans":[]},{"type":"list-item","text":"Integration with GitLab and Slack: Streamline communication and issue tracking.","spans":[]},{"type":"list-item","text":"Easy login to running review environments via command line tools.","spans":[]},{"type":"paragraph","text":"As e-commerce requirements continue to grow, Magento agencies must adopt efficient and robust development practices. Our comprehensive solution, tailored for Magento agencies, enables developers to effectively leverage these practices, resulting in faster, more stable and secure Magento deployments. ","spans":[]},{"type":"paragraph","text":"With an impressive array of features designed to address every aspect of the development process, this solution is poised to revolutionize the way Magento agencies work and help them deliver exceptional results in the ever-changing world of e-commerce.","spans":[]},{"type":"paragraph","text":"If you are interested in using our CI/CD solution, please use the contact form on this site or at www.magecicd.com.","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$986aca32-7a03-4bbb-82f0-416df1af9bcf","slice_type":"blog_content","slice_label":null}]}},{"id":"ZLU6ZBAAACYAkneM","uid":"partner-highlight-rumvision","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZLU6ZBAAACYAkneM%22%29+%5D%5D","tags":[],"first_publication_date":"2023-07-17T12:59:30+0000","last_publication_date":"2023-07-17T12:59:30+0000","slugs":["partner-highlight-rumvision"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKCrxIAAPU0r8o-","type":"blog","lang":"en-us","uid":"partner-highlight-rumvision"}],"data":{"Title":[{"type":"heading1","text":"Partner highlight: RUMvision","spans":[]}],"image":{"dimensions":{"width":1801,"height":1003},"alt":"Partner highlight: RUMvision","copyright":null,"url":"https://images.prismic.io/elgentos-next/5043184c-a403-4c9a-a283-8b1e41314154_image+%281%29.png?auto=compress,format","id":"ZLU6VhAAACUAkndN","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2023-07-17","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Partner highlight: RUMvision","image":{"dimensions":{"width":1801,"height":1003},"alt":"Partner highlight: RUMvision","copyright":null,"url":"https://images.prismic.io/elgentos-next/5043184c-a403-4c9a-a283-8b1e41314154_image+%281%29.png?auto=compress,format","id":"ZLU6VhAAACUAkndN","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2023-07-17"},"id":"hero$8d59b10e-b503-4c72-afa6-808ca9929d3b","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"sectionHeader":[{"type":"heading2","text":"Improve performance with real user monitoring","spans":[]}],"text":[{"type":"paragraph","text":"Our main focus is building the best technical solution for our customers. To monitor frontend performance and UX we are partnering with RUMvision. The possibility to react in realtime to performance and UX changes is going to be a game changer on tackling CCW metrics. Together we are going to strive for the highest UX performance scores possible because this directly relates to more revenue. Our in-house developed Magento 2 extension for RUMvision allows you to easily integrate RUMvision into your store! You can find it here; https://github.com/elgentos/magento2-rumvision","spans":[]}],"image":{}},"id":"text_and_or_asset$668a53d7-d432-4234-8957-bfc15988795b","slice_type":"text_and_or_asset","slice_label":null}]}},{"id":"ZIbt9REAACEA0Bes","uid":"partner-highlight-sansec","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZIbt9REAACEA0Bes%22%29+%5D%5D","tags":[],"first_publication_date":"2023-06-12T10:05:44+0000","last_publication_date":"2023-07-04T10:13:18+0000","slugs":["partner-highlight-sansec"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZIbqwREAACUA0AkP","type":"blog","lang":"en-us","uid":"partner-highlight-sansec"}],"data":{"Title":[{"type":"heading1","text":"Partner highlight: Sansec","spans":[]}],"image":{"dimensions":{"width":1844,"height":1032},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/ab59eacc-947b-47dd-985c-e2d015fe2832_image.png?auto=compress,format","id":"ZIbs7hEAACMA0BLf","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2023-06-12","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Partner highlight: Sansec","image":{"dimensions":{"width":1844,"height":1032},"alt":"Partner: Sansec","copyright":null,"url":"https://images.prismic.io/elgentos-next/ab59eacc-947b-47dd-985c-e2d015fe2832_image.png?auto=compress,format","id":"ZIbs7hEAACMA0BLf","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2023-06-12"},"id":"hero$27dd2862-077e-4632-8502-17907a910a30","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"sectionHeader":[{"type":"heading2","text":"Magento is a very popular open-source e-commerce platform","spans":[]}],"text":[{"type":"paragraph","text":"Unfortunately, this widespread use also means that Magento stores can be a lucrative target for criminals, highlighting the critical importance of security for these stores. Shocking fact: 1 in 5 stores is hit by malware every year.","spans":[]},{"type":"heading2","text":"Expensive burglaries","spans":[]},{"type":"paragraph","text":"A breach of your store can incur costs in the form of lost sales due to downtime, reputational damage (especially if the hack has to be disclosed) and costs associated with removing the hack and making sure the store is secure again. These costs typically range from $50,000 to $2,000,000 in damages, fines, investigation costs and reputation restoration.","spans":[]},{"type":"paragraph","text":"We do everything we can to prevent such situations. One of the most important actions we take to prevent a hack is making sure that your Magento store is always running on the latest version. We also make sure that the extensions the store uses are up-to-date.","spans":[]},{"type":"heading2","text":"Direct notifications","spans":[]},{"type":"paragraph","text":"To be notified of potential problems, we rely on our partner Sansec. Sansec is a cybersecurity company specializing in e-commerce security. Their main product, eComscan, is an automated security scanner designed to protect Magento stores from attackers. It is used and trusted by leading Magento agencies and hosting companies.","spans":[]},{"type":"paragraph","text":"eComscan offers several benefits. It helps identify the cause of hacks, prevents shopping downtime and suspension of ad campaigns, secures online reputation, ensures compliance with Payment Card Industry (PCI) standards and prevents leaks of personally identifiable information (PII). It is designed to detect malicious activity early, which can help mitigate or prevent the impact of a data leak.","spans":[]},{"type":"heading2","text":"How we leverage Sansec","spans":[]},{"type":"paragraph","text":"We use Sansec to scan all our stores daily and do an in-depth scan weekly. If irregularities are found, we get immediate notification from the monitoring service via Slack, email and SMS for urgent issues.","spans":[]},{"type":"paragraph","text":"We also make use of Sansec's integrity checker. Because of the extensive database of code it checks, it can identify anomalous code by collecting the correct code from all installed versions. When a particular package's code deviates from the average by a certain threshold, we get a notification that something may be wrong. In this way, we can detect malicious code even when the vulnerability used is not yet public. We perform this check every time we do a deployment to ensure that infected code cannot reach the production server via a supply chain attack.","spans":[]},{"type":"paragraph","text":"In short, Sansec helps us sleep easy at night, knowing that your store is in safe hands and closely monitored.","spans":[]}],"image":{}},"id":"text_and_or_asset$fe793c6a-18dd-4185-9cbf-32679e61a67d","slice_type":"text_and_or_asset","slice_label":null}]}},{"id":"ZILXWxEAACUAviyo","uid":"verbeter-je-magento-2-checkout-met-hyva-checkout","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZILXWxEAACUAviyo%22%29+%5D%5D","tags":[],"first_publication_date":"2023-06-09T07:40:14+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZILTzxEAACUAvhzV","type":"blog","lang":"en-us","uid":"level-up-magento-2-checkout-experience-with-hyva-checkout"}],"data":{"Title":[{"type":"heading1","text":"Improve your Magento 2 checkout with the Hyvä Checkout","spans":[]}],"image":{"dimensions":{"width":1200,"height":627},"alt":"Hyvä Checkout in the Wild","copyright":null,"url":"https://images.prismic.io/elgentos-next/6dad4388-3cea-4690-b299-d3e2c7f28da5_screenshotr_2023-6-9T9-18-48.png?auto=compress,format","id":"ZILTYhEAACMAvhry","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2023-06-09","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Improve your Magento 2 checkout with the Hyvä Checkout","image":{"dimensions":{"width":1200,"height":627},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/6dad4388-3cea-4690-b299-d3e2c7f28da5_screenshotr_2023-6-9T9-18-48.png?auto=compress,format","id":"ZILTYhEAACMAvhry","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":null,"date":"2023-06-09"},"id":"hero$3f9455ac-5f33-4f90-a1d4-f11abbc97259","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Why settle for average when you can have the best? 🤩","spans":[]},{"type":"paragraph","text":"As the final step in the customer's shopping experience, the conversion rate of the checkout page plays a crucial role. Hyvä Checkout addresses this by significantly improving the design and usability of the checkout process, which can lead to an increase of as much as 35% in conversion rates.","spans":[]},{"type":"paragraph","text":"Hyvä Checkout revolutionizes the checkout experience for all parties involved. For shoppers, it offers a fast, smooth and intuitive checkout process, with a load time 13 times faster on mobile devices than the standard Magento Luma checkout page. For merchants, it leads to more sales with better conversion rates and out-of-the-box configuration options. And for developers, it reduces the time and effort required to customize Magento Checkout by at least a factor of ten.","spans":[]},{"type":"paragraph","text":"Hyvä Checkout comes standard with several layout options, such as a three-step checkout process or a \"one-step checkout,\" where all steps are displayed on one page. These layout options can be changed quite easily, and adding a custom layout is also simple.","spans":[]},{"type":"paragraph","text":"Hyvä Checkout is built on top of Magewire, a system that allows our back-end developers to create the logic for the checkout page in PHP and XML and connect it to the frontend without having to write large sections of JavaScript. Because the logic is placed on the server side, the checkout page is very fast because it does not rely on client-side performance.","spans":[]}]},{"blogContent":[{"type":"heading2","text":"Integrations","spans":[]},{"type":"paragraph","text":"Adding functionalities such as payment methods, shipping methods and address validation is many times faster than building them in the old Luma checkout page. The Hyvä Checkout was built from the beginning with the idea of easy customizability, which really shows.","spans":[]},{"type":"paragraph","text":"The currently available integrations for Hyvä Checkout are:","spans":[]},{"type":"list-item","text":"Mollie","spans":[]},{"type":"list-item","text":"Multisafepay","spans":[]},{"type":"list-item","text":"PostNL","spans":[]},{"type":"list-item","text":"ShipperHQ","spans":[]},{"type":"list-item","text":"EU VAT calculation","spans":[]},{"type":"list-item","text":"PayPal","spans":[]},{"type":"list-item","text":"Amazon Pay","spans":[]},{"type":"paragraph","text":"Internally, we also built an integration with Paazl.","spans":[]},{"type":"paragraph","text":"For another client, we implemented a list of available UPS pickup points as a shipping method. This took only a few hours instead of days.","spans":[]}]},{"blogContent":[{"type":"heading2","text":"Technical depth","spans":[]},{"type":"paragraph","text":"For the implementation, we only needed to send the shopping cart data via PHP to an API endpoint and get the pickup points back. Using the standard Hyvä Modal, we could easily convert this data into a user-friendly interface for the customer. When the user selects a pickup point, Magewire sends the selected point to the quote.","spans":[]},{"type":"paragraph","text":"One of the great features offered by the checkout page is validation methods. With Magewire, you can easily use the createBlocking method, which is part of the EvaluationResultFactory, to verify that the user has already selected a pickup point. If not, it blocks the user from placing an order. Combined with other methods such as createSuccess and createErrorMessage, it is very easy to validate the user's input to the backend and automatically provide feedback to them in the frontend.","spans":[]},{"type":"paragraph","text":"These methods also make it very easy to break up the checkout process in different ways. It is very easy to create a single-page checkout process, but the same goes for a multi-step checkout process.","spans":[]},{"type":"paragraph","text":"For front-end validation, the checkout page uses a combination of Magewire along with standard Hyvä Form validation. This makes it easy to display messages while the user fills out the form in an accessible way.","spans":[]},{"type":"paragraph","text":"The advantage of the checkout page not containing large portions of JavaScript nor a framework also makes it easy to add payment or shipping options that work with iFrames or are based on standard JS, such as Paazl. Normally, you would have to \"hook\" these into RequireJS or integrate them into a framework such as React. In Hyvä Checkout, you can just use the standard scripts and maybe add some event listeners to send feedback to the backend. This way we can reuse large parts of the original extension.","spans":[]},{"type":"paragraph","text":"These simple methods built into Magewire make it really easy to expand and fully customize the checkout page in much less time than before, without compromising the required reliability of a checkout page.","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$0de1a6bc-5fe9-4df1-9734-137b17d73fd1","slice_type":"blog_content","slice_label":null}]}},{"id":"ZEY6MBAAACEAH1IG","uid":"magetitans-uk-2023-recap","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22ZEY6MBAAACEAH1IG%22%29+%5D%5D","tags":[],"first_publication_date":"2023-04-24T08:13:40+0000","last_publication_date":"2024-10-18T13:21:43+0000","slugs":["magetitans-uk-2023-recap"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZEY5_RAAACUAH1Eh","type":"blog","lang":"en-us","uid":"magetitans-uk-2023-recap"}],"data":{"Title":[{"type":"heading1","text":"MageTitans UK 2023 recap","spans":[]}],"image":{"dimensions":{"width":2048,"height":1536},"alt":"MageTitans UK 2023 Family Photo","copyright":null,"url":"https://images.prismic.io/elgentos-next/427bba1b-5526-4ee5-87cb-0e7ab3fa080b_magetitans-family-photo.jpeg?auto=compress,format","id":"ZEY50RAAACAAH1BX","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2023-04-24","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"MageTitans UK 2023 recap","image":{"dimensions":{"width":2048,"height":1536},"alt":"MageTitans UK 2023 Family Photo","copyright":null,"url":"https://images.prismic.io/elgentos-next/427bba1b-5526-4ee5-87cb-0e7ab3fa080b_magetitans-family-photo.jpeg?auto=compress,format","id":"ZEY50RAAACAAH1BX","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"author":"Peter Jaap Blaakmeer","date":"2023-04-24"},"id":"hero$52b76cb8-90f1-4d7a-91d2-e7d9f663435b","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"heading2","text":"MageTitans UK 2023 summary","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Last weekend, Jeroen and I went to the MageTitans conference in Manchester. Last organized in 2018, this edition was a great comeback! Again organized by Space48, it was a great event with around 150 participants from the UK, Germany, Poland, the Netherlands and several other countries.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The concept is simple; one room filled with mostly tech-focused Magento enthusiasts being updated on the latest, by the greatest.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"With a total of 12 speakers, it was an action-packed day with lots of (technical) content. If you are interested in watching the talks, they will be published soon, so keep an eye on their twitter for news on this; https://twitter.com/magetitans","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The day ended with an after-party on the roof and, for some of us, the after-after-party at the hotel bar.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"This day really got us excited for a possible comeback of MageTitans Groningen, which we organized in 2018 and 2019. So who knows!","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$51997c8d-a092-40ef-89a5-502e17ce14ad","slice_type":"blog_content","slice_label":null}]}},{"id":"Yo41nhEAACgA0S_X","uid":"wegwerp-magento-testomgevingen-met-kubernetes","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yo41nhEAACgA0S_X%22%29+%5D%5D","tags":[],"first_publication_date":"2022-05-25T13:56:50+0000","last_publication_date":"2024-10-18T13:24:30+0000","slugs":["wegwerp-magento-testomgevingen-met-kubernetes"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"Yp9IlRIAAC0Aghs8","type":"blog","lang":"en-us","uid":"disposable-magento-testing-environments-with-kubernetes"}],"data":{"Title":[{"type":"heading1","text":"Disposable Magento test environments with Kubernetes","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":476,"height":350},"alt":"Jeroen pointing at things","copyright":null,"url":"https://images.prismic.io/elgentos-next/f3948bfb-553f-403f-9625-2bf6cd966ac0_elgentos-39+2.png?auto=compress,format","id":"Y61xfxAAACAA-iXo","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2022-05-25","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Disposable Magento test environments with Kubernetes","image":{},"author":"Peter Jaap Blaakmeer","date":"2022-05-25"},"id":"hero$b841f284-8649-4785-bd85-7cbbd21b2617","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"16 min reading time","spans":[{"start":0,"end":15,"type":"strong"}],"direction":"ltr"}]},{"blogContent":[{"type":"paragraph","text":"In this blog, we are going to talk about how we set up disposable test environments (\"Review Apps\" in Gitlab terminology) using Gitlab, Docker, Google Cloud Platform (GCP) and Kubernetes (k8s), among others. We now use disposable test environments in our daily flow, which allows us to push a particular branch, tag or specific commit to a test environment that is automatically built on push and aborted on merge, along with a database and all media files.","spans":[],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/d29886e9-8cf4-4570-a6bb-12f2d8adda0f_gitlab-k8s-docker.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":646,"height":366},"id":"Yo41GxEAACgA0S1m","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"heading4","text":"Retrospective","spans":[],"direction":"ltr"},{"type":"paragraph","text":"A little over a year ago, we wrote about our internal development process. I'm very glad I wrote that blog because it generated a lot of interest and discussion, both online and offline. Nothing significant has changed in that process. We've made some adjustments here and there, but overall it's still the same.","spans":[{"start":48,"end":79,"type":"hyperlink","data":{"link_type":"Web","url":"https://elgentos.com/blog/our-development-process-uncovered/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"I ended that blog by saying that we would be rolling out Review Apps to all of our customers in the coming weeks. Technically that turned out to be true, if you consider 52 weeks later as \"coming weeks. I also said that we hoped to be at 75/25 Magento 2 / Magento 1, and fortunately that indeed turned out to be true. I would say we are now at 90/10 and fully invested to be Magento 1-free by the end of the year.","spans":[],"direction":"ltr"},{"type":"heading4","text":"Terms","spans":[],"direction":"ltr"},{"type":"paragraph","text":"There are a couple of reasons why it took us 52 weeks to roll this out. We were obviously not working on it full-time, but there were a number of developments that made us invest some time in this project again after it had been on the back burner for a while. These were our necessary building blocks to make Review Apps a pragmatic reality.","spans":[],"direction":"ltr"},{"type":"heading5","text":"Databases & media files","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The first major hurdle we had to overcome was deciding where to get our databases from. While technically feasible, we didn't want to log into the production server every time we launched a Review App to retrieve a database dump. That would force us to store a private key that accessed our production server somewhere we didn't want to.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We decided that pushing a stripped down version of the production database at the quietest time of day from a store (usually our night) to an Amazon S3 instance would be the best way. We thought about pushing it to another service (DigitalOcean Storage, Google Cloud Storage, Google Drive, Dropbox, etc.) but found that Amazon had the most granular IAM control to restrict access to the database dumps. We set up separate roles for pushing dumps to specific file locations and for retrieving the dumps from those specific file locations.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The same goes for media; we push a tared media file to an S3 bucket where the Review App deployment process can retrieve it.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We also purchased a top-level domain and set the nameservers to those of DigitalOcean. That way we can use the DigitalOcean API via doctl to automatically add and remove A records. We could move this to Google Cloud DNS in the future to eliminate the dependence on doctl.","spans":[],"direction":"ltr"},{"type":"heading5","text":"Kubernetes","spans":[],"direction":"ltr"},{"type":"paragraph","text":"When we started building this, we first used the DigitalOcean API to create droplets with a pre-configured image. While this worked well, we wanted to use Docker to have a resilient and easy-to-maintain image. We then decided to run the Docker image on the DigitalOcean droplets and deploy our Docker image on that droplet.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The problem with running a DO droplet with Docker and a Docker container running in the droplet is that you have to go through the droplet itself to get to the Docker container, or open non-standard secondary ports for each service. This proved to be both tricky and headache-inducing, because we had to mentally keep track of where we were in the process; were we executing a particular command on the runner (which is itself a Docker image on a pod in a node in a Kubernetes cluster on GCP), in the DigitalOcean droplet or in the Docker container running on that droplet? This is why we decided to run our Docker containers on Kubernetes, eliminating the intermediate droplet and reducing the mental complexity of the process.","spans":[],"direction":"ltr"},{"type":"heading5","text":"hypernode-docker","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Another requirement was to use a Docker image that looked as much like our production servers as possible. Fortunately (and not at all coincidentally), all of our customers run on Byte's Hypernode offering. This means that all of our production servers are initially set up and managed in exactly the same way. We started by building our Docker image based on equit/gitlab-ci-magento2 and went from there. Fortunately, Hypernode's Rick van de Loo created a Hypernode Docker image for local development and for CI/CD purposes; hypernode-docker. Hooray! We took this image and created our own Docker image to add/set a few things;","spans":[{"start":178,"end":194,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.hypernode.com/","target":"_blank"}},{"start":381,"end":405,"type":"hyperlink","data":{"link_type":"Web","url":"https://hub.docker.com/r/equit/gitlab-ci-magento2/","target":"_blank"}},{"start":463,"end":478,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/vdloo_","target":"_blank"}},{"start":565,"end":581,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/ByteInternet/hypernode-docker","target":"_blank"}}],"direction":"ltr"},{"type":"list-item","text":"Removing the default Hypernode insecure key","spans":[],"direction":"ltr"},{"type":"list-item","text":"Adding our own public keys for SSH","spans":[],"direction":"ltr"},{"type":"list-item","text":"Disable password login for SSH","spans":[],"direction":"ltr"},{"type":"list-item","text":"Enable base auth with a default user/pass (mainly to block bots from accidentally indexing test-engv's)","spans":[],"direction":"ltr"},{"type":"list-item","text":"Allow Let's Encrypt challenges via base auth","spans":[],"direction":"ltr"},{"type":"list-item","text":"Install gcloud to manage GCP","spans":[],"direction":"ltr"},{"type":"list-item","text":"Install awscli to retrieve our database and media dumps","spans":[],"direction":"ltr"},{"type":"list-item","text":"Install kubectl to orchestrate our k8s clusters","spans":[],"direction":"ltr"},{"type":"list-item","text":"Install doctl to add DNS records to our test domain name","spans":[],"direction":"ltr"},{"type":"list-item","text":"Install Deployer to deploy","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We are pushing this Docker image to our internal Gitlab Container Registry. Although the Hypernode docker is specifically focused on Magento, we also use it as a base to build, test and deploy this site, which is built with the static site generator Jekyll (written in Ruby).","spans":[],"direction":"ltr"},{"type":"heading5","text":"Runners","spans":[],"direction":"ltr"},{"type":"paragraph","text":"As of version 10.6, Gitlab offers native k8s integration. While this got us very excited at first, it appears to still be in its infancy. Gitlab makes it easy to create a cluster on GCP using the Google Kubernetes Engine (GKE). But this is a cluster that is always running and thus not suitable for our disposable environments.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The advantage is that it is now very easy to create this cluster and use it as a runner for your project. We used to run DigitalOcean droplets with Docker for this purpose, but we have done away with those in favor of Runners running on GCP via Gitlab's native k8s integration. Added bonus; we can run those runners at Google's europe-west4 location, which is the newly opened data center in Eemshaven, just 30km north of us!","spans":[],"direction":"ltr"},{"type":"heading4","text":"Setting up review apps","spans":[],"direction":"ltr"},{"type":"paragraph","text":"This is what we strive for;","spans":[],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/c8b8bdc6-8f48-493b-83a8-61db91b45160_pipeline-success.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1306,"height":137},"id":"Yo41NxEAACsA0S3t","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"The main file we're using to set up Review Apps is .gitlab-ci.yml. We've already been using this to deploy our shops to our production and staging servers but we need to add a bunch of commands to deploy to our disposable environments.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Generally speaking, these are the steps that we need to take in our create-cluster-job-review job;","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Authenticate gcloud","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Create k8s cluster","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Create k8s namespace","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Create secret to access our Gitlab Container Registry","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Create new deployment with the Hypernode image","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Apply service to expose the deployment (web & ssh ports)","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Retrieve IP address of deployment","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Authenticate doctl","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Create A record in our domain (using doctl)","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Then we deploy in our deploy-job-review job;","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Update review yaml file for Deployer","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Deploy!","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We also add a stop-job-review job which runs when a merge request is merged;","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Authenticate doctl","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Remove A record from domain","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Authenticate glcoud","spans":[],"direction":"ltr"},{"type":"o-list-item","text":"Remove cluster (this will also remove the deployment and the service attached to it)","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The reason why we have a create-cluster and a deploy job is so we can exit the create-cluster job when the cluster already exists. The deploy job subsequently deploys a new release on the same cluster.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"In these jobs, we make use of a number of environment variables that are either set by Gitlab itself or by us manually. Here is the list of environment variables we use and what they do. If we have a Magento 1 and a Magento 2 project for a client, certain variables can be set at the group level.","spans":[{"start":87,"end":100,"type":"hyperlink","data":{"link_type":"Web","url":"https://docs.gitlab.com/ee/ci/variables/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"preformatted","text":"+--------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------+ | Variable | Description | Can be set at group level? | +--------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------+ | AWS_ACCESS_KEY_ID | Projects' AWS credentials | No | | AWS_SECRET_ACCESS_KEY | Projects' AWS credentials | No | | AWS_DEFAULT_REGION |","spans":[],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading3","text":"Slack notification","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Because we use stripped database dumps without personal information, the admin users are also stripped. In our Deployer recipe, we generate a random user/pass combination and save those values. We later on use the Slack recipe to pull those variables and pass them on to a shared channel with our client, who can then use the credentials to log in to the admin.","spans":[],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/c9b83168-48ed-4965-9378-e94ec17ce34f_deployer-slack.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":615,"height":321},"id":"Yo41VhEAACoA0S6B","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"heading3","text":"File templates","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The following file templates are for Magento 2. Some paths and file templates would be a little bit different for Magento 1, but I'm sure you'll figure it out.","spans":[],"direction":"ltr"},{"type":"heading5","text":"Dockerfile","spans":[],"direction":"ltr"},{"type":"preformatted","text":"FROM docker.hypernode.com/byteinternet/hypernode-docker:latest MAINTAINER Peter Jaap Blaakmeer # Add public key ADD key.pub /tmp/key.pub RUN cat /tmp/key.pub > /root/.ssh/authorized_keys RUN cat /tmp/key.pub > /data/web/.ssh/authorized_keys RUN rm -f /tmp/deployment.pub # Disable password login RUN sed -i 's/PasswordAuthentication\\ yes/PasswordAuthentication\\ no/g' /etc/ssh/sshd_config # Enable passwordless sudo for app user (see https://github.com/ByteInternet/hypernode-docker/issues/6) RUN echo \"app ALL = (ALL) NOPASSWD: ALL\" >> /etc/sudoers # Enable basic auth RUN echo \"user:encodedpassword\" > /data/web/htpasswd RUN sed -i 's/#auth_basic/auth_basic/g' /data/web/nginx/server.basicauth # Allow Lets Encrypt challenges RUN printf '\\nlocation ^~ /.well-known/acme-challenge/ { {} off;/data/web/nginx/server.basicauth # Remove default *.hypernode.local certificate to avoid nginx errors when using LE RUN rm -rf /etc/nginx/ssl # Install gcloud RUN export CLOUD_SDK_REPO=\"cloud-sdk-$(lsb_release -c -s)\" && ☐ echo \"deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main\" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && ☐ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && apt-get update -y && apt-get install google-cloud-sdk -y # Install awscli RUN apt-get install -y libpython-dev python-dev libyaml-dev python-pip RUN pip install awscli --upgrade --user RUN echo \"export PATH=~/.local/bin:$PATH\" >> ~/.bash_profile # Install kubectl RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.10.5/bin/linux/amd64/kubectl RUN echo \"dbe431b2684f8ff4188335b3b3cea185d5a9ec44 kubectl\" > checksum.txt && sha1sum -c checksum.txt RUN chmod +x ./kubectl && mv ./kubectl /usr/bin/kubectl # Install doctl - use version 1.8.0, newer version has bugs RUN curl -sL https://github.com/digitalocean/doctl/releases/download/v1.8.0/doctl-1.8.0-linux-amd64.tar.gz | tar -xzv RUN mv doctl /usr/local/bin/doctl # Install Deployer through composer globally - use version 6.0.5 for PHP 7.0 RUN composer global require deployer/deployer:6.0.5 RUN alias dep=/root/composer/vendor/bin/dep","spans":[],"direction":"ltr"},{"type":"heading5","text":".gitlab-ci.yml","spans":[],"direction":"ltr"},{"type":"preformatted","text":"image: before_script: - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - eval $(ssh-agent -s) - ssh-add <(echo \"$SSH_PRIVATE_KEY\") - mkdir -p ~/.ssh - echo \"$SSH_PRIVATE_KEY\" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa - '[[ -f /.dockerenv ]] && echo -e \"Host *\"HostrictHostKeyChecking no\" > ~/.ssh/config' # Switch back to PHP 7.0 if needed #- sed -i 's/php7.1/php7.0/g' /etc/my_init.d/60_restart_services.sh #- update-alternatives --set php $(which php7.0) #- bash /etc/my_init.d/60_restart_services.sh # Set gcloud variables - export GCLOUD_CLUSTER_NAME=$(echo ${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME} | cut -c 1-40 | sed 's/-$//') # strip to 40 characters (max for cluster names) and remove dash if that is the last character stages: - build - test - create-cluster - deploy ## PLACE PRODUCTION JOBS HERE create-cluster-job-review: stage: create-cluster only: - /^review-.*$/ script: # Authenticate gcloud - echo $GCLOUD_SA_JSON >> google_sa.json - gcloud auth activate-service-account --key-file google_sa.json - export GOOGLE_APPLICATION_CREDENTIALS=google_sa.json # Check if cluster is running - STATUS=$(gcloud beta container --project \"${GCLOUD_PROJECT_ID}\" clusters list | grep ${GCLOUD_CLUSTER_NAME} | awk '{print$8}') - if [ \"${STATUS}\" = \"RUNNING\" ]; then exit 0; fi # Create Kubernetes cluster - gcloud beta container --project \"${GCLOUD_PROJECT_ID}\" clusters create \"${GCLOUD_CLUSTER_NAME}\" --zone \"${GCLOUD_ZONE}\" --username \"admin\" --cluster-version \"1.8.10-gke.0\" --machine-type \"${GCLOUD_MACHINE_TYPE}\" --image-type \"COS\" --disk-type \"pd-standard\" --disk-size \"100\" --scopes \"https://www.googleapis.com/auth/compute\", \"https://www.googleapis.com/auth/devstorage.read_only\", \"https://www.googleapis.com/auth/logging.write\", \"https://www.googleapis.com/auth/monitoring\", \"https://www.googleapis.com/auth/servicecontrol\", \"https://www.googleapis.com/auth/service.management.readonly\", \"https://www.googleapis.com/auth/trace.append\" --num-nodes \"${GCLOUD_NUM_NODES}\" --enable-cloud-logging --enable-cloud-monitoring --network \"default\" --subnetwork \"default\" --addons HorizontalPodAutoscaling,HttpLoadBalancing,KubernetesDashboard --no-enable-autoupgrade --enable-autorepair # Create namespace - kubectl create namespace gitlab-managed-apps # Create secret for Gitlab Container Registry - kubectl create secret docker-registry gitlab-container-registry --docker-server=$CI_REGISTRY --docker-username=$CI_REGISTRY_USER --docker-password=$CI_REGISTRY_PASSWORD --docker-email=$GITLAB_USER_EMAIL # Create new deployment with the Hypernode image - kubectl apply -f config/kubernetes/hypernode-deployment.yaml # Add label for deployment - kubectl label deployments hypernode-deployment app=${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME} --overwrite # Wait for it to become available - sleep 60 # Apply service to expose the image - kubectl expose deployment hypernode-deployment --type=LoadBalancer --name=hypernode-service # Wait for it to become available - sleep 60 # Add label for service - kubectl label services hypernode-service app=${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME} --overwrite # Get & save IP address - while [ \"${IP_ADDRESS}\" = \"\" ]; do IP_ADDRESS=$(kubectl get service hypernode-service | grep hypernode-service | awk '{print$4}'); done - echo \"IP address found; ${IP_ADDRESS}\" # Authenticate doctl - doctl auth init --access-token ${DO_ACCESS_TOKEN} # Create hostname entry in DigitalOcean Networking - doctl compute domain records create testdomain.tld --record-data ${IP_ADDRESS} --record-name ${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME} --record-type A deploy-job-review: stage: deploy only: - /^review-.*$/ environment: name: review/${CI_COMMIT_REF_NAME} url: https://${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME}.testdomain.tld on_stop: stop-job-review # Update review yaml file for Deployer - mv config/servers/template.yaml.example config/servers/review.yaml - sed -i s/HOST/${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME}.testdomain.tld/g config/servers/review.yaml - sed -i s/ENVIRONMENT/review/g config/servers/review.yaml - sed -i s/STAGE/review/g config/servers/review.yaml - sed -i s/PORT/22/g config/servers/review.yaml - sed -i s/DBNAME/magento/g config/servers/review.yaml # Deploy - /root/.composer/vendor/bin/dep deploy review -n -vvv stop-job-review: stage: deploy only: - /^review-.*$/ variables: GIT_STRATEGY: none script: # Authenticate doctl - doctl auth init --access-token ${DO_ACCESS_TOKEN} # Remove A record from domain testdomain.tld - DNS_RECORD_ID=$(doctl compute domain records list testdomain.tld | grep ${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME} | awk '{print$1}') - doctl compute domain records delete testdomain.tld ${DNS_RECORD_ID} --force; true # Authenticate gcloud - echo $GCLOUD_SA_JSON >> google_sa.json - gcloud auth activate-service-account --key-file google_sa.json - export GOOGLE_APPLICATION_CREDENTIALS=google_sa.json # Check if cluster is running - STATUS=$(gcloud beta container --project \"${GCLOUD_PROJECT_ID}\" clusters list | grep ${GCLOUD_CLUSTER_NAME} | awk '{print$8}') - if [ \"${STATUS}\" != \"RUNNING\" ]; then exit 0; fi # Remove cluster - gcloud beta container clusters delete ${GCLOUD_CLUSTER_NAME} --zone ${GCLOUD_ZONE} --project ${GCLOUD_PROJECT_ID} --async --quiet when: manual environment: name: review/${CI_COMMIT_REF_NAME} action: stop","spans":[],"direction":"ltr"},{"type":"heading5","text":"env_template.php","spans":[],"direction":"ltr"},{"type":"preformatted","text":"\n ' array ( 'frontName' => '{% raw %}{{REVIEW_BACKEND_FRONTNAME}}{% endraw %}', ), 'crypt' => array ( 'key' => '{% raw %}{{REVIEW_CRYPT_KEY}}{% endraw %}', ), 'session' => array ( 'save' => 'files', ),\n 'db' => array ( 'table_prefix' => '{% raw %}{{REVIEW_DB_PREFIX}}{% endraw %}', 'connection' => array ( 'default' => array ( 'host' => '{% raw %}{{REVIEW_DB_HOST}}{% endraw %}',\n 'dbname' => '{% raw %}{{REVIEW_DB_NAME}}{% endraw %}', 'username' => '{% raw %}{{REVIEW_DB_USER}}{% endraw %}', 'password' => '{% raw %}{{REVIEW_DB_PASS}}{% endraw %}', 'model' => 'mysql4', 'engine' => 'innodb', 'initStatements' => 'SET NAMES utf8;', 'active' => '1', ), ), ), 'resource' => array ( 'default_setup' => array ( 'connection' => 'default', ), ), 'x-frame-options' => 'SAMEORIGIN', 'MAGE_MODE' => 'production', 'cache_types' => array ( 'config' => 1, 'layout' => 1,\n ' 'block_html' => 1, 'collections' => 1, 'reflection' => 1, 'db_ddl' => 1, 'eav' => 1, 'customer_notification' => 1, 'full_page' => 1, 'config_integration' => 1, 'config_integration_api' => 1, 'translate' => 1, 'config_webservice' => 1, ), 'install' => array ( 'date' => 'Tue, 1 Jan 2018 13:33:37 +0000', ), );","spans":[],"direction":"ltr"},{"type":"heading5","text":"Deployer recipes","spans":[],"direction":"ltr"},{"type":"preformatted","text":"getArgument('internship'); if ($stage != 'review') { return; } // Get REVIEW_DB_PASS from .my.cnf $reviewDbPass = run('cat /data/web/.my.cnf | grep pass | awk \\'{print$3}'); $envFileContent = file_get_contents('config/env_template.php'); $envVariables = [ 'REVIEW_BACKEND_FRONTNAME', 'REVIEW_DB_HOST', 'REVIEW_DB_NAME', 'REVIEW_DB_USER', 'REVIEW_DB_PASS', 'REVIEW_CRYPT_KEY', 'REVIEW_DB_PREFIX', ]\n\n $defaults = [ 'REVIEW_BACKEND_FRONTNAME' => 'management', 'REVIEW_DB_HOST' => 'Please define me in Gitlab CI Secret Variables', 'REVIEW_DB_NAME' => 'Please define me in Gitlab CI Secret Variables',\n 'REVIEW_DB_USER' => 'Please define me in Gitlab CI Secret Variables', 'REVIEW_CRYPT_KEY' => 'Please define me in Gitlab CI Secret Variables', 'REVIEW_DB_PASS' => (string) $reviewDbPass, 'REVIEW_DB_PREFIX' => '', ]\n\n foreach ($envVariables as $envVariable) { $envFileContent = str_replace('{% raw %}{{' . $envVariable . '}}{% endraw %}', (empty(getenv($envVariable)) ? $defaults[$envVariable] : getenv($envVariable)), $envFileContent); } $envFilename = 'env.php'; file_put_contents($envFilename, $envFileContent); upload($envFilename, '{% raw %}{{release_path}}{% endraw %}/app/etc/env.php'); unlink($envFilename); }); task('magento:create:admin-user', function () { $stage = input()->getArgument('stage'); if ($stage != 'review') { return; } function generateRandomString($length = 12, $abc = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\", $digits = '0123456789') { return substr(str_shuffle($abc), 0, ceil(($length / 2))) . substr(str_shuffle($digits), 0, ceil(($length / 2))); } $user = generateRandomString(); $pass = generateRandomString(); $email = 'info@example.com'; $firstname = 'Review'; $lastname = 'Admin'; run(sprintf('/usr/local/bin/magerun2 --root-dir={% raw %}{{release_path}}{% endraw %} admin:user:create --admin-user=%s --admin-password=%s --admin-email=%s --admin-firstname=%s --admin-lastname=%s', $user, $pass, $email, $firstname, $lastname)); // We set these in variables so we can for example use the Slack recipe to pass these on set('review_admin_user', $user); set('review_admin_pass', $pass); }); task('magento:create:public:symlink', function () { $stage = input()->getArgument('stage'); if ($stage != 'review') { return; } run('rm -rf public && ln -s /data/web/current/pub/ /data/web/public'); }); task('magento:request:ssl', function () { $stage = input()->getArgument('stage'); if ($stage != 'review') { return; } // We need to add the app user to the passwordless sudo list in our Docker image to be able to run nginx_config_reloader // See https://github.com/ByteInternet/hypernode-docker/issues/6 run('echo \"' . get('host') . '\" > /data/web/.dehydrated/domains.txt && dehydrated -c --create-dirs && hypernode-ssl-config-generator && /usr/bin/nginx_config_reloader'); }); task('magento:config:set', function () { $stage = input()->getArgument('stage'); if ($stage != 'review') { return; } $config = [ 'dev/debug/debug_logging' => 1, ]; foreach ($config as $path => $value) { run(sprintf('mysql magento -e \"UPDATE core_config_data SET value = \\\"%s\\\" WHERE path = \\\"%s\\\";\"', $value, $path)); } } }); // Review hooks before('magento:maintenance:enable', 'authenticate:aws'); before('magento:maintenance:enable', 'magento:create:env'); after('magento:maintenance:enable', 'import:database'); after('magento:maintenance:enable', 'import:media'); after('import:database', 'magento:create:admin-user'); after('deploy:symlink', 'magento:create:public:symlink'); after('deploy:symlink', 'magento:request:ssl'); before('magento:cache:flush', 'magento:config:set');","spans":[],"direction":"ltr"},{"type":"heading3","text":"Future","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Using Review Apps has already proven to be a great productivity booster. Clients are able to review & test new features quicker than before we implemented Review Apps. There are some improvements to be done in the future, such as;","spans":[],"direction":"ltr"},{"type":"list-item","text":"Automatically updating the Gitlab issue with the user/pass combination;","spans":[],"direction":"ltr"},{"type":"list-item","text":"Automatically applying a QA label following some trigger to let the QA team know they can start testing it;","spans":[],"direction":"ltr"},{"type":"list-item","text":"Anonymize database so we have near-production data available;","spans":[],"direction":"ltr"},{"type":"list-item","text":"Seed the stripped database with specified testing data.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"I hope you liked this blog. If you have any questions, hit me up on Twitter.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Ps. interested in a workshop to implement this for your company? Contact me.","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$778d3016-12e8-4eb2-b662-336734b55fd0","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9JexIAACwAgh-L","uid":"tool-magento-upgrade-gui","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9JexIAACwAgh-L%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T12:50:06+0000","last_publication_date":"2024-04-07T11:52:10+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"Yp9JhRIAAC4Agh_B","type":"blog","lang":"en-us","uid":"tool-magento-upgrade-gui"}],"data":{"Title":[{"type":"heading1","text":"Tool: Magento Upgrade GUI","spans":[]}],"image":{"dimensions":{"width":1807,"height":898},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/9c2c5a23-e952-4367-b06c-da7524bac8cb_magento-upgrade-gui.png?auto=compress,format","id":"Yp9JdhIAACsAgh9q","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2022-03-29","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Tool: Magento Upgrade GUI","image":{},"author":null,"date":"2022-03-29"},"id":"hero$5d0cec44-e097-42b4-8a54-ba864535bb82","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Have you ever struggled with a Magento 2 upgrade? Quite a daunting task at times....","spans":[]},{"type":"paragraph","text":"But since we started working with Ampersand's Upgrade Patch Helper, upgrading Magento 2 installs became a lot easier! Especially since Luke Rodgers built in support for third-party extensions.","spans":[{"start":34,"end":43,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/company/ampersand-commerce/","target":"_blank"}},{"start":46,"end":66,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/AmpersandHQ/ampersand-magento2-upgrade-patch-helper","target":"_blank"}}]},{"type":"paragraph","text":"The past year we've been working on a tool internally to make this process easier still - a GUI to do your Magento 2 upgrades!","spans":[]},{"type":"paragraph","text":"This is a desktop app to make it easier for you to read and process the output files of the Upgrade Patch Helper.","spans":[]},{"type":"paragraph","text":"We just open-sourced it, so it is available for everyone on Github: elgentos/magento2-upgrade-gui","spans":[{"start":68,"end":97,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/magento2-upgrade-gui","target":"_blank"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$7cf2a05e-2c2a-42fb-9425-977e8f1f0782","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9JCRIAACsAgh1t","uid":"tool-elgentos-http-statuscode-checker","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9JCRIAACsAgh1t%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T12:48:13+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"Yp9I_xIAACkAgh04","type":"blog","lang":"en-us","uid":"tool-elgentos-http-statuscode-checker"}],"data":{"Title":[{"type":"heading1","text":"Tool: HTTP Status Code Checker","spans":[]}],"image":{"dimensions":{"width":1698,"height":1018},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/cf8dc777-b09a-4a98-bd19-be76f8ac1bf4_http-statuscode-checker.png?auto=compress,format","id":"ZKQqGBAAACQASHjt","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2022-03-28","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Tool: HTTP Status Code Checker","image":{"dimensions":{"width":1698,"height":1018},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/cf8dc777-b09a-4a98-bd19-be76f8ac1bf4_http-statuscode-checker.png?auto=compress,format","id":"ZKQqGBAAACQASHjt","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":"Peter Jaap Blaakmeer","date":"2022-03-28"},"id":"hero$203668d1-998c-4df3-8ac1-446526877afa","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"We regularly build and open-source tools that are useful in our day-to-day work. One of these is our HTTP Status Code Checker.","spans":[]},{"type":"paragraph","text":"With this command-line tool you can check the HTTP status codes (200, 404, etc.) based on your sitemap XML or a CSV file with URLs (which you can get from the UA Query Explorer, for example).","spans":[]},{"type":"paragraph","text":"Especially useful before and after a migration to check whether your redirects are set up correctly!","spans":[]},{"type":"paragraph","text":"See the tool on Github; elgentos/http-status-code-checker","spans":[{"start":24,"end":56,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/http-statuscode-checker","target":"_blank"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$b828887e-a2fa-417f-a537-ec79424b8f66","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9HmxIAAC0AghZp","uid":"extensie-imgix-magento-2","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9HmxIAAC0AghZp%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T12:42:07+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"Yp9HNBIAACkAghSv","type":"blog","lang":"en-us","uid":"extension-imgix-magento-2"}],"data":{"Title":[{"type":"heading1","text":"Extension: Imgix for Magento 2","spans":[]}],"image":{"dimensions":{"width":1195,"height":627},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/c02816ca-0437-4ea0-aae1-2905f546e6ab_imgix-logo.png?auto=compress,format","id":"Yp9HMhIAAC0AghSk","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2022-03-27","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Extension: Imgix for Magento 2","image":{"dimensions":{"width":1195,"height":627},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/c02816ca-0437-4ea0-aae1-2905f546e6ab_imgix-logo.png?auto=compress,format","id":"Yp9HMhIAAC0AghSk","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":null,"date":"2022-03-27"},"id":"hero$33310310-329c-4a94-916c-20b5f8de1fd5","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Ever wondered how to quickly optimize images in your Magento 2 store? We've been working with image optimization solution Imgix for a while now. We're such big fans, we even wrote and open-sourced a Magento 2 extension for their service!","spans":[]},{"type":"paragraph","text":"Using this extension, all images on your store (not just the product & category images) are optimized. That means their file size is smaller and they are served in WebP if possible. But Imgix is much more powerful than that; you can also do all kinds of image manipulation.","spans":[]},{"type":"paragraph","text":"An example of this can be seen on our client Publisher Pluim's writers page. The writers' photos (served from the Prismic CMS) are automatically cropped & zoomed in on the writer's face - no manual editing required!","spans":[{"start":72,"end":88,"type":"hyperlink","data":{"link_type":"Web","url":"https://uitgeverijpluim.nl/schrijvers","target":"_blank"}}]},{"type":"paragraph","text":"You can get the extension here; elgentos/magento2-imgix","spans":[{"start":33,"end":56,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/magento2-imgix","target":"_blank"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$0060fd5b-d8f9-4140-b1fa-01ed7a6eec16","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9GqhIAACoAghJr","uid":"extensie-cypress-magento-2","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9GqhIAACoAghJr%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T12:38:05+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"Yp9FShIAAC4AggwI","type":"blog","lang":"en-us","uid":"extension-cypress-magento-2"}],"data":{"Title":[{"type":"heading1","text":"Extension: Cypress for Magento 2","spans":[]}],"image":{"dimensions":{"width":2524,"height":1300},"alt":"A screenshot of the Cypress interface and some code","copyright":null,"url":"https://images.prismic.io/elgentos-next/37b728d9-5e5a-4167-9f69-50551f80e0d3_cypress-rwa.png?auto=compress,format","id":"Yp9FPRIAAC8AggvP","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2022-03-26","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Extension: Cypress for Magento 2","image":{},"author":null,"date":"2022-03-26"},"id":"hero$dc28d8c2-3db6-4fd6-a95b-723cec7e4922","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Cypress.io tests for Magento 2 publicly available!!! 🥳","spans":[{"start":0,"end":10,"type":"hyperlink","data":{"link_type":"Web","url":"https://cypress.io","target":"_blank"}}]},{"type":"paragraph","text":"Testing Magento (especially after upgrades) is a lot of work. It is time consuming and there are many moving parts to keep track of.","spans":[]},{"type":"paragraph","text":"After testing hundreds of Magento installations after their upgrades, we decided we could do better. Faster. Cheaper.","spans":[]},{"type":"paragraph","text":"Cypress.io gave us the tools we needed to create an automated functional test suite to automatically, repetitively and reliably test Magento 2 stores.","spans":[]},{"type":"paragraph","text":"It features Hyvä and Luma tests for both desktop & mobile 🔥 We have identified 78 common scenarios to test and currently have 72% of these covered.","spans":[]},{"type":"paragraph","text":"And after nearly 400 hours of work spread over months, we are very happy to announce the public availability of our open source Cypress test suite for Magento 2!","spans":[]},{"type":"paragraph","text":"Click here to see the code on Github; elgentos/magento2-cypress-testing-suite","spans":[{"start":40,"end":79,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/magento2-cypress-testing-suite","target":"_blank"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$7789e885-a4fa-48b0-ae8d-717a8bd7157e","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9ENRIAACgAggcF","uid":"extensie-prismic-io-headless-cms","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9ENRIAACgAggcF%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T12:29:10+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"Yp9EPBIAAC0Aggci","type":"blog","lang":"en-us","uid":"extension-prismic-io"}],"data":{"Title":[{"type":"heading1","text":"Extension: Prismic.io headless CMS","spans":[]}],"image":{"dimensions":{"width":800,"height":450},"alt":"The Prismic logo","copyright":null,"url":"https://images.prismic.io/elgentos-next/c91d5e18-cd9e-4bf7-b5ac-3e8053a8a902_Prismic.jpg?auto=compress,format","id":"Yp9EHxIAACkAggad","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2022-03-25","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Extension: Prismic.io","image":{"dimensions":{"width":800,"height":450},"alt":"The Prismic logo","copyright":null,"url":"https://images.prismic.io/elgentos-next/c91d5e18-cd9e-4bf7-b5ac-3e8053a8a902_Prismic.jpg?auto=compress,format","id":"Yp9EHxIAACkAggad","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"author":null,"date":"2022-03-25"},"id":"hero$df8c257a-9e0f-437e-9d69-c5f7cd06f66e","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"Managing CMS content in Magento is no fun.","spans":[]},{"type":"paragraph","text":"So, we've said it. Although anyone who has worked with Magento already knows this. Magento released Page Builder for all merchants last year, which is great. But there are other solutions, such as Prismic.io.","spans":[{"start":212,"end":222,"type":"hyperlink","data":{"link_type":"Web","url":"https://prismic.io","target":"_blank"}}]},{"type":"paragraph","text":"Prismic.io is a headless CMS. The big advantage of a headless CMS is that all content lives in a system built specifically to manage this type of content. You also have the advantage of decoupling your e-commerce software from your marketing content, which is especially useful when you're dealing with larger teams and larger amounts of content.","spans":[]},{"type":"paragraph","text":"We built and open-sourced the Prismic.io Magento 2 extension, which allows you to seamlessly integrate your Prismic content into your Magento 2 store.","spans":[]},{"type":"paragraph","text":"Here are a few of our customers where we have implemented Prismic in their Magento 2 store;","spans":[]},{"type":"paragraph","text":"- www.dutchlabelshop.com","spans":[{"start":2,"end":24,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.dutchlabelshop.com","target":"_blank"}}]},{"type":"paragraph","text":"- www.takeaware.nl","spans":[{"start":2,"end":18,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.takeaware.nl","target":"_blank"}}]},{"type":"paragraph","text":"- www.limburgiavlaai.nl","spans":[{"start":2,"end":23,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.limburgiavlaai.nl","target":"_blank"}}]},{"type":"paragraph","text":"- www.uitgeverijpluim.nl","spans":[{"start":2,"end":24,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.uitgeverijpluim.nl","target":"_blank"}}]},{"type":"paragraph","text":"Watch this video on how easy it is to implement:","spans":[]},{"type":"embed","oembed":{"embed_url":"https://www.youtube.com/watch?v=ze1j91-4_OE","type":"video","version":"1.0","title":"Magento 2 + Prismic.io quick & easy blog integration","author_name":"Peter Jaap Blaakmeer","author_url":"https://www.youtube.com/user/PeedyNL","provider_name":"YouTube","provider_url":"https://www.youtube.com/","cache_age":null,"thumbnail_url":"https://i.ytimg.com/vi/ze1j91-4_OE/hqdefault.jpg","thumbnail_width":480,"thumbnail_height":360,"html":""}},{"type":"paragraph","text":"You can find the extension here on Github; elgentos/magento2-prismicio","spans":[{"start":43,"end":70,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/magento2-prismicio","target":"_blank"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$4040f911-74fd-4237-ae4a-ea8f9d1b3245","slice_type":"blog_content","slice_label":null}]}},{"id":"Yo4vgREAAC8A0RML","uid":"magento-monoliths-strongest-weakness","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yo4vgREAAC8A0RML%22%29+%5D%5D","tags":[],"first_publication_date":"2022-05-25T13:33:24+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKYDRIAAP82sCk4","type":"blog","lang":"en-us","uid":"magento-monoliths-strongest-weakness"}],"data":{"Title":[{"type":"heading1","text":"Magento Monolith's Strongest Weakness","spans":[]}],"image":{"dimensions":{"width":6720,"height":4480},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/284a4f33-45e4-49ef-a7fe-7dd2a31a5f62_monolith.jpg?auto=compress,format","id":"ZKQqcRAAACQASHqJ","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2021-09-24","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Magento Monolith's Strongest Weakness","image":{"dimensions":{"width":6720,"height":4480},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/284a4f33-45e4-49ef-a7fe-7dd2a31a5f62_monolith.jpg?auto=compress,format","id":"ZKQqcRAAACQASHqJ","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"author":"Peter Jaap Blaakmeer","date":"2021-09-24"},"id":"hero$77940cee-0a2c-4d51-a8b9-c397037bdc08","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"13 min read","spans":[{"start":0,"end":11,"type":"strong"}]}]},{"blogContent":[{"type":"paragraph","text":"A personal message from Jeroen Boersma and why my name is on the Open Letter.","spans":[{"start":24,"end":38,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/srcoder"}},{"start":61,"end":76,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mage-os.community/blog/the-future-of-magento"}}]},{"type":"heading2","text":"TLDR","spans":[{"start":0,"end":4,"type":"strong"}]},{"type":"paragraph","text":"Why a fork could be the future. It would be awesome if it could go hand-in-hand with Magento. Some insights into my years working with the system.","spans":[]},{"type":"heading2","text":"Introduction","spans":[]},{"type":"paragraph","text":"After years of working with Magento, a part of the community finally voiced a clear signal. Things need to change and people and companies need to wake-up! We all have great ideas, but it feels like we are swimming upstream. Currently there is a pile of work, in the form of Open Issues (I don't even know where to start), unmerged Merge Requests (some that I created myself) and stale branches. There is no clear perspective on what the future will bring (also known as a roadmap) or what to expect as an agency to do some proper planning.","spans":[{"start":69,"end":90,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mage-os.community/blog/the-future-of-magento"}}]},{"type":"paragraph","text":"A lot already has been said. I know for sure that Adobe is awake and paying attention. For the first time I have the feeling that I need to put some words down. It'll be a long read. I think it is that important! A trigger for me was the latest panel discussion.","spans":[]},{"type":"heading2","text":"Panel discussion","spans":[]},{"type":"paragraph","text":"Last Monday (September 20th, 2021) there was a panel discussion at Meet Magento Poland (great to see they made room for the voice of the community) which I think made clear what the open letter is about! Community members from all over the world booked tickets to be there in person and to support that statement. I think it was a pity to see only two members from the Magento Association there in person. However, they came empty-handed. What I hoped for was that they reached out and said, \"Hear, hear our beloved community, we heard you, we spoke to our stakeholders, we have done our stinking best and have set up the following schedule to get everything on track\". Someone stated that Adobe couldn't be there because of COVID restrictions. Although I understand that, I'm flabbergasted that while in 2021, with people working from home, which are able to have online video calls and chats, Adobe has issues with doing a simple video call. I think this was a missed opportunity from Adobe to get in touch with their community. (update: it was by choice of the organization to not do a mixed panel) It was great to see someone from an existing community (Typo3 <3) drop in to share their thoughts and give real and valuable input. This is also something that we shared already with the Magento Association.","spans":[{"start":47,"end":63,"type":"hyperlink","data":{"link_type":"Web","url":"https://youtu.be/adTHJXy-c3Y"}}]},{"type":"heading2","text":"About me","spans":[]},{"type":"paragraph","text":"For years I've been working in the Magento ecosystem, the wonderful community, where I feel at home. I'm so happy about all those people I met over years and more in the years to come. I started off as early as Magento 1.3 (played around with 1.0, killed some servers doing so). Started giving back and contributing through visiting Hackathons, meetups, Meet Magento events, sleepovers, (un-)conferences and starting fresh chats with those lovely people around me. When Magento 2.0 was released it started a whole new era (of problems) with better technology and a better stack (or so they said). As a company, we already had some existing experience with the IT industry and made a wise decision to not jump the Magento 2.0 train yet for commercial implementations.","spans":[]},{"type":"paragraph","text":"I still know a Hackathon in Groningen (It'll be fun they said) and I was able to see in real life the whole bunch of experienced Magento developers needing a whole day to just install a fresh Magento 2 development version. After that first hands-on experience we upgraded all our hardware for all developers to make working with Magento 2 feasible. We started creating our first MRs, creating issues and having fun!","spans":[]},{"type":"paragraph","text":"As much as I loved hackathons, I always felt a little uncomfortable attending Magento Contribution days, it felt like just doing normal work, processing issues, but in our spare time. For me, hackathons were kickstarters for great new ideas and added value to the existing product. Contribution Days weren't focused on that.","spans":[]},{"type":"heading2","text":"Current state from a technical point of view","spans":[]},{"type":"paragraph","text":"I want to sum up a few things which have been bothering me for years, for most of us things we have accepted, but are not actually normal. As everything needs to be on the table, this also needs to be addressed.","spans":[]},{"type":"heading3","text":"Slow release cycle","spans":[]},{"type":"paragraph","text":"Magento 2 chose to maintain one Git repository to rule them all, so all (important) stuff is in there. I guess this is the reason that my name is on the letter. It is also something I already addressed several times in in-person meetings but so far, no clear answer was given as to why. It is the cause why we have planned (big bang) releases, use patches (why??), have bundled unused crapware and errors which means we have big replaces sections in our composer.json files. All in all, this has brought a whole new level of security risks because of merchants choosing not to upgrade even if this means that they run vulnerable software.","spans":[{"start":28,"end":46,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/magento/magento2"}}]},{"type":"heading3","text":"Patches","spans":[]},{"type":"paragraph","text":"Magento 2 uses package managers for distributing code. The Composer implementation for Magento via their repo.magento.com built into shippable packages from the Monolith is just weird to me. The whole idea behind Composer is that you can quickly apply semeantic versioning and release patches and/or minor/major releases to your project without you thinking about it.","spans":[{"start":213,"end":221,"type":"hyperlink","data":{"link_type":"Web","url":"https://getcomposer.org/"}},{"start":252,"end":272,"type":"hyperlink","data":{"link_type":"Web","url":"https://semver.org/"}}]},{"type":"paragraph","text":"However, Magento chose their own approach. Everything needs to be released and signed off and scripts need to run to update the private repository. A two-line bug fix needs to go through the whole release pipeline where everything is tagged and released at once and this can quickly take up to three months before being available upstream. People got fed up with this and composer patches specifically tailored for Magento were born in return. Magento released \"quality patches\" to apply to your project before the next big bang release. Currently we also have half of the packages served via packagist.org and also available in repo.magento.com, which can and may differ or be outdated, causing conflicts to occur.","spans":[]},{"type":"paragraph","text":"The good thing about the curated repository is that they can run a marketplace, where you can buy/sell stuff quickly to merchants via a token based authentication. IMHO everything else could just be served via existing channels if you ask me.","spans":[]},{"type":"heading3","text":"Version numbers","spans":[]},{"type":"paragraph","text":"So you tell me you're running Magento 2.4.1-p1. Yes, that is the commercial version convention. Internally it starts at 100 + the minor of the commercial release number. AFAIC it makes sense knowing that if applying semver, 2.3 to 2.4 is a major version bump is, so technically, 103.* to 104.*. So a package running version 102.0.1-p1 should line up with 2.2.0-p1. I found examples where this didn't match, so it's technical debt.","spans":[]},{"type":"heading3","text":"Replaces","spans":[]},{"type":"paragraph","text":"Magento ships a whole lot of awesomeness by default, however there are also paying vendors (which could be fine) we don't need in our installations. There is no other solution than adding all these packages to the replaces section in composer.json. You tell composer, \"I handle this myself\". In this case it creates a black hole. So in the next release it becomes problematic if new code is released and depends on this black-holed code, the build process breaks. Another reason to put packages in the replace section is because not all packages need to go to production.","spans":[]},{"type":"heading3","text":"Fixing the release cycle","spans":[]},{"type":"paragraph","text":"When Magento at first announced the \"headless\" future for Magento 2 I was very happy - this would be an opportunity to break the current Git repository into smaller packages, deprecate and decouple a lot, which then could be moved and maintained by the community. We were talking about this three years ago and currently this idea is on hold. We need to break the big Git repository down into smaller packages, making the release cycle a lot smoother because we can just apply regular semver. No more scripting involved, no more patching. Let people run RenovateBot or dependabot and update that one package as soon as it is available, like most community packages. The Magento Multi Source Inventory modules are an example of this effort.","spans":[]},{"type":"paragraph","text":"These fixes should also be done if a fork would happen to remove a lot of technical debt. We can run a build script to fill the private repository, why not create or update all individual packages once in new Git repositories, create a reference from the root projects composer.json and everything will keep working as expected.","spans":[]},{"type":"paragraph","text":"We gain from this that we can still have commercial releases on 2.4.4(-p1) on the metapackage but also can update a patch for a package invoking composer update {PACKAGE} because packages are tagged individually. No more patching required! Root package would still be able to run all tests like before.","spans":[]},{"type":"heading3","text":"Fixing the replaces section","spans":[]},{"type":"paragraph","text":"If the repository is fixed, new meta packages can be created, where we finally can have a Magento bare|minimal|default, without packages like the rest API, or GraphQL, or a backend. or a CMS. Upgrades would work because all classes can be found and dependencies are correct, also patches could always be applied, because there aren't any left.","spans":[]},{"type":"heading2","text":"The Community","spans":[]},{"type":"paragraph","text":"In the last couple of years I have seen people whom I consider friends leaving the Magento community in favor of other platforms or other work. Some even went through time of uncertainty in terms of work and money. Some even needed to find a whole new way of making a living. I think that people leaving for something else is more scary than people standing up writing an open letter because the latter still care before walking away!","spans":[]},{"type":"paragraph","text":"We are not only talking about individuals. If your company makes a decision to move elsewhere and your services and knowledge is no longer needed, you can feel left alone or lost. These voices are being heard and ignored for a long time. This has nothing to do with a fork. If a fork would make people feel more involved again, that would directly feed into the success of the community, not for Magento, an Adobe product. This is why we reach out to you, Adobe, I think we got your attention, now it's time to act.","spans":[]},{"type":"paragraph","text":"It worries me hearing voices in favor of leaving (again; reasons stated over and over); \"not feeling heard\", \"development pace is slow\", \"I'm fed up waiting for my MR to be merged\", \"today I'll be adding my 23rd patch\".","spans":[]},{"type":"paragraph","text":"People I trust working with Magento for years leave Adobe and all I read is a brief Twitter message or LinkedIn update. It breaks my trust in the product and it raises questions about the future. These people made me feel included, where I could reach out too if I had a question. One tip I would make to Adobe is to make sure that your community is well-informed.","spans":[]},{"type":"heading2","text":"The Open Letter","spans":[]},{"type":"paragraph","text":"So why did I sign the open letter to the Community? I just knew it was the right and only thing to do. On behalf of the whole community, it's when you want to act now. The way it is evolving, having it set up correctly and doing this the right way! Seeing people care about this too, as of moment of writing, more than 1400 additional people signed the letter. It confirms my first thought signing the letter.","spans":[]},{"type":"heading3","text":"Upstream compatible","spans":[]},{"type":"paragraph","text":"We want to create an upstream compatible fork. Here's a quote from the letter:","spans":[]},{"type":"paragraph","text":"The fork will be upstream-compatible with Magento Open Source as long as it is supported by Adobe. That means, when the monolith is ultimately deprecated, all companies who want to remain on the monolith platform will be able to do so.","spans":[]},{"type":"paragraph","text":"New features in a fork can also be fed back into the main repo, just delivered faster. This could mean that the fork will evolve more quickly as the main repository, a change I am waiting for for years but where everyone profits.","spans":[]},{"type":"heading2","text":"Our company","spans":[]},{"type":"paragraph","text":"We do Magento development and we will do so in years to come. I wrote this with my personal view on things, also how I see that we as a company will benefit from this. We cannot run this alone, we need to do this together, that's why we as a company signed, and also want to invest in this community, with organizing events, hackathons, meetups or even making a fork.","spans":[]},{"type":"heading2","text":"Wrap up","spans":[]},{"type":"paragraph","text":"I hope that writing this gives more insight into why I think that there needs to be a change.It's up to you to form your own opinions. If you are with me and haven't signed yet, please do so.","spans":[{"start":178,"end":190,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mage-os.community/blog/the-future-of-magento#sign-form"}}]},{"type":"paragraph","text":"Thanks for bearing with me to the end,","spans":[]},{"type":"paragraph","text":"Jeroen","spans":[]},{"type":"heading2","text":"What others say","spans":[]},{"type":"paragraph","text":"Finally I want to share all good reads from last couple of weeks. I think all people are awesome and I sorted the list alphabetically.","spans":[]},{"type":"list-item","text":"A constructive response to Igor about Magento 2 OSCA - Damian Retzinger","spans":[{"start":0,"end":71,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/pulse/constructive-response-igor-magento-2-osca-damien-retzinger/?trackingId=aQoR0HcVTFi2l%2Bk%2FKV5kOw%3D%3D"}}]},{"type":"list-item","text":"Accelerating Innovation through a Simplified Release Strategy - Magento","spans":[{"start":0,"end":71,"type":"hyperlink","data":{"link_type":"Web","url":"https://magento.com/blog/accelerating-innovation-through-simplified-release-strategy"}}]},{"type":"list-item","text":"Re: A constructive response to Igor about Magento 2 OSCA - Igor Miniailo","spans":[{"start":0,"end":72,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/pulse/constructive-response-igor-magento-2-osca-damien-retzinger/?trackingId=aQoR0HcVTFi2l%2Bk%2FKV5kOw%3D%3D"}}]},{"type":"list-item","text":"Adobe's response on public forum","spans":[{"start":0,"end":32,"type":"hyperlink","data":{"link_type":"Web","url":"https://community.magento.com/t5/Magento-DevBlog/Building-the-Future-of-Magento-Open-Source-Together/ba-p/482344"}}]},{"type":"list-item","text":"Die Zukunft von Magento Open Source zwischen Adobe - Matthias Zeis","spans":[{"start":0,"end":66,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.matthias-zeis.com/magento-2/editionen/open-source-zukunft"}}]},{"type":"list-item","text":"Here and back again - Jisse Reitsma","spans":[{"start":0,"end":35,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.yireo.com/blog/2021-09-20-here-and-back-again-the-magento-open-letter"}}]},{"type":"list-item","text":"Imagine - Karen Baker","spans":[{"start":0,"end":21,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/pulse/imagine-karen-baker/?trackingId=xkt2FcC2M6%2B7wDydVUfuXg%3D%3D"}}]},{"type":"list-item","text":"Jisse Reitsma | Mage Open Source Community Alliance - Brent Peterson","spans":[{"start":0,"end":68,"type":"hyperlink","data":{"link_type":"Web","url":"https://talk-commerce.com/2021/09/21/jisse-reitsma-mage-open-source-community-alliance/"}}]},{"type":"list-item","text":"Magento Open Source Future - Thoughts - Jakub Winkler","spans":[{"start":0,"end":53,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/pulse/magento-open-source-future-thoughts-jakub-winkler/"}}]},{"type":"list-item","text":"Magento 2 OpenSource/CE Inches Towards a Fork - Alan Storm","spans":[{"start":0,"end":58,"type":"hyperlink","data":{"link_type":"Web","url":"https://alanstorm.com/do-not-try-to-bend-the-fork-thats-impossible/"}}]},{"type":"list-item","text":"Open Letter to the Magento Community","spans":[{"start":0,"end":36,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mage-os.community/blog/the-future-of-magento"}}]},{"type":"list-item","text":"Open Source, Leadership and Direction - Kristoff Ringleff","spans":[{"start":0,"end":57,"type":"hyperlink","data":{"link_type":"Web","url":"https://fooman.com/blog/open-source-leadership-and-direction.html"}}]},{"type":"list-item","text":"Re: Open letter to the Magento Community - Guido Jansen","spans":[{"start":0,"end":55,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/pulse/re-open-letter-magento-community-guido-x-jansen-/"}}]},{"type":"list-item","text":"Upcoming releases - Magento","spans":[{"start":0,"end":27,"type":"hyperlink","data":{"link_type":"Web","url":"https://devdocs.magento.com/release/"}}]},{"type":"list-item","text":"The Future of Magento Open Source - Hyva","spans":[{"start":0,"end":40,"type":"hyperlink","data":{"link_type":"Web","url":"https://hyva.io/blog/news/the-future-of-magento-open-source.html"}}]},{"type":"list-item","text":"The Future of Magento: An Update for the Magento Community - Wagento","spans":[{"start":0,"end":68,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.wagento.com/blog/the-future-of-magento-an-update-for-the-magento-community/"}}]},{"type":"list-item","text":"The Magento Community Alliance (and more!) - Brent Peterson","spans":[{"start":0,"end":59,"type":"hyperlink","data":{"link_type":"Web","url":"https://talk-commerce.com/2021/09/20/the-magento-community-alliance-and-more/"}}]},{"type":"list-item","text":"There isn't much to add - Thomas Goletz","spans":[{"start":0,"end":39,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.linkedin.com/posts/thomas-goletz_there-isnt-much-to-add-from-the-both-first-activity-6845317932512358400-cPOE/"}}]},{"type":"list-item","text":"Why and why not - Marius","spans":[{"start":0,"end":24,"type":"hyperlink","data":{"link_type":"Web","url":"https://marius-strajeru.blogspot.com/2021/09/why-and-why-not.html"}}]},{"type":"paragraph","text":"Social interactions:","spans":[]},{"type":"list-item","text":"Reddit: Reactions","spans":[{"start":0,"end":17,"type":"hyperlink","data":{"link_type":"Web","url":"https://teddit.net/r/Magento/comments/pobeqh/open_letter_to_the_magento_community_mage_open/"}}]},{"type":"list-item","text":"Twitter: It's not gonna work - Guido Jansen","spans":[{"start":0,"end":43,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/guido/status/1438234639727013895"}}]},{"type":"list-item","text":"Twitter: LOL - Max","spans":[{"start":0,"end":18,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/maksek_ua/status/1438237183509348352"}}]},{"type":"list-item","text":"Twitter: Magento PHP Monolith - Aaron Sheehan","spans":[{"start":0,"end":45,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/aaronsheehan/status/1438626451088105474?s=21"}}]},{"type":"list-item","text":"Twitter: No Microservices and deprecation - Muhammed Vatansever","spans":[{"start":0,"end":63,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/vatansevermu/status/1438589044850495498"}}]},{"type":"list-item","text":"Twitter: Uprising Community - Kalen Jordan","spans":[{"start":0,"end":42,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/kalenjordan/status/1437794510923354119"}}]},{"type":"list-item","text":"Twitter: Thread on builds - Anton Krill","spans":[{"start":0,"end":39,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/AntonKril/status/1439982842461687816"}}]},{"type":"list-item","text":"Twitter: No deprecation of services - Matthew Zimmerman","spans":[{"start":0,"end":55,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/mattz_mg/status/1438940686540283904"}}]},{"type":"list-item","text":"Youtube: MM21PL: Building Magento Open Source Future","spans":[{"start":0,"end":52,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.youtube.com/watch?v=adTHJXy-c3Y"}}]},{"type":"heading3","text":"Updates","spans":[]},{"type":"list-item","text":"Adobe couldn't be there because of travel restrictions, MMPL decided not to do a mixed panel, updated section.","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$a05ea5b5-2370-4388-9561-b7ef70a33809","slice_type":"blog_content","slice_label":null}]}},{"id":"Yo4wjBEAACoA0RgU","uid":"the-future-of-magento","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yo4wjBEAACoA0RgU%22%29+%5D%5D","tags":[],"first_publication_date":"2022-05-25T13:35:12+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKYYBIAAOA2sCqq","type":"blog","lang":"en-us","uid":"the-future-of-magento"}],"data":{"Title":[{"type":"heading1","text":"The Future of Magento","spans":[]}],"image":{"dimensions":{"width":5434,"height":3623},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/49df244c-88ce-4838-bfcc-b70444f23e1d_future-of-magento.jpg?auto=compress,format","id":"ZKQqnRAAACUASHrk","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2021-09-14","author":"Peter Jaap Blaakmeer","slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"The Future of Magento","image":{"dimensions":{"width":5434,"height":3623},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/49df244c-88ce-4838-bfcc-b70444f23e1d_future-of-magento.jpg?auto=compress,format","id":"ZKQqnRAAACUASHrk","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"author":"Peter Jaap Blaakmeer","date":"2021-09-14"},"id":"hero$55d04628-f7d4-4c5b-8337-8d9dfc96454d","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"1 min read","spans":[{"start":0,"end":10,"type":"strong"}]}]},{"blogContent":[{"type":"paragraph","text":"Magento has been a big part of our life over here at elgentos. We started with Magento almost 10 years ago and went through a lot of ups and downs with it, but we still love it! The Magento Community is a strong and vibrant one, and one that has given us lots of joy, knowledge and meaningful relationships over the years.","spans":[]},{"type":"paragraph","text":"However, since Adobe has acquired Magento, there are some concerns about the lifespan of what is generally called the Magento Monolith.","spans":[{"start":118,"end":134,"type":"em"}]},{"type":"paragraph","text":"We are kickstarting an intitiave to making sure the Magento Community has a bright future ahead. To safeguard this, we have pledged our support to the Future of Magento by signing an open letter to the Magento Community, written by what is for now called the Mage Open Source Community Alliance. This letter is signed by multiple prominent agencies and individuals working with Magento Community who want to make clear Magento is here to stay.","spans":[]},{"type":"paragraph","text":"You can read our open letter and join us here; The Future of Magento.","spans":[{"start":47,"end":68,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mage-os.community/blog/the-future-of-magento"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$37f2f675-027d-4e00-8237-59939461d1e3","slice_type":"blog_content","slice_label":null}]}},{"id":"Yo4zahEAAC0A0SV_","uid":"the-first-pwa-on-deity-falcon-for-uitgeverij-pluim","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yo4zahEAAC0A0SV_%22%29+%5D%5D","tags":[],"first_publication_date":"2022-05-25T13:47:43+0000","last_publication_date":"2024-10-18T13:20:34+0000","slugs":["de-eerste-pwa-op-deity-falcon-voor-uitgeverij-pluim"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKY9RIAAP82sC0-","type":"blog","lang":"en-us","uid":"the-first-pwa-on-deity-falcon-for-uitgeverij-pluim"}],"data":{"Title":[{"type":"heading1","text":"The first PWA on Deity Falcon for Publisher Pluim","spans":[],"direction":"ltr"}],"image":{"dimensions":{"width":700,"height":488},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/eac82be0-4596-4e4a-b4ca-796a04dea3c7_uitgeverijpluim-screenshot.webp?auto=compress,format","id":"Y48acBEAACIAV5St","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2019-07-08","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"The first PWA on Deity Falcon for Publisher Pluim","image":{"dimensions":{"width":476,"height":350},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/86cf7d36-b659-48bb-890e-5f7d673a47de_EGlpBtZW4AARIOp+2.png?auto=compress,format","id":"Y61xfxAAACIA-iXq","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":null,"date":"2019-07-08"},"id":"hero$bd258820-af96-48aa-b60a-e927fa94dae3","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"13 min read","spans":[{"start":0,"end":11,"type":"strong"}]}]},{"blogContent":[{"type":"paragraph","text":"Last week we launched our first PWA project for a Magento 2 project. In this blog post we explain why and how we built this sophisticated and lightning-fast webshop. If you can't wait to see the result (we totally understand!) you can visit the webshop here; uitgeverijpluim.nl.","spans":[{"start":295,"end":313,"type":"hyperlink","data":{"link_type":"Web","url":"https://uitgeverijpluim.nl/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"In the Magento PWA scene, there has been a lot of talk and little action regarding actually building shops. We've seen proof of concepts, but that doesn't get you anywhere. So when we had the opportunity to build a PWA shop, we decided to just go for it and see where we would end up. We were lucky enough to come across a client who was willing to take on this adventure with us!","spans":[],"direction":"ltr"},{"type":"heading3","text":"Customer requirements","spans":[],"direction":"ltr"},{"type":"paragraph","text":"When the client approached us, they had no idea what a PWA was and what it entailed. Publisher Pluim is a Dutch publishing company that started in 2018. They publish literary fiction and non-fiction books aimed at the Dutch market. We pitched them PWA and explained what it could do for them. Two of the main advantages of PWA are the speed of the webshop and the high Google Pagespeed Insight scores that are relatively easily achievable compared to a standard Magento 2 frontend implementation.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The requirements were pretty straightforward, which gave us the confidence to start building a PWA. Currently, PWAs are not as feature-rich as the standard Magento 2 frontend, and equivalent features are still a long way off. The main initial requirements for phase 1 were","spans":[],"direction":"ltr"},{"type":"list-item","text":"Ability to show authors","spans":[],"direction":"ltr"},{"type":"list-item","text":"Ability to display books","spans":[],"direction":"ltr"},{"type":"list-item","text":"Ability to display upcoming agenda items","spans":[],"direction":"ltr"},{"type":"list-item","text":"Ability to display news items","spans":[],"direction":"ltr"},{"type":"list-item","text":"Easily maintainable","spans":[],"direction":"ltr"},{"type":"list-item","text":"Quick website","spans":[],"direction":"ltr"},{"type":"paragraph","text":"The following requirements for phase 2 were;","spans":[],"direction":"ltr"},{"type":"list-item","text":"Ability to sell products","spans":[],"direction":"ltr"},{"type":"list-item","text":"Ability to use iDeal as a payment method","spans":[],"direction":"ltr"},{"type":"list-item","text":"Possibility of basic email marketing","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Because of the relatively small amount of books in the shop (about 20 to 50 at any given time), we could do without filter options and search function. Because of the limitation to the Dutch-speaking market, we could also do without internationalization and complex tax settings, etc.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We then began looking for ways to separate these two phases and get the content site (without e-commerce functionality) online as quickly as possible.","spans":[],"direction":"ltr"},{"type":"heading3","text":"React","spans":[],"direction":"ltr"},{"type":"paragraph","text":"First, we talk about our underlying tech stack. We are traditionally a PHP agency - we've always worked with Magento and a few years ago we started developing microservices and support tools for e-commerce in Laravel, which is also a PHP framework (but not specifically focused on any particular type of web app). Our frontend stack has always just been HTML/JS/CSS (and XML with respect to Magento).","spans":[],"direction":"ltr"},{"type":"paragraph","text":"PWA offerings, however, are not built in PHP. They are written in Javascript and, more specifically, in a Javascript front-end framework. There are two major players here: Vue and React. We considered both and ultimately chose React. I won't go into detail about why we chose React over Vue; you can read up on that choice yourself. In the end, it comes down to personal preference (and arguing often sounds like a religious debate).","spans":[{"start":180,"end":183,"type":"hyperlink","data":{"link_type":"Web","url":"https://vuejs.org/","target":"_blank"}},{"start":187,"end":192,"type":"hyperlink","data":{"link_type":"Web","url":"https://reactjs.org/","target":"_blank"}},{"start":316,"end":319,"type":"hyperlink","data":{"link_type":"Web","url":"https://medium.com/fundbox-engineering/react-vs-vue-vs-angular-163f1ae7be56","target":"_blank"}},{"start":320,"end":325,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.codica.com/blog/react-vs-vue-2019/","target":"_blank"}},{"start":326,"end":329,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.monterail.com/blog/vue-vs-react-2019","target":"_blank"}},{"start":330,"end":332,"type":"hyperlink","data":{"link_type":"Web","url":"https://programmingwithmosh.com/javascript/react-vs-vue-a-wholesome-comparison/","target":"_blank"}},{"start":333,"end":337,"type":"hyperlink","data":{"link_type":"Web","url":"https://hackr.io/blog/react-vs-vue","target":"_blank"}},{"start":338,"end":345,"type":"hyperlink","data":{"link_type":"Web","url":"https://skywell.software/blog/vue-vs-react-what-to-choose-in-2019/","target":"_blank"}}],"direction":"ltr"},{"type":"heading4","text":"Gatsby","spans":[],"direction":"ltr"},{"type":"paragraph","text":"After deciding to work with React a while ago, we started playing around with building pure React apps (like our tableratesgenerator.com) and building sites using Gatsby, which is a React-based static site generator. In fact, the site you're reading this on is built on Gatsby. Gatsby allows us to efficiently and quickly build extremely fast sites in a relatively short time and without much training for developers. React components are ultimately just little bundles of Javascript, making it quite easy for anyone with some Javascript experience to understand what is going on in such a framework. Moreover, the Gatsby docs are just excellent and the community is very welcoming (hello Jason!).","spans":[{"start":142,"end":165,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.tableratesgenerator.com/","target":"_blank"}},{"start":206,"end":212,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.gatsbyjs.org/","target":"_blank"}},{"start":690,"end":707,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.gatsbyjs.org/docs/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"Since the first milestone was to get a content site up and running, we decided to go for Gatsby to get the site up and running. We had a design in pure HTML/JS/CSS and rewrote it into a Gatsby theme, which is basically a collection of React components. This allowed us to later port these components to our PWA solution with relative ease, since we also chose React when we went the PWA route for e-commerce.","spans":[{"start":227,"end":239,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.gatsbyjs.org/docs/themes/what-are-gatsby-themes/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"The Gatsby site is still accessible here.","spans":[{"start":29,"end":33,"type":"hyperlink","data":{"link_type":"Web","url":"http://kind-jepsen-2ed2ec.netlify.com/","target":"_blank"}}],"direction":"ltr"},{"type":"heading4","text":"Prismic","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Now it is time to introduce Prismic. Prismic is one of many interesting headless CMS offerings. A headless CMS is a content management system that allows you to manage your content without having any say in how that content is presented on the frontend. For example, if you use Wordpress without a frontend, but only use the Wordpress API to pull your data from the Wordpress backend and display it in your own custom frontend, you're essentially using it as a headless CMS.","spans":[{"start":18,"end":25,"type":"hyperlink","data":{"link_type":"Web","url":"https://prismic.io/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"We did a fairly in-depth comparison between a number of headless CMS providers such as Prismic, Sanity, Contentful, ButterCMS and Cockpit. This comparison could be a blog post in itself, but in summary we chose Prismic because of its price point (cheap but not too cheap), feature set (fairly complete) and their responsiveness to our inquiries (very fast). We also wanted a hosted SaaS solution (to minimize overhead), so self-hosted solutions were not considered. Contentful is a great option but insanely expensive.","spans":[{"start":99,"end":106,"type":"hyperlink","data":{"link_type":"Web","url":"https://prismic.io/","target":"_blank"}},{"start":108,"end":114,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.sanity.io/","target":"_blank"}},{"start":116,"end":126,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.contentful.com/","target":"_blank"}},{"start":128,"end":137,"type":"hyperlink","data":{"link_type":"Web","url":"https://buttercms.com/","target":"_blank"}},{"start":141,"end":148,"type":"hyperlink","data":{"link_type":"Web","url":"https://getcockpit.com/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"Setting up Prismic is a very pleasant process; their backend is fast and pleasing to the eye. Their documentation is extensive and thorough, and above all, the amount of libraries and tools they offer in a wide variety of languages and frameworks is unmatched. This seems to be a typically French thing, as the Algolia integrations are just as endless. In particular, the NodeJS and React components for Prismic.io were real time savers during our project.","spans":[{"start":105,"end":117,"type":"hyperlink","data":{"link_type":"Web","url":"https://prismic.io/docs","target":"_blank"}},{"start":174,"end":195,"type":"hyperlink","data":{"link_type":"Web","url":"https://prismic.io/docs/rest-api/prismic-api-client/libraries-and-tools","target":"_blank"}},{"start":335,"end":354,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.algolia.com/integrations/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"Prismic also provides a GraphQL API endpoint that we used to extract data from it.","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Once we set up Prismic, we asked the customer to enter all the data. We created custom types for a number of entities;","spans":[],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/44955174-89fe-427c-9028-2fdfb4754297_prismic-types.png?auto=compress,format","alt":"Screenshot of Prismic custom types overview","copyright":null,"dimensions":{"width":1572,"height":722},"id":"Yo4xqBEAACgA0R1Z","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"We then linked Prismic to Gatsby via the gatsby-source-prismic package to make all the data available in our Gatsby site. Since Gatsby builds a static site and does not require a server after it is implemented, we used Netlify for (free!) hosting of the site.","spans":[{"start":51,"end":72,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/angeloashmore/gatsby-source-prismic","target":"_blank"}},{"start":249,"end":256,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.netlify.com/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading4","text":"Magento 2","spans":[],"direction":"ltr"},{"type":"paragraph","text":"On to e-commerce! By this time we had the content site almost ready to go, but we needed to display products on the site, albeit without e-commerce functionality. We set up a fairly standard Magento 2 instance and configured it so the client could add products to it. After they uploaded their products into Magento, we were able to use the gatsby-source-magento2 to retrieve product data from Magento 2.3 via GraphQL.","spans":[{"start":360,"end":382,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.gatsbyjs.org/packages/gatsby-source-magento2/","target":"_blank"}}],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/aac8d99c-eef2-462a-827a-0bdd040669e5_magento2-products.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1503,"height":909},"id":"Yo4xqBEAACoA0R1a","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading4","text":"Deity Falcon","spans":[],"direction":"ltr"},{"type":"paragraph","text":"This is where the magic happens. We created all the conditions to get started with the PWA implementation; we had a Magento 2 instance with product data and a Prismic instance with all the other content, such as authors, calendar items, news items and content pages. We wanted to work in React so we could port the components we had already written in Gatsby to the PWA solution. We wanted to work in React so we could port the components we had already written in Gatsby to the PWA solution. We had some pleasant conversations with Eindhoven-based Deity, which develops a range of products for e-commerce, including Falcon and PushPro.","spans":[{"start":612,"end":617,"type":"hyperlink","data":{"link_type":"Web","url":"https://deity.io/","target":"_blank"}},{"start":681,"end":687,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon.deity.io/","target":"_blank"}},{"start":691,"end":698,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.pushpro.io/","target":"_blank"}}],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/c0b006d9-b801-4df2-8869-20d1cd64f1d5_deity-demo-shop.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1496,"height":1172},"id":"Yo4xqREAACwA0R1e","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"Their Falcon product is a PWA solution written in React that consists of two main components; Falcon Server and Falcon Client. Falcon Server acts as the glue between our backend systems (such as Magento and Prismic) and the frontend. The frontend in this case is Falcon Client, but the system is set up independently - we could have chosen to use our Gatsby site as the frontend and use Falcon Server to introduce e-commerce functionality into our project.","spans":[{"start":98,"end":111,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon.deity.io/docs/falcon-server/basics","target":"_blank"}},{"start":115,"end":128,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon.deity.io/docs/falcon-client/basics","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"So why did we choose to implement our design in Falcon Client rather than e-commerce functionality in the form of a Falcon Server implementation in Gatsby? Because we already had many custom React components written in Gatsby, it was fairly easy to port this over. We also had no dependency regarding data, as this was abstracted in Magento and Prismic. We guessed that this was less work than writing our own implementation of Falcon Server for Gatsby, and in retrospect, it looks like we made the right choice, at least in terms of time. Note that we did not use Falcon UI, which is a UI component library for React. This is because we did not build the design from scratch; we had already converted it to plain HTML/JS/CSS. Falcon UI would be an option to use when we still needed to convert the design from Figma to code, in addition to other UI component library such as Storybook.","spans":[{"start":665,"end":674,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon-ui.docs.deity.io/","target":"_blank"}},{"start":985,"end":994,"type":"hyperlink","data":{"link_type":"Web","url":"https://storybook.js.org/","target":"_blank"}}],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading5","text":"Custom work in Falcon API providers","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Until now, the only major chunk of custom development work had to do with the frontend. But getting Prismic data to Falcon Server was something that hadn't been done before; we had to write our own sourcing packages. We ended up writing 5 API providers to source the 5 custom content types from Prismic (Calendar, Author, Homepage, News, Page). Books were already sourced from Magento through Falcon's Magento 2 module, although we had to write an extension to pass custom fields from Magento through Falcon Server to Falcon Client (see the Deity Falcon data flow diagram):","spans":[{"start":252,"end":265,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon.deity.io/docs/falcon-server/api-providers","target":"_blank"}},{"start":415,"end":431,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon.deity.io/docs/backend/installing-magento2","target":"_blank"}},{"start":559,"end":592,"type":"hyperlink","data":{"link_type":"Web","url":"https://falcon.deity.io/docs/getting-started/intro#overall-deity-falcon-data-flow-schema","target":"_blank"}}],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/16cc155a-eafc-45f1-ad24-5b122c396f3b_falcon-data-flow.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":2000,"height":1906},"id":"Yo4xqREAACoA0R1d","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading5","text":"Sitemap generator","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We also wrote a sitemap generator in Falcon Server. We couldn't use the Magento 2 sitemap because it would give us the Magento URLs for the products instead of the Falcon URLs and it would only have URLs for the books, but we have 5 other content types that we want indexed as well. Falcon Server is the place to do this; this is the only system that is aware of all the routes available within the site. We will open-source this sitemap generator soon, so keep an eye on our Github page.","spans":[{"start":535,"end":548,"type":"hyperlink","data":{"link_type":"Web","url":"http://github.com/elgentos","target":"_blank"}}],"direction":"ltr"},{"type":"heading5","text":"Images CDN","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Then we had the issue of images; we had images coming in from Magento and we had images coming in from Prismic. This would essentially work fine, but we wanted a single repository to manage these images and we wanted to implement a CDN for images. We decided to go with Cloudinary because we had heard good things and they have a great Magento 2 extension. By using this extension, Magento would return us the Cloudinary version of the product image, but Prismic does not do this. Therefore, we wrote our @elgentos/cloudinary-automatic-image-uploader package for Falcon Server. When we receive an image URL from Prismic, we hash it and check if a corresponding file exists in Cloudinary. If yes, we return that URL. If not, we upload it to Cloudinary using their NodeJS SDK and retrieve the resulting Cloudinary URL. One of the (many) cool things is that we can apply face image transformation for the author photos, so Cloudinary automatically crops to the author's face.","spans":[{"start":335,"end":345,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloudinary.com/","target":"_blank"}},{"start":411,"end":429,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/cloudinary/cloudinary_magento2","target":"_blank"}},{"start":917,"end":931,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloudinary.com/documentation/node_integration#quick_example_file_upload","target":"_blank"}},{"start":1023,"end":1049,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloudinary.com/documentation/image_transformations","target":"_blank"}}],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/6b4b280d-a4e5-4f5b-b0a0-14c127740696_cloudinary-face-transformation.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":871,"height":413},"id":"Yo4xqREAAC8A0R1b","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading5","text":"iDeal payment method integration","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Out of the box, Falcon offers a PayPal and an Adyen integration. Unfortunately, the Adyen integration only offers a credit card implementation and no iDeal integration, which we needed. We wanted to go for a simple iDeal payment implementation and chose Mollie because of their extremely simple API and their great Magento 2 extension. The Magento 2 extension actually does all the heavy lifting; we just had to extend the extension a bit to pass another redirect URL. This is because we need to redirect the customer back to the Falcon frontend instead of the default Magento 2 frontend. You can find us on our Github page; Falcon Mollie implementation for Magento 2.","spans":[{"start":315,"end":333,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/mollie/magento2","target":"_blank"}},{"start":649,"end":691,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/FalconMollie","target":"_blank"}}],"direction":"ltr"},{"type":"heading5","text":"Pagespeed Insights optimization","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We wanted a fast site, and a fast site is what we got! We spent a lot of time optimizing the code and the Webpack bundles that make up our final site, but here is the result (and yes, we are quite proud of it!);","spans":[],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/ffeac4f6-a7fe-4c51-aeb9-5acee9fdd17e_pagespeed-100.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":783,"height":345},"id":"Yo4xqBEAACoA0R1X","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading4","text":"Hosting","spans":[],"direction":"ltr"},{"type":"paragraph","text":"We have long been fans of Byte's Hypernode offering and run all of our Magento websites on that platform. Since this is a relatively lightweight site, we wanted to be able to run the entire stack on a single Hypernode. This had its challenges because, of course, we were the first ones to try this. Hypernode is a managed platform, so we didn't have a completely free hand to set everything up to our liking. But we got great help from Hypernode's support team and we got it working. In short, here is some of the work we had to do to make our Hypernode compatible with Falcon;","spans":[{"start":34,"end":50,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.hypernode.com/","target":"_blank"}}],"direction":"ltr"},{"type":"list-item","text":"Install Node and yarn (or npm)","spans":[],"direction":"ltr"},{"type":"list-item","text":"Set up crons that will start the Falcon client and server when not running","spans":[],"direction":"ltr"},{"type":"list-item","text":"Configure Nginx so that it passes port 80/443 proxy to port 3000 (on which Falcon client runs)","spans":[],"direction":"ltr"},{"type":"list-item","text":"Configure Nginx so that the Magento site will be available on a sub-subdomain (magento.projectname.hypernode.io)","spans":[],"direction":"ltr"},{"type":"list-item","text":"Redirect www to non-www","spans":[],"direction":"ltr"},{"type":"list-item","text":"Redirect http to https","spans":[],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading4","text":"Hours","spans":[],"direction":"ltr"},{"type":"paragraph","text":"How long did all this take? We had a start-up period where the customer defined their requirements, created content, etc. We started building the Gatsby site in mid-April, building the Falcon shop in mid-May and we launched the shop on July 4. Here is a visual overview;","spans":[],"direction":"ltr"},{"type":"image","url":"https://images.prismic.io/elgentos-next/c7fafcf5-8471-42b4-a9fd-42ce0dc03a77_project-graph.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1027,"height":494},"id":"Yo4xqBEAAC0A0R1Y","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"Here is a breakdown of the major project components we discussed;","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Component Sub-component Hours","spans":[{"start":0,"end":28,"type":"strong"}],"direction":"ltr"},{"type":"paragraph","text":"Magento 2 setup 9","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Prismic setup 17","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Gatsby implementation 48","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Falcon implementation total 250","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Prismic 65","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Cloudinary 32","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Mollie 16","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Hosting 7","spans":[],"direction":"ltr"},{"type":"paragraph","text":"CI/CD setup 4","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Sitemap generator 2","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Other 124","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Project Management 20","spans":[],"direction":"ltr"},{"type":"paragraph","text":"Total 344","spans":[],"direction":"ltr"},{"type":"paragraph","text":"","spans":[],"direction":"ltr"},{"type":"heading4","text":"Conclusion","spans":[],"direction":"ltr"},{"type":"paragraph","text":"All in all, we are very happy with our process and how the web shop turned out. We learned a lot and feel we could do a similar project in half the time (don't blame us ;-)). Developing in Falcon/React was a bit of a learning curve because we came from the PHP world. Falcon is not bug-free or feature-complete yet, but the team is working hard and they are very responsive to Slack. They listen to feedback and implement suggested changes pretty quickly. We are extremely proud to have launched the first Deity Falcon webshop on their new architecture, and we are excited to see what the future holds!","spans":[],"direction":"ltr"}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$5cafd314-7433-4fd6-8957-ffc784a0a82a","slice_type":"blog_content","slice_label":null}]}},{"id":"Yo40rhEAACoA0Str","uid":"anonymizing-databases-introducing-masquerade","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yo40rhEAACoA0Str%22%29+%5D%5D","tags":[],"first_publication_date":"2022-05-25T13:52:50+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKZlxIAACo3sDAH","type":"blog","lang":"en-us","uid":"anonymizing-databases-introducing-masquerade"}],"data":{"Title":[{"type":"heading1","text":"Anonymizing databases - introducing Masquerade","spans":[]}],"image":{"dimensions":{"width":929,"height":508},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/fe298e1f-89c4-4dbe-b05a-bfe31de06b60_Screenshot+from+2023-07-04+16-24-21.png?auto=compress,format","id":"ZKQrmxAAACYASH7N","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2019-03-28","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Anonymizing databases - introducing Masquerade","image":{"dimensions":{"width":924,"height":1458},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/8a6f7ac4-8922-4c6f-843a-c6108215cde5_maquerade.png?auto=compress,format","id":"ZKQrphAAACQASH8C","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":null,"date":"2019-03-28"},"id":"hero$bf0b8820-7464-481e-a7f0-5c2fc75f2434","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"6 min read","spans":[{"start":0,"end":10,"type":"strong"}]}]},{"blogContent":[{"type":"heading4","text":"Retrospective","spans":[]},{"type":"paragraph","text":"A bit less than a year ago, we wrote about our deployment pipeline with Kubernetes. That blog post details how we leverage Gitlab CI with Docker & Kubernetes to create disposable Magento testing environments.","spans":[{"start":43,"end":82,"type":"hyperlink","data":{"link_type":"Web","url":"https://elgentos.com/blog/disposable-magento-testing-environments-with-k8s"}}]},{"type":"paragraph","text":"I ended that blog with a few bullet points for future implementation, of which one was:","spans":[]},{"type":"list-item","text":"Anonymize database so we have near-production data available","spans":[]},{"type":"paragraph","text":"We now have tackled this issue and in this blog post I'll explain the why and the how.","spans":[]},{"type":"heading4","text":"Why anonymize the database ?","spans":[]},{"type":"paragraph","text":"When we set up a review environment for our clients, we download the database from our backup location on Amazon S3. We store three versions of the database there; the full backup including all customer and order data, a stripped version without any customer or order data and recently, also an anonymized version where all personally identifiable information has been anonymized, that is; replaced with fake data.","spans":[]},{"type":"paragraph","text":"When working with customer data, we feel it is our duty to handle these as carefully as possible to avoid data leakage (not to mention GDPR). The best way to avoid data leakage is to avoid using the data in the first place, which is why we decided to only ever use the production data when it is absolutely necessary. For development and testing purposes, we usually use the stripped database. Sometimes however, this empty database isn't enough; we need some actual data to test certain features or bugfixes. This is where the anonymized version comes in.","spans":[]},{"type":"heading4","text":"What were the options?","spans":[]},{"type":"paragraph","text":"We started by looking at the systems that were available to anonymize databases. We found two that looked interesting, we'll discuss them here.","spans":[]},{"type":"paragraph","text":"integer-net/Anonymizer","spans":[{"start":0,"end":22,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/integer-net/Anonymizer"}}]},{"type":"paragraph","text":"This Magento extension does pretty much what we want; it uses the popular fake data generation package fzaninotto/Faker to replace the actual values with fake data. However, this is a Magento 1 extension and we migrated (almost) all our clients to Magento 2. Porting it would've been an option but we felt like a Magento extension wasn't the most flexible way to do this; we'd actually need a Magento installation up and running to anonymize the database and we didn't want to do that.","spans":[{"start":103,"end":119,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/fzaninotto/Faker"}}]},{"type":"paragraph","text":"experius/Magento-2-Module-Experius-FakeMyData","spans":[{"start":0,"end":45,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/experius/Magento-2-Module-Experius-FakeMyData"}}]},{"type":"paragraph","text":"This is a Magento 2 module that basically does the same as the integer-net Magento 1 extension. Unfortunately, its still a Magento 2 extension and not a standalone tool. It also doesn't offer any customization options. It does use names from personas from Game of Thrones, so that's cool.","spans":[]},{"type":"paragraph","text":"DivanteLtd/anonymizer","spans":[{"start":0,"end":21,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/DivanteLtd/anonymizer"}}]},{"type":"paragraph","text":"This project looked a lot more promising and indeed, it is a pretty impressive package. Even so impressive, that we felt the large plethora of options were a bit too much. We had a hard time getting it up and running, mainly because it's written in Ruby, which most of the devs at our company aren't too familiar with. We feel more at home with PHP-based packages. We worked with Ruby in the past when we used Capistrano and when we moved to its PHP-based clone Deployer, we were better off since everybody could work on the project instead of just the people with enough Ruby experience.","spans":[]},{"type":"paragraph","text":"We decided to write our own. We're developers after all!","spans":[]},{"type":"heading4","text":"Introducing Masquerade","spans":[]},{"type":"paragraph","text":"We had a few demands for our own anonymizer;","spans":[]},{"type":"list-item","text":"It needed to be platform-agnostic;","spans":[]},{"type":"list-item","text":"It needed to have Magento 2 configuration files out of the box;","spans":[]},{"type":"list-item","text":"It needed to be configurable to a certain extent;","spans":[]},{"type":"list-item","text":"It needed to be a standalone PHAR application (no extension or package for a certain framework).","spans":[]},{"type":"paragraph","text":"This is why we built Masquerade. Masquerade can anonymize a MySQL database for you following a set of rules configured in YAML files. Each YAML file refers to a group, which can contain multiple database tables, each with multiple columns.","spans":[{"start":21,"end":31,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/masquerade"}}]},{"type":"image","url":"https://images.prismic.io/elgentos-next/51cfda72-ee6c-4e1a-84e8-30f7dacadc98_42574650-30e8d186-851f-11e8-9693-c23b426c43f2.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":924,"height":1458},"id":"Yo4z3REAAC4A0SeO","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},{"type":"paragraph","text":"An example of a YAML file for Magento 2 is this (part of);","spans":[]},{"type":"preformatted","text":"// customer.yaml customer: customer_entity: columns: email: formatter: email unique: true nullColumnBeforeRun: true prefix: formatter: name: fixed value: firstname: formatter: name: firstName middlename: formatter: name: fixed value: lastname: formatter: lastName suffix: formatter: name: fixed value:","spans":[]},{"type":"paragraph","text":"It uses Faker formatters for most data that needs to be generated and you can add your own Provider with a Formatter (or several) to generate custom data. This is a pretty simple PHP class that you can place in ~/.masquerade or in the relative dir .masquerade from where you run Masquerade. The PHP class looks like this;","spans":[]},{"type":"preformatted","text":"<?php namespace Custom; use FakerFormatter; class WoopFormatter extends Base { public function woopwoop() { $woops = ['woop', 'wop', 'wopwop', 'woopwoop']; return $woops[array_rand($woops)]; } }","spans":[]},{"type":"paragraph","text":"If we were to use this formatter for the firstname column in the customer_entity table in the customer group, we'd configure it like this;","spans":[]},{"type":"preformatted","text":"customer: customer_entity: columns: firstname: provider: \\►CustomWoopFormatter formatter: name: woopwoop","spans":[]},{"type":"paragraph","text":"You can find more information about how to download, install and run Masquerade on the Masquerade Github page.","spans":[{"start":83,"end":109,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/elgentos/masquerade"}}]},{"type":"heading4","text":"Running it nightly","spans":[]},{"type":"paragraph","text":"We wanted to have our anonymized databases readily available at the tip of our fingers (or for our review app deployments) so we needed a way to anonymize the production database nightly. We were hesitant to run this on the production server since that would increase the chance we'd mistakenly anonymize the production database. After some testing with different approaches, we settled on using Gitlab CI to run this process. We already had Gitlab CI set up with runners that were idle during the night anyway, so why not leverage those to do the hard work?","spans":[]},{"type":"paragraph","text":"For every client in our Gitlab instance, we created anonymize project with just one .gitlab-ci.yml file. This runs the anonymization script, which boils down to;","spans":[]},{"type":"o-list-item","text":"Configure AWS credentials to be able to access the clients' S3 bucket containing the database dumps;","spans":[]},{"type":"o-list-item","text":"Downloading the database from the bucket;","spans":[]},{"type":"o-list-item","text":"Importing it into a MySQL database;","spans":[]},{"type":"o-list-item","text":"Running Masquerade on said database;","spans":[]},{"type":"o-list-item","text":"Dumping the anonymized database back out to a SQL file;","spans":[]},{"type":"o-list-item","text":"Uploading the dumped database to the S3 bucket.","spans":[]},{"type":"paragraph","text":"We based this on a Docker container which we created to include the AWS CLI utility, MySQL and Masquerade. Here's the Dockerfile in full;","spans":[]},{"type":"preformatted","text":"FROM romeoz/docker-apache-php:7.1 MAINTAINER Peter Jaap Blaakmeer RUN apt-get update # Install awscli RUN apt-get install -y libpython-dev python-dev libyaml-dev python-pip RUN pip install awscli --upgrade --user # Install mysql-client RUN apt-get install -y mysql-client # Install masquerade RUN curl -LO https://github.com/elgentos/masquerade/releases/download/0.1.9/masquerade.phar RUN chmod +x ./masquerade.phar && mv ./masquerade.phar /usr/bin/masquerade # Run original image's entrypoint manually CMD [\"/sbin/entrypoint.sh\"]","spans":[]},{"type":"heading4","text":"Conclusion","spans":[]},{"type":"paragraph","text":"Masquerade allows us to anonymize databases quickly and easily and it does this for multiple frameworks. We also build Laravel applications to support our Magento web shops in various ways and also use Masquerade there to create anonymized databases.","spans":[]},{"type":"paragraph","text":"Allowing our clients to have a testing/review environment with anonymized data enabled them to quickly test new features without taking an unnecessary risk concerning data leakage.","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$f88c9ed6-a396-460f-891b-268e9d6971e8","slice_type":"blog_content","slice_label":null}]}},{"id":"Yo42AxEAACoA0TGz","uid":"logging-magento-logs-with-the-elk-stack","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yo42AxEAACoA0TGz%22%29+%5D%5D","tags":[],"first_publication_date":"2022-05-25T13:58:31+0000","last_publication_date":"2024-10-18T13:29:18+0000","slugs":["magento-logbestanden-vastleggen-met-de-elk-stack"],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKaLxIAACY3sDKn","type":"blog","lang":"en-us","uid":"logging-magento-logs-with-the-elk-stack"}],"data":{"Title":[{"type":"heading1","text":"Capturing Magento log files with the ELK stack","spans":[]}],"image":{"dimensions":{"width":476,"height":350},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/86cf7d36-b659-48bb-890e-5f7d673a47de_EGlpBtZW4AARIOp+2.png?auto=compress,format","id":"Y61xfxAAACIA-iXq","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2017-10-22","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Capturing Magento log files with the ELK stack","image":{},"author":null,"date":"2017-10-22"},"id":"hero$50cb0742-30e6-42dd-8be4-519803f39a51","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"7 min reading","spans":[{"start":0,"end":11,"type":"strong"}]}]},{"blogContent":[{"type":"paragraph","text":"This is a quick tutorial on how to capture Magento's log files using the ELK stack. ELK stands for Elasticsearch, Logstash and Kibana. I won't go into too much detail, but broadly speaking, Elasticsearch is used to store and quickly retrieve log entries, Logstash is responsible for pulling the data into Elasticsearch, and Kibana is used to create overviews and visualizations of the large amount of log entries.","spans":[]},{"type":"paragraph","text":"In addition to ELK, we will use the command-line tool log-courier to send the logs directly from our production server to the ELK stack via an encrypted connection.","spans":[{"start":41,"end":52,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/driskell/log-courier/"}}]},{"type":"paragraph","text":"Please note; we are using log-courier 1.8.3 because that is the current version installed on Byte's Hypernode hosting solution (which we use for all of our customers). The latest version is now 2.0.5 and offers slightly more features, especially in the lc-admin tool.","spans":[{"start":101,"end":110,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.hypernode.com/"}}]},{"type":"paragraph","text":"Much of my research on how to set this up came from this Gist from Github user purinda, DigitalOcean's blog on ELK and log-courier's documentation.","spans":[{"start":67,"end":105,"type":"hyperlink","data":{"link_type":"Web","url":"https://gist.github.com/purinda/ede02d0d1e4fca54143b"}},{"start":107,"end":135,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-install-elasticsearch-logstash-and-kibana-elk-stack-on-ubuntu-14-04"}},{"start":139,"end":170,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/driskell/log-courier/tree/master/docs"}}]},{"type":"heading2","text":"Setting up the ELK stack","spans":[]},{"type":"paragraph","text":"DigitalOcean used to offer a one-click app for the ELK stack. Unfortunately, it is no longer available. I leave it up to you to set up your own ELK stack. See DO's blog linked above or choose a Docker image like this one. You can also try emailing DigitalOcean to see if they have an image for you. Just skip the Filebeat part of the DO blog; we'll use log-courier to get our logs into ELK.","spans":[{"start":236,"end":240,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/spujadas/elk-docker"}}]},{"type":"paragraph","text":"Kibana and Elasticsearch are pretty easy, just follow the blog. Remember where you placed your certificate file and your secret key file for Logstash.","spans":[]},{"type":"heading3","text":"Configuring Logstash","spans":[]},{"type":"paragraph","text":"A Logstash configuration has at least three parts; input, output and filter.","spans":[]},{"type":"heading3","text":"Entry","spans":[]},{"type":"paragraph","text":"The input configuration file defines how the logs enter Logstash. In our case, we will use log-courier, so we need the logstash-input-courier plugin for Logstash;","spans":[{"start":138,"end":160,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/driskell/logstash-input-courier"}}]},{"type":"list-item","text":"SSH to your ELK stack","spans":[]},{"type":"list-item","text":"Go to /opt/logstash/","spans":[]},{"type":"list-item","text":"Run sudo bin/plugin install logstash-input-courier","spans":[]},{"type":"paragraph","text":"Now we are going to set up the input;","spans":[]},{"type":"list-item","text":"Go to /etc/logstash/conf.d/","spans":[]},{"type":"list-item","text":"Create a new file called 01-courier-input.conf","spans":[]},{"type":"list-item","text":"Use this content;","spans":[]},{"type":"preformatted","text":"input { courier { port => 5043 ssl_certificate => \"/etc/pki/tls/certs/logstash-forwarder.crt\" ssl_key => \"/etc/pki/tls/private/logstash-forwarder.key\" } }","spans":[]},{"type":"list-item","text":"Possibly replace the ssl certificate and ssl key with your paths.","spans":[{"start":24,"end":45,"type":"em"}]},{"type":"list-item","text":"Open port 5043 in your firewall (ufw insert 1 allow from $PRODUCTION-SERVER-IP to any port 5043)","spans":[]},{"type":"paragraph","text":"You can read more about setting up Logstash in log-courier here.","spans":[{"start":66,"end":70,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/driskell/log-courier/blob/v1.8.3/docs/LogstashIntegration.md"}}]},{"type":"heading3","text":"Output","spans":[]},{"type":"paragraph","text":"Logstash needs to know where to send the received data. Since we are using ELK, Elasticsearch is the output. In this configuration, we assume it is running on the same machine (localhost) and on port 9200.","spans":[]},{"type":"list-item","text":"Go to /etc/logstash/conf.d/","spans":[]},{"type":"list-item","text":"Create a new file called 10-elasticsearch-output.conf","spans":[]},{"type":"list-item","text":"Use this content;","spans":[]},{"type":"preformatted","text":"output { elasticsearch { hosts => [\"localhost:9200\"] manage_template => false document_type => \"%{[@metadata][type]}\" } }","spans":[]},{"type":"heading3","text":"Filter","spans":[]},{"type":"paragraph","text":"The third element is a filter; the incoming data must be understood by Logstash, so we must specify the format in which it can expect data. This is done using a regex-like syntax called grok. I have written two groks (for Magento 1 and Magento 2). There is a great tool to test your groks; Grok Constructor Matcher.","spans":[]},{"type":"list-item","text":"Go to /etc/logstash/conf.d/","spans":[]},{"type":"list-item","text":"Create a new file called 20-magento-filter.conf","spans":[]},{"type":"list-item","text":"Use this content;","spans":[]},{"type":"preformatted","text":"filter { if [type] == \"magento2\" { grok { match => { \"message\" => \"%{TIMESTAMP_ISO8601:timestamp} %{DATA:log_level} %{GREEDYDATA:message}\"} add_field => [ \"received_at\", \"%{@timestamp}\" ] } } if [type] == \"magento1\" { grok { match => { \"message\" => \"%{TIMESTAMP_ISO8601:date} %{DATA:log_level} \\} add_field => [ \"received_at\", \"%{@timestamp}\" ] } } }","spans":[]},{"type":"paragraph","text":"Note; this is only for system.log and does not take into account multi-line log entries. You can find a multi-line log entry grok for Magento here.","spans":[]},{"type":"paragraph","text":"Test your Logstash configuration with service logstash configtest. If you see Configuration OK, run service logstash restart. Check that it is listening on port 5034 with sudo lsof -i:5043. It should say something like;","spans":[]},{"type":"preformatted","text":"COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 2737 logstash 16h IPv6 54468 0t0 TCP *:5043 (LISTEN)","spans":[]},{"type":"heading2","text":"Setting up your production server","spans":[]},{"type":"paragraph","text":"Now we need to set up your production server to send data to Logstash.","spans":[]},{"type":"list-item","text":"Create a folder where you store log-courier related settings, such as ~/log-courier/","spans":[]},{"type":"list-item","text":"Create a config.json file in that directory with these contents;","spans":[]},{"type":"preformatted","text":"{ \"general\": { \"admin enabled\": true }, \"network\": { \"servers\": [ \"ELK-IP-ADDRESS:5043\" ], \"ssl ca\":\"/absolute/path/to/your/log-courier/logstash.cer\" }, \"files\": [ { \"paths\": [ \"/path/to/magento2/var/log/*.log\" ], \"fields\": { \"type\": \"magento2\" } } }","spans":[]},{"type":"list-item","text":"Change the path to your Magento var/log folder","spans":[]},{"type":"list-item","text":"Change the type to magento1 if you are using Magento 1 (this matches the types set in the filter file you created earlier)","spans":[]},{"type":"list-item","text":"Change ELK-IP-ADDRESS to the hostname or IP address of your ELK stack","spans":[]},{"type":"list-item","text":"If desired, disable management by changing true to false","spans":[]},{"type":"list-item","text":"Copy the Logstash certificate from your ELK stack to ~/log-courier/logstash.cer (in my example, the path of the certificate on the ELK stack was /etc/pki/tls/certs/logstash-forwarder.crt)","spans":[]},{"type":"list-item","text":"Launch log-courier manually from the CLI by running log-courier -config ~/log-courier/config.json &","spans":[]},{"type":"paragraph","text":"Note: The management tool helps you understand what's going on when something doesn't work. See this Github page to see what it can do and how it works.","spans":[]},{"type":"heading2","text":"Configuring Kibana","spans":[]},{"type":"paragraph","text":"I will talk briefly about configuring Kibana because there is a lot of information available on how to configure it and it largely depends on your usage scenario.","spans":[]},{"type":"list-item","text":"Open Kibana by going to your ELK hostname with the browser","spans":[]},{"type":"list-item","text":"Go to Settings and under 'Index name or pattern' type 'logstash-'.","spans":[]},{"type":"list-item","text":"Click on Create","spans":[]},{"type":"list-item","text":"Go to the Discover tab and you should see potential logs coming in","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/92816e91-afae-44e4-82b4-5ad20c017646_kibana.jpg?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":632},"id":"Yo413xEAACsA0TET","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"heading2","text":"Troubleshooting","spans":[]},{"type":"paragraph","text":"If you don't see any logs coming in, make sure there are logs by checking the configured directory on your production server. If so, SSH to your ELK stack and run sudo lsof -i:5043. If a connection has been established from your production server to your ELK stack, you should see something like this;","spans":[]},{"type":"preformatted","text":"COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 2737 logstash 16u IPv6 54468 0t0 TCP *:5043 (LISTEN) java 2737 logstash 41u IPv6 55003 0t0 TCP ELK-IP-ADDRESS:5043->ELK-IP-ADDRESS:40176 (ESTABLISHED) java 2737 logstash 45u IPv6 54474 0t0 TCP ELK-IP-ADDRESS:5043->ELK-IP-ADDRESS:56348 (ESTABLISHED)","spans":[]},{"type":"paragraph","text":"You can run the same command for ports 9200 and 5601 to check Elasticsearch and Kibana, respectively.","spans":[]},{"type":"paragraph","text":"If there is no connection, check that Logstash is really running with ps aux | grep -i logstash. If it is not running, check Logstash's error and log files in /var/log/logstash/logstash.err and /var/log/logstash/logstash.log.","spans":[]},{"type":"paragraph","text":"If there are logs and there is a connection and no logs come into Kibana, you can check these things;","spans":[]},{"type":"list-item","text":"Check your grok patterns. If nothing matches, nothing appears","spans":[]},{"type":"list-item","text":"Run status in lc-admin on your production server to see if it processes lines","spans":[]},{"type":"list-item","text":"Check your index pattern in Kibana","spans":[]},{"type":"heading2","text":"Install cron on Hypernode","spans":[]},{"type":"paragraph","text":"Because Hypernode is a managed hosting solution, we cannot change the default configuration. We have to start log-courier manually. This also means that it will not be started automatically on any system reboot. Therefore, we need to set up a cron to ensure that log-courier is executed.","spans":[]},{"type":"list-item","text":"Add this to your crontab; */30 * * * * flock -n ~/.log-courier.lock -c 'log-courier -config /absolute/path/to/your/log-courier/config.json &'","spans":[]},{"type":"list-item","text":"Make sure your directory for log-courier is not ~/.log-courier (note the dot) as this is a file that log-courier creates when running","spans":[]},{"type":"list-item","text":"Make sure the certificate file in config.json is absolutely referenced","spans":[]},{"type":"paragraph","text":"Good luck!","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$7a18c350-d6d1-4dd1-9371-c49474972baf","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9aZxIAACoAgmob","uid":"our-development-process-uncovered","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9aZxIAACoAgmob%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T14:02:19+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKafhIAADU3sDQL","type":"blog","lang":"en-us","uid":"our-development-process-uncovered"}],"data":{"Title":[{"type":"heading1","text":"Our development process uncovered","spans":[]}],"image":{"dimensions":{"width":5029,"height":3353},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/94efb2a1-98ee-442f-8de1-c73c7aac0cc7_development-process.jpg?auto=compress,format","id":"ZKQrGBAAACYASHzL","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2017-06-30","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Our development process uncovered","image":{"dimensions":{"width":5029,"height":3353},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/94efb2a1-98ee-442f-8de1-c73c7aac0cc7_development-process.jpg?auto=compress,format","id":"ZKQrGBAAACYASHzL","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"author":null,"date":"2017-06-30"},"id":"hero$6a16f4b3-2af8-4bbf-a8a4-ffcdeebdad04","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"13 min read","spans":[{"start":0,"end":11,"type":"strong"}]}]},{"blogContent":[{"type":"paragraph","text":"When we started with elgentos back in 2011, a large part of our development and deployment process was manual. At that time, most of the tools we use today were not available. Over the years, we realized that processes take a rather large amount of our time, while we're in the business of automating things! Gradually, we started implementing industry standards aside from adopting cutting-edge technology and, well, cutting ourselves.","spans":[]},{"type":"paragraph","text":"This is an overview of our journey from back in 2011 to where we are today, which technology stack we're using to automate our internal processes and where we want to go from here.","spans":[]},{"type":"heading4","text":"Development process","spans":[]},{"type":"paragraph","text":"Our development process changed a lot over the years, growing with our company as it increased in team members. At the start, we were with 2 developers and pretty much the only tool we used was Git. We used some servers we had laying around as our development servers and synced the codebase on our local machine to the development servers, which we accessed over the Internet. The main advantage of this was that our clients could look in on our development process. The main disadvantage of this was that the shops usually were in a state of disrepair since we were actively developing on them, causing panicked clients to call all day long.","spans":[]},{"type":"paragraph","text":"We later realized this did not scale. We decided to move from developing on a development server to developing locally and only pushing the develop branch to the development server. Since most of us ran OS X, but a few (sometimes) ran Linux (in its ever-changing varieties), we couldn't standardize on a development environment. Pretty much every dev was on its own when it came to installing and maintaining the LAMP/MAMP stack. Needless to say, version numbers were all over the place and only by sheer coincidence matched the production versions. We've always outsourced our hosting since the beginning (we've fully switched to Byte's Hypernode offering by now) so we didn't have full control over what ran there.","spans":[]},{"type":"paragraph","text":"Along come Vagrant and it was all the rage. Unfortunately, Vagrant is slow and bulky. So it didn't run well on our i5 128GB SSD Mac's. We decided to ignore Vagrant; although we sometimes had issues with differing versions, it wasn't that big of a problem to make us want to work with Vagrant.","spans":[]},{"type":"paragraph","text":"Fast-forward a few years and suddenly Docker appeared! Lightweight, containerized and native to Linux, but uh-oh, not to Mac. We dabbled with it for a while but it was too cumbersome (especially with the MySQL volumes at first and later also with syncing files from guest to host) and abandoned it. Thankfully by this time Homebrew had come onto the scene and made our life working with Mac's a lot easier. We could kinda keep our differing versions in check and generally, we were pleased.","spans":[]},{"type":"heading4","text":"Composer & Packagist","spans":[]},{"type":"paragraph","text":"Installing extensions in Magento has always been a bit weird. There are a lot of edge cases you need to keep in mind while installing and especially when removing an extension. Composer was the new hot thing in PHP-land and Magento 1 developers started adopting it. This helped us keep extension versions and constraints in control and facilitated adding and removing extensions to and from Magento. Right now, we run a private Packagist repository on Packagist.com with our extensions, which are hosted in our Gitlab installation.","spans":[{"start":452,"end":465,"type":"hyperlink","data":{"link_type":"Web","url":"http://packagist.com/"}},{"start":511,"end":517,"type":"hyperlink","data":{"link_type":"Web","url":"http://gitlab.com/"}}]},{"type":"heading4","text":" Codebase as our project management system","spans":[]},{"type":"paragraph","text":"Since our start, we worked with Codebase for our project and Git repository management. What we liked about Codebase was the combination of Git repository and ticketing in one system, a feature pretty standard nowadays but pretty unique at that time (a lot of companies were using Beanstalk for Git and Zendesk for ticketing at the time, for example).","spans":[]},{"type":"paragraph","text":"Codebase gradually developed new features over the years but our preferences and tastes concerning development also changed. We felt Codebase didn't keep up with our methodologies, which become more and more agile (scrum) based as time went on.","spans":[]},{"type":"paragraph","text":"About two years ago we decided to introduce code reviewing. At first, it was asking a colleague over to your desk to look at pieces of code you just wrote. Gradually, we started implementing code reviews for all our code using Merge Requests (exactly the same as Github's Pull Requests but with a better descriptive name).","spans":[]},{"type":"paragraph","text":"We told the developers to create a merge request in Codebase and assign it to a colleague. The workflow was like this;","spans":[]},{"type":"list-item","text":"Merchant creates ticket with a feature request or bug","spans":[]},{"type":"list-item","text":"Developer goes to his terminal, checks out the project and creates a new branch","spans":[]},{"type":"list-item","text":"Developer does the work","spans":[]},{"type":"list-item","text":"Developer pushes the branch upstream","spans":[]},{"type":"list-item","text":"Developer creates a merge request, assigns it to a colleague","spans":[]},{"type":"list-item","text":"Developer assigns ticket to colleague, gives status 'Needs Merge'","spans":[]},{"type":"list-item","text":"Colleague does code review, maybe assigning and reassigning ticket to original developer","spans":[]},{"type":"list-item","text":"Colleague merges branch","spans":[]},{"type":"list-item","text":"Colleague closes ticket","spans":[]},{"type":"list-item","text":"Colleague removes branch","spans":[]},{"type":"list-item","text":"Colleague tells developer it's merged","spans":[]},{"type":"list-item","text":"Developer deploys functionality","spans":[]},{"type":"paragraph","text":"There were a number of manual actions that were open to human error, sometimes resulting in an accidentally abandoned ticket or branch. We needed to, and could, do better.","spans":[]},{"type":"heading4","text":"Deployment process","spans":[]},{"type":"paragraph","text":"Initially, everything concerning deployment was manual. Log in to SSH server, git pull origin master, run database updates, clear caches, reindex indexers, etc. Because all of those steps were manual and sometimes optional (purging Varnish, purging CDN cache, deploying to multiple web servers, etc) these were open to human error and unavoidably resulted in unforeseen (and always ill-timed) downtime.","spans":[]},{"type":"paragraph","text":"We decided to implement an automated deployment process that begin with simple shell scripts but we quickly switched to Capistrano. This is a deployment system written in Ruby. It suited us well but since we are PHP developers, writing deployments in Ruby didn't go as fast or easy as we wished.","spans":[]},{"type":"paragraph","text":"We then ran into Deployer. Deployer is basically a Capistrano clone in PHP. It uses the same structure for deployments as we were used to (with releases, shared and current folders and symlinks) so swapping Capistrano with Deployer was pretty easily done. By now, we have pretty much all of our clients in a Deployer setup, eliminating human error in our deployment process. Deployer also fires our functional testing suite powered by Ghostinspector.","spans":[{"start":17,"end":25,"type":"hyperlink","data":{"link_type":"Web","url":"http://deployer.org/"}},{"start":207,"end":217,"type":"hyperlink","data":{"link_type":"Web","url":"http://capistranorb.com/"}},{"start":435,"end":449,"type":"hyperlink","data":{"link_type":"Web","url":"http://www.ghostinspector.com/"}}]},{"type":"heading4","text":"GrumPHP","spans":[]},{"type":"paragraph","text":"In an effort to raise code quality on a purely syntactically basis, we implemented GrumPHP. This is a tool written by our Belgian neighbors at PHPro to run automated checks on a per-commit basis. We have it configured to run the following tests;","spans":[{"start":83,"end":90,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/phpro/grumphp"}}]},{"type":"list-item","text":"PHP Code Sniffer with Magento's EQP standards (based on PSR-2);","spans":[]},{"type":"list-item","text":"PHP7 Compatibility Checker to ensure we don't use deprecated functions;","spans":[]},{"type":"list-item","text":"SecurityChecker to check for vulnerabilities in our composer modules;","spans":[]},{"type":"list-item","text":"Git Blacklist to prevent committing blacklisted words or functions such as var_dump, exit or die;","spans":[]},{"type":"list-item","text":"Composer validate to make sure our composer files are valid;","spans":[]},{"type":"list-item","text":"XMLLint to check our XML files are syntactically valid;","spans":[]},{"type":"list-item","text":"JSONLint to check our JSON files are syntactically valid.","spans":[]},{"type":"heading3","text":"Docker development environment","spans":[]},{"type":"paragraph","text":"When Jeroen joined our company, he made it very clear to us he is a big Docker fan. He also convinced us that by then, Docker was a feasible environment for our development needs, although Mac support still is a bit slow and/or sketchy. The majority of developers by now had moved to Linux though.","spans":[{"start":72,"end":78,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.docker.com/"}}]},{"type":"paragraph","text":"We now make extensive use of Jeroen's Docker development environment. This allows our developers to use exactly the same environment. We can instantly and effortlessly switch between PHP5.6, PHP7.0 and PHP7.1 just by changing the hostname. We can also set the hostname to include .magento. or .magento2. to have it set the correct environment variables. The environment also includes Redis, Varnish, Mailhog, Percona, Xdebug and Blackfire, all ready to go.","spans":[{"start":38,"end":68,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/JeroenBoersma/docker-compose-development"}},{"start":400,"end":407,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/mailhog/MailHog"}},{"start":429,"end":438,"type":"hyperlink","data":{"link_type":"Web","url":"http://blackfire.io/"}}]},{"type":"heading4","text":"Gitlab","spans":[]},{"type":"paragraph","text":"Now that we had a formalized deployment process up and running, we wanted to go a bit further and dive into this whole Continuous Integration / Continuous Delivery concept. We tried Jenkins and Travis a few times but it didn't really stick for various reasons. Then we found Gitlab-CI, the Continuous Integration toolset built into Gitlab. The more we looked into Gitlab, the more excited we got. Gitlab solved a lot of issues we ran into with Codebase and was a more robust and feature-rich project and Git management system than Codebase was (and was likely to become in the short term). Extra bonus; Gitlab is originally a Dutch company!","spans":[]},{"type":"paragraph","text":"We spent a few months writing a Codebase to Gitlab migration tool which migrated over all our projects, tickets, attachments, users, Git repositories (client projects, internal projects and extensions) and all the other little details. Now we could unleash the power of Gitlab to streamline our processes.","spans":[]},{"type":"heading4","text":"Merge Request flow in Gitlab","spans":[]},{"type":"paragraph","text":"Gitlab significantly improved our merge request flow by automating several steps. The biggest change is the ability to create a new branch from Gitlab, straight from the issue page. When viewing an issue, we now click the 'Create new branch' button to immediately created a branch with the issue's name.","spans":[]},{"type":"paragraph","text":"After pushing code to this branch, a developer can create a merge request by clicking the URL Gitlab gives us right in the terminal. Thirdly, after a colleague approves the merge request, the branch is automatically deleted. These may seem incremental changes and they are, but it really adds up when you do 30 merge requests a week, per developer.","spans":[]},{"type":"paragraph","text":"We also use a Chrome plugin called TamperMonkey to inject a custom created 'Track Time' button on the issue page. When clicked, a pop-up opens that allows us to start a timer in Harvest, our time tracking and invoicing tool. The project is automatically selected and the issue name is inserted as the time entry's description.","spans":[{"start":35,"end":47,"type":"hyperlink","data":{"link_type":"Web","url":"https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo"}},{"start":178,"end":185,"type":"hyperlink","data":{"link_type":"Web","url":"http://harvestapp.com/"}}]},{"type":"heading4","text":"Protected branches","spans":[]},{"type":"paragraph","text":"Gitlab offers a feature called protected branches. We use the default Gitlab approach; the master branch is protected, which means no-one (not even Masters, the highest role in Gitlab) can push to the master branch. Only Masters can merge a merge request into master. Our project leads are the Masters for every project. This way, the project lead ensures quality by checking all code that comes into the master branch through merge requests. Usually, developers create a merge request for their feature branch into the current release branch. At the end of a sprint, the project lead will merge the release branch into master.","spans":[]},{"type":"paragraph","text":"This allowed us to have a better control of the flow of code through releases and minimize code breakages or bugs.","spans":[]},{"type":"heading4","text":"Gitlab CI & Pipelines","spans":[]},{"type":"paragraph","text":"Then we set up Gitlab CI to automatically deploy code that is merged into master to the production environment. We booted up a few Digital Ocean droplets with the one-click Docker install and registered them as a 'runner' in Gitlab. A runner is a server that can execute any code you tell it to through your CI configuration. In Gitlab, CI configuration is done by adding a .gitlab-ci.yaml file to the root of the project. When the pipeline is run, the Git repository is automatically checked out and your commands are run on the runner. We use the pipeline for Magento 1 to run code checks such as linting, PHP Code Sniffer (with Magento's Extension Quality Program (EQP) standards), composer Security Checker and more (we also use GrumPHP to run these on a per-commit basis locally).","spans":[]},{"type":"paragraph","text":"Back to Docker! Gitlab has a Container Registry built into every project; we can build Docker images locally and push them to the Container Registry for them to be used by our runners. These Docker containers are the basis for our pipelines; they contain all prerequisites for building, testing & deploying a Magento 1 or Magento 2 installation.","spans":[]},{"type":"paragraph","text":"After the testing stage has finished, Deployer will be invoked on the runner just as we did on our local machine to deploy to production. When the pipeline is finished, the new release will be live. Most of our clients are in a separate channel in our Slack, through which way they are notified of a new deployment (with the latest commit messages) so they know exactly what is deployed and when.","spans":[]},{"type":"paragraph","text":"For Magento 2, we also do SASS generation on the runner, as well as static content deployment and DI compilation. We then upload the generated files to the production server. The test stage is where we run Magento 2's unit tests as well.","spans":[]},{"type":"paragraph","text":"For our extensions, we have a separate pipeline that runs PHP CodeSniffer on our code to make sure it complies with Magento's EQP standards.","spans":[{"start":114,"end":137,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/magento/marketplace-eqp"}}]},{"type":"heading4","text":"Dynamic Environments","spans":[]},{"type":"paragraph","text":"All the things we talked about in this blog were done to finally culminate in our grand vision; dynamic environments. This is a term Gitlab uses and we were very happy to find out Gitlab actually has built in support for this. Dynamic environments are the basis for what Gitlab calls 'Review Apps'. In short, it's an automatic deployment on branch level, which feeds the URL of the temporary environment for a specific branch back into the Merge Request.","spans":[]},{"type":"paragraph","text":"This allows a developer to create a merge request, and another developer that will review it to click a link and immediately see the work the developer has done in a fully fledged copy of the shop with this specific branch checked out. These environments run in a Docker container on a Digital Ocean droplet which we automatically spin up. This also allows us to have our QA team test our merge requests much, much faster. Since it automatically deploys the branch, it doesn't discriminate in what it deploys; small textual changes or big refactors; everything gets its own test environment.","spans":[]},{"type":"paragraph","text":"When the reviewing developer approves the merge request, the code is merged into the release branch and the dynamic environment is automatically deleted, along with the DNS record that pointed to it.","spans":[]},{"type":"paragraph","text":"We will be rolling out Dynamic Environments to all our projects in the upcoming weeks.","spans":[]},{"type":"heading4","text":"Future","spans":[]},{"type":"paragraph","text":"We continue on our mission to create a better development and deployment process for both our developers and our customers. We are planning to integrate Docker more and more in our daily life since it allows us to easily and quickly replicate dependable environments. We also would like to expand on using GrumPHP and writing custom tasks for it to ward off common mistakes that are not caught by the default tasks.","spans":[]},{"type":"paragraph","text":"Magento 2 is taking a more and more prominent role in our company; right now we're at about 50/50 Magento 1 and Magento 2 and aim to be at 25/75 by the end of the year. This also makes writing and running unit, functional and integration tests a lot easier so we are taking a more BDD/DDD approach to writing our software.","spans":[]},{"type":"paragraph","text":"On to better software!","spans":[]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$8151073e-4152-4a77-87dd-df8b58d93a0f","slice_type":"blog_content","slice_label":null}]}},{"id":"Yp9bvRIAAC4AgnBb","uid":"mageunconference-2017-cologne","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9bvRIAAC4AgnBb%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T14:08:00+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKa6RIAAEU3sDXl","type":"blog","lang":"en-us","uid":"mageunconference-2017-cologne"}],"data":{"Title":[{"type":"heading1","text":"MageUnconference 2017 Cologne","spans":[]}],"image":{"dimensions":{"width":1000,"height":750},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/d8f00ebb-2fa7-4918-ab26-8d419cc7ba60_mageunconference-2017-elgentos-team.jpg?auto=compress,format","id":"Yp9bhRIAACkAgm9Y","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"date":"2017-03-09","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"MageUnconference 2017 Cologne","image":{"dimensions":{"width":1000,"height":750},"alt":"Roadtrippin' (and stopping at our yellow friends with the M)","copyright":null,"url":"https://images.prismic.io/elgentos-next/eb5024fd-5f0f-4df9-b2c5-e2e1c4ef0b88_roadtrip.jpg?auto=compress,format","id":"Yp9bCRIAAC4Agm0N","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},"author":null,"date":"2017-03-09"},"id":"hero$b231cdbf-f189-47a0-aa3e-1db57998c047","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":" 4 min read","spans":[{"start":0,"end":11,"type":"strong"}]}]},{"blogContent":[{"type":"paragraph","text":"Last weekend (March 3rd - 5th), we attended the MageUnconference in Cologne with the entire team. The German unconference was this year organized again by the Firegento association, in particular Carmen, Sonja, Rico and Fabian. First of all, thanks for your hard work!","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/eb5024fd-5f0f-4df9-b2c5-e2e1c4ef0b88_roadtrip.jpg?auto=compress,format","alt":"Roadtrippin'","copyright":null,"dimensions":{"width":1000,"height":750},"id":"Yp9bCRIAAC4Agm0N","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"paragraph","text":"This was not the first time we attended an unconference. We also attended the MageUnconference in Berlin last year, aswell as the MageUnconference in Utrecht (The Netherlands), which we also sponsored. Unconferences are our favorite type of conferences, and we'll use this blog post to explain why.","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/c05f0135-e2ab-4440-a83c-19185b4b263f_cologne.jpg?auto=compress,format","alt":"Cologne Cathedral","copyright":null,"dimensions":{"width":1000,"height":563},"id":"Yp9bORIAACgAgm3t","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"heading4","text":"The talks","spans":[]},{"type":"paragraph","text":"Which talks? Well, you don't know beforehand. An unconference is a conference with speakers, they're just not set when you enter the venue. Everybody is invited to put up a topic they either want to hear about or want to talk about. When the windows are filled with A4 with topics (not necessarily Magento related), the voting starts. The topics with the most votes get in the schedule and we're off.","spans":[]},{"type":"paragraph","text":"Some examples of the topics; - Future of Magento - Javascript basics - Dependency Injection 101 - Bee keeping & its benefits - Continuous Deployments - Using Docker in production - Help, my shop is hacked! - Module Migration - First aid: Paramedics insides - Magento 1.9.x Frontend Performance","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/77372388-b34e-4fbd-8de0-b0fcfc2f23ee_david.jpg?auto=compress,format","alt":"David Manners","copyright":null,"dimensions":{"width":1000,"height":750},"id":"Yp9bYRIAACoAgm6q","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"paragraph","text":"Why is this a good thing? Because it makes the conference as relevant as possible. No 'case studies' which actually are sales talks or ego-boosting one-man shows. Only topics that people want to hear about will be chosen. Besides that, because of the (usually) short time of preparation beforehand, the talks usually end up being group discussions, which is great for everybody attending.","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/cb4a1131-1ab7-46d9-8f18-eb039b0dd6b7_fabrizio.jpg?auto=compress,format","alt":"Fabrizio Branca","copyright":null,"dimensions":{"width":1000,"height":750},"id":"Yp9bhRIAAC8Agm9U","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"paragraph","text":"Talking at an unconference is a low-barrier entry to the speaking world. Testing the water with like-minded people who have actually shown their interest in the topic you are talking about beforehand - you can't really get a better springboard to start speaking than that. Unconference fosters new speakers, and that's a great thing.","spans":[]},{"type":"heading4","text":"The social aspect of it","spans":[]},{"type":"paragraph","text":"Parties and drinks, those are just the ingredients. The actual worth in those parties and drinks are the people you meet. The chats you have with people who do the same thing as you on a daily basis, the stuff you find out you have in common with them. Because at the end of every conference, I come to realize that I have more in common with all attendees than we have in difference. But you learn from those differences, it what makes us better programmers; look around, talk to people and learn from them. And you learn just as much (if not more) from those brilliant minds at the (after-)party than at the conference itself.","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/b9101b0e-8331-4723-a514-bce5dd7c5405_retro.jpg?auto=compress,format","alt":"elgentos retro gaming booth","copyright":null,"dimensions":{"width":1000,"height":750},"id":"Yp9bhRIAACsAgm9V","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"heading4","text":"Team building","spans":[]},{"type":"paragraph","text":"We try to attend these unconferences with the whole team. It's not obligated in any way, but we highly encourage participation. So far, everybody has enjoyed these bi-yearly outings very much. It strengthens team spirit, widens the gaze, narrows tunnel vision and teaches everybody on a wide variety of subjects (including lockpicking!). This is why we also sponsor the Unconference in The Netherlands; we feel that it is conferences like these that is the glue of the Magento community.","spans":[]},{"type":"image","url":"https://images.prismic.io/elgentos-next/d8f00ebb-2fa7-4918-ab26-8d419cc7ba60_mageunconference-2017-elgentos-team.jpg?auto=compress,format","alt":"A part of the elgentos team","copyright":null,"dimensions":{"width":1000,"height":750},"id":"Yp9bhRIAACkAgm9Y","edit":{"x":0,"y":0,"zoom":1,"background":"#fff"}},{"type":"paragraph","text":"At our unconference retrospective this week, everybody agreed it was a very fun event and they learned tons. It certainly gave everybody new tools & techniques to learn about, like vim, regular expressions, Knockout JiSse, Prometheus, git rerere and even lockpicking.","spans":[]},{"type":"paragraph","text":"Walking toward the venue - 360 video","spans":[{"start":0,"end":37,"type":"em"}]},{"type":"heading4","text":"2017; The Netherlands","spans":[]},{"type":"paragraph","text":"The next MageUnconference will be the one in Utrecht, the Netherlands. It's held at an actual fortress and this year it's in summer (August 25 - 27) so you can pitch your own tent if you feel like it! Visit the site for more information, we hope to see you there!","spans":[{"start":207,"end":215,"type":"hyperlink","data":{"link_type":"Web","url":"http://nl.mageuc.org/"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$3ae51c9d-7b84-4cdf-b9c8-66da80be13ff","slice_type":"blog_content","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"sectionHeader":[],"text":[{"type":"paragraph","text":"","spans":[]}],"image":{}},"id":"text_and_or_asset$aa7722ca-cfe0-47e3-a8f9-a1ab047445b7","slice_type":"text_and_or_asset","slice_label":null}]}},{"id":"Yp9c_RIAACkAgnYo","uid":"magento-shop-via-de-command-line","url":null,"type":"blog","href":"https://elgentos-next.cdn.prismic.io/api/v2/documents/search?ref=ZxJj5xAAAB8A1-mr&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22Yp9c_RIAACkAgnYo%22%29+%5D%5D","tags":[],"first_publication_date":"2022-06-07T14:13:20+0000","last_publication_date":"2024-04-07T12:18:54+0000","slugs":[],"linked_documents":[],"lang":"nl-nl","alternate_languages":[{"id":"ZhKcUhIAAGY3sDwk","type":"blog","lang":"en-us","uid":"manage-your-magento-shop-via-the-command-line"}],"data":{"Title":[{"type":"heading1","text":"Manage your Magento shop via the command line - n98-magerun","spans":[]}],"image":{"dimensions":{"width":965,"height":441},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/d2b0526d-2dc2-4d73-b8d6-b00fe049d09b_Screenshot+from+2023-07-04+16-23-23.png?auto=compress,format","id":"ZKQrZRAAACYASH3f","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"date":"2014-02-20","author":null,"slices":[{"variation":"blog","version":"sktwi1xtmkfgx8626","items":[{}],"primary":{"title":"Manage your Magento shop via the command line - n98-magerun","image":{"dimensions":{"width":965,"height":441},"alt":null,"copyright":null,"url":"https://images.prismic.io/elgentos-next/d2b0526d-2dc2-4d73-b8d6-b00fe049d09b_Screenshot+from+2023-07-04+16-23-23.png?auto=compress,format","id":"ZKQrZRAAACYASH3f","edit":{"x":0,"y":0,"zoom":1,"background":"transparent"}},"author":null,"date":"2022-12-22"},"id":"hero$6f242b8e-dc95-4064-a750-ee7c7474259d","slice_type":"hero","slice_label":null},{"variation":"default","version":"sktwi1xtmkfgx8626","items":[{"blogContent":[{"type":"paragraph","text":"4 min read","spans":[{"start":0,"end":10,"type":"strong"}]}]},{"blogContent":[{"type":"heading2","text":"n98-what?","spans":[{"start":0,"end":8,"type":"strong"}]},{"type":"paragraph","text":"n98-magerun (ore 'magerun') is a command line (SSH) tool developed by Christian Münch of German netz98, among others. This tool was built to make managing and developing for Magento shops more pleasant by performing frequently occurring tasks via one-line SSH commands. A large number of less common but more difficult tasks can also be started via magerun.","spans":[{"start":0,"end":11,"type":"hyperlink","data":{"link_type":"Web","url":"http://magerun.net/"}},{"start":83,"end":98,"type":"hyperlink","data":{"link_type":"Web","url":"http://blog.muench-worms.de/"}},{"start":114,"end":120,"type":"hyperlink","data":{"link_type":"Web","url":"http://www.netz98.de/"}}]},{"type":"paragraph","text":"Several command line tools have been developed for Magento, such as wiz or my own (buried) mate, for example. However, due to its many features, good build & code and ongoing development, Magerun has gained a large following within the Magento community and by now can be considered the de facto Magento command line tool.","spans":[{"start":80,"end":83,"type":"hyperlink","data":{"link_type":"Web","url":"http://www.classyllama.com/magento/introducing-wiz-a-cli-tool-magento"}},{"start":120,"end":124,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/peterjaap/mate"}}]},{"type":"heading3","text":"Some functionalities for webshop administrators","spans":[]},{"type":"paragraph","text":"Magerun's entire feature list can be found on their Github page, among others, but I will explain a few commonly used ones.","spans":[{"start":58,"end":71,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/netz98/n98-magerun"}}]},{"type":"preformatted","text":"$ n98-magerun.phar cache:flush","spans":[]},{"type":"paragraph","text":"This option allows you to quickly flush the entire cache. The entire Magento XML configuration is flushed, not just the files in var/cache.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar admin:user:create [username] [email] [password] [firstname] [lastname] [role]","spans":[]},{"type":"paragraph","text":"Creates an admin user. Useful if you want to give automated access to multiple users.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar sys:cron:list","spans":[]},{"type":"paragraph","text":"This option lists all cronjobs and when they are started. Normally you should open the cron_schedule folder in the database, but even then you only see the scheduled ticks and not when they are actually started. Indispensable feature to keep a good overview in your shop.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar sys:url:list --add-products 4","spans":[]},{"type":"paragraph","text":"This option displays all product URLs in your shop. Useful for example to check crawlers or to send a bot for (Varnish/Redis) cache warming. The sys:url:list option also allows you to do this for categories and CMS pages. A few features for developers","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar install","spans":[]},{"type":"paragraph","text":"This feature allows you to quickly and easily set up a Magento installation. All required information will be asked to you during this process. First run n98-magerun.phar self-update to get the latest version so that the most recent Magento version is available.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar sys:setup:incremental","spans":[]},{"type":"paragraph","text":"This function runs all setup scripts one by one. This gives you more transparency in the update process, especially useful for large-scale updates (e.g. from CE 1.6 to CE 1.8). If the update process goes wrong, you know exactly where.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar dev:template-hints [store_code] $ n98-magerun.phar dev:template-hints-blocks [store_code]","spans":[]},{"type":"paragraph","text":"These functions allow you to enable and disable template (block) hints for different stores.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar dev:theme:duplicates theme [originalTheme]","spans":[]},{"type":"paragraph","text":"Allows you to find duplicate files within your themes to detect cascading templates.","spans":[]},{"type":"preformatted","text":"$ n98-magerun.phar dev:module:rewrite:conflicts","spans":[]},{"type":"paragraph","text":"With this feature, magerun searches for conflicts within your Magento shop; extensions that override the same classes and/or blocks become easily and quickly visible.","spans":[]},{"type":"paragraph","text":"There are many more options like creating a database dump, quickly putting down an extension skeleton, listening and installing extensions, opening a MySQL console, etc. Should you develop locally and work with the phpStorm IDE, you can use magerun from within your IDE. Check out the magerun.net site for more information on how to set this up.","spans":[{"start":307,"end":323,"type":"hyperlink","data":{"link_type":"Web","url":"http://magerun.net/quick-tip-phpstorm-command-line-tool-support/"}}]},{"type":"heading3","text":"extend n98-magerun with your own extensions","spans":[]},{"type":"paragraph","text":"You can easily extend magerun itself with proprietary or third-party extensions in the form of Symfony2 commands. A great example is the 'Create dummy order' extension by Kalen Jordan that allows you to shoot a test order from the command line. How to do this yourself can be read here. Also, Magento guru Alan Storm has written a nice blog about this; Developing Commands for n98-magerun.","spans":[{"start":177,"end":189,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/kalenjordan/magerun-addons"}},{"start":284,"end":297,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/netz98/n98-magerun/wiki/Add-custom-commands"}},{"start":372,"end":407,"type":"hyperlink","data":{"link_type":"Web","url":"http://alanstorm.com/developing_commands_for_n98-magerun"}}]},{"type":"paragraph","text":"Meanwhile, in the develop branch in the Github repository is a version of magerun (written by Byte) that includes bash completion, so you can use the tab key to complete command names; a welcome addition given the sometimes long commands.","spans":[]},{"type":"heading3","text":"Adoption by Byte","spans":[]},{"type":"paragraph","text":"Byte has made magerun available by default on their servers. If your shop is not running on a Byte server, check out the Github page or the magerun.net page on how to install magerun on your server (or move your shop to Byte ;-)).","spans":[{"start":119,"end":132,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/netz98/n98-magerun"}},{"start":139,"end":157,"type":"hyperlink","data":{"link_type":"Web","url":"http://magerun.net/installation/"}}]},{"type":"paragraph","text":"This blog is intended to give a brief introduction to magerun. If you are interested in how the specific commands work, what they do and what you can do with them, check out Alan Storm's n98-magerun blog series.","spans":[{"start":203,"end":226,"type":"hyperlink","data":{"link_type":"Web","url":"http://magento-quickies.alanstorm.com/tagged/n98magerun"}}]}]}],"primary":{"contact_block":{"id":"ZhKItxIAAF01r-UO","type":"contact_block","tags":[],"lang":"nl-nl","slug":"-","first_publication_date":"2024-04-07T11:51:22+0000","last_publication_date":"2024-04-07T11:58:10+0000","link_type":"Document","isBroken":false}},"id":"blog_content$9e19c728-91e4-4018-8dcc-108d638b66cc","slice_type":"blog_content","slice_label":null}]}}],"cases":[],"vacancies":[]}},"__N_SSG":true},"page":"/blog/[[...slug]]","query":{},"buildId":"H3qPIhs8CyjLRHbXGUP1B","isFallback":false,"gsp":true,"locale":"nl-nl","locales":["nl-nl","en-us"],"defaultLocale":"nl-nl","domainLocales":[{"domain":"elgentos.com","defaultLocale":"nl-nl"},{"domain":"elgentos.com","defaultLocale":"en-us"}],"scriptLoader":[]}