remp/crm-application-module

CRM Application Module

0.25.0 2020-11-03 06:33 UTC

README

Configuration

You can configure default Redis keys prefix, which is used if implementation using RedisClientTrait enables prefixing via usePrefix() method.

crm_application:
    redis_client_factory:
        prefix: foo_

You can turn on prefixing for specific service using RedisClientTrait by calling usePrefix() method in configuration.

configsCache:
		setup:
			- usePrefix()

Commands

application:heartbeat

If your Hermes worker (application:hermes_worker) is losing connection to MySQL after a long period of inactivity, add application:heartbeat into your scheduler (e.g. crontab) with small interval (e.g. 1 minute).

 WARNING: Change paths to PHP and command.php according to your installation.

# emit heartbeat event
*/1 * * * * /usr/bin/php /var/www/html/bin/command.php application:heartbeat

Event is handled by HeartbeatMysql handler which pings MySQL. This simple process keeps Hermes worker alive.

application:hermes_shutdown

Command application:hermes_shutdown can be used to gracefully shutdown Hermes worker and all other workers which integrate Hermes' RestartInterface (eg. Scenarios worker present in ScenariosModule). This can be used after CRM update when it's needed to reload all workers to new version.

 WARNING: Change paths to PHP and command.php according to your installation.

/usr/bin/php /var/www/html/bin/command.php application:hermes_shutdown

User confirmation is required to proceed with shutdown of all worker.

In case you need to run this command without user interaction (eg. CI, tests), use --assume-yes flag:

/usr/bin/php /var/www/html/bin/command.php application:hermes_shutdown --assume-yes

Components

FrontendMenu

User-facing frontend menu expected to be used in your application layout.

Example use

Use within your layout by using:

{control fronendMenu}

You can override the default layout of menu in your config.local.neon:

# ...
services:
    frontendMenu:
        setup:
            - setTemplate('../../../../../app/modules/DemoModule/templates/frontend_menu.latte')
# ...
Preview

alt text

Graphs

Following is a set of various charts provided by the ApplicationModule out of the box.

GoogleBarGraph

Simple bar graph based on Google charts. Usually used within GoogleBarGraphGroup component, but can be also used separately if you need to manage your groups (keys within $data parameter) manually.

Example use

To use the chart, create similar method in your presenter or widget:

namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
    }

    public function createComponentGoogleUserSubscribersRegistrationSourceStatsGraph()
    {
        $control = $this->factory->create();

        $results = $this->database->table('subscriptions')
            ->where('subscriptions.start_time < ?', $this->database::literal('NOW()'))
            ->where('subscriptions.end_time > ?', $this->database::literal('NOW()'))
            ->group('user.source')
            ->select('user.source, count(*) AS count')
            ->order('count DESC')
            ->fetchAll();

        $data = [];

        foreach ($results as $row) {
            $data[$row['source']] = $row['count'];
        }

        $control->addSerie($this->translator->translate('dashboard.users.active_sub_registrations.serie'), $data);

        return $control;
    }
    // ...
}

In your templates/Demo/default.latte template, use the component as needed:

<div class="row">
    <div class="col-md-12">
        {control googleUserSubscribersRegistrationSourceStatsGraph}
    </div>
</div>
preview

alt text

GoogleBarGraphGroup

Simple bar graph based on Google charts. Component is able to create multiple groups based on ->setGroupBy method and results of your query. Internally uses GoogleBarGraph to render the chart.

example
namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
    }

    public function createComponentGoogleUserActiveSubscribersRegistrationsSourceStatsGraph(GoogleBarGraphGroupControlFactoryInterface $factory)
    {
        $graphDataItem = new GraphDataItem();

        $graphDataItem->setCriteria(
            (new Criteria)->setTableName('payments')
                ->setTimeField('created_at')
                ->setJoin('JOIN users ON payments.user_id = users.id')
                ->setWhere("AND payments.status = '" . PaymentsRepository::STATUS_PAID . "'")
                ->setGroupBy('users.source') // <-- THIS LINE DEFINES THE GROUPPING
                ->setSeries('users.source') // <-- THIS LINE DEFINES CHART SERIES
                ->setValueField('count(*)')
                ->setStart(DateTime::from($this->dateFrom))
                ->setEnd(DateTime::from($this->dateTo))
        );

        $control = $factory->create();
        $control->setGraphTitle($this->translator->translate('dashboard.payments.registration.title'))
            ->setGraphHelp($this->translator->translate('dashboard.payments.registration.tooltip'))
            ->addGraphDataItem($graphDataItem);

        return $control;
    }

In your templates/Demo/default.latte template, use the component as needed:

<div class="row">
    <div class="col-md-12">
        {control googleUserActiveSubscribersRegistrationsSourceStatsGraph}
    </div>
</div>
preview

alt text

GoogleLineGraph

Simple line graph based on Google charts. This component is only being used by GoogleLineGraphGroup and is not advised to be used directly unless you really need to provide raw data for the chart.

preview

alt text

GoogleLineGraphGroup

Simple line graph based on Google charts. Component is able to create multiple series based on data grouped within built query.

example
namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
    }

    public function createComponentGoogleSubscriptionsEndGraph(GoogleLineGraphGroupControlFactoryInterface $factory)
    {
        $items = [];

        $graphDataItem = new GraphDataItem();
        $graphDataItem->setCriteria((new Criteria())
            ->setTableName('subscriptions')
            ->setTimeField('end_time')
            ->setValueField('count(*)')
            ->setStart($this->dateFrom)
            ->setEnd($this->dateTo));
        $graphDataItem->setName($this->translator->translate('dashboard.subscriptions.ending.now.title'));
        $items[] = $graphDataItem;

        $graphDataItem = new GraphDataItem();
        $graphDataItem->setCriteria((new Criteria())
            ->setTableName('subscriptions')
            ->setWhere('AND next_subscription_id IS NOT NULL')
            ->setTimeField('end_time')
            ->setValueField('count(*)')
            ->setStart($this->dateFrom)
            ->setEnd($this->dateTo));
        $graphDataItem->setName($this->translator->translate('dashboard.subscriptions.ending.withnext.title'));
        $items[] = $graphDataItem;

        $control = $factory->create()
            ->setGraphTitle($this->translator->translate('dashboard.subscriptions.ending.title'))
            ->setGraphHelp($this->translator->translate('dashboard.subscriptions.ending.tooltip'));

        foreach ($items as $graphDataItem) {
            $control->addGraphDataItem($graphDataItem);
        }
        return $control;
    }
    // ...
}

In your templates/Demo/default.latte template, use the component as needed:

<div class="row">
    <div class="col-md-12">
        {control googleSubscriptionsEndGraph}
    </div>
</div>
preview

alt text

InlineBarGraph

Inline bar chart is meant to be used directly within grids or with other inline bar charts to provide quick information about the data.

example

Following is example of multiple inline bar charts that are displayed in the grid listing of available payment gateways.

namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
    }

    public function createComponentSmallGraph()
    {
        return new Multiplier(function ($id) {
            $control = new InlineBarGraph;

            $graphDataItem = new GraphDataItem();
            $graphDataItem
                ->setCriteria(
                    (new Criteria())
                        ->setTableName('payments')
                        ->setWhere('AND payment_gateway_id = ' . $id)
                        ->setGroupBy('payment_gateway_id')
                        ->setStart('-3 months')
                );

            $graphData = $this->context->getService('graph_data');
            $graphData->clear();
            $graphData->addGraphDataItem($graphDataItem);
            $graphData->setScaleRange('day');

            $data = $graphData->getData();
            if (!empty($data)) {
                $data = array_pop($data);
            }

            $control->setGraphTitle($this->translator->translate('payments.admin.payment_gateways.small_graph.title'))
                ->addSerie($data);
            return $control;
        });
    }
    // ...
}

In your templates/Demo/default.latte template, use the component as needed:

<div class="row">
    {foreach $paymentGateways as $gateway}
    <div class="col-md-3">
        {control smallGraph-$gateway->id}
    </div>
    {/foreach}
</div>
preview

alt text

SmallBarchart

SmallBarChart is targeted to be used within widgets across the CRM admin, but can be also used in user-facing frontend to provide simple countable information.

example
namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
    }

    public function createComponentPaidPaymentsSmallBarGraph(SmallBarGraphControlFactoryInterface $factory)
    {
        return $this->generateSmallBarGraphComponent(PaymentsRepository::STATUS_PAID, 'Paid', $factory);
    }

    private function generateSmallBarGraphComponent($status, $title, SmallBarGraphControlFactoryInterface $factory)
    {
        $data = $this->paymentsHistogramFactory->paymentsLastMonthDailyHistogram($status);

        $control = $factory->create();
        $control->setGraphTitle($title)->addSerie($data);

        return $control;
    }
    // ...
}

In your templates/Demo/default.latte template, use the component as needed:

<div class="row">
    <div class="col-md-3">
        {control paidPaymentsSmallBarGraph}
    </div>
</div>
preview

alt text

GoogleSankeyGraphGroup

A sankey graph is a component based on Google Sankey diagram. It's used to depict a flow from one set of values to another.

example
namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
    }

    public function createComponentGoogleSankeyGraph(GoogleSankeyGraphGroupControlFactoryInterface $factory)
    {
        $graph = $factory->create();
        $graph->setGraphHelp($this->translator->translate('Graph help');
        $graph->setGraphTitle($this->translator->translate('Graph title');
        $graph->setRows([
            ['A', 'X', 1],
            ['A', 'Y', 3],
            ['A', 'Z', 2],
            ['B', 'X', 4],
            ['B', 'Y', 2],
            ['B', 'Z', 2],
        ]);
        $graph->setColumnNames('From', 'To', 'Count');
        return $graph;
    }
    // ...
}

In your templates/Demo/default.latte template, use the component as needed:

<div class="row">
    <div class="col-md-12">
        {control googleSankeyGraph}
    </div>
</div>
preview

alt text

VisualPaginator

Paginator is used to limit and offset your results displayed in lists all around the system. Paginator keeps the current page/limit and provides the information to your data-fetching blocks of code.

example

Following is a paginator usage for scenarios listing.

namespace Crm\DemoModule\Presenters;

class DemoPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    // ...
    public function renderDefault()
    {
        $scenarios = $this->scenariosRepository->all();

        $filteredCount = $this->template->filteredCount = $products->count('*');

        $vp = new VisualPaginator();
        $this->addComponent($vp, 'scenarios_vp');
        $paginator = $vp->getPaginator();
        $paginator->setItemCount($filteredCount);
        $paginator->setItemsPerPage(50);

        $this->template->vp = $vp;
        $this->template->scenarios = $scenarios->limit($paginator->getLength(), $paginator->getOffset());
    }
    // ...
}

In your templates/Demo/default.latte template, use the component as needed (usually below or above the listing). Name of the control should be the same as you used within ->addComponent 2nd argument.

{control scenarios_vp}
preview

alt text

Widgets

Following is a set of widget wrappers provided by ApplicationModule to be used by your widgets. Application provides three set of wrappers:

SimpleWidget

Simple widget is the component allowing simple extension of modules's view by other modules. Module can provide placeholder for widgets in the action's template (in .latte file) and other module can register their implementations of widget in their Module class.

You can read more about creating and registering widgets in CRM skeleton documentation available at github.com/remp2020/crm-skeleton.

SingleStatWidget

Widget provides wrapper for simple table with single statistic - each provided by separate widget implementation. The primary scenario for this use are dashboards.

preview

alt text