PaiWeb

or
HUB.coupons.com
(Tech talk 3/18/14)

What is it?

  • Web Application Platform
  • Designed to be used by internal (Support) & external (Client & Partners) users

  • Vision: 
 - single touch point for clients and partners 
 - consolidation of 13 tools used in offer provisioning and IO/account management

What it will be used for and when?

V1: manage IOs, Invoices, Support tickets
V2/V3: Offer creation
V4: Reporting
V5+: Convert all client tools to the HUB

Which Data Sources are used?

Platfom APIs
SalesForce APIs
Local DB

How does it look? (Demo)


Design mocks


Design

TEch stack


TECH stack Client side


Code Samples HTML


Angular attributes

    <div class="btn-group list-filter">
        <a class="btn btn-default btn-sm dropdown-toggle" 
        	data-toggle="dropdown" href="#">
            {{ selectedFilterName }}
            <span class="caret"></span>
        </a>
        <ul class="dropdown-menu">
            <li ng-repeat="f in filters">
            	<a href="#" ng-click="selectFilter(f.id)">
            	    {{ f.name }}
            	</a>
            </li>
        </ul>
    </div>

KENDO ATTRIBUTES

using Angular-Kendo adapter

    <div class="k-grid k-widget" kendo-grid id="k-grid"
         k-data-source="invoices"
         k-sortable="true"
         k-resizable="true"
         k-pageable="pagination"
         k-columns="columns"
         data-role="grid"
         k-on-data-binding="gridInit(kendoEvent)"></div>


code samples JS

Angularjs controller

angular.module('paiApp').controller('SearchPageCtrl',
        ['$scope','Ios','Invoices','Tickets', 'Msgs',
             function($scope, Ios, Invoices, Tickets, Msgs) {

    //-- error handler
    if(PAI_CONTEXT.HAS_VIEW_ERR == 1) {
        Msgs.addDataLoadErr({'detail': PAI_CONTEXT.ERR_DESC}, 'Search Results');
    }

    //==== list/grid support ====
    $scope.data = PAI_CONTEXT.SEARCH_RESULTS || 
            {"Insertion_Order__c":[],"Invoice_CI__c":[],"Case":[]};

    $scope.io_columns = Ios.columns();
    $scope.invoice_columns = Invoices.columns();
    $scope.ticket_columns = Tickets.columns();

    $scope.ios = new kendo.data.DataSource({
        data: $scope.data.Insertion_Order__c,
        schema : { model: { fields: {
            CreatedDate : { type: "date" },
            Commit_Amount__c : { type: "number" }
        } } },
        pageSize: 5
    });
    $scope.invoices = new kendo.data.DataSource({
        data: $scope.data.Invoice_CI__c,
        schema : { model: { fields: {
            Due_Date__c : { type: "date" },
            Amount_Due__c : { type: "number" }
        } } },
        pageSize: 5
    });
    $scope.tickets = new kendo.data.DataSource({
        data: $scope.data.Case,
        schema : { model: { fields: { CreatedDate__c : { type: "date" } } } },
        pageSize: 5
    });

}]); //SearchPageCtrl

ANgularjs service

paiApp.factory('RecentlyViewedServ',
    ['Restangular', function(Restangular) {

    var mockData = [
        {date: "2014/01/13", title: "SF-27857", link: "/account/SF-27857"},
        {date: "2014/01/17", title: "Account: Kraft Foods", link: "/account/SF-27857"},
        {date: "2014/01/27", title: "SF-27857", link: "/account/SF-27857"},
        {date: "2014/01/10", title: "Account: Kraft Foods", link: "/account/SF-27857"},
        {date: "2014/01/09", title: "Account: Kraft Foods", link: "/account/SF-27857"}
    ];

    //-- public API
    return {
        getMockRecentItems: function() {
            return mockData;
        },
        getRecentItems: function(item_count) {
            return Restangular.one('api/sections/recent_items',item_count).get();
        },
        postRecentItem: function(item) {
            var obj = Restangular.one('api/sections/recent_items');
            obj.url = item.url;
            obj.title = item.title;
            return obj.post();
        }
    };

}]);

angularjs filter

/**
 * angular filter to display default value if the property is empty(falsy)
 * usage:
 *    {{ value | default: 'defaultValue' }}
 */
angular.module('paiApp').filter('default', function() {
    return function(input, defaultVal) {
        return (input) ? input : defaultVal;
    }
});

Paiweb utils

addKendoTopPaging: function(event, pagination) {
    var grid = event.sender,             //grid from kendo event
        g = $("#k-grid"),                //grid from jquery selector
        pager = $('#div .k-pager-wrap'), //regular bottom pager
        pagerTop = $("#pager_top"),      //additional top pager
        nextElem = g.find(".k-grouping-header"); // if grouping enabled
    if (nextElem.length == 0)
        nextElem = g.find(".k-grid-header");     // if grouping disabled
    if (pagerTop.length == 0) {
        var newPager = $('<div id="pager_top" class=k-pager-wrap pagerTop" />')
                .insertBefore(nextElem);
        g.topPager = new kendo.ui.Pager(newPager, $.extend({}, 
                pagination, { dataSource: grid.dataSource }));
        g.topPager.refresh(); // DataSource change not fired, call this manually
    }
},

server side Front end 



 

Base template

{% load sekizai_tags %}
<!doctype html>
    {% block adds %}{% endblock adds %}
    <head>
        {% render_block "meta" %}
        <title>
            {% block title %}Hub.coupons{% endblock title %}
        </title>
        {% render_block "css" %}
    </head>
    <body>
        {% block body %}
            {% block content %}
                Base Html template
            {% endblock content %}
        {% endblock body %}
        {% render_block "js" %}
    </body>
</html>

ADDTOBLOCK

{% addtoblock "css" %}         <link href="{{STATIC_URL}}vendor/css/bootstrap-3.0.3.min.css" rel="stylesheet" type="text/css">
        <link href="{{STATIC_URL}}app/css/paiw-site.css" rel="stylesheet" type="text/css">
        <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css" rel="stylesheet">
{% endaddtoblock %}


{% addtoblock "js" %} <script type="text/javascript"> PAI_CONTEXT.OBJ_ID = "{{ id }}"; </script> <script type="text/javascript" src="{{ STATIC_URL }}app/js/io/ioServ.js"></script> <script type="text/javascript" src="{{ STATIC_URL }}app/js/io/ioCtrl.js"></script>
{% endaddtoblock %}

common page TEmplates

{% block body %}
<div ng-app="paiApp" class="pai-application waiting-for-angular">
    <div id="initializing-panel"></div>
    <div id="content-outer" class="page-container" ng-controller="AuthCtrl">
        {%  block app_header %}
        <div class="header container-fluid">
            {% include "component/header.html" %}
            {% main_nav view_id %}
        </div>
        {% endblock app_header %}

        <div id="content" style="">
            {% block content %}{% endblock %}
        </div>

        <div class="clear"></div>
        {% block footer %}
            {% include "component/footer.html" %}
        {% endblock footer %}
    </div>
    {% block bottom %}{# add custom markup needed in а page like modal dialog sections #}
    {% endblock %}
</div>
{% endblock %}

Specific page part

{% block right_pane %}
    <div ng-controller="IoCtrl">
        <section>
            <p class="pane-header">
                <span> {% trans "Insertion Order" %}: </span>
                <span> {% verbatim %}{{ io.Name }}{% endverbatim %}</span>
            </p>
            <p>
                <a class="back-link" href="#" ng-click="backToList()">< {% trans "Back to List: Insertion Orders" %}</a>
            </p>

            {% include "component/create_ticket_modal.html" %}

        </section>

        <section>
            <h4 class="section-header">
                {% trans "IO Information" %}
            </h4>
...

SASS example

@import "compass"

/* -------- Variables */

$text-color: #333
$quiet-color: #ccc
$mid-color: #555
$loud-color: #111
$heading-color: #111

/* ---- collapsible containers --- */
.panel-heading
  .panel-title
    font-size: 18px
    color: $mid-color
    font-weight: bold

    &:hover, &:visited:hover
      color: $mid-color
      text-decoration: underline

  &:hover
    .accordion-toggle:after
      /* symbol for "opening" panels */
      font-family: 'Glyphicons Halflings'  /* for enabling glyphicon  */
      content: "\e114"    /* adjust as needed, taken from bootstrap.css */
      float: right        /* adjust as needed */
      color: grey         /* adjust as needed */

    .accordion-toggle.collapsed:after
      /* symbol for "collapsed" panels */
      content: "\e080"    /* adjust as needed, taken from bootstrap.css */

Server side architecture


SINGLE SIGN ON (SSO)


Server side control flow



Testing technologies

Django Unit Test Framework
Mock library for python
Coverage library for Python
JS unit testing Karma for Angular
E2E testing Protractor + Selenium
Grunt task runner

code coverage

Report in PyCharm

code coverage (cont.)

Report in a browser

Code coverage (cont.)

Coverage details

Pulling all together: Request flow


Q & A

PaiWeb

By pbosin

PaiWeb

  • 1,466