Skip to content

Commit

Permalink
Merge pull request #737 from yalesites-org/YSP-593-service-now-feed
Browse files Browse the repository at this point in the history
YSP-593: ServiceNow Knowledge Base Syncing
  • Loading branch information
dblanken-yale authored Oct 1, 2024
2 parents 307aebf + 958e94f commit a1c951a
Show file tree
Hide file tree
Showing 18 changed files with 924 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ ignored_config_entities:
- 'ys_core*'
- ys_themes.theme_settings
- 'ys_localist*'
- 'ai_engine*'
- 'ys_servicenow*'
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ module:
ys_localist: 0
ys_mail: 0
ys_node_access: 0
ys_servicenow: 0
ys_starterkit: 0
ys_toolbar: 0
ys_views_basic: 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# YaleSites ServiceNow

This is a YaleSites integration to allow ServiceNow knowledge base articles to import into the platform. The ultimate goal of this is to allow it to then go through our AI pipeline so that you can ask questions that will consider the articles synced.

## Features

Features include:

- Key-based authentication
- Block-based syncing

## Usage

1. Install the module
2. Create a new key in the keys module with the ServiceNow endpoint username and password credentials (see pantheon secrets below)
3. Visit the ServiceNow Settings under Configuration
4. Enable the module
5. Select the key you created from the drop down
6. Enter the endpoint URL you were given by the ServiceNow Team
1. Ensure that the following fields are present in the JSON output:
1. number: The KB article number
2. short_description: The title of the article
3. text: The body of the article
4. workflow_state: The state of the article (Published, etc)
7. Save
8. Upon reload, you'll notice a Sync button; click this button to do a manual sync

The service once turned on will attempt to sync hourly.

## Pantheon Secrets

Pantheon secrets can be used with their Drupal module to interact with the keys module. To do this you'd want to add a pantheon secret at the site level first with empty data:

`terminus secrets:site:set --scope web,user <siteName> servicenow_auth ""`

From there, you can then specify the multidev specific information. The key must ultimately be a JSON payload of the following:

`terminus secrets:site:set <siteName>.<env> servicenow_auth '{"username":"username","password":"password"}'`

Then simply sync pantheon secrets in the keys configuration to bring in the key; remember that there is a time delay on when that becomes available for sync.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
id: servicenow_knowledge_base_article_block
label: 'ServiceNow knowledge base article blocks'
source:
plugin: url
data_fetcher_plugin: http
data_parser_plugin: json
headers:
Accept: 'application/json; charset=utf-8'
Content-Type: application/json
authentication:
plugin: servicenow_auth
track_changes: true
urls:
callback: ys_servicenow_url_endpoint
item_selector: result
fields:
- name: servicenow_number
label: 'ServiceNow number'
selector: number
- name: servicenow_title
label: 'ServiceNow title'
selector: short_description
- name: servicenow_text
label: 'ServiceNow text'
selector: text
ids:
servicenow_number:
type: string
process:
field_text/value: servicenow_text
field_text/format:
plugin: default_value
default_value: basic_html
info: servicenow_title
reusable:
plugin: default_value
default_value: 0
destination:
plugin: 'entity:block_content'
default_bundle: text
overwrite_properties:
- field_text
- info
dependencies:
enforced:
module:
- migrate_plus
- migrate_tools
- layout_builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
id: servicenow_knowledge_base_articles
label: 'ServiceNow knowledge base articles'
source:
plugin: url
data_fetcher_plugin: http
data_parser_plugin: json
headers:
Accept: 'application/json; charset=utf-8'
Content-Type: application/json
authentication:
plugin: servicenow_auth
track_changes: true
urls:
callback: ys_servicenow_url_endpoint
item_selector: result
fields:
- name: servicenow_number
label: 'ServiceNow number'
selector: number
- name: servicenow_title
label: 'ServiceNow title'
selector: short_description
- name: servicenow_text
label: 'ServiceNow text'
selector: text
- name: servicenow_workflow_state
label: 'ServiceNow workflow state'
selector: workflow_state
ids:
servicenow_number:
type: string
process:
title: servicenow_title
block_id:
plugin: migration_lookup
migration: servicenow_knowledge_base_article_block
source: servicenow_number
layout_builder__layout:
source: servicenow_title
plugin: layout_builder_sections
moderation_state:
plugin: callback
callable: ys_servicenow_moderation_state_transformation
source: servicenow_workflow_state
destination:
plugin: 'entity:node'
default_bundle: page
overwrite_properties:
- title
- moderation_state
migration_dependencies:
required:
- servicenow_knowledge_base_article_block
dependencies:
enforced:
module:
- migrate_plus
- migrate_tools
- layout_builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace Drupal\ys_servicenow;

/**
* Provides basic authentication using keys module for the HTTP resource.
*
* @Authentication(
* id = "basic_keys",
* title = @Translation("Basic Keys")
* )
*/
class BasicAuthWithKeys {

/**
* The key ID.
*
* @var string
*/
protected $keyId;

/**
* The configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $configuration;

/**
* Constructs a new BasicAuthWithKeys object.
*
* @param \Drupal\Core\Config\Config $configuration
* The configuration.
* @param string $key_id
* The key ID.
*/
public function __construct($configuration, $key_id) {
$this->keyId = $key_id;
$this->configuration = $configuration;
}

/**
* Get the authentication options.
*
* @return array
* The authentication options.
*/
public function getAuthenticationOptions() {
if (!$this->keyId) {
throw new \Exception("Key not set");
}
$key = $this->getKey($this->keyId);
$key_object = $this->getKeyValues($key);
return [
'auth' => [
$key_object->username,
$key_object->password,
],
];
}

/**
* Given a key ID, return the key object.
*
* @param string $key_id
* The key ID.
*
* @return \Drupal\key\KeyInterface
* The key object
*/
protected function getKey($key_id) {
$key = \Drupal::service('key.repository')->getKey($key_id);

if (!$key) {
throw new \Exception("Key '$key_id' not found");
}

return $key;
}

/**
* Given a key, return the key values.
*
* @param \Drupal\key\KeyInterface $key
* The key object.
*
* @return object
* The key values.
*/
protected function getKeyValues($key) {
$json_key = $key->getKeyValue();

if (!$json_key) {
throw new \Exception("Key has no value");
}

$decoded_object = json_decode($json_key);

if (!$decoded_object) {
throw new \Exception("Key value is not valid JSON. Could you have accidentally used single quotes vs double?");
}

return $decoded_object;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace Drupal\ys_servicenow\Controller;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\ys_servicenow\ServiceNowManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
* Runs ServiceNow migrations on request.
*/
class RunMigrations extends ControllerBase implements ContainerInjectionInterface {

/**
* Configuration Factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $servicenowConfig;

/**
* The ServiceNow Manager.
*
* @var \Drupal\ys_servicenow\ServiceNowManager
*/
protected $servicenowManager;

/**
* Drupal messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;

/**
* Constructs a new RunMigrations object.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
ServiceNowManager $servicenow_manager,
MessengerInterface $messenger,
) {
$this->servicenowConfig = $config_factory->get('ys_servicenow.settings');
$this->servicenowManager = $servicenow_manager;
$this->messenger = $messenger;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('ys_servicenow.manager'),
$container->get('messenger'),
);

}

/**
* Runs all ServiceNow migrations.
*/
public function runAllMigrations() {
if ($this->servicenowConfig->get('enable_servicenow_sync')) {
$this->messenger->addMessage('Running ServiceNow migrations...');
$this->servicenowManager->runAllMigrations();
$this->messenger->addMessage('ServiceNow migrations complete.');
}
else {
$this->messenger->addMessage('ServiceNow sync is disabled. No sync was performed.');
}

$redirectUrl = Url::fromRoute('ys_servicenow.settings')->toString();
$response = new RedirectResponse($redirectUrl);
return $response;
}

}
Loading

0 comments on commit a1c951a

Please sign in to comment.