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

UHF-10941: Hakuvahti Sentry error #604

Merged
merged 3 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 4 additions & 6 deletions conf/cmi/search_api.index.job_listings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,16 @@ dependencies:
- scheduler
- search_api
- helfi_react_search
- helfi_rekry_content
id: job_listings
name: 'Job listings'
description: ''
read_only: false
field_settings:
_language:
label: 'Legacy Language'
datasource_id: 'entity:node'
property_path: langcode
label: Language
property_path: _language
type: string
dependencies:
module:
- node
employment_id:
label: 'Employment » Luokittelutermi » Termin ID'
datasource_id: 'entity:node'
Expand Down Expand Up @@ -343,6 +340,7 @@ processor_settings:
- field_recruitment_id
- field_search_id
- langcode
language_field: { }
language_with_fallback: { }
project_execution_schedule: { }
project_image_absolute_url: { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ declare(strict_types=1);
/**
* Implements hook_theme().
*/
function helfi_hakuvahti_theme() {
function helfi_hakuvahti_theme(): array {
return [
'hakuvahti_form' => [
'variables' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\Core\Utility\Token;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -78,9 +78,28 @@ public function confirm(): array {
* A render array for the confirmation result.
*/
private function handleConfirmFormSubmission(string $hash, string $subscription): array {
return $this->sendConfirmationRequest($hash, $subscription)
? $this->buildConfirmationSuccess()
: $this->buildConfirmationFailure();
try {
$response = $this->sendConfirmationRequest($hash, $subscription);

if ($response->getBody()->getContents() !== '') {
return $this->buildConfirmationSuccess();
}
}
catch (GuzzleException $exception) {
// 404 error is returned if:
// * Submission has been deleted after it expired.
// * Submission has already been confirmed.
// * Submission does not exist.
if ($exception->getCode() === 404) {
$this->logger->info('Hakuvahti confirmation request failed: ' . $exception->getMessage());

return $this->buildConfirmationFailure();
}

$this->logger->error('Hakuvahti confirmation request failed: ' . $exception->getMessage());
}

return $this->buildConfirmationFailure();
}

/**
Expand Down Expand Up @@ -138,27 +157,20 @@ private function buildConfirmationFailure(): array {
* @param string $subscriptionId
* The subscription ID.
*
* @return bool
* TRUE if the confirmation request was successful, FALSE otherwise.
* @return \Psr\Http\Message\ResponseInterface
* API response object.
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function sendConfirmationRequest(string $subscriptionHash, string $subscriptionId): bool {
protected function sendConfirmationRequest(string $subscriptionHash, string $subscriptionId): ResponseInterface {
$csrfTokenService = $this->container->get('csrf_token');
$httpClient = new Client([

return $this->httpClient->request('GET', getenv('HAKUVAHTI_URL') . "/subscription/confirm/{$subscriptionId}/{$subscriptionHash}", [
'headers' => [
'Content-Type' => 'application/json',
'token' => $csrfTokenService->get('session'),
],
]);

try {
$response = $httpClient->get(getenv('HAKUVAHTI_URL') . "/subscription/confirm/{$subscriptionId}/{$subscriptionHash}");
return $response->getBody()->getContents() !== '';
}
catch (GuzzleException $exception) {
$this->logger
->error('Hakuvahti confirmation request failed: ' . $exception->getMessage());
return FALSE;
}
}

/**
Expand Down Expand Up @@ -256,15 +268,14 @@ private function buildUnsubscribeFailedSubmission(): array {
*/
protected function sendUnsubscribeRequest(string $hash, string $subscription): bool {
$csrfTokenService = $this->container->get('csrf_token');
$httpClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'token' => $csrfTokenService->get('session'),
],
]);

try {
$response = $httpClient->delete(getenv('HAKUVAHTI_URL') . "/subscription/delete/{$subscription}/{$hash}");
$response = $this->httpClient->request('DELETE', getenv('HAKUVAHTI_URL') . "/subscription/delete/{$subscription}/{$hash}", [
'headers' => [
'Content-Type' => 'application/json',
'token' => $csrfTokenService->get('session'),
],
]);
return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
}
catch (GuzzleException $exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{ title }}
{{ message }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{ title }}
{{ message }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Drupal\Tests\helfi_hakuvahti\Kernel;

use Drupal\Core\Url;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\helfi_api_base\Traits\ApiTestTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\Traits\PropertyTrait;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

/**
* Tests for hakuvahti controller.
*/
class HakuvahtiControllerTest extends KernelTestBase {

use ApiTestTrait;
use UserCreationTrait;
use PropertyTrait;

/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'system',
'helfi_hakuvahti',
];

/**
* {@inheritDoc}
*/
public function setUp(): void {
parent::setUp();

$this->setUpCurrentUser(permissions: ['access content']);
}

/**
* Tests handleConfirmFormSubmission.
*/
public function testHandleConfirmFormSubmission(): void {
$this->container->set(ClientInterface::class, $this->createMockHttpClient([
new Response(200, body: 'success'),
new Response(404, body: 'not found'),
new Response(500, body: 'fail'),
new RequestException("womp womp", new Request('POST', 'test')),
]));

$logger = $this->prophesize(LoggerInterface::class);
$this->container->set('logger.channel.helfi_hakuvahti', $logger->reveal());

// Get request.
$response = $this->makeRequest('GET', 'helfi_hakuvahti.confirm', ['hash' => 'a', 'subscription' => 'b']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Confirm saved search', $response->getContent() ?? '');

// Success.
$response = $this->makeRequest('POST', 'helfi_hakuvahti.confirm', ['hash' => 'a', 'subscription' => 'b']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Search saved successfully', $response->getContent() ?? '');

// Not found.
$response = $this->makeRequest('POST', 'helfi_hakuvahti.confirm', ['hash' => 'a', 'subscription' => 'b']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Confirmation failed', $response->getContent() ?? '');

// Server error.
$response = $this->makeRequest('POST', 'helfi_hakuvahti.confirm', ['hash' => 'a', 'subscription' => 'b']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Confirmation failed', $response->getContent() ?? '');

// Guzzle exception.
$response = $this->makeRequest('POST', 'helfi_hakuvahti.confirm', ['hash' => 'a', 'subscription' => 'b']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Confirmation failed', $response->getContent() ?? '');
}

/**
* Process a request.
*
* @param string $method
* HTTP method.
* @param string $route
* Drupal route.
* @param array $query
* Query parameters.
*
* @return \Symfony\Component\HttpFoundation\Response
* Controller response.
*/
private function makeRequest(string $method, string $route, array $query = []): SymfonyResponse {
$url = Url::fromRoute($route, options: [
'query' => $query,
]);

$request = $this->getMockedRequest($url->toString(), $method);

return $this->processRequest($request);
}

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

declare(strict_types=1);

namespace Drupal\helfi_rekry_content\Plugin\search_api\processor;

use Drupal\Core\Entity\EntityInterface;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Processor\ProcessorProperty;

/**
* Adds `_language` to the index.
*
* This processor adds _language field to the search api index. The field was
* renamed to `search_api_language` in elasticsearch_connector v8 update. We
* cannot modify rekry search api index without breaking existing hakuvahti
* queries.
*
* @SearchApiProcessor(
* id = "language_field",
* label = @Translation("Language field"),
* description = @Translation("Add _language field to the index."),
* stages = {
* "add_properties" = 20,
* },
* locked = true,
* hidden = true,
* )
*/
class LanguageFieldProcessor extends ProcessorPluginBase {

/**
* {@inheritdoc}
*/
public function getPropertyDefinitions(DatasourceInterface $datasource = NULL): array {
$properties = [];

if (!$datasource) {
$definition = [
'label' => $this->t('Language'),
'description' => $this->t('The legacy _language field.'),
'type' => 'string',
'processor_id' => $this->getPluginId(),
];
$properties['_language'] = new ProcessorProperty($definition);
}

return $properties;
}

/**
* {@inheritdoc}
*/
public function addFieldValues(ItemInterface $item): void {
$object = $item->getOriginalObject()->getValue();

if ($object instanceof EntityInterface) {
$indexableValue = $object->language()->getId();

$itemFields = $item->getFields();
$itemFields = $this->getFieldsHelper()
->filterForPropertyPath($itemFields, NULL, '_language');

foreach ($itemFields as $itemField) {
$itemField->addValue($indexableValue);
}

}
}

}