From 6eaa9a3d25b0b402ceb3d375c256e5a3d11d7cf6 Mon Sep 17 00:00:00 2001 From: erinlefeyijimi Date: Mon, 14 Apr 2025 16:44:43 +0100 Subject: [PATCH 1/5] docs: migrated the Reports component from the old docs to the new docs --- docs/components/reports.rst | 185 ++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/docs/components/reports.rst b/docs/components/reports.rst index fe732426..db6d6b1f 100644 --- a/docs/components/reports.rst +++ b/docs/components/reports.rst @@ -1,4 +1,189 @@ Reports ####### +To add and render custom reports in Mautic, your plugin needs to listen to three events: +- ``\Mautic\ReportBundle\ReportEvents::REPORT_ON_BUILD`` +- ``ReportEvents::REPORT_ON_GENERATE`` +- ``ReportEvents::REPORT_ON_GRAPH_GENERATE`` + +This guide walks you through defining a custom report, generating report data, and rendering graphs. + +Defining the Report +------------------- + +Use the ``ReportEvents::REPORT_ON_BUILD`` event to define: + +- The report context +- Available columns +- Available filters (defaults to columns) +- Available graphs + +Column Definition +----------------- + +Each column array can include the following properties: + ++-------------+-----------+--------+---------------------------------------------------------------+ +| Key | Required? | Type | Description | ++=============+===========+========+===============================================================+ +| ``label`` | Yes | string | The language string for the column | ++-------------+-----------+--------+---------------------------------------------------------------+ +| ``type`` | Yes | string | Column data type (e.g. ``int``, ``text``) | ++-------------+-----------+--------+---------------------------------------------------------------+ +| ``alias`` | No | string | Alias for the returned value, useful with SQL formulas | ++-------------+-----------+--------+---------------------------------------------------------------+ +| ``formula`` | No | string | SQL formula instead of a column (e.g. ``SUBSTRING_INDEX(...)``) | ++-------------+-----------+--------+---------------------------------------------------------------+ +| ``link`` | No | string | Route name to turn value into a hyperlink | ++-------------+-----------+--------+---------------------------------------------------------------+ + +Filter Definition +----------------- + +Filters are optional. If not defined, Mautic will default to using the column definitions. However, filters can provide additional options such as dropdown select lists. + +Additional filter keys include: + ++-------------+-----------+--------+-------------------------------------------------------------+ +| Key | Required? | Type | Description | ++=============+===========+========+=============================================================+ +| ``list`` | No | array | Dropdown options when type is ``select`` (e.g. ``earth`` => ``Earth``) | ++-------------+-----------+--------+-------------------------------------------------------------+ +| ``operators`` | No | array | Custom list of allowed filter operators | ++-------------+-----------+--------+-------------------------------------------------------------+ + +Generating the QueryBuilder +--------------------------- + +Use the ``ReportEvents::REPORT_ON_GENERATE`` event to define how the report data is retrieved. + +- Use ``$event->checkContext()`` to check if the report belongs to this subscriber. +- Use Doctrine's DBAL QueryBuilder via ``$event->getQueryBuilder()``. +- Join commonly used relationships using helper methods like ``addCategoryLeftJoin()``. + +Generating Graphs +----------------- + +Use the ``ReportEvents::REPORT_ON_GRAPH_GENERATE`` event to render graphs for your report. + +- Check the report context with ``$event->checkContext()``. +- Clone the base ``QueryBuilder`` to manipulate queries safely. +- Use classes like ``LineChart`` and ``ChartQuery`` to generate and render graph data. + +For supported chart types and options, refer to the ``ChartQuery`` and ``LineChart`` helper classes in the Mautic codebase. + +Example: HelloWorld Report Subscriber +------------------------------------- + +Below is an example plugin file located at:: + + plugins\HelloWorldBundle\EventListener\ReportSubscriber.php + +This file subscribes to report events and provides custom logic for adding new tables, columns, filters, and graphs. + +.. code-block:: php + + namespace MauticPlugin\HelloWorldBundle\EventListener; + + use Mautic\CoreBundle\EventListener\CommonSubscriber; + use Mautic\CoreBundle\Helper\GraphHelper; + use Mautic\ReportBundle\Event\ReportBuilderEvent; + use Mautic\ReportBundle\Event\ReportGeneratorEvent; + use Mautic\ReportBundle\Event\ReportGraphEvent; + use Mautic\ReportBundle\ReportEvents; + use Mautic\CoreBundle\Helper\Chart\ChartQuery; + use Mautic\CoreBundle\Helper\Chart\LineChart; + + class ReportSubscriber extends CommonSubscriber + { + public static function getSubscribedEvents() + { + return [ + ReportEvents::REPORT_ON_BUILD => ['onReportBuilder', 0], + ReportEvents::REPORT_ON_GENERATE => ['onReportGenerate', 0], + ReportEvents::REPORT_ON_GRAPH_GENERATE => ['onReportGraphGenerate', 0], + ]; + } + + public function onReportBuilder(ReportBuilderEvent $event) + { + if ($event->checkContext(['worlds'])) { + $prefix = 'w.'; + $columns = [ + $prefix . 'visit_count' => [ + 'label' => 'mautic.hellobundle.report.visit_count', + 'type' => 'int', + ], + $prefix . 'world' => [ + 'label' => 'mautic.hellobundle.report.world', + 'type' => 'text', + ], + ]; + + $columns = $filters = array_merge( + $columns, + $event->getStandardColumns($prefix), + $event->getCategoryColumns() + ); + + $filters[$prefix . 'world']['type'] = 'select'; + $filters[$prefix . 'world']['list'] = [ + 'earth' => 'Earth', + 'mars' => 'Mars', + ]; + + $event->addTable('worlds', [ + 'display_name' => 'mautic.helloworld.worlds', + 'columns' => $columns, + 'filters' => $filters, + ]); + + $event->addGraph('worlds', 'line', 'mautic.hellobundle.graph.line.visits'); + } + } + + public function onReportGenerate(ReportGeneratorEvent $event) + { + $context = $event->getContext(); + if ($context == 'worlds') { + $qb = $event->getQueryBuilder(); + $qb->from(MAUTIC_TABLE_PREFIX . 'worlds', 'w'); + $event->addCategoryLeftJoin($qb, 'w'); + $event->setQueryBuilder($qb); + } + } + + public function onReportGraphGenerate(ReportGraphEvent $event) + { + if (!$event->checkContext('worlds')) { + return; + } + + $graphs = $event->getRequestedGraphs(); + $qb = $event->getQueryBuilder(); + + foreach ($graphs as $graph) { + $queryBuilder = clone $qb; + $options = $event->getOptions($graph); + $chartQuery = clone $options['chartQuery']; + $chartQuery->applyDateFilters($queryBuilder, 'date_added', 'v'); + + switch ($graph) { + case 'mautic.hellobundle.graph.line.visits': + $chart = new LineChart(null, $options['dateFrom'], $options['dateTo']); + $chartQuery->modifyTimeDataQuery($queryBuilder, 'date_added', 'v'); + $visits = $chartQuery->loadAndBuildTimeData($queryBuilder); + $chart->setDataset( + $options['translator']->trans('mautic.hellobundle.graph.line.visits'), + $visits + ); + $data = $chart->render(); + $data['name'] = $graph; + $data['iconClass'] = 'fa-tachometer'; + $event->setGraph($graph, $data); + break; + } + } + } + } From 676a03f4761435d4b0f07e73f83dc757ab13792f Mon Sep 17 00:00:00 2001 From: erinlefeyijimi Date: Tue, 15 Apr 2025 10:32:45 +0100 Subject: [PATCH 2/5] docs: migrated the Reports component from the old documentation to the new documentation --- docs/components/reports.rst | 81 +++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/docs/components/reports.rst b/docs/components/reports.rst index db6d6b1f..f6594d99 100644 --- a/docs/components/reports.rst +++ b/docs/components/reports.rst @@ -23,19 +23,37 @@ Column Definition Each column array can include the following properties: -+-------------+-----------+--------+---------------------------------------------------------------+ -| Key | Required? | Type | Description | -+=============+===========+========+===============================================================+ -| ``label`` | Yes | string | The language string for the column | -+-------------+-----------+--------+---------------------------------------------------------------+ -| ``type`` | Yes | string | Column data type (e.g. ``int``, ``text``) | -+-------------+-----------+--------+---------------------------------------------------------------+ -| ``alias`` | No | string | Alias for the returned value, useful with SQL formulas | -+-------------+-----------+--------+---------------------------------------------------------------+ -| ``formula`` | No | string | SQL formula instead of a column (e.g. ``SUBSTRING_INDEX(...)``) | -+-------------+-----------+--------+---------------------------------------------------------------+ -| ``link`` | No | string | Route name to turn value into a hyperlink | -+-------------+-----------+--------+---------------------------------------------------------------+ +.. vale on + +.. list-table:: + :header-rows: 1 + + * - Key + - Required? + - Type + - Description + * - ``label`` + - REQUIRED + - string + - The language string for the column. + * - type + - REQUIRED + - string + - Column type. + * - alias + - OPTIONAL + - string + - An alias for the returned value. Useful in conjuction with ``formula``. + * - formula + - OPTIONAL + - string + - SQL formula instead of a column. e.g. ``SUBSTRING_INDEX(e.type, \'.\', 1)``. + * - link + - OPTIONAL + - string + - Route name to convert the value into a hyperlink. Used usually with an ID of an Entity. The route must accept ``objectAction`` and ``objectId`` parameters. + +.. vale off Filter Definition ----------------- @@ -44,22 +62,35 @@ Filters are optional. If not defined, Mautic will default to using the column de Additional filter keys include: -+-------------+-----------+--------+-------------------------------------------------------------+ -| Key | Required? | Type | Description | -+=============+===========+========+=============================================================+ -| ``list`` | No | array | Dropdown options when type is ``select`` (e.g. ``earth`` => ``Earth``) | -+-------------+-----------+--------+-------------------------------------------------------------+ -| ``operators`` | No | array | Custom list of allowed filter operators | -+-------------+-----------+--------+-------------------------------------------------------------+ +.. vale on -Generating the QueryBuilder +.. list-table:: + :header-rows: 1 + + * - Key + - Required? + - Type + - Description + * - list + - OPTIONAL + - array + - Used when ``type`` is select for a filter. Provides the dropdown options for a select input. Format should be ``value => label``. + * - operators + - OPTIONAL + - array + - Custom list of operators to allow for this filter. See ``Mautic\ReportBundle\Builder\MauticReportBuilder::OPERATORS`` for a examples. + +.. vale off + +Generate the QueryBuilder --------------------------- -Use the ``ReportEvents::REPORT_ON_GENERATE`` event to define how the report data is retrieved. +The ``ReportEvents::REPORT_ON_GENERATE`` event is dispatched when a report is to be generated and displayed. In this function, the plugin should define the ``QueryBuilder`` object used to generate the table data. + +Use ``$event->checkContext()`` to determine if the report requested is the subscribers report. -- Use ``$event->checkContext()`` to check if the report belongs to this subscriber. -- Use Doctrine's DBAL QueryBuilder via ``$event->getQueryBuilder()``. -- Join commonly used relationships using helper methods like ``addCategoryLeftJoin()``. +Note that the ``ReportEvents::REPORT_ON_GENERATE`` event should use Doctrine’s DBAL layer QueryBuilder obtained via ``$qb = $event->getQueryBuilder();``. +There are a number of helper functions to append joins for commonly used relationships such as category, leads, ip address, etc. Refer to the ``ReportGeneratorEvent`` class for more details. Generating Graphs ----------------- From e6ef5b67fc7843b620245d0d8aace16a820a30b6 Mon Sep 17 00:00:00 2001 From: erinlefeyijimi Date: Mon, 28 Apr 2025 11:59:13 +0100 Subject: [PATCH 3/5] chore: addresed comments --- docs/components/reports.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/components/reports.rst b/docs/components/reports.rst index f6594d99..987462f1 100644 --- a/docs/components/reports.rst +++ b/docs/components/reports.rst @@ -1,24 +1,26 @@ Reports ####### -To add and render custom reports in Mautic, your plugin needs to listen to three events: +To add and render custom Reports in Mautic, your Plugin needs to listen to three events: - ``\Mautic\ReportBundle\ReportEvents::REPORT_ON_BUILD`` - ``ReportEvents::REPORT_ON_GENERATE`` - ``ReportEvents::REPORT_ON_GRAPH_GENERATE`` -This guide walks you through defining a custom report, generating report data, and rendering graphs. +This guide walks you through defining a custom Report, generating Report data, and rendering graphs. +.. vale off Defining the Report ------------------- +.. vale on Use the ``ReportEvents::REPORT_ON_BUILD`` event to define: -- The report context +- The Report context - Available columns -- Available filters (defaults to columns) +- Available filters - defaults to columns - Available graphs -Column Definition +Column definition ----------------- Each column array can include the following properties: @@ -43,11 +45,11 @@ Each column array can include the following properties: * - alias - OPTIONAL - string - - An alias for the returned value. Useful in conjuction with ``formula``. + - An alias for the returned value. Useful in conjunction with ``formula``. * - formula - OPTIONAL - string - - SQL formula instead of a column. e.g. ``SUBSTRING_INDEX(e.type, \'.\', 1)``. + - SQL formula instead of a column. for example. ``SUBSTRING_INDEX(e.type, \'.\', 1)``. * - link - OPTIONAL - string @@ -58,12 +60,11 @@ Each column array can include the following properties: Filter Definition ----------------- -Filters are optional. If not defined, Mautic will default to using the column definitions. However, filters can provide additional options such as dropdown select lists. +.. vale on +Filters are optional. If you don't define them, the system defaults to using the column definitions. However, filters can provide additional options such as dropdown select lists. Additional filter keys include: -.. vale on - .. list-table:: :header-rows: 1 @@ -85,11 +86,10 @@ Additional filter keys include: Generate the QueryBuilder --------------------------- -The ``ReportEvents::REPORT_ON_GENERATE`` event is dispatched when a report is to be generated and displayed. In this function, the plugin should define the ``QueryBuilder`` object used to generate the table data. - -Use ``$event->checkContext()`` to determine if the report requested is the subscribers report. +The system dispatches the ``ReportEvents::REPORT_ON_GENERATE`` event when it needs to generate and display a report. In this function, the plugin defines the QueryBuilder object used to generate the table data. +Call ``$event->checkContext()`` to determine if the requested report is the subscriber's report. -Note that the ``ReportEvents::REPORT_ON_GENERATE`` event should use Doctrine’s DBAL layer QueryBuilder obtained via ``$qb = $event->getQueryBuilder();``. +Use Doctrine’s DBAL layer QueryBuilder for the ``ReportEvents::REPORT_ON_GENERATE`` event by obtaining it with ``$qb = $event->getQueryBuilder();``. There are a number of helper functions to append joins for commonly used relationships such as category, leads, ip address, etc. Refer to the ``ReportGeneratorEvent`` class for more details. Generating Graphs @@ -106,11 +106,11 @@ For supported chart types and options, refer to the ``ChartQuery`` and ``LineCha Example: HelloWorld Report Subscriber ------------------------------------- -Below is an example plugin file located at:: +Below is an example Plugin file located at:: plugins\HelloWorldBundle\EventListener\ReportSubscriber.php -This file subscribes to report events and provides custom logic for adding new tables, columns, filters, and graphs. +This file subscribes to Report events and provides custom logic for adding new tables, columns, filters, and graphs. .. code-block:: php From 19662e08b3f15248e46821bf8a6f52d00af63499 Mon Sep 17 00:00:00 2001 From: erinlefeyijimi Date: Tue, 27 May 2025 08:41:54 +0100 Subject: [PATCH 4/5] docs: migrated the Stages component from the old docs to the new docs --- docs/rest_api/stages.rst | 452 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 docs/rest_api/stages.rst diff --git a/docs/rest_api/stages.rst b/docs/rest_api/stages.rst new file mode 100644 index 00000000..abcc0183 --- /dev/null +++ b/docs/rest_api/stages.rst @@ -0,0 +1,452 @@ +Stages +####### + +Use this endpoint to obtain details on Mautic’s contact stages. + +.. code-block:: php + + newAuth($settings); + $apiUrl = "https://your-mautic.com"; + $api = new MauticApi(); + $stageApi = $api->newApi("stages", $auth, $apiUrl); + + +.. vale off + +Get a Stage +************ + +.. vale on + +.. code-block:: php + + get($id); + + + +Get an individual Stage by ID. + +.. vale off + +**HTTP Request** + +.. vale on + +``GET /stages/ID`` + +**Response** + +``Expected Response Code: 200`` + +.. code-block:: json + + + { + "stage": { + "id": 47, + "isPublished": 1, + "dateAdded": "2015-07-21T12:27:12-05:00", + "createdBy": 1, + "createdByUser": "Joe Smith", + "dateModified": "2015-07-21T14:12:03-05:00", + "modifiedBy": 1, + "modifiedByUser": "Joe Smith", + "name": "Stage A", + "category": null, + "description": "This is my first stage created via API.", + "weight": 0, + "publishUp": "2015-07-21T14:12:03-05:00", + "publishDown": "2015-07-21T14:12:03-05:00" + } + +**Stage Properties** + +.. list-table:: + :header-rows: 1 + + * - Name + - Type + - Description + * - ``id`` + - int + - ID of the Stage + * - ``isPublished`` + - boolean + - Whether the stage is published + * - ``dateAdded`` + - datetime + - Date/time stage was created + * - ``createdBy`` + - int + - ID of the user that created the stage + * - ``createdByUser`` + - string + - Name of the user that created the stage + * - ``dateModified`` + - datetime/null + - Date/time stage was last modified + * - ``modifiedBy`` + - int + - ID of the user that last modified the stage + * - ``modifiedByUser`` + - string + - Name of the user that last modified the stage + * - ``name`` + - string` + - Stage name + * - ``category`` + - int + - Stage category ID + * - ``description`` + - string + - Stage description + * - ``weight`` + - int + - Stage's weight + * - ``publishUp`` + - datetime + - Date/time stage should be published + * - ``publishDown`` + - datetime + - Date/time stage should be unpublished + +.. vale off + +List Contact Stages +************ + +.. vale on + +.. code-block:: php + + getList($searchFilter, $start, $limit, $orderBy, $orderByDir, $publishedOnly, $minimal); + +.. vale off + +**HTTP Request** + +.. vale on + +``GET /stages`` + +**Response** + +``Expected Response Code: 200`` + +.. code-block:: json + + { + "total": 4, + "stages": [ + { + "id": 47, + "isPublished": 1, + "dateAdded": "2015-07-21T12:27:12-05:00", + "createdBy": 1, + "createdByUser": "Joe Smith", + "dateModified": "2015-07-21T14:12:03-05:00", + "modifiedBy": 1, + "modifiedByUser": "Joe Smith", + "name": "Stage A", + "category": null, + "description": "This is my first stage created via API.", + "weight": 0, + "publishUp": "2015-07-21T14:12:03-05:00", + "publishDown": "2015-07-21T14:12:03-05:00" + }, + ... + ] +} + +**Stage Properties** + +.. list-table:: + :header-rows: 1 + + * - Name + - Type + - Description + * - ``total`` + - int + - Count of all stages + * - ``id`` + - int + - ID of the stage + * - ``isPublished`` + - boolean + - Whether the stage is published + * - ``dateAdded`` + - datetime + - Date/time stage was created + * - ``createdBy`` + - int + - ID of the user that created the stage + * - ``createdByUser`` + - string + - Name of the user that created the stage + * - ``dateModified`` + - datetime + - Date/time stage was last modified + * - ``modifiedBy`` + - int + - ID of the user that last modified the stage + * - ``modifiedByUser`` + - string + - Name of the user that last modified the stage + * - ``name`` + - string` + - Stage name + * - ``category`` + - int + - Stage category ID + * - ``description`` + - string + - Stage description + * - ``weight`` + - int + - Stage's weight + * - ``publishUp`` + - datetime + - Date/time stage should be published + * - ``publishDown`` + - datetime + - Date/time stage should be unpublished + +.. vale off + +Create Stage +************ + +Create a new stage. + +.. vale on + +.. code-block:: php + + 'Stage A', + 'weight' => 5, + 'description' => 'This is my first stage created via API.', + 'isPublished' => 1 + ); + + $stage = $stageApi->create($data); + +.. vale off + +**HTTP Request** + +.. vale on + +``POST /stages/new`` + +**POST Parameters** + +.. list-table:: + :header-rows: 1 + + * - Name + - Description + * - ``name`` + - Stage name is the only required field + * - ``weight`` + - int + * - ``description`` + - A description of the stage. + * - ``isPublished`` + - A value of 0 or 1 + + +**Response** + +``Expected Response Code: 201`` + +**Properties** + +Same as `Get Stage <#get-stage>`_. + +.. vale off + +Edit Stage +********** + +.. vale on + +.. code-block:: php + 'New stage name', + 'isPublished' => 0 + ); + + // Create new a stage of ID 1 is not found? + $createIfNotFound = true; + + $stage = $stageApi->edit($id, $data, $createIfNotFound); + +Edit a new Stage. Note that this supports PUT or PATCH depending on the desired behavior. + +**PUT** creates a stage if the given ID does not exist and clears all the stage information, adds the information from the request. +**PATCH** fails if the stage with the given ID does not exist and updates the stage field values with the values form the request. + +.. vale off + +**HTTP Request** + +.. vale on + +To edit a Stage and return a 404 if the Stage isn't found: + +``PATCH /stages/ID/edit`` + +To edit a Asset and create a new one if the Asset isn't found: + +``PUT /stages/ID/edit`` + +**POST Parameters** + +.. list-table:: + :header-rows: 1 + + * - Name + - Description + * - ``name`` + - Stage name is the only required field + * - ``alias`` + - Name alias generated automatically if not set + * - ``description`` + - A description of the stage. + * - ``isPublished`` + - A value of 0 or 1. + * - ``weight`` + - int + +**Response** + +If ``PUT``\ , the expected response code if editing the Asset is ``200`` or ``201`` if created. + +If using ``PATCH``\ , the expected response code is ``200``. + +**Properties** + +Same as `Get Stage <#get-stage>`_. + +.. vale off + +Delete Stage +************ + +.. vale on + +.. code-block:: php + + delete($id); + +Delete a stage. + +.. vale off + +**HTTP Request** + +.. vale on + +``DELETE /stages/ID/delete`` + +**Response** + +``Expected Response Code: 200`` + +**Properties** + +Same as `Get Stage <#get-stage>`_. + +.. vale off + +Add Contact to a Stage +************ + +.. vale on + +.. code-block:: php + + addContact($stageId, $contactId); + if (!isset($response['success'])) { + // handle error + } + +Manually add a contact to a specific stage. + +.. vale off + +**HTTP Request** + +.. vale on + +``POST /stages/STAGE_ID/contact/CONTACT_ID/add`` + +**Response** + +``Expected Response Code: 200`` + +.. code-block:: json + { + "success": true + } + + +.. vale off + +Remove Contact from a Stage +************ + +.. vale on + +.. code-block:: php + + removeContact($stageId, $contactId); + if (!isset($response['success'])) { + // handle error + } + +Manually remove a contact from a specific stage. + +.. vale off + +**HTTP Request** + +.. vale on + +``POST /stages/STAGE_ID/contact/CONTACT_ID/remove`` + +**Response** + +``Expected Response Code: 200`` + +.. code-block:: json + { + "success": true + } \ No newline at end of file From daa5b735a32dda5fd645f4d7649a4df9dc4ce35a Mon Sep 17 00:00:00 2001 From: erinlefeyijimi Date: Wed, 28 May 2025 09:51:14 +0100 Subject: [PATCH 5/5] docs:migrated the Stages Component from the old docs to the new docs --- docs/index.rst | 1 + docs/rest_api/stages.rst | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index df41b368..a41f2af5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -166,6 +166,7 @@ There are several ways to support Mautic other than contributing with code. rest_api/point_groups rest_api/reports rest_api/text_messages + rest_api/stages .. toctree:: :maxdepth: 2 diff --git a/docs/rest_api/stages.rst b/docs/rest_api/stages.rst index abcc0183..071ce4aa 100644 --- a/docs/rest_api/stages.rst +++ b/docs/rest_api/stages.rst @@ -122,7 +122,7 @@ Get an individual Stage by ID. .. vale off List Contact Stages -************ +********************* .. vale on @@ -227,7 +227,7 @@ List Contact Stages .. vale off Create Stage -************ +************** Create a new stage. @@ -282,7 +282,7 @@ Same as `Get Stage <#get-stage>`_. .. vale off Edit Stage -********** +************ .. vale on @@ -350,7 +350,7 @@ Same as `Get Stage <#get-stage>`_. .. vale off Delete Stage -************ +************** .. vale on @@ -381,7 +381,7 @@ Same as `Get Stage <#get-stage>`_. .. vale off Add Contact to a Stage -************ +************************ .. vale on @@ -418,7 +418,7 @@ Manually add a contact to a specific stage. .. vale off Remove Contact from a Stage -************ +***************************** .. vale on