Install the Product Offer + Service Points feature

Edit on GitHub

This document describes how to install the Product Offer + Service Points feature.

Install feature core

Follow the steps below to install the Product Offer + Service Points feature core.

Prerequisites

Install the required features:

NAME VERSION INSTALLATION GUIDE
Product Offer 202404.0 Install the Product Offer feature
Service Points 202404.0 Install the Service Points feature
Show allHide

1) Install the required modules

composer require spryker-feature/product-offer-service-points: "202404.0" --update-with-dependencies
Verification

Make sure that the following modules have been installed:

MODULE EXPECTED DIRECTORY
ProductOfferServicePoint vendor/spryker/product-offer-service-point
ProductOfferServicePointDataImport vendor/spryker/product-offer-service-point-data-import
ProductOfferServicePointGui vendor/spryker/product-offer-service-point-gui
ProductOfferServicePointStorage vendor/spryker/product-offer-service-point-storage
Show allHide

2) Set up database schema and transfer objects

  1. Adjust the schema definition so entity changes trigger events.
AFFECTED ENTITY TRIGGERED EVENTS
spy_product_offer_service Entity.spy_product_offer_service.create
Entity.spy_product_offer_service.delete
Show allHide

src/Pyz/Zed/ProductOfferServicePoint/Persistence/Propel/Schema/spy_product_offer_service.schema.xml

<?xml version="1.0"?>
<database xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="zed" xsi:noNamespaceSchemaLocation="http://static.spryker.com/schema-01.xsd" namespace="Orm\Zed\ProductOfferServicePoint\Persistence" package="src.Orm.Zed.ProductOfferServicePoint.Persistence">

    <table name="spy_product_offer_service">
        <behavior name="event">
            <parameter name="spy_product_offer_service_all" column="*"/>
        </behavior>
    </table>

</database>
  1. Apply database changes and generate transfer changes:
console transfer:generate
console propel:install
console transfer:entity:generate
console frontend:zed:build
Verification

Make sure that the following changes have been applied in the database:

DATABASE ENTITY TYPE EVENT
spy_product_offer_service table created
spy_product_offer_service_storage table created
Show allHide

Make sure that propel entities have been generated successfully by checking their existence. Also, make generated entity classes extending respective Spryker core classes.

CLASS NAMESPACE EXTENDS
\Orm\Zed\ProductOfferServicePoint\Persistence\SpyProductOfferService \Spryker\Zed\ProductOfferServicePoint\Persistence\Propel\AbstractSpyProductOfferService
\Orm\Zed\ProductOfferServicePoint\Persistence\SpyProductOfferServiceQuery \Spryker\Zed\ProductOfferServicePoint\Persistence\Propel\AbstractSpyProductOfferServiceQuery
\Orm\Zed\ProductOfferServicePointStorage\Persistence\SpyProductOfferServiceStorage \Spryker\Zed\ProductOfferServicePointStorage\Persistence\Propel\AbstractSpyProductOfferServiceStorage
\Orm\Zed\ProductOfferServicePointStorage\Persistence\SpyProductOfferServiceStorageQuery \Spryker\Zed\ProductOfferServicePointStorage\Persistence\Propel\AbstractSpyProductOfferServiceStorageQuery
Show allHide

Make sure the following changes have been applied in transfer objects:

TRANSFER TYPE EVENT PATH
ServicePoint class created src/Generated/Shared/Transfer/ServicePointTransfer
ServicePointCollection class created src/Generated/Shared/Transfer/ServicePointCollectionTransfer
StoreRelation class created src/Generated/Shared/Transfer/StoreRelationTransfer
Store class created src/Generated/Shared/Transfer/StoreTransfer
Sort class created src/Generated/Shared/Transfer/SortTransfer
Pagination class created src/Generated/Shared/Transfer/PaginationTransfer
DataImporterConfiguration class created src/Generated/Shared/Transfer/DataImporterConfigurationTransfer
DataImporterReport class created src/Generated/Shared/Transfer/DataImporterReportTransfer
ServicePointStorage class created src/Generated/Shared/Transfer/ServicePointStorageTransfer
ProductOfferService class created src/Generated/Shared/Transfer/ProductOfferServiceTransfer
ProductOfferServices class created src/Generated/Shared/Transfer/ProductOfferServicesTransfer
ProductOfferServiceCollection class created src/Generated/Shared/Transfer/ProductOfferServiceCollectionTransfer
ProductOfferServiceCollectionRequest class created src/Generated/Shared/Transfer/ProductOfferServiceCollectionRequestTransfer
ProductOfferServiceCollectionResponse class created src/Generated/Shared/Transfer/ProductOfferServiceCollectionResponseTransfer
ProductOfferServiceCollectionResponse class created src/Generated/Shared/Transfer/ProductOfferServiceCollectionResponseTransfer
ProductOfferServiceCriteria class created src/Generated/Shared/Transfer/ProductOfferServiceCriteriaTransfer
ProductOfferServiceConditions class created src/Generated/Shared/Transfer/ProductOfferServiceConditionsTransfer
IterableProductOfferServicesCriteria class created src/Generated/Shared/Transfer/IterableProductOfferServicesCriteriaTransfer
IterableProductOfferServicesConditions class created src/Generated/Shared/Transfer/IterableProductOfferServicesConditionsTransfer
ProductOfferServiceStorage class created src/Generated/Shared/Transfer/ProductOfferServiceStorageTransfer
ProductOfferServiceStorageCollection class created src/Generated/Shared/Transfer/ProductOfferServiceStorageCollectionTransfer
ServicePointStorageCollection class created src/Generated/Shared/Transfer/ServicePointStorageCollectionTransfer
ServicePointStorageCriteria class created src/Generated/Shared/Transfer/ServicePointStorageCriteriaTransfer
ServicePointStorageConditions class created src/Generated/Shared/Transfer/ServicePointStorageConditionsTransfer
SynchronizationData class created src/Generated/Shared/Transfer/SynchronizationDataTransfer
Filter class created src/Generated/Shared/Transfer/FilterTransfer
Show allHide

3) Import service points

  1. Prepare your data according to your requirements using our demo data:

data/import/common/common/product_offer_service.csv

>product_offer_reference,service_key
offer419,s1
offer420,s1
offer421,s1
offer422,s1
offer423,s1
offer424,s1
COLUMN REQUIRED DATA TYPE DATA EXAMPLE DATA EXPLANATION
product_offer_reference string offer419 Unique reference of the product offer.
service_key string s1 Unique key of the service.
Show allHide
  1. Enable data imports at your configuration file—for example:

data/import/local/full_EU.yml

    - data_entity: product-offer-service
      source: data/import/common/common/marketplace/product_offer_service.csv
  1. Register the following data import plugins:
PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
ProductOfferServiceDataImportPlugin Imports product offer services into the database. None \Spryker\Zed\ProductOfferServicePointDataImport\Communication\Plugin\DataImport
Show allHide

src/Pyz/Zed/DataImport/DataImportDependencyProvider.php

<?php

namespace Pyz\Zed\DataImport;

use Spryker\Zed\DataImport\DataImportDependencyProvider as SprykerDataImportDependencyProvider;
use Spryker\Zed\ProductOfferServicePointDataImport\Communication\Plugin\DataImport\ProductOfferServiceDataImportPlugin;

class DataImportDependencyProvider extends SprykerDataImportDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\DataImport\Dependency\Plugin\DataImportPluginInterface>
     */
    protected function getDataImporterPlugins(): array
    {
        return [
            new ProductOfferServiceDataImportPlugin(),
        ];
    }
}
  1. Enable the behaviors by registering the console commands:

src/Pyz/Zed/Console/ConsoleDependencyProvider.php

<?php

namespace Pyz\Zed\Console;

use Spryker\Zed\Kernel\Container;
use Spryker\Zed\Console\ConsoleDependencyProvider as SprykerConsoleDependencyProvider;
use Spryker\Zed\DataImport\Communication\Console\DataImportConsole;
use Spryker\Zed\ProductOfferServicePointDataImport\ProductOfferServicePointDataImportConfig;

class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return array<\Symfony\Component\Console\Command\Command>
     */
    protected function getConsoleCommands(Container $container): array
    {
        $commands = [
            // ...
            new DataImportConsole(DataImportConsole::DEFAULT_NAME . static::COMMAND_SEPARATOR . ProductOfferServicePointDataImportConfig::IMPORT_TYPE_PRODUCT_OFFER_SERVICE),
        ];

        return $commands;
    }
}
  1. Import data:
console data:import:product-offer-service
“Verification”

Make sure that entities were imported to the following database tables respectively:

  • spy_service_point
  • spy_service_point_store
  • spy_service_point_address
  • spy_service_type
  • spy_service
  • spy_product_offer_service

4) Add translations

  1. Append the glossary according to your configuration:
>product_offer_service_point.validation.product_offer_reference_not_found,Product offer '%product_offer_reference%' not found.,en_US
product_offer_service_point.validation.product_offer_reference_not_found,Product offer '%product_offer_reference%' nicht gefunden.,de_DE
product_offer_service_point.validation.product_offer_has_multiple_service_points,Product offer '%product_offer_reference%' can have only one service point.,en_US
product_offer_service_point.validation.product_offer_has_multiple_service_points,Das Product Offer '%product_offer_reference%' kann nur einen Service Point haben.,de_DE
product_offer_service_point.validation.service_uuid_not_found,Services with uuids '%service_uuids%' not found.,en_US
product_offer_service_point.validation.service_uuid_not_found,Services mit den uuids '%service_uuids%' wurde nicht gefunden.,de_DE
product_offer_service_point.validation.service_not_unique,A service for product offer '%product_offer_reference%' with the same uuid already exists in request.,de_DE
product_offer_service_point.validation.service_not_unique,Ein Service für Product Offer '%product_offer_reference%' mit derselben UUID ist bereits in der Anfrage vorhanden.,de_DE
product_offer_service_point.validation.product_offer_not_unique,A product offer with the same reference already exists in request.,de_DE
product_offer_service_point.validation.product_offer_not_unique,Ein Product Offer mit der gleichen Referenz liegt bereits in der Anfrage vor.,de_DE
  1. Import data:
console data:import glossary

5) Configure export to Redis

Configure tables to be published and synchronized to the Storage on create, edit, and delete changes.

  1. In src/Pyz/Client/RabbitMq/RabbitMqConfig.php, adjust the RabbitMq module’s configuration:

src/Pyz/Client/RabbitMq/RabbitMqConfig.php

<?php

namespace Pyz\Client\RabbitMq;

use Spryker\Client\RabbitMq\RabbitMqConfig as SprykerRabbitMqConfig;
use Spryker\Shared\ProductOfferServicePointStorage\ProductOfferServicePointStorageConfig;

class RabbitMqConfig extends SprykerRabbitMqConfig
{
    /**
     * @return array<mixed>
     */
    protected function getSynchronizationQueueConfiguration(): array
    {
        return [
            ProductOfferServicePointStorageConfig::QUEUE_NAME_SYNC_STORAGE_PRODUCT_OFFER_SERVICE,
        ];
    }
}
  1. Register new queue message processor:

src/Pyz/Zed/Queue/QueueDependencyProvider.php

<?php

namespace Pyz\Zed\Queue;

use Spryker\Shared\ProductOfferServicePointStorage\ProductOfferServicePointStorageConfig;
use Spryker\Zed\Queue\QueueDependencyProvider as SprykerDependencyProvider;

class QueueDependencyProvider extends SprykerDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return array<\Spryker\Zed\Queue\Dependency\Plugin\QueueMessageProcessorPluginInterface>
     */
    protected function getProcessorMessagePlugins(Container $container): array
    {
        return [
            ProductOfferServicePointStorageConfig::QUEUE_NAME_SYNC_STORAGE_PRODUCT_OFFER_SERVICE => new SynchronizationStorageQueueMessageProcessorPlugin(),
        ];
    }
}
  1. Configure synchronization pool and event queue name:

src/Pyz/Zed/ProductOfferServicePointStorage/ProductOfferServicePointStorageConfig.php

<?php

/**
 * This file is part of the Spryker Suite.
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

namespace Pyz\Zed\ProductOfferServicePointStorage;

use Pyz\Zed\Synchronization\SynchronizationConfig;
use Spryker\Shared\Publisher\PublisherConfig;
use Spryker\Zed\ProductOfferServicePointStorage\ProductOfferServicePointStorageConfig as SprykerProductOfferServicePointStorageConfig;

class ProductOfferServicePointStorageConfig extends SprykerProductOfferServicePointStorageConfig
{
    /**
     * @return string|null
     */
    public function getProductOfferServiceSynchronizationPoolName(): ?string
    {
        return SynchronizationConfig::DEFAULT_SYNCHRONIZATION_POOL_NAME;
    }

    /**
     * @return string|null
     */
    public function getEventQueueName(): ?string
    {
        return PublisherConfig::PUBLISH_QUEUE;
    }
}
  1. Set up publisher plugins:
PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
ProductOfferWritePublisherPlugin Publishes product offer services data by SpyProductOffer entity events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOffer
ProductOfferServiceWriteByPublishPublisherPlugin Publishes product offer service data by SpyProductOfferService publish events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferService
ProductOfferServiceWritePublisherPlugin Publishes product offer service data by SpyProductOfferService entity events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferService
ProductOfferStoreWritePublisherPlugin Publishes product offer services data by SpyProductOfferStore entity events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferStore
ServiceWritePublisherPlugin Publishes product offer services data by SpyService entity events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\Service
ServicePointWritePublisherPlugin Publishes product offer services data by SpyServicePoint entity events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ServicePoint
ServicePointStoreWritePublisherPlugin Publishes product offer services data by SpyServicePointStore entity events. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ServicePointStore
ProductOfferServicePublisherTriggerPlugin Allows to populate product offer service storage table with data and trigger further export to Redis. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher
Show allHide
src/Pyz/Zed/Publisher/PublisherDependencyProvider.php
<?php

namespace Pyz\Zed\Publisher;

use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOffer\ProductOfferWritePublisherPlugin as ProductOfferServiceProductOfferWritePublisherPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferService\ProductOfferServiceWriteByPublishPublisherPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferService\ProductOfferServiceWritePublisherPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferServicePublisherTriggerPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ProductOfferStore\ProductOfferStoreWritePublisherPlugin as ProductOfferServiceProductOfferStoreWritePublisherPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\Service\ServiceWritePublisherPlugin as ProductOfferServiceServiceWritePublisherPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ServicePoint\ServicePointWritePublisherPlugin as ProductOfferServiceServicePointWritePublisherPlugin;
use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Publisher\ServicePointStore\ServicePointStoreWritePublisherPlugin as ProductOfferServiceServicePointStoreWritePublisherPlugin;
use Spryker\Zed\Publisher\PublisherDependencyProvider as SprykerPublisherDependencyProvider;

class PublisherDependencyProvider extends SprykerPublisherDependencyProvider
{
    /**
     * @return array
     */
    protected function getPublisherPlugins(): array
    {
        return array_merge(
            $this->getProductOfferServicePointStoragePlugins(),
        );
    }

    /**
     * @return array<\Spryker\Zed\PublisherExtension\Dependency\Plugin\PublisherTriggerPluginInterface>
     */
    protected function getPublisherTriggerPlugins(): array
    {
        return [
            new ProductOfferServicePublisherTriggerPlugin(),
        ];
    }

    /**
     * @return list<\Spryker\Zed\PublisherExtension\Dependency\Plugin\PublisherPluginInterface>
     */
    protected function getProductOfferServicePointStoragePlugins(): array
    {
        return [
            new ProductOfferServiceWritePublisherPlugin(),
            new ProductOfferServiceWriteByPublishPublisherPlugin(),
            new ProductOfferServiceProductOfferWritePublisherPlugin(),
            new ProductOfferServiceProductOfferStoreWritePublisherPlugin(),
            new ProductOfferServiceServiceWritePublisherPlugin(),
            new ProductOfferServiceServicePointWritePublisherPlugin(),
            new ProductOfferServiceServicePointStoreWritePublisherPlugin(),
        ];
    }
}
  1. Set up synchronization plugins:
PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
ProductOfferServiceSynchronizationDataBulkRepositoryPlugin Allows synchronizing the product offer service storage table content into Redis. Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Synchronization
Show allHide

src/Pyz/Zed/Synchronization/SynchronizationDependencyProvider.php

<?php

namespace Pyz\Zed\Synchronization;

use Spryker\Zed\ProductOfferServicePointStorage\Communication\Plugin\Synchronization\ProductOfferServiceSynchronizationDataBulkRepositoryPlugin;
use Spryker\Zed\Synchronization\SynchronizationDependencyProvider as SprykerSynchronizationDependencyProvider;

class SynchronizationDependencyProvider extends SprykerSynchronizationDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\SynchronizationExtension\Dependency\Plugin\SynchronizationDataPluginInterface>
     */
    protected function getSynchronizationDataPlugins(): array
    {
        return [
            new ProductOfferServiceSynchronizationDataBulkRepositoryPlugin(),
        ];
    }
}
Verification

Make sure that the product-offer-service trigger plugin works correctly:

  1. Fill the spy_product_offer_service table with data.
  2. Run the console publish:trigger-events -r product_offer_service command.
  3. Make sure that the spy_product_offer_service_storage table has been filled with respective data.
  4. Make sure that, in your system, storage entries are displayed with kv:product_offer_service:{store}:{product_offer_reference} mask.

Make sure that the product-offer-service synchronization plugin works correctly:

  1. Fill the spy_product_offer_service_storage table with some data.
  2. Run the console sync:data -r product_offer_service command.
  3. Make sure that, in your system, storage entries are displayed with the kv:product_offer_service:{store}:{product_offer_reference} mask.

Make sure when a product offer service is created via DataImport, it’s exported to Redis accordingly.

Make sure that, in Redis, data is displayed in the following format:

{
    "productOfferReference": "offer1",
    "servicePointUuid": "262feb9d-33a7-5c55-9b04-45b1fd22067e",
    "serviceUuids": ["f34c6ee7-8c73-4542-a621-846d91fafa56", "f34c6ee7-8c73-4542-a621-846d91fafa56", "f34c6ee7-8c73-4542-a621-846d91fafa56"],
    "_timestamp": 1683216744.8334839
}

7) Set up behavior

To expand product offers with services, register the plugins:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
ServiceProductOfferPostCreatePlugin Creates the product offer service entities. Spryker\Zed\ProductOfferServicePoint\Communication\Plugin\ProductOffer
ServiceProductOfferPostUpdatePlugin Updates the product offer service entities. Spryker\Zed\ProductOfferServicePoint\Communication\Plugin\ProductOffer
ServiceProductOfferExpanderPlugin Expands product offer transfers with services. Spryker\Zed\ProductOfferServicePoint\Communication\Plugin\ProductOffer
ServiceProductOfferStorageExpanderPlugin Expands product offer storage transfers with services from the storage. Spryker\Client\ProductOfferServicePointStorage\Plugin\ProductOfferStorage
ServiceProductOfferViewSectionPlugin Expands the product offer view section with services. Spryker\Zed\ProductOfferServicePointGui\Communication\Plugin\ProductOfferGui
Show allHide
src/Pyz/Zed/ProductOffer/ProductOfferDependencyProvider.php
<?php

namespace Pyz\Zed\ProductOffer;

use Spryker\Zed\ProductOffer\ProductOfferDependencyProvider as SprykerProductOfferDependencyProvider;
use Spryker\Zed\ProductOfferServicePoint\Communication\Plugin\ProductOffer\ServiceProductOfferExpanderPlugin;
use Spryker\Zed\ProductOfferServicePoint\Communication\Plugin\ProductOffer\ServiceProductOfferPostCreatePlugin;
use Spryker\Zed\ProductOfferServicePoint\Communication\Plugin\ProductOffer\ServiceProductOfferPostUpdatePlugin;

class ProductOfferDependencyProvider extends SprykerProductOfferDependencyProvider
{
        /**
     * @return array<\Spryker\Zed\ProductOfferExtension\Dependency\Plugin\ProductOfferPostCreatePluginInterface>
     */
    protected function getProductOfferPostCreatePlugins(): array
    {
        return [
            ...
            new ServiceProductOfferPostCreatePlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductOfferExtension\Dependency\Plugin\ProductOfferPostUpdatePluginInterface>
     */
    protected function getProductOfferPostUpdatePlugins(): array
    {
        return [
            ...
            new ServiceProductOfferPostUpdatePlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductOfferExtension\Dependency\Plugin\ProductOfferExpanderPluginInterface>
     */
    protected function getProductOfferExpanderPlugins(): array
    {
        return [
            ...
            new ServiceProductOfferExpanderPlugin(),
        ];
    }
}

src/Pyz/Client/ProductOfferStorage/ProductOfferStorageDependencyProvider.php

<?php

namespace Pyz\Client\ProductOfferStorage;

use Spryker\Client\ProductOfferServicePointStorage\Plugin\ProductOfferStorage\ServiceProductOfferStorageExpanderPlugin;
use Spryker\Client\ProductOfferStorage\ProductOfferStorageDependencyProvider as SprykerProductOfferStorageDependencyProvider;

class ProductOfferStorageDependencyProvider extends SprykerProductOfferStorageDependencyProvider
{
    /**
     * @return array<\Spryker\Client\ProductOfferStorageExtension\Dependency\Plugin\ProductOfferStorageExpanderPluginInterface>
     */
    protected function getProductOfferStorageExpanderPlugins(): array
    {
        return [
            new ServiceProductOfferStorageExpanderPlugin(),
        ];
    }
}

src/Pyz/Zed/ProductOfferGui/ProductOfferGuiDependencyProvider.php

<?php

namespace Pyz\Zed\ProductOfferGui;

use Spryker\Zed\ProductOfferServicePointGui\Communication\Plugin\ProductOfferGui\ServiceProductOfferViewSectionPlugin;

class ProductOfferGuiDependencyProvider extends SprykerProductOfferGuiDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\ProductOfferGuiExtension\Dependency\Plugin\ProductOfferViewSectionPluginInterface>
     */
    public function getProductOfferViewSectionPlugins(): array
    {
        return [
            new ServiceProductOfferViewSectionPlugin(),
        ];
    }
}
Verification
  1. In the Back Office, go to the Marketplace > Offers.
  2. On the Offers page, next to a product offer, click View. This opens the View Offer: {offer ID} page.
  3. Scroll down the page and make sure the SERVICES pane is displayed.