Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SHS-5980: Dashboard: Notifications/Announcements to Site Editors #1745

Merged
merged 17 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions config/default/dashboard.dashboard.dashboard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies:
- views.view.new_default_image_alt_text
- views.view.site_dashboard_active_site_editors
module:
- hs_dashboard
- layout_builder
- layout_discovery
- views
Expand Down Expand Up @@ -58,47 +59,58 @@ layout:
label: 'Section one col'
context_mapping: { }
components:
b393947f-61c3-4752-939e-6c084e51b70f:
uuid: b393947f-61c3-4752-939e-6c084e51b70f
fdff49bf-c002-4f14-a705-911da9e979a8:
uuid: fdff49bf-c002-4f14-a705-911da9e979a8
region: content
configuration:
id: 'views_block:site_dashboard_active_site_editors-block_1'
id: 'views_block:editoria11y_results-block_top_results'
label: ''
label_display: visible
provider: views
context_mapping: { }
views_label: ''
items_per_page: none
exposed: { }
weight: 0
weight: 7
additional: { }
50ecf2ae-f119-4527-89bc-78bdb5a63e7b:
uuid: 50ecf2ae-f119-4527-89bc-78bdb5a63e7b
c2e96c4c-3687-40e8-baba-030bd9a7ea35:
uuid: c2e96c4c-3687-40e8-baba-030bd9a7ea35
region: content
configuration:
id: 'views_block:new_default_image_alt_text-block_1'
id: hs_dashboard_hsdp_announcements
label: 'HSDP Announcements'
label_display: visible
provider: hs_dashboard
context_mapping: { }
weight: 8
additional: { }
b393947f-61c3-4752-939e-6c084e51b70f:
uuid: b393947f-61c3-4752-939e-6c084e51b70f
region: content
configuration:
id: 'views_block:site_dashboard_active_site_editors-block_1'
label: ''
label_display: visible
provider: views
context_mapping: { }
views_label: ''
items_per_page: none
exposed: { }
weight: 1
weight: 2
additional: { }
fdff49bf-c002-4f14-a705-911da9e979a8:
uuid: fdff49bf-c002-4f14-a705-911da9e979a8
50ecf2ae-f119-4527-89bc-78bdb5a63e7b:
uuid: 50ecf2ae-f119-4527-89bc-78bdb5a63e7b
region: content
configuration:
id: 'views_block:editoria11y_results-block_top_results'
id: 'views_block:new_default_image_alt_text-block_1'
label: ''
label_display: visible
provider: views
context_mapping: { }
views_label: ''
items_per_page: none
exposed: { }
weight: 2
weight: 6
additional: { }
third_party_settings: { }
weight: 0
4 changes: 4 additions & 0 deletions docroot/modules/humsci/hs_dashboard/hs_dashboard.services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
hs_dashboard.announcements_manager:
class: Drupal\hs_dashboard\AnnouncementsManager
arguments: ['@http_client', '@logger.factory', '@file_system', '@date.formatter']
305 changes: 305 additions & 0 deletions docroot/modules/humsci/hs_dashboard/src/AnnouncementsManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
<?php

declare(strict_types=1);

namespace Drupal\hs_dashboard;

use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Link;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Class to handle HDSP Announcements.
*/
class AnnouncementsManager implements ContainerInjectionInterface {

use StringTranslationTrait;

/**
* Announcement CSV location.
*/
const ANNOUNCEMENTS_CSV = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTQzSuPudq048D1NadRBE9h_s_-w-o4YtcC6AHfCdcqn3gX52akZNOaF5KAG9SeXkCV6PvIVmRtQ0HR/pub?gid=1146337887&single=true&output=csv';

/**
* The HTTP client to fetch announcement data.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;

/**
* The logger channel service.
*
* @var Drupal\Core\Logger\LoggerChannel
*/
protected $logger;

/**
* The file system interface.
*
* @var Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;

/**
* Date formatter interface.
*
* @var Drupal\Core\Datetime\DateFormatterInterface
*/
protected DateFormatterInterface $dateFormatter;

/**
* Constructs a new ViewsBasicManager object.
*
* @param GuzzleHttp\ClientInterface $http_client
* The guzzle http client.
* @param Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger interface.
* @param Drupal\Core\File\FileSystemInterface $file_system
* The logger interface.
* @param Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter interface.
*/
public function __construct(
ClientInterface $http_client,
LoggerChannelFactoryInterface $logger_factory,
FileSystemInterface $file_system,
DateFormatterInterface $date_formatter,
) {
$this->httpClient = $http_client;
$this->logger = $logger_factory->get('hs_dashboard');
$this->fileSystem = $file_system;
$this->dateFormatter = $date_formatter;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('http_client'),
$container->get('logger.factory'),
$container->get('file_system'),
$container->get('date.formatter'),
);
}

/**
* Retrieves the CSV from Google Sheets.
*
* @param string $url
* The URL of the CSV file to retrieve.
*
* @return array
* Returns a csv array - if one is not found, an empty array is returned.
*/
private function getCsvAnnouncements($url): array {
try {
$response = $this->httpClient->request('GET', $url, [
'headers' => [
'Accept' => 'text/csv',
],
'timeout' => 10,
]);

if ($response->getStatusCode() !== 200) {
$this->logger->error('Invalid response status from {url} { message }: ', [
'url' => $url,
'message' => $response->getStatusCode(),
]);
throw new \Exception('Invalid response status: ' . $response->getStatusCode());
}

$csv_content = $response->getBody()->getContents();
return $this->parseCsv($csv_content);

}
catch (RequestException $e) {
$this->logger->error('Error retrieving CSV from {url}: {message}', [
'url' => $url,
'message' => $e->getMessage(),
]);
return [];
}
}

/**
* Parses a CSV file.
*
* @param string $csv_content
* The CSV content.
*
* @return array
* An array of CSV data.
*/
private function parseCsv(string $csv_content): array {
$rows = [];

$temp_file_path = 'temporary://csv_import.csv';

$file_uri = $this->fileSystem->saveData($csv_content, $temp_file_path, FileExists::Replace);

if ($file_uri) {
$handle = fopen($this->fileSystem->realpath($file_uri), 'r');

if ($handle !== FALSE) {
// Skip first header row.
$first_row = TRUE;
while (($data = fgetcsv($handle)) !== FALSE) {
if ($first_row) {
$first_row = FALSE;
continue;
}

// Removes empty rows.
if (empty($data[1]) || (empty($data[2]) && empty($data[3]))) {
continue;
}

if (isset($data[1])) {
$data[1] = $this->convertDateToTimestamp(trim($data[1]));
}

if (isset($data[2])) {
$data[2] = $this->convertMarkdownLinks(trim($data[2]));
}

if (isset($data[3])) {
$data[3] = $this->convertMarkdownLinks(trim($data[3]));
}

$rows[] = $data;
}
fclose($handle);
}
}

// Sort by date descending.
usort($rows, function ($a, $b) {
return $b[1] <=> $a[1];
});

// Convert dates.
foreach ($rows as &$row) {
$row[1] = $this->formatDate($row[1]);
}

return $rows;
}

/**
* Converts dates from Google Sheets into formatted dates.
*
* @param string $value
* A string of text to convert into a formatted date.
*
* @return int
* A Unix timestamp.
*/
private function convertDateToTimestamp(string $value): int {
$value = str_replace("\u{A0}", ' ', $value);
$date = \DateTime::createFromFormat('M d, Y', $value);

return $date ? $date->getTimestamp() : 0;

}

/**
* Converts a Unix timestamp into a Drupal formatted date.
*
* @param int $timestamp
* The Unix timestamp.
*
* @return string
* A formatted Drupal date.
*/
private function formatDate(int $timestamp): string {
return $this->dateFormatter->format($timestamp, 'medium');
}

/**
* Convert markdown links into HTML links.
*
* @param string $text
* Text to covert from markdown into HTML.
*/
private function convertMarkdownLinks(string $text): string {
$markdown_link_regex = "/\[(.*?)\]\((https?:\/\/.*?)\)/";

return preg_replace_callback($markdown_link_regex, function ($matches) {
$converted_link = Link::fromTextAndUrl(
$this->t('@link_text', ['@link_text' => $matches[1]]),
Url::fromUri($matches[2])
);

return $converted_link->toString()->__toString();
}, $text);

}

/**
* Returns table headers. These are statically set.
*
* @return array
* An array of table headers.
*/
public function getTableHeader(): array {

$tableHeader = [
[
'data' => $this->t('Date'),
],
[
'data' => $this->t('Title'),
],
[
'data' => $this->t('Description'),
],
];

return $tableHeader;
}

/**
* Returns table rows based on the announcements found in csv.
*
* @return array
* An array of table rows with announcement data.
*/
public function getTableRows(): array {
$table_rows = [];

if ($cache = \Drupal::cache()->get('hs_dashboard_csv_announcements')) {
return $cache->data;
}
$csv_data = $this->getCsvAnnouncements(static::ANNOUNCEMENTS_CSV);

foreach ($csv_data as $row) {
$table_rows[] = [
'data' => [
['data' => $row[1]],
[
'data' => [
'#markup' => $row[2],
'#prefix' => '<strong>',
'#suffix' => '</strong>',
],
],
['data' => ['#markup' => $row[3]]],
],
];
}

\Drupal::cache()->set('hs_dashboard_csv_announcements', $table_rows, time() + 120);
return $table_rows;
}

}
Loading