Skip to content

Commit

Permalink
SHS-5980: Dashboard: Notifications/Announcements to Site Editors (#1745)
Browse files Browse the repository at this point in the history
* feat(SHS-5979 and SHS-5982): Add dashboard views and custom view code

* chore(SHS-5979 and SHS-5982): Temp add chemistry as a Tugboat site

* chore(SHS-5979 and SHS-5982): Temp add chemistry CI settings

* feat(SHS-5979): Add pager for duplicate people - possible fix for timeout on Tugboat

* feat(SHS-5979 and SHS-5982): Change no results text

* chore(shs-5979): add latest edits and duplicate people blocks to test dashboard

* feat(SHS-5980): Add announcements block

* fix(SHS-5980): Fix linting error

* fix(shs-5980): remove tugboat chemistry config

* fix(SHS-5980): Fix caching and empty CSV rows

* fix(SHS-5980): Minor fixes from code review

* feat(SHS-5980): Remove title column

---------

Co-authored-by: Andrés Díaz Soto <andres.diaz.soto@gmail.com>
  • Loading branch information
codechefmarc and cienvaras authored Feb 28, 2025
1 parent c06e05b commit 7ffad47
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 12 deletions.
35 changes: 23 additions & 12 deletions config/default/dashboard.dashboard.dashboard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,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: { }
639187d0-99c0-40de-a51c-1adfcfc5a258:
uuid: 639187d0-99c0-40de-a51c-1adfcfc5a258
Expand Down
3 changes: 3 additions & 0 deletions docroot/modules/humsci/hs_dashboard/hs_dashboard.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ services:
hs_dashboard.imports_info_manager:
class: Drupal\hs_dashboard\ImportsInfoManager
arguments: ['@entity_type.manager', '@config.factory']
hs_dashboard.announcements_manager:
class: Drupal\hs_dashboard\AnnouncementsManager
arguments: ['@http_client', '@logger.factory', '@file_system', '@date.formatter']
291 changes: 291 additions & 0 deletions docroot/modules/humsci/hs_dashboard/src/AnnouncementsManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
<?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[3])) {
continue;
}

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

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('Update'),
],
];

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[3]]],
],
];
}

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

}
Loading

0 comments on commit 7ffad47

Please sign in to comment.