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

Review: 1567-check-expected-behaviour-of-publishing-progress-box-when-exiting-to-view-data-quality-issues #1740

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
129 changes: 129 additions & 0 deletions app/Helpers/BulkPublishCacheHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace App\Helpers;

use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;

class BulkPublishCacheHelper
{
/**
* Default cache structure when there is no cache found for the organization.
*
* @var array
*/
private const EMPTY_CACHE = [
'status' => false,
'activity_ids' => [],
];

/**
* The cache key format used to store bulk publish data for an organization.
*
* @var string
*/
private const CACHE_KEY = 'ongoing_bulk_publish_%s';

/**
* Get the cache key for the organization.
*
* @param int $orgId
* @return string
*/
private static function getCacheKey(int $orgId): string
{
return sprintf(self::CACHE_KEY, (string) $orgId);
}

/**
* Retrieve the bulk publish cache for the given organization.
*
* @param int $orgId
* @return array The bulk publish cache or empty cache if none found
*/
public static function getOrganisationBulkPublishCache(int $orgId): array
{
return Cache::get(self::getCacheKey($orgId), self::EMPTY_CACHE);
}

/**
* Check if there is an ongoing bulk publish for a given organization.
*
* @param int $orgId
* @return bool True if there is an ongoing bulk publish, false otherwise
*/
public static function hasOngoingBulkPublish(int $orgId): bool
{
return Arr::get(self::getOrganisationBulkPublishCache($orgId), 'status', false);
}

public static function getActivityIdsInCache(int $orgId): array
{
return Arr::get(self::getOrganisationBulkPublishCache($orgId), 'activity_ids', []);
}

public static function activitiesHaveChanged(int $orgId): bool
{
return count(self::getActivityIdsInCache($orgId)) > 0;
}

/**
* Set the initial bulk publish cache for a given organization.
*
* @param int $orgId
* @return void
*/
public static function setInitialBulkPublishCache(int $orgId): void
{
if (!self::hasOngoingBulkPublish($orgId)) {
$cacheValue = self::EMPTY_CACHE;
$cacheValue['status'] = true;

self::updateCache($orgId, $cacheValue);
}
}

/**
* Append an activity ID in the bulk publish cache for a given organization.
*
* @param int $orgId
* @param int $activityId
* @return void
*/
public static function appendActivityIdInBulkPublishCache(int $orgId, int $activityId): void
{
$cacheValue = self::getOrganisationBulkPublishCache($orgId);

if (!in_array($activityId, $cacheValue['activity_ids'], true)) {
$cacheValue['activity_ids'][] = $activityId;

self::updateCache($orgId, $cacheValue);
}
}

/**
* Clear the cache for a given organization.
* This removes the bulk publish data from the cache.
*
* @param int $orgId
* @return void
*/
public static function clearBulkPublishCache(int $orgId): void
{
Cache::forget(self::getCacheKey($orgId));
}

/**
* Helper method to update the cache for the given organization.
*
* @param int $orgId
* @param array $cacheValue
* @return void
*/
private static function updateCache(int $orgId, array $cacheValue): void
{
Cache::put(self::getCacheKey($orgId), $cacheValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,10 @@ public function importValidatedActivities(Request $request): mixed
$orgId = Auth::user()->organization_id;
$filetype = Session::get('import_filetype') ?? ImportCacheHelper::getSessionConsistentFiletype($orgId);

logger('ImportCacheHelper::organisationHasCompletedValidatingData($orgId)');
logger(ImportCacheHelper::organisationHasCompletedValidatingData($orgId));
logger('ImportCacheHelper::getImportStep($orgId)');
logger(ImportCacheHelper::getImportStep($orgId));

if (!ImportCacheHelper::organisationHasCompletedValidatingData($orgId)) {
return response()->json(['success' => false, 'message' => 'No data to import.', 'type' => $filetype]);
}

logger('thichna paiyo00');
ImportCacheHelper::setImportStepToImported($orgId);

if ($activities) {
Expand Down
85 changes: 75 additions & 10 deletions app/Http/Controllers/Admin/Workflow/BulkPublishingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Constants\Enums;
use App\Exceptions\MaxBatchSizeExceededException;
use App\Exceptions\MaxMergeSizeExceededException;
use App\Helpers\BulkPublishCacheHelper;
use App\Http\Controllers\Controller;
use App\IATI\Elements\Xml\XmlGenerator;
use App\IATI\Services\Activity\ActivityPublishedService;
Expand Down Expand Up @@ -56,10 +57,11 @@ class BulkPublishingController extends Controller
/**
* BulkPublishingController Constructor.
*
* @param BulkPublishingService $bulkPublishingService
* @param ActivityService $activityService
* @param ActivityWorkflowService $activityWorkflowService
* @param BulkPublishingStatusService $publishingStatusService
* @param BulkPublishingService $bulkPublishingService
* @param ActivityService $activityService
* @param ActivityWorkflowService $activityWorkflowService
* @param BulkPublishingStatusService $publishingStatusService
* @param ActivityPublishedService $activityPublishedService
*/
public function __construct(
BulkPublishingService $bulkPublishingService,
Expand Down Expand Up @@ -263,20 +265,33 @@ public function startBulkPublish(Request $request): JsonResponse
}

if ($this->publishingStatusService->ongoingBulkPublishing($organization->id)) {
$pubishingStatus = $this->bulkPublishingService->getOrganisationBulkPublishingStatus();
$publishingStatus = $this->bulkPublishingService->getOrganisationBulkPublishingStatus();

return response()->json([
'success' => false,
'message' => 'Another bulk publishing is already in progress.',
'data' => $pubishingStatus['publishingData'],
'in_progress' => $pubishingStatus['inProgress'],
'data' => $publishingStatus['publishingData'],
'in_progress' => $publishingStatus['inProgress'],
]);
}

$activityIds = json_decode($request->get('activities'), false, 512, JSON_THROW_ON_ERROR);

if (!empty($activityIds)) {
$activities = $this->activityService->getActivitiesHavingIds($activityIds);
$filteredActivityIds = $this->bulkPublishingService->getPublishableActivityIds($activityIds);

if (empty($filteredActivityIds)) {
return response()->json(
[
'success' => false,
'status' => 'error',
'message' => 'All of the selected activities have critical error.',
'data' => ['activity_ids' => $activityIds],
]
);
}

$activities = $this->activityService->getActivitiesHavingIds($filteredActivityIds);

if (!count($activities)) {
return response()->json(['success' => false, 'message' => 'No activities selected.']);
Expand Down Expand Up @@ -311,7 +326,6 @@ public function startBulkPublish(Request $request): JsonResponse
return response()->json(['success' => false, 'message' => 'No activities selected.']);
} catch (Exception $e) {
DB::rollBack();
logger()->error($e->getMessage());
logger()->error($e);

return response()->json(['success' => false, 'message' => 'Bulk publishing failed.']);
Expand All @@ -337,6 +351,10 @@ public function getBulkPublishStatus(): JsonResponse
if ($publishStatus && count($publishStatus)) {
$response = $this->bulkPublishingService->getPublishingResponse($publishStatus);

if ($this->bulkPublishCompleted($response)) {
BulkPublishCacheHelper::clearBulkPublishCache($organizationId);
}

return response()->json(
[
'success' => true,
Expand Down Expand Up @@ -371,9 +389,12 @@ public function cancelBulkPublishing(): JsonResponse
{
try {
DB::beginTransaction();
$deletedIds = $this->bulkPublishingService->stopBulkPublishing(auth()->user()->organization->id);
$orgId = auth()->user()->organization_id;
$deletedIds = $this->bulkPublishingService->stopBulkPublishing($orgId);
$numberOfDeletedRows = count($deletedIds);

BulkPublishCacheHelper::clearBulkPublishCache($orgId);

if ($deletedIds) {
DB::commit();

Expand All @@ -389,6 +410,7 @@ public function cancelBulkPublishing(): JsonResponse
return response()->json(['success' => true, 'message' => 'No bulk publish were cancelled.']);
} catch (Exception $e) {
DB::rollBack();
BulkPublishCacheHelper::clearBulkPublishCache(auth()->user()->organization_id);
logger()->error($e->getMessage());
logger()->error($e);

Expand Down Expand Up @@ -505,6 +527,10 @@ public function getValidationStatus(Request $request): JsonResponse
$filteredActivityIds = $this->filterOutPublishedStateActivityIds($activityIds, $activities->toArray());
$response = $this->bulkPublishingService->getActivityValidationStatus($filteredActivityIds);

if ($this->validationCompleted($response)) {
BulkPublishCacheHelper::setInitialBulkPublishCache(auth()->user()->organization_id);
}

$hasFailedStatus = $response['failed_count'] > 0;

return response()->json(['success' => !$hasFailedStatus, 'data' => $response]);
Expand Down Expand Up @@ -559,6 +585,7 @@ public function deleteValidationStatus(): JsonResponse
$this->bulkPublishingService->deleteValidationResponses();
Cache::put('activity-validation-delete', true);
Cache::forget('activity-validation-' . auth()->user()->id);
BulkPublishCacheHelper::clearBulkPublishCache(auth()->user()->organization_id);

return response()->json(['success' => true, 'message' => 'Successfully Deleted.']);
} catch (Exception|\Throwable $exception) {
Expand All @@ -570,6 +597,34 @@ public function deleteValidationStatus(): JsonResponse
}
}

/**
* @return JsonResponse
*/
public function detectChange(): JsonResponse
{
try {
$orgId = auth()->user()->organization_id;
$hasChanged = BulkPublishCacheHelper::activitiesHaveChanged($orgId);
$affectedActivities = BulkPublishCacheHelper::getActivityIdsInCache($orgId);

return response()->json([
'success' => true,
'message' => $hasChanged ? 'Change detected.' : 'No change detected.',
'data' => [
'has_changed' => $hasChanged,
'affected_activities' => $affectedActivities,
],
]);
} catch (Exception $e) {
logger()->error($e);

return response()->json([
'success' => false,
'message' => 'Error has occurred while detecting change.',
]);
}
}

/**
* Filter out published state activity id's.
*
Expand Down Expand Up @@ -632,4 +687,14 @@ private function getPublishBatchSize($activityIds): float|int

return $batchSize;
}

private function validationCompleted(array $response): bool
{
return Arr::get($response, 'status') === 'completed';
}

private function bulkPublishCompleted(array $response): bool
{
return Arr::get($response, 'status') === 'completed';
}
}
36 changes: 35 additions & 1 deletion app/IATI/Repositories/Activity/ValidationStatusRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public function getActivitiesValidationStatus(array $activityIds): array
$response['complete_count'] = 0;
$response['failed_count'] = 0;
$response['activities'] = [];
$response['activity_error_stats'] = [];

$validatorStatuses = $this->model->with('activity')->whereIn('activity_id', $activityIds)->get();

Expand Down Expand Up @@ -142,7 +143,11 @@ public function getActivitiesValidationStatus(array $activityIds): array

if ($validatorStatus->status === 'completed') {
$response['complete_count']++;
$result[$validatorStatus->activity_id]['is_valid'] = json_decode($validatorStatus->response, true)['response']['valid'];
$activityId = $validatorStatus->activity_id;
$responsePropertyOfValidatorResponse = Arr::get(json_decode($validatorStatus->response, true), 'response');
$result[$activityId]['is_valid'] = Arr::get($responsePropertyOfValidatorResponse, 'valid');
$responseSummary = Arr::get($responsePropertyOfValidatorResponse, 'summary', []);
$result[$activityId]['top_level_error'] = $this->getHighestSeverityErrorFromResponse($responseSummary);
}
}

Expand Down Expand Up @@ -250,4 +255,33 @@ public function insertInitialValidatorResponseDataForProperResponse(array $activ

$this->model->insert($insertableValidationStatus);
}

/**
* @param array $responseSummary
*
* @return string|null
*/
private function getHighestSeverityErrorFromResponse(array $responseSummary): ?string
{
$severityLevels = collect(['critical', 'error', 'warning', 'advisory']);

return $severityLevels->first(function ($level) use ($responseSummary) {
return Arr::get($responseSummary, $level, 0) > 0;
});
}

/**
* Returns array of activity_id of the activities that do not contain critical error.
*
* @param array $activityIds
*
* @return array
*/
public function getActivitiesWithNoCriticalErrors(array $activityIds): array
{
return $this->model->whereIn('activity_id', $activityIds)
->where('status', 'completed')
->whereRaw("(response->'response'->'summary'->>'critical')::integer = 0")
->pluck('activity_id')->toArray();
}
}
12 changes: 12 additions & 0 deletions app/IATI/Services/Workflow/BulkPublishingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -505,4 +505,16 @@ public function getActivitiesWithDeprecatedValueArray(mixed $activityIds, Activi

return $returnArr;
}

/**
* Returns array of activity_id of the activities that do not contain critical error.
*
* @param array $activityIds
*
* @return array
*/
public function getPublishableActivityIds(array $activityIds): array
{
return $this->validationStatusRepository->getActivitiesWithNoCriticalErrors($activityIds);
}
}
Loading